#=
fft:
- Julia version:
- Author: ymocquar
- Date: 2019-11-15
=#
# function that reverse the order of the pos lowest bits
using FFTW
function _reverse_num(num, pos)
    result = 0
    pos_m1 = pos - 1
    for i = 0:pos_m1
        if (num & (1 << i)) != 0
            result |= 1 << (pos_m1 - i)
        end
    end
    return result
end
"""
    PrepareFftBig( size_fft::Unsigned, [T=BigFloat])

Immutable structure to operate fft transform,
x is the type of non transformed data also called signal.

# Arguments :
- `size_fft::Integer` : Number of values, must be a power of two
- `[T=BigFloat | x::T ]` : type of the values

# Implementation
- size_fft : size of the signal
- tab_permut : permutation
- root_one : size order roots of one
- root_one_conj : conjugate of root_one

"""
struct PrepareFftBig
    size_fft::Any
    tab_permut::Any
    root_one::Any
    root_one_conj::Any
    type::DataType
    function PrepareFftBig(size_fft::Integer, x::T) where {T<:AbstractFloat}
        @assert prevpow(2, size_fft) == size_fft "size_fft=$size_fft is not a power of 2"
        power = convert(Int64, log2(size_fft))
        tab_permut = zeros(Int64, size_fft)
        root_one = zeros(Complex{T}, size_fft)
        root_one_conj = zeros(Complex{T}, size_fft)
        prec = precision(T)
        setprecision(prec + 32) do
            for i = 1:size_fft
                tab_permut[i] = _reverse_num(i - 1, power) + 1
                root_one[i] = round(
                    exp(one(T) * 2big(pi) * im * (i - 1) / size_fft),
                    digits = prec + 16,
                    base = 2,
                )
                root_one_conj[i] = round(
                    exp(-one(T) * 2big(pi) * im * (i - 1) / size_fft),
                    digits = prec + 16,
                    base = 2,
                )
            end
        end
        return new(
            size_fft,
            tab_permut,
            Complex{T}.(root_one),
            Complex{T}.(root_one_conj),
            typeof(x),
        )
    end
end
PrepareFftBig(size_fft::Integer, type::DataType) = PrepareFftBig(size_fft, one(type))
PrepareFftBig(size_fft::Integer) = PrepareFftBig(size_fft, one(BigFloat))
function fftbig!(par::PrepareFftBig, signal; flag_inv = false)
    s = size(signal, 2)
    @assert prevpow(2, s) == s "size_fft(signal)=$s is not a power of 2"
    s_div2 = div(s, 2)
    len = s
    n_len = len >> 1
    nb_r = 1
    rootO = flag_inv ? par.root_one : par.root_one_conj
    #    prec= precision(real(rootO[1]))
    #    setprecision(prec+32) do
    while n_len != 0
        start = 1
        suite = start + n_len
        for i = 1:nb_r
            deb = 1
            for j = deb:nb_r:s_div2
                signal[:, start], signal[:, suite] = (signal[:, start] + signal[:, suite]),
                (signal[:, start] - signal[:, suite]) * rootO[j]
                start += 1
                suite += 1
            end
            start = suite
            suite = start + n_len
        end
        len = n_len
        n_len >>= 1
        nb_r <<= 1
    end
    #    end
    signal .= signal[:, par.tab_permut]

    if flag_inv
        signal ./= s
    end
    return signal
end
function fftbig(par::PrepareFftBig, signal; flag_inv = false)
    fl = flag_inv
    return fftbig!(
        par::PrepareFftBig,
        copy(convert(Array{Complex{par.type}}, signal)),
        flag_inv = fl,
    )
end
fftgen(_::Any, t::Array{Complex{Float64}}) = fft(t, (2,))
fftgen(_::Any, t::Array{Float64}) = fft(t, (2,))
fftgen(p::PrepareFftBig, t::Array{T}) where {T<:AbstractFloat} = fftbig(p, t)
fftgen(p::PrepareFftBig, t::Array{Complex{T}}) where {T<:AbstractFloat} = fftbig(p, t)
ifftgen(_::Any, t::Array{Complex{Float64}}) = ifft(t, (2,))
ifftgen(_::Any, t::Array{Float64}) = ifft(t, (2,))
ifftgen(p::PrepareFftBig, t::Array{T}) where {T<:AbstractFloat} =
    fftbig(p, t, flag_inv = true)
ifftgen(p::PrepareFftBig, t::Array{Complex{T}}) where {T<:AbstractFloat} =
    fftbig(p, t, flag_inv = true)


mutable struct QuadraticModel <: TransistorModel
    mos::MOSStructure
    Vth
    Cox
    W
    L
    μ
    T0
end

setW!(Wn,m::QuadraticModel) = begin m.W = Wn end
setL!(Ln,m::QuadraticModel) = begin m.L = Ln end
function setT!(T,m::QuadraticModel)
    Sdist = m.mos.semiconductor.dist
    Ssemi = m.mos.semiconductor.material
    nMos = MOSStructure(m.mos.metal,m.mos.oxide,SemiconductorData(T,Sdist,Ssemi),m.mos.tox,m.mos.tsi)
    nModel = QuadraticModel(m.W,m.L,nMos;μ=m.μ)
    m.mos = nMos
    m.Vth = nModel.Vth
    m.Cox = nModel.Cox
    m.W = nModel.W
    m.L = nModel.L
    m.μ = nModel.μ
end
function Vth_Quadratic(Vfb,Na,tox,T,phif,sigma=0.0)
    phit = kb*T
    G = γ(Na,tox)
    k = 0#log(n/(n-1))
    #uprintln(n,",",phit,",",G,",",k,",",Vfb,",",Na,",",tox,",",T,",",phif)
    return Vfb+2*phif+phit*k+G*sqrt(2*phif)
end

function Vth_Quadratic(mos::MOSStructure)
    return Vth_Quadratic(VFB(mos),abs(C(mos)),mos.tox,Temperature(mos),ϕb(mos))
end

QuadraticModel(W,L,mos::MOSStructure;μ=520.0) = QuadraticModel(mos,Vth_Quadratic(mos),Cox(mos),W,L,μ,Temperature(mos))

function Id(Vg,Vd,Vs,Vb,mdl::QuadraticModel)
    Vgs = Vg-Vs
    Vds = Vd-Vs
    ϕt = Temperature(mdl.mos)*kb
    @unpack mos,Vth,Cox,W,L,μ,T0 = mdl
    Vov = Vgs-Vth
    S = W/L
    if Vov < 0.0
        return S*μ*(T0/300.0)^(-3/2.0)*Cox*ϕt^2*(exp(Vov/ϕt))*(1-exp(-Vds/ϕt))
    elseif Vds < Vov
        return S*μ*(T0/300.0)^(-3/2.0)*Cox*(Vov*Vds-Vds^2*0.5)
    else
        return S*μ*(T0/300.0)^(-3/2.0)*Cox*0.5*(Vov)^2
    end

end

function id(Vg,Vd,Vs,Vb,mdl::QuadraticModel)
    Vgs = Vg-Vs
    Vds = Vd-Vs
    ϕt = Temperature(mdl.mos)*kb
    @unpack mos,Vth,Cox,W,L,μ = mdl
    Vov = Vgs-Vth
    S = W/L
    if Vov < 0.0
        return ϕt^2*(exp(Vov/ϕt))*(1-exp(-Vds/ϕt))
    elseif Vds < Vov
        return (Vov*Vds-Vds^2*0.5)
    else
        return 0.5*(Vov)^2
    end

end

Id(Vg,Vd,Vs,m::QuadraticModel) = Id(Vg,Vd,Vs,Vs,m::QuadraticModel)
# Symbolics.@register Id(Vg,Vd,Vs,m::QuadraticModel)
Id(Vg,Vd,m::QuadraticModel) = Id(Vg,Vd,0.0,m::QuadraticModel)
gm(Vg,Vd,Vs,Vb,m::QuadraticModel) = Zygote.ForwardDiff.derivative(x->Id(x,Vd,Vs,Vb,m),Vg)
IdNR(vg,vd,vs,m::QuadraticModel) = Id(vg,vd,vs,m)-gm(vg,vd,vs,m)*(vg-vs)-gds(vg,vd,vs,m)*(vd-vs)
# Symbolics.@register IdNR(vg,vd,vs,m::QuadraticModel)
# Symbolics.@register gm(Vg,Vd,Vs,Vb,m::QuadraticModel)
gm(Vg,Vd,Vs,m::QuadraticModel) = Zygote.ForwardDiff.derivative(x->Id(x,Vd,Vs,m),Vg)
gm(Vg,Vd,m::QuadraticModel) = Zygote.ForwardDiff.derivative(x->Id(x,Vd,0.0,m),Vg)
gmId(Vg,Vd,m::QuadraticModel) = gm(Vg,Vd,m)/Id(Vd,Vg,m)
gds(Vg,Vd,Vs,m::QuadraticModel) = Zygote.ForwardDiff.derivative(x->Id(Vg,x,Vs,m),Vd)
# ModelingToolkit.@register gds(Vg,Vd,Vs,Vb,m::QuadraticModel)

gdsId(Vg,Vd,m::QuadraticModel;order=5) = gds(Vg,Vd,m)/Id(Vd,Vg,m)


using ElectronicStructure
using ElectronicStructure.LagrangeFunction

function test_main( NN::Array{Int64} )

  println("\nNN = ", NN)

  Nx = NN[1]
  Ny = NN[2]
  Nz = NN[3]
  Npoints = Nx*Ny*Nz

  AA = [0.0, 0.0, 0.0]
  BB = [16.0, 16.0, 16.0]

  # Initialize LF
  LF = init_LF3d_c( NN, AA, BB )

  @printf("build_nabla2: ")
  @time nabla2 = build_nabla2( LF )

  @printf("get_Laplacian3d_kron: ")
  @time nabla2_v2 = get_Laplacian3d_kron( LF )

  srand(123)
  x = rand(Npoints)

  # Compare multiplication result
  Lx = nabla2 * x
  Lx_v2 = nabla2_v2 * x

  @printf("Diff = ")
  println( sum(Lx-Lx_v2) )

end

test_main( [10, 10, 10] )
test_main( [20, 20, 20] )
test_main( [30, 30, 30] )
test_main( [40, 40, 40] )
#test_main( [3,2,4] )


# Scheduling isn't really possible directly with the solver yet :/
# Most of this file is based on http://www.hakank.org/julia/constraints/
# which is a website by Håkan Kjellerstrand
# please check it out :)

# by  Håkan Kjellerstrand
function cumulative(model, start, duration, resource, limit; times_max = nothing)
    tasks = [i for i in 1:length(start) if resource[i] > 0 && duration[i] > 0]
    num_tasks = length(tasks)

    times_min = minimum(round.(Int,[JuMP.lower_bound(start[i]) for i in tasks]))
    if times_max === nothing
        times_max = maximum(round.(Int,[JuMP.upper_bound(start[i])+duration[i] for i in tasks]))
    end
    for t in times_min:times_max
        b  = @variable(model, [1:num_tasks], Bin)
        for i in tasks
            # The following don't work since ConstraintSolver don't
            # support nonlinear constraints
            # @constraint(model,sum([(start[i] <= t) * (t <= start[i] + duration[i])*resource[i] for i in tasks])  <= b)

            # is this task active during this time t?
            @constraint(model, b[i] := { start[i] <= t && t < start[i]+duration[i] }) # is this task active in time t ?
        end
        # Check that there's no conflicts in time t
        @constraint(model,sum([b[i]*resource[i] for i in tasks]) <= limit)
  end
end

#=
  Furniture moving (scheduling) in Julia ConstraintSolver.jl
  From Marriott & Stuckey: "Programming with constraints", page  112f
  Changed for this test case to be easier ;)

  Model created by Hakan Kjellerstrand, hakank@gmail.com
  See also his Julia page: http://www.hakank.org/julia/
=#
function furniture_moving()
    model = Model(optimizer_with_attributes(CS.Optimizer,
                                                "logging"=>[],
                                                "branch_split"=>:InHalf,
                                                "time_limit"=>50
                                        ))

    # Furniture moving
    n = 4
    # [piano, chair, bed, table]
    durations = [30,10,15,15]
    # resource needed per task
    resources = [1,1,1,1] # <- changed for this test case to solve it faster
    max_end_time = 45
    @variable(model, 0 <= start_times[1:n] <= max_end_time, Int)
    @variable(model, minimum(durations) <= end_times[1:n]  <= max_end_time, Int)
    @variable(model, 1 <= limit <= 3, Int)
    @variable(model, 0 <= max_time <= max_end_time,Int)

    for i in 1:n
        @constraint(model,end_times[i] == start_times[i] + durations[i])
    end
    @constraint(model, end_times .<= max_time)
    cumulative(model, start_times, durations, resources, limit; times_max = max_end_time)

    # Solve the problem
    @objective(model, Min, max_time)
    optimize!(model)

    status = JuMP.termination_status(model)
    @test status == MOI.OPTIMAL
    @test JuMP.value(limit) ≈ 3
    @test JuMP.value(max_time) ≈ 30
    @test JuMP.value(start_times[1]) ≈ 0
    for i = 1:n
        @test JuMP.value(start_times[i])+durations[i] ≈ JuMP.value(end_times[i])
    end
end

@testset "Scheduling" begin
@testset "Furniture" begin
    furniture_moving()
end
end


"""
    simplicial_subdivision(k, E)

Generate generic rules for generating vertices when splitting an E-dimensional
simplex with a size reducing factor of k.

Note: Subsimplices containing any of the original vertices is not considered.

This function comes in two versions. Both versions returns information about
all the subsimplices of the splitted original simplex. They differ in what
information they return about the vertices furnishing these simplices.

The SimplicialSubdivisionMultiple function returns an array of vertices that
containONLY THE NEW VERTICES. This is to prevent duplication when considering
triangulations, in which simplices might share vertices.

SimplicialSubdivisionSingle(k, E) returns an array of all the vertices
furnishing the subidivded simplex, including the vertices of the original
simplex. Use this function when you want to split a single simplex.

SimplexSplittingOutput the decomposition part of a SimplexSplitting output.
This is a (k^E * (E+1) x k) array of integers. (so, ignore the orientations
	for now)
"""
function simplicial_subdivision(k::Int, E::Int)

    # Get simplex splitting rules, ignore orientations for now.
    splits::Array{Int, 2} = simplex_split(k, E, orientations = false)

    # Retrieve integer labels from the tensor decomposition. This is a a
    #  way of uniquely identifying the rows of the 'splits' array. Each integer
    # corresponds to a row in the splits array (but are not necessarily in order).
    integer_labels::Vector{Int} = (splits .- 1) * ((E+1) .^ collect(0:(k-1)))

    # Find the unique labels and sort them
    uniquelabels = sort(unique(integer_labels))

    # How many unique rows are there?
    n_rows = size(uniquelabels, 1)

    J = indexin(integer_labels, uniquelabels)

    # IndicesOfUnqiueIntegersinOriginal
    I = Vector{Int}(undef, length(uniquelabels))
    for i = 1:length(uniquelabels)
        I[i] = something(findfirst(isequal(uniquelabels[i]), integer_labels), 0)
    end
    V::Array{Int, 2} = repeat(uniquelabels, 1, E + 1)

    tmp = repeat((((E+1)^k - 1) ./ E) * transpose(collect(0:E)), n_rows, 1)

    tmp = heaviside0(-abs.(V - tmp)) .* repeat(collect(1:n_rows), 1, E + 1)

	#@show tmp
    #@show findall(tmp)
    #tmp = tmp[findall(tmp)]
    tmp = tmp[tmp .> 0]

    aux = round.(Int, tmp)
    Caux = round.(Int, complementary(tmp, n_rows))
    Ipp = [aux; Caux]

    Ip = zeros(Int, size(I))

    # for any i\in Ipp, Ip(i) is the number j\in {1,...,size(Ipp,1)} such that
    # Ipp(j)=i
    Ip[Ipp] = transpose(collect(1:size(I, 1)))

    # Indices of the non-repeated vertices that are not part of the original
    # simplex. These are the truly new vertices generated by the splitting.
    new_vertices = splits[I, :][Caux, :]

    # Subarrays of dimension E+1 x 1 vertically concatenated. Each subarray
    # contains the indices in the rows of NewVert corresponding to the vertices
    # furnishing the corresponding simplex (analogous to the output of
    # SimplexSplitting, check notes).
    subtriangulation_inds = Ip[J]

    subtriangulation_inds = copy(transpose(reshape(subtriangulation_inds, E + 1, k^E)))

    return new_vertices, subtriangulation_inds
end


using Test
using TropicalNumbers

@testset "counting tropical" begin
    ct1 = CountingTropical(2.0, 4.0)
    ct2 = CountingTropical(2.0, 3.0)
    @test ct1 * true == ct1
    @test ct1 * false == CountingTropical(-Inf, 1.0)
    @test one(ct1) == CountingTropical(0.0, 1.0)
    @test zero(ct1) == CountingTropical(-Inf, 1.0)
    res1 = ct1 + ct2
    @test res1.n == 2
    @test res1.c == 7
    res2 = ct1 * ct2
    @test res2.n == 4
    @test res2.c == 12
    @test res2 ≈ CountingTropical(4, 12)
    println(CountingTropical{Float64}(3))

    ct1 = CountingTropical(2, 4)
    ct2 = CountingTropical(2.0, 3.0)
    @test CountingTropicalF64(ct1) === CountingTropical(2.0, 4.0)
    @test promote(ct1, ct2) === (CountingTropical(2.0, 4.0), ct2)

    @test CountingTropical{Float64}(2) === CountingTropical{Float64,Float64}(2.0, 1.0)
    @test CountingTropical{Int64, Float64}(2) === CountingTropical{Int64,Float64}(2, 1.0)
    @test CountingTropical{Int64, Float64}(TropicalF64(2)) === CountingTropical{Int64,Float64}(2, 1.0)
    @test CountingTropical{Int64, Float64}(CountingTropicalF64(2)) === CountingTropical{Int64,Float64}(2, 1.0)
    @test zero(CountingTropical{Int64,Float64}) === CountingTropical{Int64,Float64}(-999999, 0.0)
    @test one(CountingTropical{Int64,Float64}) === CountingTropical{Int64,Float64}(0, 1.0)

    @test promote(CountingTropical{Float64,Float32}(1, 2), CountingTropical{Int64,Int32}(1, 2)) == (CountingTropical{Float64,Float32}(1, 2), CountingTropical{Float64,Float32}(1, 2))
    @test promote(CountingTropical{Float64,Float32}(1, 2)) == (CountingTropical{Float64,Float32}(1, 2),)
    @test promote_type(CountingTropical{Float64,Float32}, CountingTropicalF32, CountingTropical{Int32,Int32}) == CountingTropical{Float64,Float32}

    @test one(CountingTropical{Float64}) == one(CountingTropicalF64)
    @test isnan(CountingTropical(NaN))
    @test !isnan(CountingTropical(-Inf))
end


# --- PRIVATE METHODS --------------------------------------------------------------------------------------- #
function _obj_function_breakeven(x,contractSet::Set{PSAbstractAsset})

    # what is the test price?
    underlyingPrice = x[1]

    # what is the PL value at this price?
    result = compute_profit_loss_value(contractSet,underlyingPrice)
    if (isa(result.value,Exception) == true)
        return result
    end
    pl_value = result.value

    # compute the objective function -
    obj_value = (pl_value^2)+10000000*max(0.0,-1*underlyingPrice)

    # return -
    return obj_value
end
# ----------------------------------------------------------------------------------------------------------- #

# --- PUBLIC METHODS ---------------------------------------------------------------------------------------- #
function compute_breakeven_price(contractSet::Set{PSAbstractAsset}; initialPrice::Float64=1.0)::PSResult

    # setup initial price -
    xinitial = [initialPrice]

    # setup the objective function -
    OF(p) = _obj_function_breakeven(p, contractSet)

    # call the optimizer -
    opt_result = optimize(OF,xinitial,BFGS())

    # breakeven price -
    breakeven_price = Optim.minimizer(opt_result)[1]

    # return -
    return PSResult(breakeven_price)
end
# ----------------------------------------------------------------------------------------------------------- #


#=

  Set covering and set partition in Julia ConstraintSolver.jl

  Example from Lundgren, Ronnqvist, Varbrand "Optimeringslora", page 408.
  [This is a Swedish book about Operational Research.]

  We want to minimize the cost of the alternatives which covers all the
  objects, i.e. all objects must be choosen. The requirement is than an object
  may be selected _exactly_ once.

  Alternative        Cost        Object
  1                  19           1,6
  2                  16           2,6,8
  3                  18           1,4,7
  4                  13           2,3,5
  5                  15           2,5
  6                  19           2,3
  7                  15           2,3,4
  8                  17           4,5,8
  9                  16           3,6,8
  10                 15           1,6,7

  The problem has a unique solution of z = 49 where alternatives
  3, 5, and 9 is selected.

  If we, however, allow that an object is selected more than one time,
  then the solution is z = 45 (i.e. less cost than the first problem),
  and the alternatives 4, 8, and 10 is selected, where object 5 is
  selected twice (alt. 4 and 8). It's an unique solution as well.

  Model created by Hakan Kjellerstrand, hakank@gmail.com
  See also my Julia page: http://www.hakank.org/julia/

=#
using ConstraintSolver, JuMP
using Cbc, GLPK, Ipopt
const CS = ConstraintSolver
include("constraints_utils.jl")

function set_covering4(problem,type="",print_solutions=true,all_solutions=true)

    cbc_optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
    glpk_optimizer = optimizer_with_attributes(GLPK.Optimizer)
    ipopt_optimizer = optimizer_with_attributes(Ipopt.Optimizer)

    model = Model(optimizer_with_attributes(CS.Optimizer,   # "all_solutions"=> all_solutions,
                                                            "all_optimal_solutions"=>all_solutions,
                                                            "logging"=>[],

                                                            "traverse_strategy"=>:BFS,
                                                            # "traverse_strategy"=>:DFS,
                                                            # "traverse_strategy"=>:DBFS,

                                                            # "branch_split"=>:Smallest,
                                                            # "branch_split"=>:Biggest,
                                                            "branch_split"=>:InHalf,

                                                            # https://wikunia.github.io/ConstraintSolver.jl/stable/options/#branch_strategy-(:Auto)
                                                            "branch_strategy" => :IMPS, # default
                                                            # "branch_strategy" => :ABS, # Activity Based Search
                                                            # "activity.decay" => 0.999, # default 0.999
                                                            # "activity.max_probes" => 100, # default, 10
                                                            # "activity.max_confidence_deviation" => 20, # default 20

                                                            # "simplify"=>false,
                                                            # "simplify"=>true, # default

                                                            "time_limit"=>6,

                                                            # "backtrack" => false, # default true
                                                            # "backtrack_sorting" => false, # default true

                                                            # "lp_optimizer" => cbc_optimizer,
                                                            "lp_optimizer" => glpk_optimizer,
                                                            # "lp_optimizer" => ipopt_optimizer,
                                        ))
    println("Type:$type")
    costs = problem[:costs]
    alternatives = problem[:alternatives]

    num_alternatives, num_objects = size(alternatives)

    # Which alternative to select
    @variable(model, x[1:num_alternatives], Bin)
    @variable(model, 0 <= min_val <= num_alternatives*maximum(costs), Int)

    for j in 1:num_objects
        s = @variable(model, [1:1], CS.Integers(0:num_alternatives*sum(alternatives[:,j])))
        @constraint(model, s[1] == sum(x.*alternatives[:,j]))
        if type == "set_partition"
            # all objects must be covered _exactly_ once
            # (set partition)
            @constraint(model, s[1] == 1)
        else
            # variant: all objects must be covered _at least_ once
            # (set covering)
            @constraint(model, s[1] >= 1)
        end
    end

    scalar_product(model,costs,x,min_val)

    @objective(model,Min,min_val)

    # Solve the problem
    optimize!(model)

    status = JuMP.termination_status(model)
    # println("status:$status")
    if status == MOI.OPTIMAL
        num_sols = MOI.get(model, MOI.ResultCount())
        println("num_sols:$num_sols\n")
        if print_solutions
            for sol in 1:num_sols
                println("solution #$sol")
                x_val = convert.(Integer,JuMP.value.(x; result=sol))
                min_val_val = convert.(Integer,JuMP.value.(min_val; result=sol))
                println("min_val:$min_val_val")
                println("x:$x_val")
                println("assignments:$([i for i in 1:num_alternatives if x_val[i] == 1])")
            end
        end
    else
        println("status:$status")
    end

    return status
end

problem = Dict(
    :costs => [9, 16, 18, 13, 15, 19, 15, 17, 16, 15],
    :alternatives => resize_matrix([
        [1,0,0,0,0,1,0,0],   # alternative 1
        [0,1,0,0,0,1,0,1],   # alternative 2
        [1,0,0,1,0,0,1,0],   # alternative 3
        [0,1,1,0,1,0,0,0],   # alternative 4
        [0,1,0,0,1,0,0,0],   # alternative 5
        [0,1,1,0,0,0,0,0],   # alternative 6
        [0,1,1,1,0,0,0,0],   # alternative 7
        [0,0,0,1,1,0,0,1],   # alternative 8
        [0,0,1,0,0,1,0,1],   # alternative 9
        [1,0,0,0,0,1,1,0]]  # alternative 10
                                   )
)

@time set_covering4(problem,"set_partition",true,true)
println()
@time set_covering4(problem,"set_covering",true,true)


# one LIF neuron with constant input

push!(LOAD_PATH, abspath(@__DIR__,".."))
using LinearAlgebra,Statistics,StatsBase
using Plots,NamedColors ; theme(:dark)
using SparseArrays
using SpikingRNNs; const global S = SpikingRNNs
using ProgressMeter

function onesparsemat(w::Real)
  mat=Matrix{Float64}(undef,1,1) ; mat[1,1]=w
  return sparse(mat)
end

## Time parameters
dt = 1E-4
Ttot = 500.
# LIF neuron parameters
myτ = 0.5
v_th = 10.
v_r = -5.0
τrefr = 0.5 # refractoriness
τpcd = 0.2 # post synaptic current decay
myinput = 9.
myσ = 2.0
mynt = S.NTLIF(myτ,v_th,v_r,τrefr,τpcd)
myps = S.PSLIF(mynt,1)

## inputs here
# one static input
in_state = S.PSSimpleInput(S.InputSimpleOffset(myinput))
# ... plus noise
in_state_noise = S.PSSimpleInput(S.InputIndependentNormal(myσ))
##
mypop = S.Population(myps,(S.FakeConnection(),S.FakeConnection()),
    (in_state,in_state_noise))

## that's it, let's make the network
myntw = S.RecurrentNetwork(dt,(mypop,))

## Recorder
myrec1 = S.RecStateNow(myps,10,dt,Ttot;t_warmup=50.)
myrec2 = S.RecCountSpikes(myps,dt)

## Run!
times = (0:myntw.dt:Ttot)
nt = length(times)
# initial conditions
myps.state_now[1] = v_r

S.reset!.([myrec1,myrec2])

for (k,t) in enumerate(times)
  S.dynamics_step!(t,myntw)
  myrec1(t,k,myntw)
  myrec2(t,k,myntw)
end
r0 = S.get_mean_rates(myrec2,dt,Ttot)[1]
@info mean_and_std(myrec1.state_now[:])
##

# now, use Fokker-Planck theory and Richardson method
# it is hardcoded for now... maybe later I will convert it to
# a function
dv = 1E-3
v_lb = v_r - 2.0
ndv = round(Int64,(v_th - v_lb)/dv)
k_re = round(Int64,(v_r - v_lb)/dv)
ks = collect(0:ndv)
Vks = @. v_lb + ks * dv
js = fill(NaN,ndv+1)
ps = fill(NaN,ndv+1)

ksrev = collect(ndv+1:-1:2)

js[ndv+1]=1.0
ps[ndv+1]=0.0

for k in ksrev
  krond = k==k_re ? 1.0 : 0.0
  js[k-1] = js[k] - krond
  Gk = (Vks[k]-myinput)/(myσ^2)
  Hk = myτ*js[k]/(myσ^2)
  Ak = exp(dv*Gk)
  Bk = Gk==0 ? dv*Hk :  Hk/Gk*(Ak-1)
  ps[k-1] = ps[k]*Ak + Bk
end

r0 = inv(dv*sum(ps))

##
histogram(myrec1.state_now[:];nbins=100,normalize=true,
  label="simulation")
plot!(Vks,r0.*ps;color=:white,linewidth=2,leg=:topleft,
  label="Fokker-Planck")

## repeat for EIF neuron

dt = 1E-4
Ttot = 150.
# EIF neuron parameters
# τ::Float64 # time constant (membrane capacitance)
# g_l::Float64 # conductance leak
# v_expt::Float64 # exp term threshold
# steep_exp::Float64 # steepness of exponential term
# v_threshold::Float64 # spiking threshold
# v_reset::Float64 # reset after spike
# v_leak::Float64 # reversal potential for leak term
# τ_refractory::Float64 # refractory time

τ = 0.5
g_l = 1.0
v_expt=10.0
steep_exp = 1.0
v_th = 20.
v_r = -5.0
τrefr = 0.0
v_leak = 0.0

myinput = 7.0
myσ = 3.0

mynt = S.NTEIF(τ,g_l,v_expt,steep_exp,v_th,v_r,v_leak,τrefr)
myps = S.PSEIF(mynt,1)

## inputs here
# one static input
in_state = S.PSSimpleInput(S.InputSimpleOffset(myinput))
# ... plus noise
in_state_noise = S.PSSimpleInput(S.InputIndependentNormal(myσ))
##
mypop = S.Population(myps,(S.FakeConnection(),S.FakeConnection()),
    (in_state,in_state_noise))

## that's it, let's make the network
myntw = S.RecurrentNetwork(dt,(mypop,))

## Recorder
myrec1 = S.RecStateNow(myps,10,dt,Ttot;t_warmup=50.)
myrec2 = S.RecCountSpikes(myps,dt)

## Run!
times = (0:myntw.dt:Ttot)
nt = length(times)
# initial conditions
myps.state_now[1] = v_r

S.reset!.([myrec1,myrec2])

for (k,t) in enumerate(times)
  S.dynamics_step!(t,myntw)
  myrec1(t,k,myntw)
  myrec2(t,k,myntw)
end
r0_sim = S.get_mean_rates(myrec2,dt,Ttot)[1]
@info mean_and_std(myrec1.state_now[:])
##

# now, use Fokker-Planck theory and Richardson method
# it is hardcoded for now... maybe later I will convert it to
# a function
dv = 1E-3
v_lb = v_r - 2.0

psifun(V) = steep_exp * exp((V-v_expt)/steep_exp)

ndv = round(Int64,(v_th - v_lb)/dv)
k_re = round(Int64,(v_r - v_lb)/dv)
ks = collect(0:ndv)
Vks = @. v_lb + ks * dv
js = fill(NaN,ndv+1)
ps = fill(NaN,ndv+1)

ksrev = collect(ndv+1:-1:2)

js[ndv+1]=1.0
ps[ndv+1]=0.0

for k in ksrev
  krond = k==k_re ? 1.0 : 0.0
  js[k-1] = js[k] - krond
  Gk = (Vks[k]-myinput-psifun(Vks[k]))/(myσ^2)
  Hk = myτ*js[k]/(myσ^2)
  Ak = exp(dv*Gk)
  Bk = Gk==0 ? dv*Hk :  Hk/Gk*(Ak-1)
  ps[k-1] = ps[k]*Ak + Bk
end

r0_fp = inv(dv*sum(ps))

@info "rate sim $r0_sim , rate F-P $r0_fp"

##
histogram(myrec1.state_now[:];nbins=100,normed=true,
  label="simulation")
plot!(Vks,r0_fp.*ps;color=:white,linewidth=2,leg=:topleft,
  label="Fokker-Planck")


### A Pluto.jl notebook ###
# v0.12.19

using Markdown
using InteractiveUtils

# ╔═╡ a9d90294-61a6-11eb-1641-75e225e67315
begin
        using DataFrames
        using Plots
end

# ╔═╡ 61ca784e-5ceb-11eb-1423-5f9d48e72f26
md"# Lab 1 - Electric Field Visualization"

# ╔═╡ f4dbabba-61d4-11eb-1475-716ef6ea6109
md"
##### Info:

- Name: Omar Elrefaei
- ID: 20817426
- Date: 28/1/2021
- Code: [github](https://github.com/Omar-Elrefaei/small-projects/blob/master/ECE106/lab1-Electric_Field.jl)
- Made with: Julia Language & [Pluto Notebooks](https://github.com/fonsp/Pluto.jl/)

"

# ╔═╡ f688a336-61a3-11eb-1f9d-754da19d57ac
md"
## Question 1

![](https://i.imgur.com/nPFMTWX.png)
"

# ╔═╡ 456010ee-619e-11eb-0b5d-c5b88755b425
begin
        dataQ1 = [
                8.81 7.35 4.54 2.76 0.4 ;
                18.3 13.2 6.36 3.04 0.19]
        dataFrameQ1 = DataFrame(
                var"Before -1nC"        = dataQ1[1,:],
                var"After -1nC"         = dataQ1[2,:])
end

# ╔═╡ c5c0813e-61a2-11eb-3a8d-09f01538c980
md"
##### Answer:

When a sensor is in between the two charges: the magnitude of E-field at that sensor increases. But if the sensor is on the left/right of both charges, the value decreases.

When a sensor is (horizontally) between the two opposite charges, they both induce an E-Field in the same direction, and thus increase the magnitude. When they are in the same direction from the sensor, they cancel out instead."

# ╔═╡ 30bcb2f4-61a4-11eb-3765-a365cf2665b5
md"
## Question 2

##### Answer:

The E-field for the first 4 sensors point horizontally to the right. The vertical components of the field have been cancelled out due to both charges being opposite to each other and lying at equal horizontal distance from the sensors. On the other hand, the horizontal components compound and add to each other.

"

# ╔═╡ 0fd26dd4-61a6-11eb-23bb-bf99cdd17325
md"
## Question 3

##### Answer:
The field strength increase linearly proportional to the distance between the
two charges.

"

# ╔═╡ 719bfbe0-61b3-11eb-19ab-d9fab0dff0f0
begin
        dataQ3 = [ 200  400     600     800     1000; 0.19 0.44 0.78 1.21 1.49]
        plot(dataQ3[1,:], dataQ3[2,:], title="Plot 1", label="Real Data",
                lw=3, xlabel="Distance [m]", ylabel="E-field [V/m]")

        m = (dataQ3[2,end] - dataQ3[2,begin]) / (dataQ3[1,end] - dataQ3[1,begin])
        x = [i for i in 200:200:1000]
        y = -0.135 .+ x .* m
        plot!(x,y, label="Best fit", lw=2)
end

# ╔═╡ 06c52ba4-61bb-11eb-27a2-e9219195e2db
        dataFrameQ3 = DataFrame(var"Distance between charges" = dataQ3[1,:],
        var"Feild at remote sensor" = dataQ3[2,:])

# ╔═╡ 613aceaa-61a6-11eb-3ebb-47d2098a108e
md"
## Question 4

##### Answer:
It doubles from `0.19 V/m` to `0.38 V/m`

"

# ╔═╡ 738a33da-61b9-11eb-360f-aff73553fd68
md"
## Question 5

##### Answer:

$
\begin{aligned} E \displaystyle \propto dq
\end{aligned}$

"

# ╔═╡ ab45850e-5cf0-11eb-09de-cdd1a970c6e5
md"## Question 6"

# ╔═╡ 148bb47a-5cea-11eb-2c24-b702525f32e5
md"![](https://i.imgur.com/JRmAd9P.png)"

# ╔═╡ f3deac54-619c-11eb-164d-3b50118d91c5
data = [
        50              100             150     200     250     300     350;
        25.6    5.09    1.71    0.82    0.44    0.27    0.17]

# ╔═╡ 0edcf9f8-5cf3-11eb-3c35-db30a5695de9
dataFrame = DataFrame(Distance = data[1,:], Magnitude= data[2,:])

# ╔═╡ bdd8daf4-5cf0-11eb-1f64-9149e5e74e12
begin
        distances = data[1,:]; magnitudes = data[2,:]
        plot(distances, magnitudes, title="Plot 1", label="Real Data", lw=3)
        line2 = [1/x^2 for x in 1:7]*25
        plot!(distances, line2 , label="1/x^2")

        line2 = [1/x^3 for x in 1:7]*25
        plot!(distances, line2, label="1/x^3")
        plot!(xlabel="Distance [m]", ylabel="E-field [V/m]")

end

# ╔═╡ 9bf1fd0c-5cf6-11eb-10c7-19216015602d
md"### Answer:
the value of `n` is going to be between `2` and `3` as seen in the graph.
"

# ╔═╡ 0369de5e-5cfd-11eb-2421-534f52fe3db1
md"## Question 7
### Answer:

$
\begin{aligned}E_{total}=E_{1}+E_{2}=\dfrac{kq}{\left(r+d\right) ^{2}}-\dfrac{kq}{r^{2}}=\\
=\dfrac{kq}{\left( r+d\right) ^{2}}-\dfrac{kq}{r^{2}}=kq\left( \dfrac{r^{2}-\left( r+d\right) ^{2}}{r^{2}\left( r+d\right) ^{2}}\right) \\ =kq\left( \dfrac{r^{2}-r^{2}-2rd-d^{2}}{r^{2}\left( r+d\right) ^{2}}\right) \\ =kq\left( -d\right) \left( \dfrac{2r+d}{r^{2}\left( r+1\right) ^{2}}\right)\\

\begin{aligned}\because r >>d \ ; \ \therefore E_{tota1}\approx kq\left( \dfrac{-d}{r^{3}}\right)\end{aligned}\approx 0

\end{aligned}$

The equation matches confirms the observation in the graph. As the r increases, the field strength decays exponentially.
"

# ╔═╡ ad446368-5d20-11eb-3cad-c72fd941d953
md"
## Question 8

No, the observation would not change with a change of polarity

## Question 9
![](https://i.imgur.com/DvrqFgI.png)
The charge Q is in an **unstable equilibrium**, because at far away distance, when nudged there is no (neglegible) restoring force.

## Question 10
![](https://i.imgur.com/0CkOshH.png)
The charge Q is in an **unstable equilibrium**, because any small nudge vertically will throw it away. Interestingly, if charges were -ve, Q is stable vertically but unstable horizontally.

## Question 11
![](https://i.imgur.com/vHrfXYp.jpg)
The charge Q is in an **unstable equilibrium**, because any small nudge horizontally will throw it away.

## Question 12
![](https://i.imgur.com/YGXZ1C4.png)
The charge Q is in an **unstable equilibrium**, because any small nudge horizontally will throw it away. Again, one direction is stable with one polarity, and switches with the polarity changes.

"

# ╔═╡ Cell order:
# ╟─61ca784e-5ceb-11eb-1423-5f9d48e72f26
# ╟─a9d90294-61a6-11eb-1641-75e225e67315
# ╟─f4dbabba-61d4-11eb-1475-716ef6ea6109
# ╟─f688a336-61a3-11eb-1f9d-754da19d57ac
# ╠═456010ee-619e-11eb-0b5d-c5b88755b425
# ╟─c5c0813e-61a2-11eb-3a8d-09f01538c980
# ╟─30bcb2f4-61a4-11eb-3765-a365cf2665b5
# ╟─0fd26dd4-61a6-11eb-23bb-bf99cdd17325
# ╠═06c52ba4-61bb-11eb-27a2-e9219195e2db
# ╠═719bfbe0-61b3-11eb-19ab-d9fab0dff0f0
# ╟─613aceaa-61a6-11eb-3ebb-47d2098a108e
# ╟─738a33da-61b9-11eb-360f-aff73553fd68
# ╟─ab45850e-5cf0-11eb-09de-cdd1a970c6e5
# ╟─148bb47a-5cea-11eb-2c24-b702525f32e5
# ╠═f3deac54-619c-11eb-164d-3b50118d91c5
# ╠═0edcf9f8-5cf3-11eb-3c35-db30a5695de9
# ╠═bdd8daf4-5cf0-11eb-1f64-9149e5e74e12
# ╟─9bf1fd0c-5cf6-11eb-10c7-19216015602d
# ╟─0369de5e-5cfd-11eb-2421-534f52fe3db1
# ╟─ad446368-5d20-11eb-3cad-c72fd941d953


struct Rect{T <: Number}
    lxv::T
    lyv::T
    rxv::T
    ryv::T
    Rect{T}(lxv::T, lyv::T, rxv::T, ryv::T) where T <: Number =
        new(min(lxv, rxv), min(lyv, ryv), max(lxv, rxv), max(lyv, ryv))

end

function Rect{T}(m::Matrix{T}) where T <: Number
    @assert size(m) == (2, 2) "Invalid values"
    return Rect{T}(m[1, 1], m[2, 1], m[1, 2], m[2, 2])
end
Rect{T}(r::Rect) where T <: Number =
    Rect{T}(convert(T, r.lxv), convert(T, r.lyv),
            convert(T, r.rxv), convert(T, r.ryv))
Rect(m::Matrix{T}) where T <: Number = Rect{T}(m)

matrix(r::Rect) = [r.lxv r.rxv; r.lyv r.ryv]

function Rect(lx::Number, ly::Number, rx::Number, ry::Number)
    t = promote(lx, ly, rx, ry)
    return Rect{eltype(t)}(t...)
end

Base.convert(::Type{Rect{T}}, r::Rect{S}) where {T <: Number, S <: Number} =
    T === S ? r : Rect{T}(r)

Base.promote_rule(::Type{Rect{T}},
                  ::Type{Rect{S}}) where {T <: Number, S <: Number} =
    Rect{promote_type(T, S)}

Base.show(io::IO, r::Rect) =
    print(io, "Rect:[$(lx(r)) $(ly(r)) $(rx(r)) $(ry(r))]")

lb(r) = (r.lxv, r.lyv)
ru(r) = (r.rxv, r.ryv)
lx(r) = r.lxv
ly(r) = r.lyv
rx(r) = r.rxv
ry(r) = r.ryv

xplot(r::Rect{T}) where T = T[lx(r), rx(r), rx(r), lx(r)]
yplot(r::Rect{T}) where T = T[lx(r), lx(r), rx(r), rx(r)]

coord(r::Rect, axis::Int) = axis == 1 ? (r.lxv, r.rxv) : (r.lyv, r.ryv)
x(r) = coord(r, 1)
y(r) = coord(r, 2)
c_lo(r, axis)   = axis == 1 ? r.lxv : r.lyv
c_hi(r, axis)   = axis == 1 ? r.rxv : r.ryv

hlines(r::Rect) =
    [Line(lx(r), ly(r), rx(r), ly(r)), Line(lx(r), ry(r), rx(r), ry(r))]
vlines(r::Rect) =
    [Line(lx(r), ly(r), lx(r), ry(r)), Line(rx(r), ly(r), rx(r), ry(r))]
lines(r::Rect) = append!(hlines(r), vlines(r))
olines(r::Rect) =
    (ls = lines(r); [ls[1], ls[4], reverse(ls[2]), reverse(ls[3])])
diags(r::Rect)  =
    [Line(lx(r), ly(r), rx(r), ry(r)), Line(rx(r), ly(r), lx(r), ry(r))]
cg(r::Rect) = div(diags(r)[1], 1//2)

Base.union(r1::Rect, r2::Rect) =
    Rect(min(lx(r1), lx(r2)), min(ly(r1), ly(r2)),
         max(rx(r1), rx(r2)), max(ry(r1), ry(r2)))

Base.union(r1::Rect, r2::Rect, y::Rect...) = Base.union(r1, union(r2, y...))
Base.union(r::Rect) = r
function Base.union(rs::AbstractVector{Rect{T}}) where T
    lxv, lyv, rxv, ryv = typemax(T), typemax(T), typemin(T), typemin(T)
    for r in rs
        lxv > lx(r) && (lxv = lx(r))
        lyv > ly(r) && (lyv = ly(r))
        rxv < rx(r) && (rxv = rx(r))
        ryv < ry(r) && (ryv = ry(r))
    end
    return Rect(lxv, lyv, rxv, ryv)
end

Base.intersect(r1::Rect, r2::Rect) = intersect(promote(r1, r2)...)

function Base.intersect(r1::Rect{T}, r2::Rect{T}) where T <: Number
    l = max.(lb(r1), lb(r2))
    r = min.(ru(r1), ru(r2))
    l1 = l .+ pcTol(T)
    any(l1 .>= r) && return nothing
    return Rect(l[1], l[2], r[1], r[2])
end

Base.:(==)(r1::Rect{T}, r2::Rect{T}) where {T <: Number} =
    abs(r1.lxv - r2.lxv) <= pcTol(T) &&
    abs(r1.lyv - r2.lyv) <= pcTol(T) &&
    abs(r1.rxv - r2.rxv) <= pcTol(T) &&
    abs(r1.ryv - r2.ryv) <= pcTol(T)
Base.:(==)(r1::Rect, r2::Rect) = ==(promote(r1, r2)...)

inside(p::Tuple{T, T}, r::Rect{T}) where T <: Number =
    r.lxv <= p[1] <= r.rxv && r.lyv <= p[2] <= r.ryv

function inside(p::Tuple{T1, T2}, r::Rect{S}) where {T1 <: Number,
                                                     T2 <: Number,
                                                     S  <: Number}
    ST = promote_type(S, T1, T2)
    inside(convert(Tuple{ST, ST}, p), convert(Rect{ST}, r))
end

inside(ri::Rect, ro::Rect) = intersect(ri, ro) == ri

intersects(r1::Rect, r2::Rect) = intersect(r1, r2) !== nothing

function intersects(r::Rect, l::Line)
    (inside((sx(l), sy(l)), r) || inside((ex(l), ey(l)), r)) &&
        return true
    for tl in lines(r)
        intersects(tl, l) && return true
    end
    return false
end

h(r::Rect) = ry(r) - ly(r)
w(r::Rect) = rx(r) - lx(r)
area(r::Rect) = h(r) * w(r)

perimeter(r::Rect) =  (s = h(r) + w(r); s + s)
to_plot_shape(r::Rect) =
    ([lx(r), rx(r), rx(r), lx(r)], [ly(r), ly(r), ry(r), ry(r)])

xsort(r1::Rect, r2::Rect, reverse=false) =
    sortr(r1, r2, reverse=reverse, axis=1)
ysort(r1::Rect, r2::Rect, reverse=false) =
    sortr(r1, r2, reverse=reverse, axis=2)

sortr(r1::Rect, r2::Rect; reverse=false, axis=1) =
    (!reverse && c_lo(r1, axis) > c_lo(r2, axis)) ? (r2, r1) : (r1, r2)

has_x_overlap(r1::Rect, r2::Rect; isopen=false) =
    has_overlap(r1::Rect, r2::Rect, axis=1, isopen=isopen)
has_y_overlap(r1::Rect, r2::Rect; isopen=false) =
    has_overlap(r1::Rect, r2::Rect, axis=2, isopen=isopen)

function has_overlap(ri1::Rect{T1}, ri2::Rect{T2};
                     axis=1, isopen=false) where {T1, T2}
    r1, r2 = promote(ri1, ri2)
    T = typeof(r1.lxv)
    tol = isopen ? pcTol(T) : zero(T)
    rt = axis == 1 ? Rect(lx(r1) + tol, ly(r1), rx(r1) - tol, ry(r1)) :
                     Rect(lx(r1), ly(r1) + tol, rx(r1), ry(r1) - tol)
    r1, r2 = sortr(rt, r2, reverse=false, axis=axis)
    return c_lo(r1, axis) <= c_lo(r2, axis) <= c_hi(r1, axis)
end

"""
```
    projectX(r1::Rect, r2::Rect) -> (left, overlap, right)
```
Projects the rectangles along the X-axis and returns three parts of rectangles.

`left`: The left segment of the projection
`overlap`: If there is any overlap between the rectangles
`right`: The right segment of the projection

Each portion is returned as a tuple.

If the rectangle is part of the first rectangle, it's returned as the first
element of the tuple. `nothing` is returned for a part when a portion is not
available.
"""
projectX(r1::Rect, r2::Rect) = project(r1, r2, axis=1)

"""
```
    projectY(r1::Rect, r2::Rect) -> (bottom, overlap, top)
```
Projects the rectangles along the Y-axis and returns three parts of rectangles.

`bottom`: The bottom segment of the projection
`overlap`: If there is any overlap between the rectangles
`top`: The top segment of the projection

Each portion is returned as a tuple.

If the rectangle is part of the first rectangle, it's returned as the first
element of the tuple. `nothing` is returned for a part when a portion is not
available.
"""
projectY(r1::Rect, r2::Rect) = project(r1, r2, axis=2)

project(r1::Rect, r2::Rect; axis::Int=1) = project(promote(r1, r2)...; axis=axis)
function project(r1::Rect{T}, r2::Rect{T}; axis::Int=1) where T <: Number
    # l=low and h=high
    lr, hr = sortr(r1, r2, axis=axis)
    flip = lr != r1

    cl = coord(lr, axis)
    ch = coord(hr, axis)
    axl = sort([cl[1], cl[2], ch[1], ch[2]])

    # Low segment
    if c_lo(lr, axis) + pcTol(T) < c_lo(hr, axis)
        l = axis == 1 ? Rect{T}(axl[1], ly(lr), axl[2], ry(lr)) :
                        Rect{T}(lx(lr), axl[1], rx(lr), axl[2])
        low = !flip ? (l, nothing) : (nothing, l)
        # Non-overlap
        l == lr &&
            return low, (nothing, nothing), !flip ? (nothing, hr) : (hr, nothing)
    else
        low = nothing, nothing
    end

    #Overlap segment
    om1, om2 = matrix(r1), matrix(r2)
    om1[axis, 1] = om2[axis, 1] = axl[2]
    om1[axis, 2] = om2[axis, 2] = axl[3]

    overlap = Rect{T}(om1), Rect{T}(om2)

    #High segment
    if -pcTol(T) <= c_hi(r1, axis) - c_hi(r2, axis) <= pcTol(T)
        high = nothing, nothing
    else
        hm = c_hi(r1, axis) > c_hi(r2, axis) ? matrix(r1) : matrix(r2)
        hm[axis, 1] = axl[3]
        hm[axis, 2] = axl[4]
        high = c_hi(r1, axis) > c_hi(r2, axis) ? (Rect{T}(hm), nothing) :
                                                 (nothing, Rect{T}(hm))
    end
    return low, overlap, high
end

"""
```
    visibleX(r1::Rect, r2::Rect) -> Rect
    visibleY(r1::Rect, r2::Rect) -> Rect
```
Projects the rectangles along the X-axis (Y-axis) and returns a rectangle area
which is completely visible from both rectangles.

`nothing` is returned when there is no overlap along the X-axis.
"""
visibleX(r1::Rect, r2::Rect) = visible(r1::Rect, r2::Rect, axis=1)
visibleY(r1::Rect, r2::Rect) = visible(r1::Rect, r2::Rect, axis=2)

visible(r1::Rect, r2::Rect; axis::Int=1) = visible(promote(r1, r2)...; axis=axis)

function visible(r1::Rect{T}, r2::Rect{T}; axis::Int=1) where T <: Number
    l, ox, r = project(r1, r2, axis=axis)
    ox == (nothing, nothing) && return nothing
    saxis = (axis == 1 ? 2 : 1)
    tr1, tr2 = sortr(ox[1], ox[2], axis=saxis)
    m = matrix(tr1)
    m[saxis, 1] = c_hi(tr1, saxis)
    m[saxis, 2] + pcTol(T) > c_lo(tr2, saxis) &&
        return Line(lx(tr2), ly(tr2), lx(tr2), ry(tr2))
    m[saxis, 2] = c_lo(tr2, saxis)
    return Rect(m)
end

function get_overlapped_dist(l, h, f, a1, a2)
    # l = low and h = high
    lr, adl = l[1] === nothing ? (l[2], a2) : (notvoid(l[1]), a1)
    wlal    = lr   === nothing ? zero(a1)   : f(notvoid(lr))*area(notvoid(lr))

    hr, adh = h[1] === nothing ? (h[2], a2) : (notvoid(h[1]), a1)
    whah    = hr   === nothing ? zero(a1)   : f(notvoid(hr))*area(notvoid(hr))

    d = Float64(wlal)/Float64(adl) + Float64(whah)/Float64(adh)
    return d/2.0
end

"""
```
    avg_min_dist(r1::Rect, r2::Rect) -> dx::Float64, dy::Float64
```
Rectangles are essentially point sets. Hence, one can perceive existence of a
minimum distance of one point in `r1` from `r2`. Similar, distance would also
exist for every point in `r2` from `r1`. While, technically Euclidean distance
metric can exist, the computation is fairly cumborsome. Here, we use the city
block distance or L1-metric.

`dx`: The distance in the x-direction
`dy`: The distance in the y-direction

The minimum distance will be `zero` when the rectangles are intersecting.
The distance also will be lower in a specific direction if there is an overlap of
 the rectangles in that direction
"""
function avg_min_dist(r1::Rect, r2::Rect)
    intersect(r1, r2) != nothing && return 0.0, 0.0
    h1, h2 = Float64(h(r1)),    Float64(h(r2))
    w1, w2 = Float64(w(r1)),    Float64(w(r2))
    a1, a2 = Float64(area(r1)), Float64(area(r2))

    hg = Float64(min(abs(ry(r2) - ly(r1)), abs(ry(r1) - ly(r2))))
    wg = Float64(min(abs(rx(r2) - lx(r1)), abs(rx(r1) - lx(r2))))

    dy = hg + (h1*a1 + h2*a2)/(2.0*(a1 + a2))
    dx = wg + (w1*a1 + w2*a2)/(2.0*(a1 + a2))

    l, ox, r = projectX(r1, r2)
    ox != (nothing, nothing) &&
        return get_overlapped_dist(l, r, w, a1, a2), dy
    b, oy, t = projectY(r1, r2)
    oy != (nothing, nothing) && return dx, get_overlapped_dist(b, t, h, a1, a2)
    return dx, dy
end

"""
```
    min_dist(r1::Rect, r2::Rect) -> dx, dy
```
Minimum distance or gap between two rectangles.

`dx`: The distance in the x-direction
`dy`: The distance in the y-direction

The minimum distance will be `zero` when the rectangles are overlapping in a
direction.
"""
function min_dist(r1::Rect{T}, r2::Rect{T}) where T <: Number
    r1, r2 = xsort(r1, r2)
    dx = has_x_overlap(r1, r2) ? zero(T) : lx(r2) - rx(r1)
    r1, r2 = ysort(r1, r2)
    dy = has_y_overlap(r1, r2) ? zero(T) : ly(r2) - ry(r1)
    return dx, dy
end
min_dist(r1::Rect, r2::Rect) = min_dist(promote(r1, r2)...)

mutable struct OrderedRectMap{T <: Number, V, D}
    data::IntervalTree{T, IntervalTree{T, V}}
    reverseMax::T
    function OrderedRectMap{T, V, D}(
        ;reverseMax::T1=zero(T)) where {T <: Number, V, D, T1 <: Number}
        @assert reverseMax >= zero(T) "Invalid max value for reverse ordering"
        new(IntervalTree{T, IntervalTree{T, V}}(), convert(T, reverseMax))
    end
end

const OrderedRectMapX{T, V} = OrderedRectMap{T, V, 1}
const OrderedRectMapY{T, V} = OrderedRectMap{T, V, 2}

function create_ordered_map(rects::AbstractVector{Rect{T}},
                            values::AbstractVector{V};
                            dir::Int=1,
                            reverseMax::T1=zero(T)) where {T <: Number,
                                                           V,
                                                           T1 <: Number}
    map = OrderedRectMap{T, V, dir}(reverseMax=reverseMax)
    nr = iterate(rects)
    nv = iterate(values)
    odir = dir == 1 ? 2 : 1
    while nr !== nothing
        rect, itr = nr
        v2,   itv = nv
        nr = iterate(rects, itr)
        nv = iterate(values, itv)
        insert_rect!(map, rect, v2)
    end
    return map
end

function Base.intersect(orm::OrderedRectMap{T1, V, D},
                        rect::Rect{T2}, dX::T1, dY::T1;
                        dirX=0, dirY=0,
                        fstop::Function=(x) -> false) where {T1 <: Number,
                                                             T2 <: Number, V, D}
    dl = dirX > 0  ? zero(T1) : -dX
    dr = dirX < 0  ? zero(T1) :  dX
    dd = dirY > 0  ? zero(T1) : -dY
    du = dirY < 0  ? zero(T1) :  dY

    r = convert(Rect{T1}, rect)
    ors, ovs = Vector{Rect{T1}}(), Vector{V}()
    while true
        tr = Rect(lx(r) + dl, ly(r) + dd, rx(r) + dr, ry(r) + du)
        rs, vs = intersect(orm, tr)
        length(rs) == 0 && return rs, vs
        ttr = union(rs)
        fstop(ttr) && return ors, ovs
        r == ttr && return rs, vs
        r, ors, ovs = ttr, rs, vs
    end
end

function Base.intersect(orm::OrderedRectMap{T1, V, D},
                        r::Rect{T2};
                        isopen = true) where {T1 <: Number, T2 <: Number, V, D}
    rect = convert(Rect{T1}, r)
    dir = D
    odir = dir == 1 ? 2 : 1
    minv1, maxv1 = coord(rect, dir)
    if orm.reverseMax != zero(T1)
        minv1, maxv1 = (orm.reverseMax - maxv1), (orm.reverseMax - minv1)
    end
    minv2, maxv2 = coord(rect, odir)
    tol = isopen ? pcTol(T1) : zero(T1)
    imv1 = intersect(orm.data, Interval(minv1 + tol, maxv1 - tol))
    retr = Vector{Rect{T1}}()
    retv = Vector{V}()
    nimv1 = iterate(imv1)
    while nimv1 !== nothing
        v1, iv1 = nimv1
        imv2 = intersect(v1[2], Interval(minv2 + tol, maxv2 - tol))
        nimv2 = iterate(imv2)
        while nimv2 !== nothing
            v2, iv2 = nimv2
            m = zeros(T1, (2,2))
            m[ dir, 1], m[ dir, 2] =
                (orm.reverseMax == zero(T1)) ? (v1[1].lo, v1[1].hi) :
                (orm.reverseMax - v1[1].hi, orm.reverseMax - v1[1].lo)
            m[odir, 1], m[odir, 2] = v2[1].lo, v2[1].hi
            push!(retr, Rect{T1}(m))
            push!(retv, v2[2])
            nimv2 = iterate(imv2, iv2)
        end
        nimv1 = iterate(imv1, iv1)
    end
    return retr, retv
end

function insert_rect!(orm::OrderedRectMap{T1, V, D},
    r::Rect{T2}, v::V) where {T1 <: Number, T2 <: Number, V, D}
    rect = convert(Rect{T1}, r)
    dir = D
    odir = dir == 1 ? 2 : 1
    minv1, maxv1 = coord(rect, dir)
    if orm.reverseMax != zero(T1)
        minv1, maxv1 = (orm.reverseMax - maxv1), (orm.reverseMax - minv1)
    end
    imv = get!(orm.data, Interval(minv1, maxv1), IntervalTree{T1, V}())
    minv2, maxv2 = coord(rect, odir)
    itv2 = Interval(minv2, maxv2)
    tv = get!(imv, itv2, v)
    tv === v && return nothing
    tv = delete!(imv, itv2)
    imv[itv2] = v
    return tv
end

function delete_rect!(orm::OrderedRectMap{T1, V, D},
    r::Rect{T2}) where {T1 <: Number, T2 <: Number, V, D}
    rect = convert(Rect{T1}, r)
    dir = D
    odir = dir == 1 ? 2 : 1
    minv1, maxv1 = coord(rect, dir)
    if orm.reverseMax != zero(T1)
        minv1, maxv1 = (orm.reverseMax - maxv1), (orm.reverseMax - minv1)
    end
    itv1 = Interval(minv1, maxv1)
    imv = get(orm.data, itv1, IntervalTree{T1, V}())
    isempty(imv) && return nothing
    minv2, maxv2 = coord(rect, odir)
    ret = delete!(imv, Interval(minv2, maxv2))
    isempty(imv) && delete!(orm.data, itv1)
    return ret === nothing ? ret : ret[2]
end

function line_xsection(rect::Rect{T1}, ls::Vector{Line{T2}},
                       flines::Function, flt::Function,
                       o::Tuple{Int, Int}) where {T1 <: Number, T2 <: Number}
    r = convert(Rect{T2}, rect)
    l = flines(r)
    xlines = Vector{Int}()
    minv = searchsortedfirst(ls, l[o[1]], lt=flt)
    maxv = searchsortedlast(ls,  l[o[2]], lt=flt)
    for k = minv:maxv
        !intersects(r, ls[k]) && continue
        push!(xlines, k)
    end
    return xlines
end

"""
    Given a Rectangle and a set of already sorted set of vertical lines ordered
    left to right, provides the indices that intersect the rectangle.
"""
vline_xsection(r::Rect{T1}, vls::Vector{Line{T2}}) where {T1 <: Number,
                                                          T2 <: Number} =
    line_xsection(r, vls, vlines, vert_asc, (1, 2))

"""
    Given a Rectangle and a set of a sorted set of horizontal lines ordered
    top to bottom, provides the indices that intersect the rectangle.
"""
hline_xsection(r::Rect{T1}, hls::Vector{Line{T2}}) where {T1 <: Number,
                                                          T2 <: Number} =
    line_xsection(r, hls, hlines, horiz_desc, (2, 1))


using MatchingMarkets
using Test

include("test_deferred_acceptance.jl")
include("test_matching_tools.jl")
include("test_util.jl")


### A Pluto.jl notebook ###
# v0.18.1

using Markdown
using InteractiveUtils

# ╔═╡ 8c8388cf-9891-423c-8db2-d40d870bb38e
using PlutoUI

# ╔═╡ 54778212-3702-467d-8a96-4fa18b3ccd63
using LinearAlgebra

# ╔═╡ 6f4274b5-87e2-420d-83d2-83a8408650cd
# edit the code below to set your name and kerberos ID (i.e. email without @mit.edu)

student = (name = "Jazzy Doe", kerberos_id = "jazz")

# you might need to wait until all other cells in this notebook have completed running.
# scroll around the page to see what's up

# ╔═╡ ea28bf57-ba62-41ce-8be6-d38ca2c5caa3
[j for j=1:1000 if j*(1/j) != 1]

# ╔═╡ f0e59676-fd8e-4609-833e-f7d1a5a6e9e3
smallest_j = 49

# ╔═╡ a5be0775-68de-41ce-95cd-1465723d099b
error = 1 - smallest_j * (1 / smallest_j)

# ╔═╡ 4abec609-09cd-4f86-8d86-a7d02325cc7b
log2(error)

# ╔═╡ d221c61c-a4ab-4a82-b89d-52735d957cae
32 * 23 - 736

# ╔═╡ c3d49b98-a9c1-4aba-becc-7fa84f4bbc75
3.2 * 23 - 73.6

# ╔═╡ 7b33c09c-2ef2-4b97-b5a5-5fdf9268f76b
3.2 * 2.3 - 7.36

# ╔═╡ 615b88b6-8505-11eb-0a7c-294ee5ae7474
begin
	struct FirstRankOneMatrix
		# Your code here
		v::Vector{Float64}
		w::Vector{Float64}
		FirstRankOneMatrix(v, w) = new(v, w)
	end

	# Add the extra constructor here
	FirstRankOneMatrix(v) = FirstRankOneMatrix(v, v)

end

# ╔═╡ b0db7388-850c-11eb-0915-597f1fa5ab93
ten_twelve = FirstRankOneMatrix(1:10, 1:12) # Your code here

# ╔═╡ 2dfc2ef9-3efa-4b82-960f-f2ef0171e9eb
sqrt === Base.sqrt

# ╔═╡ d4a0baea-cc02-4d91-a938-8181dea7b47a
filter === Base.filter

# ╔═╡ fada4734-8505-11eb-3f2b-d1f1ef391ba4
function Base.size(M::FirstRankOneMatrix)
	return length(M.v), length(M.w)
end

# ╔═╡ 590dfe1a-8506-11eb-0069-d7cd91f02a65
function Base.getindex(M::FirstRankOneMatrix, i, j)
	return M.v[i] * M.w[j]
end

# ╔═╡ 941b6b10-8ae9-11eb-3bf8-b732f5f60af3
ten_twelve

# ╔═╡ a243a400-8af3-11eb-0637-cf8f80aae86d
ones(10, 12) # An example matrix (two-dimensional array)

# ╔═╡ aa756549-1b9f-41a1-a2e1-713166e4211a
(ones(12, 12) + ones(12, 12)) * (ones(12, 12) + ones(12, 12))

# ╔═╡ b577420c-8501-11eb-267a-719125315fe1
begin
	struct RankOneMatrix{T} <: AbstractMatrix{T}
		v::AbstractVector{T}
		w::AbstractVector{T}
		RankOneMatrix(v::AbstractVector{T}, w::AbstractVector{T}) where T = new{T}(v, w)
	end

	# Add the two extra constructors
	# (Should we make these missing by default? if so - remove hint below)
	RankOneMatrix(v) = RankOneMatrix(v, v)
end

# ╔═╡ eb612772-8b06-44bb-a36a-827435cbb2ee
RankOneMatrix([1,2], [3,4,5]);

# ╔═╡ d3e7c8d1-4b4a-47b6-9c96-150333078f42
RankOneMatrix(1:10);

# ╔═╡ f2d8b45c-8501-11eb-1c6a-5f819c240d9d
function Base.size(M::RankOneMatrix)
	return length(M.v), length(M.w)
end

# ╔═╡ ed72e880-8afa-11eb-3a4a-175a838188d9
function Base.getindex(M::RankOneMatrix, i, j)
	return M.v[i] * M.w[j]
end

# ╔═╡ 7b3fb0ef-9a9e-401c-8c09-e5615134a4ad
R2 = RankOneMatrix([1,2], [3,4,5])

# ╔═╡ fc962c72-8501-11eb-2821-cbb7a52d5f61
M = RankOneMatrix(1:10)

# ╔═╡ 0887ee78-0e8a-41c7-90e7-44237acc1477
collect(M)

# ╔═╡ 1705dc7b-9f93-440b-acbb-ecb362d08125
typeof(collect(M))

# ╔═╡ ba3cb45a-8502-11eb-2141-6369b0e08807
begin
	struct RankTwoMatrix{T} <: AbstractMatrix{T}
		# Your code here
		A::RankOneMatrix{T}
		B::RankOneMatrix{T}
		RankTwoMatrix(A::RankOneMatrix{T}, B::RankOneMatrix{T}) where T = new{T}(A, B)
	end

	# Add a constructor that uses two vectors/ranges
	RankTwoMatrix(v1, v2) = RankTwoMatrix(RankOneMatrix(v1), RankOneMatrix(v2))
end

# ╔═╡ f5a95dd8-850d-11eb-2aa7-2dcb1868577f
RankTwoMatrix(1.0:10.0, 0.0:0.1:0.9);

# ╔═╡ c784e02c-8502-11eb-3efa-7f4c45f4274c
function Base.getindex(M::RankTwoMatrix, i, j)
	return M.A[i, j] + M.B[i, j]
end

# ╔═╡ 0bab818e-8503-11eb-02b3-178098599847
function Base.size(M::RankTwoMatrix)
	return size(M.A)
end

# ╔═╡ b6717812-8503-11eb-2729-39bfdc1fd2f9
struct LowRankMatrix <: AbstractMatrix{Float64}
	Ms::Vector{RankOneMatrix}
	rank::Int
	LowRankMatrix(Ms, rank) = new(Ms, rank)
end

# ╔═╡ c49e350e-8503-11eb-15de-7308dd03dc08
function Base.getindex(M::LowRankMatrix, i, j)
	return sum(M.Ms[k][i, j] for k=1:M.rank)
end

# ╔═╡ eadb174e-2c1d-48c8-9de2-99cdc2b38d32
md"_homework 5, version 2_"

# ╔═╡ 0560cf7b-9986-402a-9c40-779ea7a7292d
md"""

Submission by: **_$(student.name)_** ($(student.kerberos_id)@mit.edu)
"""

# ╔═╡ 082542fe-f89d-4774-be20-1e1a78f21291
md"""

# **Homework 5**: _Structure_
`18.S191`, Spring 2021

This notebook contains _built-in, live answer checks_! In some exercises you will see a coloured box, which runs a test case on your code, and provides feedback based on the result. Simply edit the code, run it, and the check runs again.

_For MIT students:_ there will also be some additional (secret) test cases that will be run as part of the grading process, and we will look at your notebook and write comments.

Feel free to ask questions!
"""

# ╔═╡ aaa41509-a62d-417b-bca7-a120e3a5e5b2
md"""
#### Intializing packages
_When running this notebook for the first time, this could take up to 15 minutes. Hang in there!_
"""

# ╔═╡ ac35c6b3-a142-4b7a-921b-a2bfe1d84713
md"""
## **Exercise 1:** _When is zero not quite zero?_
Students used to pure math are sometimes surprised to see numbers like `1e-15`, `1e-16`, or `1e-17` appearing in computations when `0` might have been expected. At first glance, this behaviour seems 'random' or 'noisy', but in this exercise, we will try to demonstrate some structure behind floating point artihmetic.

#### Exercise 1.1
👉 Find all integers $j$ with $1 ≤ j ≤ 1000$ for which Julia's result satisfies `j*(1/j) ≠ 1`.

"""

# ╔═╡ a5901f93-007f-4a30-97fc-29b367ec47c6
md"""
Notice that when you re-run the computation, the result does not change. Floating-point arithmetic is not random!

#### Exercise 1.2

👉 Take the smallest number `j` you found above and compute the error, i.e. the distance between `j*(1/j)` and `1`.

Is this an integer power of 2?  Which one? (`log2` might help.)
"""

# ╔═╡ 32e073bb-943f-4fa9-b15f-5ec18feecf15
md"""
The error is indeed a power of 2, it's 1/2^53
"""

# ╔═╡ 7d1e1724-cd1a-483b-af40-f24ae5301849
md"""
#### Exercise 1.3
Caculate all of the following:
* 32 * 23  - 736
* 3.2 * 23 - 73.6
* 3.2 * 2.3 - 7.36
"""

# ╔═╡ 224acb51-c0db-4f04-b865-8349d6a28e98
md"""
We wanted to show you that floating point arithmetic has structure to it.  It is not the fuzz or slop that you may have seen as experimental errors in maybe a chemistry or physics class.  If you are interested, we may show more later in the semester.
"""

# ╔═╡ a7e9ab20-8501-11eb-2a7a-af8fd495b21e
md"""
## **Exercise 2:** _Rank-one matrices_
In this exercise we will go into some more detail about how to define types in Julia in order to make a structured-matrix type, similar to the `OneHotVector` type from class.

To begin, we will make a type to represent a rank-1 matrix. A *rank-1 matrix* is a matrix whose columns are all multiples of each other. This already tells us a lot about the matrix; in fact, we can represent any such matrix as the outer product of two vectors `v` and `w`. *Only* the two vectors will be stored; the matrix elements will be *calculated* on demand, i.e. when we *index* into the object.
"""

# ╔═╡ 8ce8c4bc-8505-11eb-2357-c50a70b8745c
md"""
#### Exercise 2.1
Let's make a `FirstRankOneMatrix` type that contains two vectors of floats, `v` and `w`. Here `v` represents a column and `w` the multpliers for each column.

We include (in the same cell, due to requirements of Pluto) a constructor that takes a single vector `v` and duplicates it.
"""

# ╔═╡ 9f534aa0-8505-11eb-3476-4d326fb19d42
md"""
👉 Create an object of type `FirstRankOneMatrix` representing the multiplication table of the numbers from 1 to 10 and the numbers from 1 to 12. Call it `ten_twelve`.
"""

# ╔═╡ 7aac43b3-49ce-46ea-854b-d292ac0591c7
md"""
#### Exercise 2.2 - _extending Base methods_
Right now, our `FirstRankOneMatrix` is just a container for two arrays. To make it _behave_ like a matrix, we add methods to functions in `Base`, Julia's standard set of functions. Most of the functions you have used so far come from base, for example:
"""

# ╔═╡ ce6e648c-ade5-4882-9f92-1ae3f878a5a3
md"""
These are built-in functions, and each function comes with a set of methods pre-defined (in Julia's source code _(which is mostly written in Julia)_). Uncomment the next cell to see the full list of methods for `size`. We will add a method to this list!
"""

# ╔═╡ 87fce3d8-765c-410c-a059-4dd51bd97254
md"""

Let's extend the `size` and `getindex` methods so that they can also work with our rank-1 matrices. This will allow us to easily access the size and entries of the matrix.
"""

# ╔═╡ e86d8ab6-8505-11eb-1995-137dfbff0191
md"""
👉 Extend `Base.size` for the type `FirstRankOneMatrix` to return the size as a tuple of values corresponding to each direction.
"""

# ╔═╡ 545a5916-8506-11eb-1a31-078957d791f8
md"""
👉 Extend `Base.getindex` for the type `FirstRankOneMatrix` to return the $(i, j)$th entry of the outer product.
"""

# ╔═╡ fe6df9bf-6059-4b76-af39-385d395ece72
Base.getindex(ten_twelve, 5, 11)

# ╔═╡ b9cb6192-8505-11eb-3d2c-790654bbc9a1
md"""
#### Exercise 2.3

If you ask Julia to display the value of an object of type `FirstRankOneMatrix`, you will notice that it doesn't do what we are used to, which is probably to display the whole matrix that it corresponds to. Let's see:
"""

# ╔═╡ 1b3cc404-9da8-4acd-9df5-5815ce167c34
md"""
This is what matrices normally look like in Julia:
"""

# ╔═╡ 4e926bfe-8734-11eb-1355-e1d1d9b36929
md"""
One possible workaround for this is to create a new function specifically to display a matrix of our custom type.

👉 Make a function `print_as_matrix` that prints the entries of a matrix of type `FirstRankOneMatrix` in a grid format. We'll test that it works below with the matrix `R` we already created.
"""

# ╔═╡ 9eb846c0-8737-11eb-101b-0191f715d8c9
md"""
We should now be able to see our `FirstRankOneMatrix`-type matrix displayed in the terminal! In Pluto, we have to use `with_terminal` to capture and display output printed to the _terminal_.
"""

# ╔═╡ a75f3c76-8506-11eb-1d83-cf5781b656a3
md"""
#### Exercise 2.4 - _AbstractMatrix_
In fact, Julia (together with Pluto) can do some of this work for us! Julia provides facilities to make our life easier when we tell Julia that our type *behaves like* an array. We do so by making the type a *subtype of* `AbstractMatrix`. This will let our type **inherit** the methods and attributes that come with the `AbstractMatrix` type by default - including some display functions that Pluto notebooks use.

Let's do all this on a new rank-one matrix type: `RankOneMatrix`. As we do this, we will also remove the restriction to `Float64`-type entries by using a *parametrised type* `T`. Parametrised types allow us flexibility to handle different types of entries without repeating a lot of code; we won't go too in-depth about these for now.
"""

# ╔═╡ 1d16e228-850e-11eb-1dca-41df85b706da
md"""
In the cell above, we added a second ('outer') constructor that takes a single vector as argument.

👉 Make sure that you can use both constructors by trying them out below.
"""

# ╔═╡ 3ed18ba0-850b-11eb-3e90-f188c54d9ce9
md"""
👉 Add `getindex` and `size` methods for this new type. These will allow us to access entries of our custom matrices with the usual index notation `M[i,j]`, as well as quickly retrieving their dimensions.
"""

# ╔═╡ 5a6dd3bc-af85-4e7d-b03c-b8c1bc88d850
Base.getindex(R2, 2, 3)

# ╔═╡ 91b1125a-850b-11eb-1a21-c7af293332fd
md"""
👉 Create an object of the new type for the $10 \times 10$ multiplication matrix, using a single range.
"""

# ╔═╡ 8f5c7c9c-850b-11eb-3d97-bf9c6b5d7d2e
md"""
You should see two things: Firstly, the matrix now contains integers, instead of floats (this is thanks to our parametrised type!). And secondly, Julia *automatically* displays the matrix as we wanted, once you have defined `getindex` and `size`, provided you have told Julia that your type is a subtype of `AbstractArray`!
"""

# ╔═╡ 248ab23e-850c-11eb-396f-5ba365f0f743
md"""
Julia also allows us to automatically convert the matrix to a normal ("dense") matrix, using either `collect` or `Array`. Let's try these out. You may need to re-run the cell below after completing the exercises.
"""

# ╔═╡ 8cab8d7d-32fc-4b43-bd54-fb0c78fa5d00
md"""
> Fun fact: we did not define a method for `Base.collect`, and yet it works! This is because we told Julia that our `RankOneMatrix` is a subtype of `AbstractMatrix`, for which `Base.collect` already has a method defined in Julia's source code. This fallback method uses `Base.getindex` and `Base.size`, which we did define.
"""

# ╔═╡ 6a08cb74-8510-11eb-2212-2f1f676f3d5d
md"""
#### Exercise 2.5

Why do we need a special type to represent special types of structured matrices? One reason is that not only do they give a more efficient representation in space (requiring less memory to store), they can also be more efficient in time, i.e. *faster*.

   For example, let's look at **matrix--vector mutiplication**. This is a *fundamental* part of many, *many* algorithms in scientific computing, and because of this, we usually want it to be as fast as possible.

   For a rank-one matrix given by $M = v w^T$, the matrix--vector product $M \cdot x$ is given by $(w \cdot x) v$. Note that $w \cdot x$ is a number (scalar) which is multiplying the vector element by element. This computation is much faster than the usual matrix-vector multiplication: we are taking advantage of structure!

"""

# ╔═╡ 61cbfc80-86f5-11eb-316f-6307cac7cbbb
md"""
👉 Define a function `matvec` that takes a `RankOneMatrix` `M` and a `Vector` `x` and carries out matrix-vector multiplication. We will be able to compare the result with doing the matrix--vector product using the corresponding dense matrix.
"""

# ╔═╡ 4864e4d6-850c-11eb-210c-0318b8660a9a
md"""
## Exercise 3: Low-rank matrices
"""

# ╔═╡ 58ce8d2a-850c-11eb-29e9-3fc966e129b1
md"""
In this exercise we will combine rank-1 matrices into low-rank matrices, which are sums of rank-1 matrices. Just like in the previous exercise, we will make custom types for these matrices, and we will be able to compute the entries of a matrix on-demand as we request them (instead of storing all of them always).
"""

# ╔═╡ 9d4d943e-8502-11eb-31cb-e11824b2dce0
md"""
#### Exercise 3.1

👉 Make a rank-2 matrix type `RankTwoMatrix` that contains two rank-1 matrices `A` and `B`.  For simplicity, you can use the `FirstRankOneMatrix` type.

   Include in the same cell a constructor for `RankTwoMatrix` that takes two vectors and makes the rank-one matrices from those vectors.

*Note*: In principle we should check when constructing the type that the input matrices have the same dimensions, but we will just assume that they do.
"""

# ╔═╡ 92a91904-850c-11eb-010e-c58ae218f541
md"""
#### Exercise 3.2
👉 Construct a rank two matrix out of the rank-1 matrix representing the multiplication table of $1.0:10.0$, together with the multiplication table of $0.0:0.1:0.9$.

   (Note that both range arguments must contain floats, so that we can add up entries.)
"""

# ╔═╡ dc714540-8afa-11eb-205b-7770074771c8
md"""
#### Exercise 3.3
👉 As with last time, extend the `getindex` and `size` methods for the `RankTwoMatrix` type. Keep in mind they are already defined for `RankOneMatrix`.
"""

# ╔═╡ aca709f0-8503-11eb-1144-1fc01cb85c39
md"""
#### Exercise 3.4

Making a custom type for rank-2 matrices is a step forward from rank-1 matrices: instead of storing two vectors, we store two rank-1 matrices themselves. What if we want to represent a rank-3 matrix? We would need to store *three* rank-1 matrices, instead of just two. What about rank-4, rank-5, and so on?

We can go even further and make a general custom type `LowRankMatrix` for rank-$k$ matrices, for general (ideally low) $k$. In this case, we should store two main things: the list of rank-1 matrices that our low-rank matrix is made up of, and also the *rank* of the matrix (which is how many rank-1 matrices we are storing).

👉 Complete the definition for the type `LowRankMatrix`. Remember to store both the rank-1 matrices and the rank of the matrix itself.
"""

# ╔═╡ 6e280730-8b00-11eb-04b7-615a389e792f
md"""
#### Exercise 3.5
👉 Extend the `getindex` and `size` methods to work with the `LowRankMatrix` type. As before, remember that these are already defined for `RankOneMatrix`.
"""

# ╔═╡ dd27f508-8503-11eb-36b9-33f5f99f78b0
function Base.size(M::LowRankMatrix)
	return size(M.Ms[1])
end

# ╔═╡ ead4c008-0e7e-4414-aced-d4a576423bd3
size === Base.size

# ╔═╡ 7adbcbab-66e7-4b58-b197-5c220ba1e9a2
methods(size);

# ╔═╡ 0b7c6cbe-57de-419d-adcb-8724791f9c89
Base.size(ten_twelve)

# ╔═╡ c7a15c5e-8505-11eb-3af2-2fa84b74b590
function print_as_matrix(M::FirstRankOneMatrix)
	m, n = size(M)
	println(m, "x", n, " FirstRankOneMatrix")

	pad = length(repr(maximum(M.w) * maximum(M.v))) + 1
	for i=1:m
		for j=1:n
			print(lpad(M[i, j], pad))
		end
		println()
	end
end

# ╔═╡ b0a0b9a4-850b-11eb-30f1-11f5270efe02
with_terminal() do
	print_as_matrix(ten_twelve)
end

# ╔═╡ 52451232-2c3d-4425-a94a-da1a955cf784
Base.size(R2)

# ╔═╡ ee58251a-8511-11eb-074c-5b1e27c4ebd4
function matvec(M::RankOneMatrix, x)
	m, _ = size(M)
	return sum(M.w[i] * x[i] for i in 1:m) * M.v
end

# ╔═╡ 93a75ea0-8b0c-11eb-3946-3d5d92487fb5
let
	comp1, comp2 = RankOneMatrix(1.0:1.0:10.0), RankOneMatrix(0.0:0.1:0.9)
	ex3 = LowRankMatrix([comp1, comp2], 2)
end

# ╔═╡ 1083d5ee-8512-11eb-0fdc-ab3987f94bc5
md"""
#### Exercise 3.6
👉 Extend the method `matvec` to work with our custom type `LowRankMatrix` (and a `Vector`). Remember that `matvec` is already defined for `RankOneMatrix`.
"""

# ╔═╡ 3fed837a-8512-11eb-1fdd-c1b72b48d07b
function matvec(M::LowRankMatrix, x)
	return sum(matvec(M.Ms[k], x) for k=1:M.rank)
end

# ╔═╡ 2d65bd1a-8512-11eb-1bd2-0313588dfa0e
md"""
#### Exercise 3.7
One of the big advantages of our rank-1 matrices is its space efficiency: to "store" a $n\times n$ matrix, we only need to *actually* store two vectors of $n$ entries, as opposed of $n^2$ total entries. Rank-2 matrices are a litte less efficient: we store two rank-1 matrices, or four vectors of $n$ vertices. As we increase the rank, we lose space efficiency, at the cost of being able to represent more kinds of matrices.

👉 For what rank will a matrix of rank $k$ need the same amount of storage as the dense version? Explain your answer.
"""

# ╔═╡ f9556098-8504-11eb-08a0-39fbe00892da
answer = md"""

From rank $k = n/2$ onwards.

Because for a rank k matrix, we store k rank-1 matrices, or $2\times k$ vectors of $n$ vertices. This corresponds to $2\times n\times k$ entries. If we solve the equation $n^2$ = 2*n*k, we get $k = n/2$.
"""

# ╔═╡ 295acdac-880a-402e-9f7e-19b0fc801130
md"""
## **Exercise 4:** _The SVD for structured matrices_
In a math class, you may or may not learn about the *singular value decomposition (SVD)*. From a computational thinking point of view, whether you have seen this before or not we hope will not matter.  Here we would like you to _get to "know" the SVD_, through experience, rather than through a math lecture.  This is how we see "computational thinking" after all. (Definitely do not look up some eigenvalue definition.)
"""

# ╔═╡ 3ba3ae50-b9ed-416e-bb47-a14f82eb4e19
md"""
#### Exercise 4.1

The `LinearAlgebra` package defines a function `svd` that computes the decomposition for us. Have a look at the result of calling the `svd` function. Try not to get intimidated by the large output (you may need to scroll the cell output), and look at the docs for `svd`.
"""

# ╔═╡ bdf3954e-2a05-4a0d-899b-1f6f59119550
biggie = rand(100,100)

# ╔═╡ a58c1c54-4f17-4ca4-ac78-22773b526dfb
md"""
👉 What are the _singular values_ of `biggie`?
"""

# ╔═╡ bb649c89-709c-49c8-8111-53044e8e682a
md"""

#### Exercise 4.2

If we try to run the `svd` function on a `RankOneMatrix`, you will see that it does not work. The error is telling us that **no `svd` method has been defined for our type**. Let's extend the `svd` function to work on objects of our type.
"""

# ╔═╡ 6f78b3bb-5440-4f34-849a-fa8e21972fa8
A = RankOneMatrix(rand(3),rand(4))

# ╔═╡ 44b8fbf1-411f-4ce4-b5d7-e12f2d6be51b
md"""
👉 Extend `svd` to work directly on a rank-one matrix, by writing a new method.

Keep things simple. Inside your method, call `LinearAlgebra.svd` on a type that it is _already defined for_.

"""

# ╔═╡ 6c9ae344-084e-459c-841c-8377451507fd
function LinearAlgebra.svd(A::RankOneMatrix)
	return LinearAlgebra.svd(Matrix(A))
end

# ╔═╡ 167b9580-fa18-4248-8643-a0fde723ecc4
svd(biggie)

# ╔═╡ 210392ff-0a22-4e55-be08-9f58804282cf
singular_values_of_biggie = svd(biggie).S

# ╔═╡ bbc4a595-a63e-4a12-9690-f39a23b30ae1
svd(A)

# ╔═╡ 9ae28bc5-d9cb-479d-8c22-7c9248cd5fa0
LinearAlgebra.svd(A)

# ╔═╡ 0cf29d7e-bd83-4c5f-bd41-e8e7c2587188
md"""
#### Exercise 4.3
👉 Look at the singular values, how many of them are approximately non-zero? Does that make sense?
"""

# ╔═╡ ed4b7ea5-3266-466d-a817-7ab2cc82ac9c
count(x -> !isapprox(x, 0, atol=1e-5), LinearAlgebra.svd(A).S)

# ╔═╡ 5c6aee48-017b-49ff-af1a-a31af30a45a9
md"""

This number (the number of singular values that are positive) is something you will (or have) learn(ed) in a linear algebra class: it is known as the *rank* of a matrix, and is usually defined through a complicated elimination procedure.

👉 Write a function `numerical_rank` that calculates the singular values of a matrix `A`, and returns how many of them are approximately zero.

To keep things simple, you can assume that "approximately zero" means: less than `tol=1e5`.
"""

# ╔═╡ a2b7f0c3-488b-4ce9-aed6-b8ccddac6a57
function numerical_rank(A::AbstractMatrix; tol=1e-5)
	return count(x -> !isapprox(x, 0, atol=tol), LinearAlgebra.svd(A).S)
end

# ╔═╡ d3420859-d558-47cb-aaf7-d51a5e2d1f6e
numerical_rank(A)

# ╔═╡ 207d2f4c-cbe8-4828-ab3f-100809bb93e9
numerical_rank(rand(3,3))

# ╔═╡ 7fdc110a-a3e3-44e5-a547-b75e03e0d21e
md"""
#### Exercise 4.4
👉 Write a function that takes an argument $k$ and generates the sum of $k$ random rank-one matrices of size mxn, and counts the number of essentially non-zero singular values.

   Hint: you can do this with a `for` loop, but it can also be done with `sum`, e.g.

   `sum(i for i=1:10)`.

"""

# ╔═╡ 01b12200-2e7e-4f19-96b9-5b6d6cb03233
function k_rank_ones(k, m, n)
	return sum(RankOneMatrix(rand(m),rand(n)) for _=1:k)
end

# ╔═╡ e1e1067b-93ba-40df-bd09-7599538e6181
k_rank_ones(1, 3, 3)

# ╔═╡ 00f5bea8-b808-483a-ad70-332f521481f5
md"""
#### Exercise 4.5
👉 What is the answer when $m$ and $n$ are both $≥ k$?  What if one of $m$ or $n$ is $< k$?
"""

# ╔═╡ a6bb92f9-0cb2-4fdd-8b67-f286edbbdcb6
numerical_rank(k_rank_ones(5, 10, 10))

# ╔═╡ 57c19601-122b-414f-bc99-56f98c794e61
numerical_rank(k_rank_ones(5, 3, 10))

# ╔═╡ 9813e9f9-9970-4a8b-b3ca-e6a699e6fda4
numerical_rank(k_rank_ones(5, 10, 4))

# ╔═╡ c8829a12-917a-4ff4-87c9-fe2b25aaa99c
numerical_rank(k_rank_ones(5, 2, 4))

# ╔═╡ 5aabbec1-a079-4936-9cd1-9c25fe5700e6
md"## Function library

Just some helper functions used in the notebook."

# ╔═╡ 42d6b87d-b4c3-4ccb-aceb-d1b020135f47
hint(text) = Markdown.MD(Markdown.Admonition("hint", "Hint", [text]))

# ╔═╡ a759c4d5-ca00-4dda-b39c-1093ba9bef0e
md"""
Try writing this without a `for` loop. Ideas:

☝ Use `filter`.

☝ Comprehensions can have `if` clauses, as in `[j for j=1:1000 if 5 < j < 17]`.
""" |> hint

# ╔═╡ e9a53ca0-86f2-11eb-2970-5d0996d1603e
hint(md"""
The extra constructor should have the same name as the type; it should take in a single argument `v`, and use it for both the column and the multipliers.
""")

# ╔═╡ 5e85ac82-8734-11eb-2f69-cda569326f68
hint(md"""
Check the docs for functions like `print`, `println`, `lpad` and `rpad`. Also, you don't have to know exactly how the `do` keyword works for now, but feel free to read about it - it can be useful with iterating!
""")

# ╔═╡ 7493be50-8738-11eb-37e7-990f1656998a
hint(md"""
If you want to specify that your argument is a range, use the `AbstractRange` type.
""")

# ╔═╡ f7fb3b32-8b00-11eb-0c0e-8f8d68f849a6
hint(md"""
How would we implement `matvec` for a rank-2 matrix - making use of the two stored rank-1 matrices? Can you generalize this to the rank-$k$ case, when we have many rank-1 matrices?
""")

# ╔═╡ 8fd9e34c-8f20-4979-8f8a-e3e2ae7d6c65
almost(text) = Markdown.MD(Markdown.Admonition("warning", "Almost there!", [text]))

# ╔═╡ d375c52d-2126-4594-b819-aba9d34fe77f
still_missing(text=md"Replace `missing` with your answer.") = Markdown.MD(Markdown.Admonition("warning", "Here we go!", [text]))

# ╔═╡ cd02acd0-8af4-11eb-3726-d7a360b98433
let
	example = FirstRankOneMatrix([1,2], [3,4])
	if ismissing(print_as_matrix(example))
		still_missing()
	end
end

# ╔═╡ 2d7565d4-88da-4e41-aad6-958ed6b3b2e0
keep_working(text=md"The answer is not quite right.") = Markdown.MD(Markdown.Admonition("danger", "Keep working on it!", [text]))

# ╔═╡ d7f45d51-f426-4353-af58-858395295ce0
yays = [md"Fantastic!", md"Splendid!", md"Great!", md"Yay ❤", md"Great! 🎉", md"Well done!", md"Keep it up!", md"Good job!", md"Awesome!", md"You got the right answer!", md"Let's move on to the next question."]

# ╔═╡ 9b2aa95d-4583-40ec-893c-9fc751ea22a1
correct(text=rand(yays)) = Markdown.MD(Markdown.Admonition("correct", "Got it!", [text]))

# ╔═╡ 29db89d6-ac40-4121-b996-e083555bc5db
try
	A = RankOneMatrix([1,2,3], [5,6,7])
	result = LinearAlgebra.svd(A)

	if result isa Missing
		still_missing()
	elseif !(result isa LinearAlgebra.SVD)
		almost(md"Like all other `svd` methods (implemented in `LinearAlgebra`), your method should return an SVD factorization (of type `LinearAlgebra.SVD`).")
	else
		reconstructed = result.U[:,1:1] * result.S[1:1] * result.Vt[1:1,:]

		if sum(abs.(A .- reconstructed)) < 1e-5
			correct()
		else
			keep_working()
		end
	end
catch e
	if (e isa MethodError) && e.f === LinearAlgebra.svd
		still_missing(md"Define a new method for `LinearAlgebra.svd` that takes a `RankOneMatrix` as argument.")
	else
		rethrow(e)
	end
end

# ╔═╡ 5dca2bb3-24e6-49ae-a6fc-d3912da4f82a
not_defined(variable_name) = Markdown.MD(Markdown.Admonition("danger", "Oopsie!", [md"Make sure that you define a variable called **$(Markdown.Code(string(variable_name)))**"]))

# ╔═╡ 30e5a813-9ea1-48d9-9039-012b8b267256
let
	if !@isdefined(ten_twelve)
		not_defined(:ten_twelve)
	else
		if ten_twelve isa Missing
			still_missing()
		elseif !(ten_twelve isa FirstRankOneMatrix)
			keep_working(md"`ten_twelve` should be a `FirstRankOneMatrix`.")
		elseif (ten_twelve.v == 1:10 && ten_twelve.w == 1:12) ||
				(ten_twelve.w == 1:10 && ten_twelve.v == 1:12)

			correct()
		else
			keep_working()
		end
	end
end

# ╔═╡ 06040694-b324-4e6b-8314-3bffc7b8010f
let
	if !@isdefined(RankOneMatrix)
		not_defined(:RankOneMatrix)
	else
		R2 = FirstRankOneMatrix([1,2], [3,4,5])

		if ismissing(Base.size(R2))
			still_missing()
		elseif size(R2) != (2, 3)
			keep_working(md"`Base.size` is not implemented correctly.")
		else
			correct()
		end
	end
end

# ╔═╡ 4ba8a0f2-850b-11eb-1c09-b7bba1763ed7
let
	if !@isdefined(FirstRankOneMatrix)
		not_defined(:FirstRankOneMatrix)
	else
		R2 = FirstRankOneMatrix([1,2], [3,4,5])

		if ismissing(Base.getindex(R2, 1, 1))
			still_missing(md"Implement both `Base.size` and `Base.getindex`.")
		elseif ismissing(Base.size(R2))
			still_missing(md"Implement both `Base.size` and `Base.getindex`.")
		elseif size(R2) != (2, 3)
			keep_working(md"`Base.size` is not implemented correctly.")
		else
			shouldbe = [
					3 4 5
					6 8 10
				]
			r = all(CartesianIndices(shouldbe)) do I
				isapprox(R2[Tuple(I)...], shouldbe[I], rtol=1-3)
			end
			if r
				correct()
			else
				keep_working(md"`Base.getindex` is not implemented correctly.")
			end
		end
	end
end

# ╔═╡ 111a8db4-a808-4f37-a75e-08d418bdfc98
let
	if !@isdefined(RankOneMatrix)
		not_defined(:RankOneMatrix)
	else
		R2 = RankOneMatrix([1,2], [3,4,5])

		if ismissing(Base.size(R2))
			still_missing()
		elseif size(R2) != (2, 3)
			keep_working(md"`Base.size` is not implemented correctly.")
		else
			correct()
		end
	end
end

# ╔═╡ 47f17b30-d87b-4d71-abf1-7a8b7f938095
let
	if !@isdefined(RankOneMatrix)
		not_defined(:RankOneMatrix)
	else
		R2 = RankOneMatrix([1,2], [3,4,5])

		if ismissing(Base.getindex(R2, 1, 1))
			still_missing(md"Implement both `Base.size` and `Base.getindex`.")
		elseif ismissing(Base.size(R2))
			still_missing(md"Implement both `Base.size` and `Base.getindex`.")
		elseif size(R2) != (2, 3)
			keep_working(md"`Base.size` is not implemented correctly.")
		else
			shouldbe = [
					3 4 5
					6 8 10
				]
			r = all(CartesianIndices(shouldbe)) do I
				isapprox(R2[Tuple(I)...], shouldbe[I], rtol=1-3)
			end
			if r
				correct()
			else
				keep_working(md"`Base.getindex` is not implemented correctly.")
			end
		end
	end
end

# ╔═╡ 3e72f920-8739-11eb-1354-57b18fa53103
if !@isdefined(matvec)
	not_defined(:matvec)
else
	let
		M = RankOneMatrix(1:10)

		if ismissing(matvec(M, ones(10)))
			still_missing()
		end

		incorrect = false
		for i in 1:5
			r = ones(10).*i
			for j in 1:10
				if matvec(M, r)[j] != (collect(M) * r)[j]
					incorrect = true
				end
			end
		end

		if incorrect
			keep_working()
		else
			correct()
		end
	end
end

# ╔═╡ 0d81b3d3-cfb4-4c27-bb00-18a55e00ab7a
let
	if !@isdefined(RankTwoMatrix)
		not_defined(:RankTwoMatrix)
	else
		R1 = RankOneMatrix([1,2], [3,4,5])
		R2 = RankTwoMatrix(R1, R1)

		if ismissing(Base.size(R2))
			still_missing()
		elseif size(R2) != (2, 3)
			keep_working(md"`Base.size` is not implemented correctly.")
		else
			correct()
		end
	end
end

# ╔═╡ 2e2f503f-c1ad-4d7f-8c0d-cd41fad1f334
let
	if !@isdefined(RankTwoMatrix)
		not_defined(:RankTwoMatrix)
	else
		R1a = RankOneMatrix([1,2], [3,4,5])
		R1b = RankOneMatrix([2,1], [3,4,5])
		R2 = RankTwoMatrix(R1a, R1b)

		if ismissing(Base.getindex(R2, 1, 1))
			still_missing(md"Implement both `Base.size` and `Base.getindex`.")
		elseif ismissing(Base.size(R2))
			still_missing(md"Implement both `Base.size` and `Base.getindex`.")
		elseif size(R2) != (2, 3)
			keep_working(md"`Base.size` is not implemented correctly.")
		else
			shouldbe = [
					3 4 5
					6 8 10
				]
			s2 = shouldbe + shouldbe[[2,1],:]
			r = all(CartesianIndices(s2)) do I
				isapprox(R2[Tuple(I)...], s2[I], rtol=1-3)
			end
			if r
				correct()
			else
				keep_working(md"`Base.getindex` is not implemented correctly.")
			end
		end
	end
end

# ╔═╡ 84d7bc07-e211-4296-98e6-f03723481e37
let
	if !@isdefined(LowRankMatrix)
		not_defined(:LowRankMatrix)
	else
		R1 = RankOneMatrix([1,2], [3,4,5])
		R2 = LowRankMatrix([R1, R1],2)

		if ismissing(Base.size(R2))
			still_missing()
		elseif size(R2) != (2, 3)
			keep_working(md"`Base.size` is not implemented correctly.")
		else
			correct()
		end
	end
end

# ╔═╡ 4ed7c189-3130-4f6a-b57b-5322fdd4cef6
let
	if !@isdefined(LowRankMatrix)
		not_defined(:LowRankMatrix)
	else
		R1a = RankOneMatrix([1,2], [3,4,5])
		R1b = RankOneMatrix([2,1], [3,4,5])
		R2 = LowRankMatrix([R1a, R1b],2)

		if ismissing(Base.getindex(R2, 1, 1))
			still_missing(md"Implement both `Base.size` and `Base.getindex`.")
		elseif ismissing(Base.size(R2))
			still_missing(md"Implement both `Base.size` and `Base.getindex`.")
		elseif size(R2) != (2, 3)
			keep_working(md"`Base.size` is not implemented correctly.")
		else
			shouldbe = [
					3 4 5
					6 8 10
				]
			s2 = shouldbe + shouldbe[[2,1],:]
			r = all(CartesianIndices(s2)) do I
				isapprox(R2[Tuple(I)...], s2[I], rtol=1-3)
			end
			if r
				correct()
			else
				keep_working(md"`Base.getindex` is not implemented correctly.")
			end
		end
	end
end

# ╔═╡ 863f4490-8b06-11eb-352b-61cec188ae57
if !@isdefined(matvec)
	not_defined(:matvec)
else

	let

		comp1, comp2 = RankOneMatrix(1.0:1.0:10.0), RankOneMatrix(0.0:0.1:0.9)
		if ismissing(comp1) | ismissing(comp2)
			still_missing()
		end

		ex4 = LowRankMatrix([comp1, comp2], 2)
		if ismissing(ex4)
			still_missing
		end

		if ismissing(matvec(ex4, ones(10)))
			still_missing()
		end

		incorrect = false
		for i in 1:10
			r = ones(10).*i
			v, c = matvec(ex4, r), collect(ex4) * r
			for j in 1:10
				if abs(v[j] - c[j]) > 1e-10
					incorrect = true
					break
				end
			end
		end

		if incorrect
			keep_working()
		else
			correct()
		end
	end
end

# ╔═╡ bec548dd-e2b9-4057-b0ac-1ab4e5b441d4
if !@isdefined(singular_values_of_biggie)
	not_defined(:singular_values_of_biggie)
else
	if singular_values_of_biggie isa Missing
		still_missing()
	elseif !(singular_values_of_biggie isa AbstractVector)
		keep_working(md"Return the singular values as a _vector_.")
	elseif isapprox(singular_values_of_biggie, svdvals(biggie), rtol=1e-5)
		correct()
	else
		keep_working()
	end
end

# ╔═╡ 2f5eadb0-4fdb-45b6-83fd-116dd4c1f9be
let
	if !@isdefined(numerical_rank)
		not_defined(:numerical_rank)
	else
		result1 = numerical_rank([
				1 2 0
				0 0 1e-10
				-5 -10 0
				])

		if result1 isa Missing
			still_missing()
		elseif !(result1 isa Integer)
			keep_working(md"You should return a number.")
		elseif result1 != 1
			keep_working()
		else
			result2 = numerical_rank([
				1 3 0
				0 0 1e-10
				-5 -10 0
				])
			if result2 == 2
				correct()
			else
				keep_working()
			end
		end
	end
end

# ╔═╡ 1276103b-8c58-4757-ae80-5ffb7f870d09
let
	if !@isdefined(k_rank_ones)
		not_defined(:k_rank_ones)
	else
		result = k_rank_ones(1, 3, 4)

		if result isa Missing
			still_missing()
		elseif !(result isa AbstractMatrix)
			keep_working(md"Make sure that you return a matrix.")
		elseif size(result) != (3, 4)
			keep_working(md"Make sure that you return a matrix of the correct size.")
		else

			numerical_rank(A) = count(>(1e-5), svdvals(A))

			if numerical_rank(result) == 1
				correct()
			else
				result2 = k_rank_ones(2, 10, 4)

				if !(result2 isa AbstractMatrix)
					keep_working(md"Make sure that you return a matrix.")
				elseif size(result2) != (10, 4)
					keep_working(md"Make sure that you return a matrix of the correct size.")
				elseif numerical_rank(result2) == 2
					correct()
				else
					keep_working()
				end
			end

		end
	end
end

# ╔═╡ ddf2a828-7823-4fc0-b944-72b60b391247
todo(text) = HTML("""<div
	style="background: rgb(220, 200, 255); padding: 2em; border-radius: 1em;"
	><h1>TODO</h1>$(repr(MIME"text/html"(), text))</div>""")

# ╔═╡ a7feaaa4-618b-4afe-8050-4bf7cc609c17
bigbreak = html"<br><br><br><br><br>";

# ╔═╡ 4ce0e43d-63d6-4cb2-88b8-8ba80e17012a
bigbreak

# ╔═╡ 36a63540-8739-11eb-21b2-a9d5ee60a0e5
bigbreak

# ╔═╡ 35d22fd9-9cfc-47c5-8adb-a01f14586be3
bigbreak

# ╔═╡ 0d1dac23-bdbd-4a90-ab13-e4c33d83777e
bigbreak

# ╔═╡ a5234680-8b02-11eb-2574-15489d0d49ea
bigbreak

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
PlutoUI = "~0.7.9"
"""

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

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

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

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

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

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

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

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

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

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

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

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

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

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "438d35d2d95ae2c5e8780b330592b6de8494e779"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.3"

[[PlutoUI]]
deps = ["Base64", "Dates", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "Suppressor"]
git-tree-sha1 = "44e225d5837e2a2345e69a1d1e01ac2443ff9fcb"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.9"

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

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

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

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

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

[[Suppressor]]
git-tree-sha1 = "a819d77f31f83e5792a76081eee1ea6342ab8787"
uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
version = "0.2.0"

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

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

# ╔═╡ Cell order:
# ╟─eadb174e-2c1d-48c8-9de2-99cdc2b38d32
# ╟─0560cf7b-9986-402a-9c40-779ea7a7292d
# ╟─082542fe-f89d-4774-be20-1e1a78f21291
# ╠═6f4274b5-87e2-420d-83d2-83a8408650cd
# ╟─aaa41509-a62d-417b-bca7-a120e3a5e5b2
# ╠═8c8388cf-9891-423c-8db2-d40d870bb38e
# ╟─4ce0e43d-63d6-4cb2-88b8-8ba80e17012a
# ╟─ac35c6b3-a142-4b7a-921b-a2bfe1d84713
# ╠═ea28bf57-ba62-41ce-8be6-d38ca2c5caa3
# ╟─a759c4d5-ca00-4dda-b39c-1093ba9bef0e
# ╟─a5901f93-007f-4a30-97fc-29b367ec47c6
# ╠═f0e59676-fd8e-4609-833e-f7d1a5a6e9e3
# ╠═a5be0775-68de-41ce-95cd-1465723d099b
# ╠═4abec609-09cd-4f86-8d86-a7d02325cc7b
# ╟─32e073bb-943f-4fa9-b15f-5ec18feecf15
# ╟─7d1e1724-cd1a-483b-af40-f24ae5301849
# ╠═d221c61c-a4ab-4a82-b89d-52735d957cae
# ╠═c3d49b98-a9c1-4aba-becc-7fa84f4bbc75
# ╠═7b33c09c-2ef2-4b97-b5a5-5fdf9268f76b
# ╟─224acb51-c0db-4f04-b865-8349d6a28e98
# ╟─a7e9ab20-8501-11eb-2a7a-af8fd495b21e
# ╟─8ce8c4bc-8505-11eb-2357-c50a70b8745c
# ╠═615b88b6-8505-11eb-0a7c-294ee5ae7474
# ╟─e9a53ca0-86f2-11eb-2970-5d0996d1603e
# ╟─9f534aa0-8505-11eb-3476-4d326fb19d42
# ╠═b0db7388-850c-11eb-0915-597f1fa5ab93
# ╟─30e5a813-9ea1-48d9-9039-012b8b267256
# ╟─7aac43b3-49ce-46ea-854b-d292ac0591c7
# ╠═2dfc2ef9-3efa-4b82-960f-f2ef0171e9eb
# ╠═ead4c008-0e7e-4414-aced-d4a576423bd3
# ╠═d4a0baea-cc02-4d91-a938-8181dea7b47a
# ╟─ce6e648c-ade5-4882-9f92-1ae3f878a5a3
# ╠═7adbcbab-66e7-4b58-b197-5c220ba1e9a2
# ╟─87fce3d8-765c-410c-a059-4dd51bd97254
# ╟─e86d8ab6-8505-11eb-1995-137dfbff0191
# ╠═fada4734-8505-11eb-3f2b-d1f1ef391ba4
# ╠═0b7c6cbe-57de-419d-adcb-8724791f9c89
# ╟─06040694-b324-4e6b-8314-3bffc7b8010f
# ╟─545a5916-8506-11eb-1a31-078957d791f8
# ╠═590dfe1a-8506-11eb-0069-d7cd91f02a65
# ╠═fe6df9bf-6059-4b76-af39-385d395ece72
# ╟─4ba8a0f2-850b-11eb-1c09-b7bba1763ed7
# ╟─b9cb6192-8505-11eb-3d2c-790654bbc9a1
# ╠═941b6b10-8ae9-11eb-3bf8-b732f5f60af3
# ╟─1b3cc404-9da8-4acd-9df5-5815ce167c34
# ╠═a243a400-8af3-11eb-0637-cf8f80aae86d
# ╠═aa756549-1b9f-41a1-a2e1-713166e4211a
# ╟─4e926bfe-8734-11eb-1355-e1d1d9b36929
# ╠═c7a15c5e-8505-11eb-3af2-2fa84b74b590
# ╟─9eb846c0-8737-11eb-101b-0191f715d8c9
# ╠═b0a0b9a4-850b-11eb-30f1-11f5270efe02
# ╟─cd02acd0-8af4-11eb-3726-d7a360b98433
# ╟─5e85ac82-8734-11eb-2f69-cda569326f68
# ╟─a75f3c76-8506-11eb-1d83-cf5781b656a3
# ╠═b577420c-8501-11eb-267a-719125315fe1
# ╟─1d16e228-850e-11eb-1dca-41df85b706da
# ╠═eb612772-8b06-44bb-a36a-827435cbb2ee
# ╠═d3e7c8d1-4b4a-47b6-9c96-150333078f42
# ╟─7493be50-8738-11eb-37e7-990f1656998a
# ╟─3ed18ba0-850b-11eb-3e90-f188c54d9ce9
# ╠═f2d8b45c-8501-11eb-1c6a-5f819c240d9d
# ╠═ed72e880-8afa-11eb-3a4a-175a838188d9
# ╠═7b3fb0ef-9a9e-401c-8c09-e5615134a4ad
# ╠═5a6dd3bc-af85-4e7d-b03c-b8c1bc88d850
# ╠═52451232-2c3d-4425-a94a-da1a955cf784
# ╟─111a8db4-a808-4f37-a75e-08d418bdfc98
# ╟─47f17b30-d87b-4d71-abf1-7a8b7f938095
# ╟─91b1125a-850b-11eb-1a21-c7af293332fd
# ╠═fc962c72-8501-11eb-2821-cbb7a52d5f61
# ╟─8f5c7c9c-850b-11eb-3d97-bf9c6b5d7d2e
# ╟─248ab23e-850c-11eb-396f-5ba365f0f743
# ╠═0887ee78-0e8a-41c7-90e7-44237acc1477
# ╠═1705dc7b-9f93-440b-acbb-ecb362d08125
# ╟─8cab8d7d-32fc-4b43-bd54-fb0c78fa5d00
# ╟─6a08cb74-8510-11eb-2212-2f1f676f3d5d
# ╟─61cbfc80-86f5-11eb-316f-6307cac7cbbb
# ╠═ee58251a-8511-11eb-074c-5b1e27c4ebd4
# ╟─3e72f920-8739-11eb-1354-57b18fa53103
# ╟─36a63540-8739-11eb-21b2-a9d5ee60a0e5
# ╟─4864e4d6-850c-11eb-210c-0318b8660a9a
# ╟─58ce8d2a-850c-11eb-29e9-3fc966e129b1
# ╟─9d4d943e-8502-11eb-31cb-e11824b2dce0
# ╠═ba3cb45a-8502-11eb-2141-6369b0e08807
# ╟─92a91904-850c-11eb-010e-c58ae218f541
# ╠═f5a95dd8-850d-11eb-2aa7-2dcb1868577f
# ╟─dc714540-8afa-11eb-205b-7770074771c8
# ╠═c784e02c-8502-11eb-3efa-7f4c45f4274c
# ╠═0bab818e-8503-11eb-02b3-178098599847
# ╟─0d81b3d3-cfb4-4c27-bb00-18a55e00ab7a
# ╟─2e2f503f-c1ad-4d7f-8c0d-cd41fad1f334
# ╟─aca709f0-8503-11eb-1144-1fc01cb85c39
# ╠═b6717812-8503-11eb-2729-39bfdc1fd2f9
# ╟─6e280730-8b00-11eb-04b7-615a389e792f
# ╠═c49e350e-8503-11eb-15de-7308dd03dc08
# ╠═dd27f508-8503-11eb-36b9-33f5f99f78b0
# ╠═93a75ea0-8b0c-11eb-3946-3d5d92487fb5
# ╟─84d7bc07-e211-4296-98e6-f03723481e37
# ╟─4ed7c189-3130-4f6a-b57b-5322fdd4cef6
# ╟─1083d5ee-8512-11eb-0fdc-ab3987f94bc5
# ╠═3fed837a-8512-11eb-1fdd-c1b72b48d07b
# ╟─863f4490-8b06-11eb-352b-61cec188ae57
# ╟─f7fb3b32-8b00-11eb-0c0e-8f8d68f849a6
# ╟─2d65bd1a-8512-11eb-1bd2-0313588dfa0e
# ╟─f9556098-8504-11eb-08a0-39fbe00892da
# ╟─35d22fd9-9cfc-47c5-8adb-a01f14586be3
# ╟─295acdac-880a-402e-9f7e-19b0fc801130
# ╠═54778212-3702-467d-8a96-4fa18b3ccd63
# ╟─3ba3ae50-b9ed-416e-bb47-a14f82eb4e19
# ╠═bdf3954e-2a05-4a0d-899b-1f6f59119550
# ╠═167b9580-fa18-4248-8643-a0fde723ecc4
# ╟─a58c1c54-4f17-4ca4-ac78-22773b526dfb
# ╠═210392ff-0a22-4e55-be08-9f58804282cf
# ╟─bec548dd-e2b9-4057-b0ac-1ab4e5b441d4
# ╟─0d1dac23-bdbd-4a90-ab13-e4c33d83777e
# ╟─bb649c89-709c-49c8-8111-53044e8e682a
# ╠═6f78b3bb-5440-4f34-849a-fa8e21972fa8
# ╠═bbc4a595-a63e-4a12-9690-f39a23b30ae1
# ╟─44b8fbf1-411f-4ce4-b5d7-e12f2d6be51b
# ╠═6c9ae344-084e-459c-841c-8377451507fd
# ╠═9ae28bc5-d9cb-479d-8c22-7c9248cd5fa0
# ╟─29db89d6-ac40-4121-b996-e083555bc5db
# ╟─0cf29d7e-bd83-4c5f-bd41-e8e7c2587188
# ╠═ed4b7ea5-3266-466d-a817-7ab2cc82ac9c
# ╟─5c6aee48-017b-49ff-af1a-a31af30a45a9
# ╠═a2b7f0c3-488b-4ce9-aed6-b8ccddac6a57
# ╠═d3420859-d558-47cb-aaf7-d51a5e2d1f6e
# ╠═207d2f4c-cbe8-4828-ab3f-100809bb93e9
# ╟─2f5eadb0-4fdb-45b6-83fd-116dd4c1f9be
# ╟─7fdc110a-a3e3-44e5-a547-b75e03e0d21e
# ╠═01b12200-2e7e-4f19-96b9-5b6d6cb03233
# ╠═e1e1067b-93ba-40df-bd09-7599538e6181
# ╟─1276103b-8c58-4757-ae80-5ffb7f870d09
# ╟─00f5bea8-b808-483a-ad70-332f521481f5
# ╠═a6bb92f9-0cb2-4fdd-8b67-f286edbbdcb6
# ╠═57c19601-122b-414f-bc99-56f98c794e61
# ╠═9813e9f9-9970-4a8b-b3ca-e6a699e6fda4
# ╠═c8829a12-917a-4ff4-87c9-fe2b25aaa99c
# ╟─a5234680-8b02-11eb-2574-15489d0d49ea
# ╟─5aabbec1-a079-4936-9cd1-9c25fe5700e6
# ╟─42d6b87d-b4c3-4ccb-aceb-d1b020135f47
# ╟─8fd9e34c-8f20-4979-8f8a-e3e2ae7d6c65
# ╟─d375c52d-2126-4594-b819-aba9d34fe77f
# ╟─2d7565d4-88da-4e41-aad6-958ed6b3b2e0
# ╟─d7f45d51-f426-4353-af58-858395295ce0
# ╟─9b2aa95d-4583-40ec-893c-9fc751ea22a1
# ╟─5dca2bb3-24e6-49ae-a6fc-d3912da4f82a
# ╟─ddf2a828-7823-4fc0-b944-72b60b391247
# ╟─a7feaaa4-618b-4afe-8050-4bf7cc609c17
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


# ---
# title: 274. H-Index
# id: problem274
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Hash Table, Sort
# link: <https://leetcode.com/problems/h-index/description/>
# hidden: true
# ---
#
# Given an array of citations (each citation is a non-negative integer) of a
# researcher, write a function to compute the researcher's h-index.
#
# According to the [definition of h-index on
# Wikipedia](https://en.wikipedia.org/wiki/H-index): "A scientist has index _h_
# if _h_ of his/her _N_ papers have **at least** _h_ citations each, and the
# other _N − h_ papers have **no more than** _h_ citations each."
#
# **Example:**
#
#
#
#     Input: citations = [3,0,6,1,5]
#     Output: 3
#     Explanation:[3,0,6,1,5] means the researcher has 5 papers in total and each of them had
#                  received 3, 0, 6, 1, 5 citations respectively.
#                  Since the researcher has 3 papers with **at least** 3 citations each and the remaining
#                  two with **no more than** 3 citations each, her h-index is 3.
#
# **Note:  **If there are several possible values for _h_ , the maximum one is
# taken as the h-index.
#
#
## @lc code=start
using LeetCode

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