using Test
# using Revise
using PolynomialBasis

PB = PolynomialBasis

function allequal(v1,v2)
    return all(v1 .≈ v2)
end

function allequal(v1,v2,tol)
    np = length(v1)
    f = length(v2) == np
    return f && all([isapprox(v1[i],v2[i],atol=tol) for i = 1:np])
end

p = [-1.0 1.0]
@test_throws AssertionError PB.tensor_product_points(0,p)
@test_throws AssertionError PB.tensor_product_points(4,p)

tp = PB.tensor_product_points(1,p)
@test allequal(p,tp)

tp = PB.tensor_product_points(2,p)
testtp = [-1.0 -1.0  1.0  1.0
          -1.0  1.0 -1.0  1.0]
@test allequal(tp,testtp)

tp = PB.tensor_product_points(3,p)
testtp = [-1.0   -1.0   -1.0   -1.0    1.0    1.0    1.0    1.0
          -1.0   -1.0    1.0    1.0   -1.0   -1.0    1.0    1.0
          -1.0    1.0   -1.0    1.0   -1.0    1.0   -1.0    1.0]
@test allequal(tp,testtp)

p = [1.0]
tp = PB.tensor_product_points(1,p')
@test allequal(p,tp)

tp = PB.tensor_product_points(2,p')
testtp = [1.0,1.0]
@test allequal(tp,testtp)

tp = PB.tensor_product_points(3,p')
testtp = [1.0;1.0;1.0]
@test allequal(tp,testtp)

basis = PB.LagrangeBasis(0)
@test_throws AssertionError PB.LagrangeTensorProductBasis(0,0)
@test_throws AssertionError PB.LagrangeTensorProductBasis(4,0)
tpb = PB.LagrangeTensorProductBasis(1,0)
@test allequal(tpb(0.0),[1.0])
@test allequal(PB.gradient(tpb,0.0),[0.0])
@test allequal(PB.gradient(tpb,[0.0]),[0.0])
@test_throws AssertionError PB.gradient(tpb,[0.0,0.0])

tpb = PB.LagrangeTensorProductBasis(1,0)
@test allequal(tpb(0.0),[1.0])
@test allequal(PB.gradient(tpb,0.0),[0.0])
@test allequal(PB.gradient(tpb,[0.0]),[0.0])
@test_throws AssertionError PB.gradient(tpb,[0.0,0.0])

tpb = PB.LagrangeTensorProductBasis(1,1)
@test allequal(tpb(-1.0),[1.0,0.0])
@test allequal(tpb(1.0),[0.0,1.0])
@test_throws AssertionError tpb([1.0,2.0])
@test allequal(tpb([1.0]),[0.0,1.0])
@test allequal(PB.gradient(tpb,-1.0),[-0.5,0.5])
@test allequal(PB.gradient(tpb,1.0),[-0.5,0.5])
@test_throws AssertionError PB.gradient(tpb,[1.0,2.0])
@test allequal(PB.gradient(tpb,[1.0]),[-0.5,0.5])

tpb = PB.LagrangeTensorProductBasis(1,2)
@test allequal(tpb(-1.0),[1.0,0.0,0.0])
@test allequal(tpb(0.0),[0.0,1.0,0.0])
@test allequal(tpb(1.0),[0.0,0.0,1.0])
@test_throws AssertionError tpb([1.0,2.0])
@test allequal(tpb([1.0]),[0.0,0.0,1.0])
@test allequal(PB.gradient(tpb,-1.0),[-1.5,2,-0.5])
@test allequal(PB.gradient(tpb,0.0),[-0.5,0,0.5])
@test allequal(PB.gradient(tpb,1.0),[0.5,-2,1.5])
@test_throws AssertionError PB.gradient(tpb,[1.0,2.0])
@test allequal(PB.gradient(tpb,[1.0]),[0.5,-2,1.5])

@test_throws AssertionError PB.LagrangeTensorProductBasis(1,-1)
@test_throws AssertionError PB.LagrangeTensorProductBasis(1,2,start=2)
@test_throws AssertionError PB.LagrangeTensorProductBasis(1,2,stop=-2)
tpb = PB.LagrangeTensorProductBasis(1,2,stop=0.0)
@test allequal(tpb.points,[-1.0 -0.5 0.0])
@test allequal(tpb(-1.0),[1.0,0.0,0.0])
@test allequal(tpb(-0.5),[0.0,1.0,0.0])
@test allequal(tpb(0.0),[0.0,0.0,1.0])
@test_throws AssertionError tpb([1.0,2.0])
@test allequal(tpb([0.0]),[0.0,0.0,1.0])
@test allequal(PB.gradient(tpb,-1.0),[-3.,4,-1])
@test allequal(PB.gradient(tpb,-0.5),[-1.0,0,1])
@test allequal(PB.gradient(tpb,0.0),[1.0,-4,3])
@test_throws AssertionError PB.gradient(tpb,[1.0,2.0])
@test allequal(PB.gradient(tpb,[0.0]),[1.0,-4,3])

function test_basis_on_points(basis::PB.LagrangeTensorProductBasis{2,T,N}) where {T,N}
    flag = true
    for i in 1:N
        vals = zeros(N)
        vals[i] = 1.0
        p = basis.points[:,i]
        flag = flag && basis(p[1],p[2]) ≈ vals
        flag = flag && basis(p...) ≈ vals
    end
    return flag
end

tp2 = PB.LagrangeTensorProductBasis(2,0)
@test allequal(tp2.points,[0.0,0.0])
@test test_basis_on_points(tp2)
@test_throws MethodError PB.gradient(tp2,1.5,1.0,2.0)
@test allequal(PB.gradient(tp2,1,0.0,0.0),[0.0])
@test allequal(PB.gradient(tp2,2,0.0,0.0),[0.0])
@test allequal(PB.gradient(tp2,2,[0.0,0.0]),[0.0])
@test allequal(PB.gradient(tp2,0.0,0.0),[0.0 0.0])
@test allequal(PB.gradient(tp2,[0.0,0.0]),[0.0 0.0])
@test_throws AssertionError PB.gradient(tp2,[0.0])
@test_throws AssertionError PB.gradient(tp2,[0.0,0.0,0.0])

v1 = [1.0,0.0,0.0]
v2 = [0.0,1.0,0.0]
v3 = [0.0,0.0,1.0]
d1 = [-1.5,2.0,-0.5]
d2 = [-0.5,0.0,0.5]
d3 = [0.5,-2.0,1.5]
tp2 = PB.LagrangeTensorProductBasis(2,2)
@test_throws AssertionError PB.gradient(tp2,0,-1,1)
@test_throws AssertionError PB.gradient(tp2,3,-1,1)
@test allequal(PB.gradient(tp2, 1, -1.0, -1.0), kron(d1,v1))
@test allequal(PB.gradient(tp2, 1, -1.0, +0.0), kron(d1,v2))
@test allequal(PB.gradient(tp2, 1, -1.0, +1.0), kron(d1,v3))
@test allequal(PB.gradient(tp2, 1, +0.0, -1.0), kron(d2,v1))
@test allequal(PB.gradient(tp2, 1, +0.0, +0.0), kron(d2,v2))
@test allequal(PB.gradient(tp2, 1, +0.0, +1.0), kron(d2,v3))
@test allequal(PB.gradient(tp2, 1, +1.0, -1.0), kron(d3,v1))
@test allequal(PB.gradient(tp2, 1, +1.0, +0.0), kron(d3,v2))
@test allequal(PB.gradient(tp2, 1, +1.0, +1.0), kron(d3,v3))

@test allequal(PB.gradient(tp2, 1, [+1.0, +1.0]), kron(d3,v3))

@test allequal(PB.gradient(tp2, 2, -1.0, -1.0), kron(v1,d1))
@test allequal(PB.gradient(tp2, 2, -1.0, +0.0), kron(v1,d2))
@test allequal(PB.gradient(tp2, 2, -1.0, +1.0), kron(v1,d3))
@test allequal(PB.gradient(tp2, 2, +0.0, -1.0), kron(v2,d1))
@test allequal(PB.gradient(tp2, 2, +0.0, +0.0), kron(v2,d2))
@test allequal(PB.gradient(tp2, 2, +0.0, +1.0), kron(v2,d3))
@test allequal(PB.gradient(tp2, 2, +1.0, -1.0), kron(v3,d1))
@test allequal(PB.gradient(tp2, 2, +1.0, +0.0), kron(v3,d2))
@test allequal(PB.gradient(tp2, 2, +1.0, +1.0), kron(v3,d3))

@test allequal(PB.gradient(tp2, 2, [+1.0, +1.0]), kron(v3,d3))

@test allequal(PB.gradient(tp2,1.0,1.0),hcat(kron(d3,v3),kron(v3,d3)))
@test_throws AssertionError PB.gradient(tp2,[1.0,1.0,1.0])
@test allequal(PB.gradient(tp2,[1.0,1.0]),hcat(kron(d3,v3),kron(v3,d3)))

function test_basis_on_points(basis::PB.LagrangeTensorProductBasis{3,N}) where {T,N}
    flag = true
    for i in 1:N
        vals = zeros(N)
        vals[i] = 1.0
        p = basis.points[:,i]
        flag = flag && basis(p[1],p[2],p[3]) ≈ vals
        flag = flag && basis(p) ≈ vals
    end
    return flag
end

tp3 = PB.LagrangeTensorProductBasis(3,2)
@test test_basis_on_points(tp3)

@test_throws AssertionError PB.gradient(tp3,0,-1.0,-1.0,-1.0)
@test_throws AssertionError PB.gradient(tp3,4,-1.0,-1.0,-1.0)
@test allequal(PB.gradient(tp3,1,-1.0,-1.0,-1.0),kron(d1,v1,v1))
@test allequal(PB.gradient(tp3,2,-1.0,0.0,-1.0),kron(v1,d2,v1))
@test allequal(PB.gradient(tp3,3,-1.0,0.0,+1.0),kron(v1,v2,d3))

@test_throws AssertionError PB.gradient(tp3,1,[1.0])
@test_throws AssertionError PB.gradient(tp3,1,[1.0,1.0])
@test_throws AssertionError PB.gradient(tp3,1,[1.0,1.0,1.0,1.0])

@test allequal(PB.gradient(tp3,3,[-1.0,-1.0,-1.0]),kron(v1,v1,d1))

@test allequal(PB.gradient(tp3,1.0,-1.0,0.0),hcat(kron(d3,v1,v2),kron(v3,d1,v2),kron(v3,v1,d2)))
@test_throws AssertionError PB.gradient(tp3,[1.0])
@test_throws AssertionError PB.gradient(tp3,[1.0,2.0,1.0,3.0])
@test allequal(PB.gradient(tp3,[1.0,-1.0,0.0]),hcat(kron(d3,v1,v2),kron(v3,d1,v2),kron(v3,v1,d2)))

tp1 = PB.LagrangeTensorProductBasis(1,2)
@test allequal(PB.hessian(tp1,-1),[1,-2,1])
@test allequal(PB.hessian(tp1,0),[1,-2,1])
@test allequal(PB.hessian(tp1,1),[1,-2,1])
@test allequal(PB.hessian(tp1,[-1]),[1,-2,1])
@test allequal(PB.hessian(tp1,[0]),[1,-2,1])
@test allequal(PB.hessian(tp1,[1]),[1,-2,1])

tp2 = PB.LagrangeTensorProductBasis(2,2)
f(x) = x[1]^2 + 2x[1]*x[2] + x[2]^2
coeffs = mapslices(f,Array(tp2.points),dims=1)
@test all([allequal(coeffs*PB.hessian(tp2,tp2.points[:,i]),[2,2,2]) for i = 1:9])

tp3 = PB.LagrangeTensorProductBasis(2,3)
f(x) = x[1]^3 + 2x[1]^2*x[2] + 18.0
tp3h(x) = [6x[1]+4x[2],4x[1],0.0]
coeffs = mapslices(f,Array(tp3.points),dims=1)
p = tp3.points
@test all([allequal(coeffs*PB.hessian(tp3,p[:,i]),tp3h(p[:,i]),1e3eps()) for i = 1:16])


using Test
using ParametricOptInterface
using MathOptInterface
using GLPK
using Ipopt
using ECOS
using SCS
using LinearAlgebra
using JuMP

const POI = ParametricOptInterface
const MOIU = MOI.Utilities
const MOIT = MOI.Test

const ATOL = 1e-4

include("production_problem_test.jl")
include("MOI_wrapper.jl")
include("basic_tests.jl")
include("dual_tests.jl")
include("quad_tests.jl")
include("sdp_tests.jl")
include("vector_affine_tests.jl")
include("modifications_tests.jl")
include("jump_tests.jl")
include("nlp_test.jl")


# Note: Terminology overall taken from:
# https://en.wikipedia.org/wiki/Interval_(mathematics)#Terminology

"""
    Bound <: Any

Abstract type representing all possible endpoint classifications (e.g. open, closed,
unbounded).
"""
:Bound

"""
    Bounded <: Bound

Abstract type indicating that the endpoint of an interval is not unbounded (e.g. open or
closed).
"""
:Bounded

"""
    Closed <: Bounded <: Bound

Type indicating that the endpoint of an interval is closed (the endpoint value is *included*
in the interval).
"""
:Closed

"""
    Open <: Bounded <: Bound

Type indicating that the endpoint of an interval is open (the endpoint value is *not
included* in the interval).
"""
:Open

"""
    Unbounded <: Bound

Type indicating that the endpoint of an interval is unbounded (the endpoint value is
effectively infinite).
"""
:Unbounded

"""
    LeftEndpoint <: Endpoint

Represents the lower endpoint of an `AbstractInterval`. Useful for comparing two endpoints
to each other.

### Examples

```jldoctest; setup = :(using Intervals; using Intervals: LeftEndpoint)
julia> LeftEndpoint(Interval(0.0, 1.0))
Intervals.Endpoint{Float64, Intervals.Direction{:Left}(), Closed}(0.0)

julia> LeftEndpoint{Closed}(1.0)
Intervals.Endpoint{Float64, Intervals.Direction{:Left}(), Closed}(1.0)

julia> LeftEndpoint{Closed}(1) < LeftEndpoint{Closed}(2)
true

julia> LeftEndpoint{Closed}(0) < LeftEndpoint{Open}(0)
true

julia> LeftEndpoint{Open}(0) <= LeftEndpoint{Closed}(0)
false
```

See also: [`RightEndpoint`](@ref)
"""
:LeftEndpoint

"""
    RightEndpoint <: Endpoint

Represents the upper endpoint of an `AbstractInterval`. Useful for comparing two endpoints
to each other.

### Examples

```jldoctest; setup = :(using Intervals; using Intervals: RightEndpoint)
julia> RightEndpoint(Interval(0.0, 1.0))
Intervals.Endpoint{Float64, Intervals.Direction{:Right}(), Closed}(1.0)

julia> RightEndpoint{Closed}(1.0)
Intervals.Endpoint{Float64, Intervals.Direction{:Right}(), Closed}(1.0)

julia> RightEndpoint{Closed}(1) < RightEndpoint{Closed}(2)
true

julia> RightEndpoint{Open}(0) < RightEndpoint{Closed}(0)
true

julia> RightEndpoint{Closed}(0) <= RightEndpoint{Open}(0)
false
```

See also: [`LeftEndpoint`](@ref)
"""
:RightEndpoint

"""
    first(interval::AbstractInterval{T}) -> Union{T,Nothing}

The value of the lower endpoint. When the lower endpoint is unbounded `nothing` will be
returned.
"""
Base.first(::AbstractInterval)

"""
    last(interval::AbstractInterval{T}) -> Union{T,Nothing}

The value of the upper endpoint. When the upper endpoint is unbounded `nothing` will be
returned.
"""
Base.last(::AbstractInterval)

"""
    span(interval::AbstractInterval) -> Any

The delta between the upper and lower endpoints. For bounded intervals returns a
non-negative value while intervals with any unbounded endpoints will throw an
`ArgumentError`.

To avoid having to capture the exception use the pattern:
```julia
Intervals.isbounded(interval) ? span(interval) : infinity
```
Where `infinity` is a variable representing the value you wish to use to represent an
unbounded, or infinite, span.
"""
span(::AbstractInterval)

"""
    isclosed(interval) -> Bool

Is a closed-interval: includes both of its endpoints.
"""
isclosed(::AbstractInterval)

"""
    isopen(interval) -> Bool

Is an open-interval: excludes both of its endpoints.
"""
Base.isopen(::AbstractInterval)

"""
    isunbounded(interval) -> Bool

Is an unbounded-interval: unbounded at both ends.
"""
isunbounded(::AbstractInterval)

"""
    isbounded(interval) -> Bool

Is a bounded-interval: either open, closed, left-closed/right-open, or
left-open/right-closed.

Note using `!isbounded` is commonly used to determine if any end of the interval is
unbounded.
"""
isbounded(::AbstractInterval)

"""
    minimum(interval::AbstractInterval{T}; [increment]) -> T

The minimum value contained within the `interval`.

If left-closed, returns `first(interval)`.
If left-open, returns `first(interval) + eps(first(interval))`
If left-unbounded, returns minimum value possible for type `T`.

A `BoundsError` is thrown for empty intervals or when the increment results in a minimum value
not-contained by the interval.
"""
minimum(::AbstractInterval; increment)

"""
    maximum(interval::AbstractInterval{T}; [increment]) -> T

The maximum value contained within the `interval`.

If right-closed, returns `last(interval)`.
If right-open, returns `first(interval) + eps(first(interval))`
If right-unbounded, returns maximum value possible for type `T`.

A `BoundsError` is thrown for empty intervals or when the increment results in a maximum value
not-contained by the interval.
"""
maximum(::AbstractInterval; increment)


include("../aimajulia.jl");

using Test;

using Main.aimajulia;

#The following CSP tests are from the aima-python doctest

@test (solution(depth_first_graph_search(aimajulia.australia_csp))...,) == (("NSW","B"),("Q","G"),("NT","B"),("T","B"),("V","G"),("SA","R"),("WA","G"));

d = CSPDict(ConstantFunctionDict(42));

@test d["life"] == 42;

@test (!(typeof(backtracking_search(aimajulia.australia_csp)) <: Nothing) == true);

@test (!(typeof(backtracking_search(aimajulia.australia_csp,
                                    select_unassigned_variable=minimum_remaining_values)) <: Nothing) == true);

@test (!(typeof(backtracking_search(aimajulia.australia_csp,
                                    order_domain_values=least_constraining_values)) <: Nothing) == true);

@test (!(typeof(backtracking_search(aimajulia.australia_csp,
                                    select_unassigned_variable=minimum_remaining_values,
                                    order_domain_values=least_constraining_values)) <: Nothing) == true);

@test (!(typeof(backtracking_search(aimajulia.australia_csp, inference=forward_checking)) <: Nothing) == true);

@test (!(typeof(backtracking_search(aimajulia.australia_csp,
                                    inference=maintain_arc_consistency)) <: Nothing) == true);

@test (!(typeof(backtracking_search(aimajulia.australia_csp,
                                    select_unassigned_variable=minimum_remaining_values,
                                    order_domain_values=least_constraining_values,
                                    inference=maintain_arc_consistency)) <: Nothing) == true);

topological_sorted_nodes, parent_dict = topological_sort(aimajulia.australia_csp, "NT");

@test topological_sorted_nodes == Any["NT","SA","Q","NSW","V","WA"];

@test haskey(parent_dict, "NT") == false;

@test parent_dict == Dict{Any,Any}(Pair("NSW","Q"), Pair("Q","SA"), Pair("V","NSW"), Pair("SA","NT"), Pair("WA","SA"));

@test length(backtracking_search(NQueensCSP(8))) == 8;

e = SudokuCSP(aimajulia.easy_sudoku_grid);

@test display(e, infer_assignment(e)) == ". . 3 | . 2 . | 6 . .\n9 . . | 3 . 5 | . . 1\n. . 1 | 8 . 6 | 4 . .\n------+-------+------\n. . 8 | 1 . 2 | 9 . .\n7 . . | . . . | . . 8\n. . 6 | 7 . 8 | 2 . .\n------+-------+------\n. . 2 | 6 . 9 | 5 . .\n8 . . | 2 . 3 | . . 9\n. . 5 | . 1 . | 3 . .";

AC3(e);

@test display(e, infer_assignment(e)) == "4 8 3 | 9 2 1 | 6 5 7\n9 6 7 | 3 4 5 | 8 2 1\n2 5 1 | 8 7 6 | 4 9 3\n------+-------+------\n5 4 8 | 1 3 2 | 9 7 6\n7 2 9 | 5 6 4 | 1 3 8\n1 3 6 | 7 9 8 | 2 4 5\n------+-------+------\n3 7 2 | 6 8 9 | 5 1 4\n8 1 4 | 2 5 3 | 7 6 9\n6 9 5 | 4 1 7 | 3 8 2";

@test !(typeof(backtracking_search(SudokuCSP(aimajulia.harder_sudoku_grid), select_unassigned_variable=minimum_remaining_values, inference=forward_checking)) <: Nothing)

@test solve_zebra(ZebraCSP(), backtracking_search) == (5,
                                                        1,
                                                        75472,
                                                        Dict{Any,Any}(Pair{Any,Any}("Tea",2),
                                                                    Pair{Any,Any}("Red",3),
                                                                    Pair{Any,Any}("Kools",1),
                                                                    Pair{Any,Any}("Green",5),
                                                                    Pair{Any,Any}("Horse",2),
                                                                    Pair{Any,Any}("Zebra",5),
                                                                    Pair{Any,Any}("OJ",4),
                                                                    Pair{Any,Any}("Milk",3),
                                                                    Pair{Any,Any}("Coffee",5),
                                                                    Pair{Any,Any}("Ukranian",2),
                                                                    Pair{Any,Any}("Japanese",5),
                                                                    Pair{Any,Any}("Snails",3),
                                                                    Pair{Any,Any}("Spaniard",4),
                                                                    Pair{Any,Any}("Water",1),
                                                                    Pair{Any,Any}("Winston",3),
                                                                    Pair{Any,Any}("Norwegian",1),
                                                                    Pair{Any,Any}("Fox",1),
                                                                    Pair{Any,Any}("Dog",4),
                                                                    Pair{Any,Any}("Ivory",4),
                                                                    Pair{Any,Any}("Englishman",3),
                                                                    Pair{Any,Any}("Yellow",1),
                                                                    Pair{Any,Any}("LuckyStrike",4),
                                                                    Pair{Any,Any}("Parliaments",5),
                                                                    Pair{Any,Any}("Blue",2),
                                                                    Pair{Any,Any}("Chesterfields",2)));


# Comment here
const bar = '\n'

"""
    test_fun(a::Int)
For test only
"""
function test_fun(a::Int, b::T) where T <: Number
    println(a)
    println("foo $(bar)")
end

@enum Unicode α=1 β=2

res = [√i for i in 1:10]

#= Dummy function =#
test_fun²(:sym, true, raw"test", `echo 1`)

# function to calculate the volume of a sphere
function sphere_vol(r)
    # julia allows Unicode names (in UTF-8 encoding)
    # so either "pi" or the symbol π can be used
    return 4/3*pi*r^3
end

# functions can also be defined more succinctly
quadratic(a, sqr_term, b) = (-b + sqr_term) / 2a

# calculates x for 0 = a*x^2+b*x+c, arguments types can be defined in function definitions
function quadratic2(a::Float64, b::Float64, c::Float64)
    # unlike other languages 2a is equivalent to 2*a
    # a^2 is used instead of a**2 or pow(a,2)
    sqr_term = sqrt(b^2-4a*c)
    r1 = quadratic(a, sqr_term, b)
    r2 = quadratic(a, -sqr_term, b)
    # multiple values can be returned from a function using tuples
    # if the return keyword is omitted, the last term is returned
    r1, r2
end

vol = sphere_vol(3)
# @printf allows number formatting but does not automatically append the \n to statements, see below
using Printf
@printf "volume = %0.3f\n" vol
#> volume = 113.097

quad1, quad2 = quadratic2(2.0, -2.0, -12.0)
println("result 1: ", quad1)
#> result 1: 3.0
println("result 2: ", quad2)
#> result 2: -2.0

s1 = "The quick brown fox jumps over the lazy dog α,β,γ"

# search returns the first index of a char
i = findfirst(isequal('b'), s1)
println(i)
#> 11
# the second argument is equivalent to the second argument of split, see below

# or a range if called with another string
r = findfirst("brown", s1)
println(r)
#> 11:15

# string replace is done thus:
r = replace(s1, "brown" => "red")
show(r); println()
#> "The quick red fox jumps over the lazy dog α,β,γ"

# search and replace can also take a regular expressions by preceding the string with 'r'
r = findfirst(r"b[\w]*n", s1)
println(r)
#> 11:15

# again with a regular expression
r = replace(s1, r"b[\w]*n" => "red")
show(r); println()
#> "The quick red fox jumps over the lazy dog α,β,γ"

# there are also functions for regular expressions that return RegexMatch types
# match scans left to right for the first match (specified starting index optional)
r = match(r"b[\w]*n", s1)
println(r)
#> RegexMatch("brown")

# RegexMatch types have a property match that holds the matched string
show(r.match); println()
#> "brown"

# eachmatch returns an iterator over all the matches
r = eachmatch(r"[\w]{4,}", s1)
for i in r print("\"$(i.match)\" ") end
#> "quick" "brown" "jumps" "over" "lazy"
println()

r = collect(m.match for m = eachmatch(r"[\w]{4,}", s1))
println(r)
#> SubString{String}["quick", "brown", "jumps", "over", "lazy"]

# a string can be repeated using the repeat function,
# or more succinctly with the ^ syntax:
r = "hello "^3
show(r); println() #> "hello hello hello "

# the strip function works the same as python:
# e.g., with one argument it strips the outer whitespace
r = strip("hello ")
show(r); println() #> "hello"
# or with a second argument of an array of chars it strips any of them;
r = strip("hello ", ['h', ' '])
show(r); println() #> "ello"
# (note the array is of chars and not strings)

# similarly split works in basically the same way as python:
r = split("hello, there,bob", ',')
show(r); println() #> SubString{String}["hello", " there", "bob"]
r = split("hello, there,bob", ", ")
show(r); println() #> SubString{String}["hello", "there,bob"]
r = split("hello, there,bob", [',', ' '], limit=0, keepempty=false)
show(r); println() #> SubString{String}["hello", "there", "bob"]
# (the last two arguements are limit and include_empty, see docs)

# the opposite of split: join is simply
r = join(collect(1:10), ", ")
println(r) #> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

function printsum(a)
    # summary generates a summary of an object
    println(summary(a), ": ", repr(a))
end

# arrays can be initialised directly:
a1 = [1,2,3]
printsum(a1)
#> 3-element Array{Int64,1}: [1, 2, 3]

# or initialised empty:
a2 = []
printsum(a2)
#> 0-element Array{Any,1}: Any[]

# since this array has no type, functions like push! (see below) don't work
# instead arrays can be initialised with a type:
a3 = Int64[]
printsum(a3)
#> 0-element Array{Int64,1}: Int64[]

# ranges are different from arrays:
a4 = 1:20
printsum(a4)
#> 20-element UnitRange{Int64}: 1:20

# however they can be used to create arrays thus:
a4 = collect(1:20)
printsum(a4)
#> 20-element Array{Int64,1}: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
#>  15, 16, 17, 18, 19, 20]

# arrays can also be generated from comprehensions:
a5 = [2^i for i = 1:10]
printsum(a5)
#> 10-element Array{Int64,1}: [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]

# arrays can be any type, so arrays of arrays can be created:
a6 = (Array{Int64, 1})[]
printsum(a6)
#> 0-element Array{Array{Int64,1},1}: Array{Int64,1}[]
# (note this is a "jagged array" (i.e., an array of arrays), not a multidimensional array,
# these are not covered here)

# Julia provided a number of "Dequeue" functions, the most common
# for appending to the end of arrays is push!
# ! at the end of a function name indicates that the first argument is updated.

push!(a1, 4)
printsum(a1)
#> 4-element Array{Int64,1}: [1, 2, 3, 4]

# push!(a2, 1) would cause error:

push!(a3, 1)
printsum(a3) #> 1-element Array{Int64,1}: [1]
#> 1-element Array{Int64,1}: [1]

push!(a6, [1,2,3])
printsum(a6)
#> 1-element Array{Array{Int64,1},1}: Array{Int64,1}[[1, 2, 3]]

# using repeat() to create arrays
# you must use the keywords "inner" and "outer"
# all arguments must be arrays (not ranges)
a7 = repeat(a1,inner=[2],outer=[1])
printsum(a7)
#> 8-element Array{Int64,1}: [1, 1, 2, 2, 3, 3, 4, 4]
a8 = repeat(collect(4:-1:1),inner=[1],outer=[2])
printsum(a8)
#> 8-element Array{Int64,1}: [4, 3, 2, 1, 4, 3, 2, 1]

# try, catch can be used to deal with errors as with many other languages
try
    push!(a,1)
catch err
    showerror(stdout, err, backtrace());println()
end
#> UndefVarError: a not defined
#> Stacktrace:
#>  [1] top-level scope at C:\JuliaByExample\src\error_handling.jl:5
#>  [2] include at .\boot.jl:317 [inlined]
#>  [3] include_relative(::Module, ::String) at .\loading.jl:1038
#>  [4] include(::Module, ::String) at .\sysimg.jl:29
#>  [5] exec_options(::Base.JLOptions) at .\client.jl:229
#>  [6] _start() at .\client.jl:421
println("Continuing after error")
#> Continuing after error

# repeat can be useful to expand a grid
# as in R's expand.grid() function:

m1 = hcat(repeat([1,2],inner=[1],outer=[3*2]),
          repeat([1,2,3],inner=[2],outer=[2]),
          repeat([1,2,3,4],inner=[3],outer=[1]))
printsum(m1)
#> 12×3 Array{Int64,2}: [1 1 1; 2 1 1; 1 2 1; 2 2 2; 1 3 2; 2 3 2; 1 1 3; 2 1 3;
#>   1 2 3; 2 2 4; 1 3 4; 2 3 4]

# for simple repetitions of arrays,
# use repeat
m2 = repeat(m1,1,2)     # replicate a9 once into dim1 and twice into dim2
println("size: ", size(m2))
#> size: (12, 6)

m3 = repeat(m1,2,1)     # replicate a9 twice into dim1 and once into dim2
println("size: ", size(m3))
#> size: (24, 3)

# Julia comprehensions are another way to easily create
# multidimensional arrays

m4 = [i+j+k for i=1:2, j=1:3, k=1:2] # creates a 2x3x2 array of Int64
m5 = ["Hi Im # $(i+2*(j-1 + 3*(k-1)))" for i=1:2, j=1:3, k=1:2]
# expressions are very flexible
# you can specify the type of the array by just
# placing it in front of the expression

import LegacyStrings
m5 = LegacyStrings.ASCIIString["Hi Im element # $(i+2*(j-1 + 3*(k-1)))" for i=1:2, j=1:3, k=1:2]
printsum(m5)
#> 2×3×2 Array{LegacyStrings.ASCIIString,3}: LegacyStrings.ASCIIString[
#>   "Hi Im element # 1" "Hi Im element # 3" "Hi Im element # 5";
#>   "Hi Im element # 2" "Hi Im element # 4" "Hi Im element # 6"]
#>
#> LegacyStrings.ASCIIString["Hi Im element # 7" "Hi Im element # 9"
#>   "Hi Im element # 11"; "Hi Im element # 8" "Hi Im element # 10" "Hi Im element # 12"]

# Array reductions
# many functions in Julia have an array method
# to be applied to specific dimensions of an array:

sum(m4, dims=3)        # takes the sum over the third dimension
sum(m4, dims=(1,3))  # sum over first and third dim

maximum(m4, dims=2)    # find the max elt along dim 2
findmax(m4, dims=3)    # find the max elt and its index along dim 3
                    # (available only in very recent Julia versions)

# Broadcasting
# when you combine arrays of different sizes in an operation,
# an attempt is made to "spread" or "broadcast" the smaller array
# so that the sizes match up. broadcast operators are preceded by a dot:

m4 .+ 3       # add 3 to all elements
m4 .+ [1,2]      # adds vector [1,2] to all elements along first dim

# slices and views
m4=m4[:,:,1] # holds dim 3 fixed
m4[:,2,:]  # that's a 2x1x2 array. not very intuititive to look at

# get rid of dimensions with size 1:
dropdims(m4[:,2,:], dims=2) # that's better

# assign new values to a certain view
m4[:,:,1] = rand(1:6,2,3)
printsum(m4)
#> 2×3 Array{Int64,2}: [3 5 3; 1 3 5]

# (for more examples of try, catch see Error Handling above)
try
    # this will cause an error, you have to assign the correct type
    m4[:,:,1] = rand(2,3)
catch err
    println(err)
end
#> InexactError(:Int64, Int64, 0.7603891754678744)

try
    # this will cause an error, you have to assign the right shape
    m4[:,:,1] = rand(1:6,3,2)
catch err
    println(err)
end
#> DimensionMismatch("tried to assign 3×2 array to 2×3×1 destination")

# dicts can be initialised directly:
a1 = Dict(1=>"one", 2=>"two")
printsum(a1)
#> Dict{Int64,String} with 2 entries: Dict(2=>"two",1=>"one")

# then added to:
a1[3]="three"
printsum(a1)
#> Dict{Int64,String} with 3 entries: Dict(2=>"two",3=>"three",1=>"one")
# (note dicts cannot be assumed to keep their original order)

# dicts may also be created with the type explicitly set
a2 = Dict{Int64, AbstractString}()
a2[0]="zero"
printsum(a2)
#> Dict{Int64,AbstractString} with 1 entry: Dict{Int64,AbstractString}(0=>"zero")

# dicts, like arrays, may also be created from comprehensions
using Printf
a3 = Dict([i => @sprintf("%d", i) for i = 1:10])
printsum(a3)
#> Dict{Int64,String} with 10 entries: Dict(7=>"7",4=>"4",9=>"9",10=>"10",
#>  2=>"2",3=>"3",5=>"5",8=>"8",6=>"6",1=>"1")

# as you would expect, Julia comes with all the normal helper functions
# for dicts, e.g., haskey
println(haskey(a1,1)) #> true

# which is equivalent to
println(1 in keys(a1)) #> true
# where keys creates an iterator over the keys of the dictionary

# similar to keys, values get iterators over the dict's values:
printsum(values(a1))
#> Base.ValueIterator for a Dict{Int64,String} with 3 entries: ["two", "three", "one"]

# use collect to get an array:
printsum(collect(values(a1)))
#> 3-element Array{String,1}: ["two", "three", "one"]

if true
    println("It's true!")
else
    println("It's false!")
end
#> It's true!

if false
   println("It's true!")
else
   println("It's false!")
end
#> It's false!

# Numbers can be compared with opperators like <, >, ==, !=

1 == 1.
#> true

1 > 2
#> false

"foo" != "bar"
#> true

# and many functions return boolean values

occursin("that", "this and that")
#> true

# More complex logical statments can be achieved with `elseif`

function checktype(x)
   if x isa Int
      println("Look! An Int!")
   elseif x isa AbstractFloat
      println("Look! A Float!")
   elseif x isa Complex
      println("Whoa, that's complex!")
   else
      println("I have no idea what that is")
   end
end

checktype(2)
#> Look! An Int!

checktype(√2)
#> Look! A Float!

checktype(√Complex(-2))
#> Whoa, that's complex!

checktype("who am I?")
#> I have no idea what that is

# For simple logical statements, one can be more terse using the "ternary operator",
# which takes the form `predicate ? do_if_true : do_if_false`

1 > 2 ? println("that's true!") : println("that's false!")
#> that's false!

noisy_sqrt(x) = x ≥ 0 ? sqrt(x) : "That's negative!"
noisy_sqrt(4)
#> 2.0
noisy_sqrt(-4)
#> That's negative!

# "Short-circuit evaluation" offers another option for conditional statements.
# The opperators `&&` for AND and `||` for OR only evaluate the right-hand
# statement if necessary based on the predicate.
# Logically, if I want to know if `42 == 0 AND x < y`,
# it doesn't matter what `x` and `y` are, since the first statement is false.
# This can be exploited to only evaluate a statement if something is true -
# the second statement doesn't even have to be boolean!

everything = 42
everything < 100 && println("that's true!")
#> "that's true!"
everything == 0 && println("that's true!")
#> false

√everything > 0 || println("that's false!")
#> true
√everything == everything || println("that's false!")
#> that's false!


"""
	trend1d(cmd0::String="", arg1=nothing, kwargs...)

Fit a [weighted] [robust] polynomial/Fourier model for y = f(x) to xy[w] data.

Full option list at [`trend1d`]($(GMTdoc)trend1d.html)

Parameters
----------

- **F** | **output** :: [Type => Str]   ``Arg = xymrw|p|P|c``

    Specify up to five letters from the set {x y m r w} in any order to create columns of output.
    ($(GMTdoc)trend1d.html#f)
- **N** | **n_model** :: [Type => Str]      ``Arg = [p|P|f|F|c|C|s|S|x]n[,…][+llength][+oorigin][+r]``

    Specify Specify the number of terms in the model, n_model, and append +r to do a robust fit. E.g., a robust bilinear model is -N4+r.
    ($(GMTdoc)trend1d.html#n)
- **C** | **condition_number** :: [Type => Number]   ``Arg = condition_number``

    Set the maximum allowed condition number for the matrix solution.
    ($(GMTdoc)trend1d.html#c)
- **I** | **confidence_level** :: [Type => Number | []]   ``Arg = [confidence_level]``

    Iteratively increase the number of model parameters, starting at one, until n_model is reachedx
    or the reduction in variance of the model is not significant at the confidence_level level.
    ($(GMTdoc)trend1d.html#i)
- **W** | **weights** :: [Type => Str | []]     ``Arg = [+s]``

    Weights are supplied in input column 3. Do a weighted least squares fit [or start with
    these weights when doing the iterative robust fit].
    ($(GMTdoc)trend1d.html#w)
- $(GMT.opt_V)
- $(GMT.opt_b)
- $(GMT.opt_d)
- $(GMT.opt_e)
- $(GMT.opt_f)
- $(GMT.opt_h)
- $(GMT.opt_i)
- $(GMT.opt_w)
- $(GMT.opt_swap_xy)
"""
function trend1d(cmd0::String="", arg1=nothing; kwargs...)

	length(kwargs) == 0 && return monolitic("trend1d", cmd0, arg1)

	d = init_module(false, kwargs...)[1]		# Also checks if the user wants ONLY the HELP mode

	cmd, = parse_common_opts(d, "", [:V_params :b :d :e :f :h :i :w :yx])
	cmd  = parse_these_opts(cmd, d, [[:C :condition_number], [:I :confidence_level], [:F :output],
	                                 [:N :n_model], [:W :weights]])
	common_grd(d, cmd0, cmd, "trend1d ", arg1)		# Finish build cmd and run it
end

# ---------------------------------------------------------------------------------------------------
trend1d(arg1, cmd0::String=""; kw...) = trend1d(cmd0, arg1; kw...)


# Miscellaneous utilities

"""
```
crossover(x::Array{T}, y::Array{T}) where {T<:Real}
```

Find where `x` crosses over `y` (returns boolean vector where crossover occurs)
"""
function crossover(x::Array{T}, y::Array{T}) where {T<:Real}
    @assert size(x,1) == size(y,1)
    out = falses(size(x))
    @inbounds for i in 2:size(x,1)
        out[i] = ((x[i] > y[i]) && (x[i-1] < y[i-1]))
    end
    return out
end

"""
```
crossunder(x::Array{T}, y::Array{T}) where {T<:Real}
```

Find where `x` crosses under `y` (returns boolean vector where crossunder occurs)
"""
function crossunder(x::Array{T}, y::Array{T}) where {T<:Real}
    @assert size(x,1) == size(y,1)
    out = falses(size(x))
    @inbounds for i in 2:size(x,1)
        out[i] = ((x[i] < y[i]) && (x[i-1] > y[i-1]))
    end
    return out
end

"""
```
wilder_sum(x::Vector{T}; n::Int=10)::Vector{T}
```

Welles Wilder summation of an array
"""
function wilder_sum(x::Vector{T}; n::Int=10)::Vector{T} where {T<:Real}
    @assert n<size(x,1) && n>0 "Argument n is out of bounds."
    nf = float(n)  # type stability -- all arithmetic done on floats
    out = zeros(size(x))
    out[1] = x[1]
    @inbounds for i = 2:size(x,1)
        out[i] = x[i] + out[i-1]*(nf-1.0)/nf
    end
    return out
end
wilder_sum(X::Matrix; n::Int=10)::Matrix = hcat((wilder_sum(X[:,j], n=n) for j in 1:size(X,2))...)

"""
(Adapted from StatsBase: https://raw.githubusercontent.com/JuliaStats/StatsBase.jl/master/src/scalarstats.jl)

Compute the mode of an arbitrary array::Array{T}
"""
function mode(a::AbstractArray{T}) where {T<:Real}
    isempty(a) && error("mode: input array cannot be empty.")
    cnts = Dict{T,Int}()
    # first element
    mc = 1
    mv = a[1]
    cnts[mv] = 1
    # find the mode along with table construction
    @inbounds for i = 2 : length(a)
        x = a[i]
        if haskey(cnts, x)
            c = (cnts[x] += 1)
            if c > mc
                mc = c
                mv = x
            end
        else
            cnts[x] = 1
            # in this case: c = 1, and thus c > mc won't happen
        end
    end
    return mv
end

"""
```
diffn(x::Vector{T}; n::Int=1)::Vector{T} where {T<:Real}
diffn(X::Matrix; n::Int=1)::Matrix = hcat([diffn(X[:,j], n=n) for j in 1:size(X,2)]...)
```

Lagged differencing
"""
function diffn(x::Vector{T}; n::Int=1)::Vector{T} where {T<:Real}
    @assert n<size(x,1) && n>0 "Argument n out of bounds."
    dx = zeros(size(x))
    dx[1:n] .= NaN
    @inbounds for i=n+1:size(x,1)
        dx[i] = x[i] - x[i-n]
    end
    return dx
end
diffn(X::Matrix; n::Int=1)::Matrix = hcat([diffn(X[:,j], n=n) for j in 1:size(X,2)]...)


mutable struct ConstantMean{T<:Real} <: MeanPrior{T}
    C::T
    opt::Optimizer
end

"""
**ConstantMean**
```julia
ConstantMean(c::T=1.0;opt::Optimizer=Adam(α=0.01))
```

Construct a prior mean with constant `c`
Optionally set an optimizer `opt` (`Adam(α=0.01)` by default)
"""
function ConstantMean(c::T=1.0;opt::Optimizer=Adam(α=0.01)) where {T<:Real}
    ConstantMean{T}(c,opt)
end

function update!(μ::ConstantMean{T},grad::AbstractVector{T}) where {T<:Real}
    μ.C += update(μ.opt,sum(grad))
end

Base.:+(x::Real,y::ConstantMean{<:Real}) = x+y.C
Base.:+(x::AbstractVector{<:Real},y::ConstantMean{<:Real}) = x.+y.C
Base.:+(x::ConstantMean{<:Real},y::Real) = y+x.C
Base.:+(x::ConstantMean{<:Real},y::AbstractVector{<:Real}) = y.+x.C
Base.:+(x::ConstantMean{<:Real},y::ConstantMean{<:Real}) = ConstantMean(x.C+y.C)
Base.:-(x::Real,y::ConstantMean) = x - y.C
Base.:-(x::AbstractVector{<:Real},y::ConstantMean) = x .- y.C
Base.:-(x::ConstantMean{<:Real},y::Real) = x.C - y
Base.:-(x::ConstantMean{<:Real},y::AbstractVector{<:Real}) = x.C .- y
Base.:-(x::ConstantMean{<:Real},y::ConstantMean{<:Real}) = ConstantMean(x.C-y.C)
Base.:*(A::AbstractMatrix{<:Real},y::ConstantMean{T}) where {T<:Real} = y.C*A*ones(T,size(A,2))
Base.:*(y::ConstantMean{T},A::AbstractMatrix{<:Real}) where {T<:Real} = y.C*ones(T,1,size(A,1))*A
Base.:convert(::T1,x::ConstantMean{T2}) where {T1<:Real,T2<:Real} = T1(x.C)


# ---
# title: 554. Brick Wall
# id: problem554
# author: Indigo
# date: 2021-05-13
# difficulty: Medium
# categories: Hash Table
# link: <https://leetcode.com/problems/brick-wall/description/>
# hidden: true
# ---
#
# There is a brick wall in front of you. The wall is rectangular and has several
# rows of bricks. The bricks have the same height but different width. You want
# to draw a vertical line from the **top** to the **bottom** and cross the
# **least** bricks.
#
# The brick wall is represented by a list of rows. Each row is a list of
# integers representing the width of each brick in this row from left to right.
#
# If your line go through the edge of a brick, then the brick is not considered
# as crossed. You need to find out how to draw the line to cross the least
# bricks and return the number of crossed bricks.
#
# **You cannot draw a line just along one of the two vertical edges of the wall,
# in which case the line will obviously cross no bricks.**
#
#
#
# **Example:**
#
#
#
#     Input: [[1,2,2,1],
#             [3,1,2],
#             [1,3,2],
#             [2,4],
#             [3,1,2],
#             [1,3,1,1]]
#
#     Output: 2
#
#     Explanation:
#     ![](https://assets.leetcode.com/uploads/2018/10/12/brick_wall.png)
#
#
#
#
# **Note:**
#
#   1. The width sum of bricks in different rows are the same and won't exceed INT_MAX.
#   2. The number of bricks in each row is in range [1,10,000]. The height of wall is in range [1,10,000]. Total number of bricks of the wall won't exceed 20,000.
#
#
## @lc code=start
using LeetCode

function least_bricks(wall::Vector{Vector{Int}})
    tb = Dict{Int,Int}()
    for row in wall
        acc = 0
        for i in @view(row[1:(end - 1)])
            acc += i
            haskey(tb, acc) ? tb[acc] += 1 : tb[acc] = 1
        end
    end
    isempty(tb) && return length(wall)
    return length(wall) - maximum(values(tb))
end
## @lc code=end


using QuasiArrays, FillArrays, LinearAlgebra, StaticArrays, Random, Base64, Test
import QuasiArrays: AbstractQuasiFill

@testset "QuasiFill" begin
    @testset "fill array constructors and convert" begin
        for (Typ, funcs, func) in ((:QuasiZeros, :zeros, :zero), (:QuasiOnes, :ones, :one))
            @eval begin
                @test $Typ((Base.OneTo(5),)) isa AbstractQuasiVector{Float64}
                @test $Typ((Base.OneTo(5),Base.OneTo(5))) isa AbstractQuasiMatrix{Float64}

                for T in (Int, Float64)
                    ax = [1,3,4]
                    Z = $Typ{T}(ax)
                    qZ = QuasiArray($funcs(T,length(ax)),(ax,))
                    @test eltype(Z) == T
                    @test Z[1] ≡ $func(T)
                    @test Z[3] ≡ $func(T)
                    @test Z[4] ≡ $func(T)
                    @test_throws BoundsError Z[2]

                    @test QuasiArray(Z) == qZ
                    @test QuasiArray{T}(Z) == qZ
                    @test QuasiArray{T,1}(Z) == qZ

                    @test convert(AbstractQuasiArray,Z) ≡ Z
                    @test convert(AbstractQuasiArray{T},Z) ≡ AbstractQuasiArray{T}(Z) ≡ Z
                    @test convert(AbstractQuasiVector{T},Z) ≡ AbstractQuasiVector{T}(Z) ≡ Z

                    Z = $Typ{T}(ax, ax)
                    qZ = QuasiArray($funcs(T,length(ax),length(ax)),(ax,ax))
                    @test eltype(Z) == T
                    @test QuasiArray(Z) == qZ
                    @test QuasiArray{T}(Z) == qZ
                    @test QuasiArray{T,2}(Z) == qZ

                    @test convert(AbstractQuasiArray,Z) ≡ convert(AbstractQuasiFill,Z) ≡ Z
                    @test convert(AbstractQuasiArray{T},Z) ≡ convert(AbstractQuasiFill{T},Z) ≡ AbstractQuasiArray{T}(Z) ≡ Z
                    @test convert(AbstractQuasiMatrix{T},Z) ≡ convert(AbstractQuasiFill{T,2},Z) ≡ AbstractQuasiMatrix{T}(Z) ≡ Z

                    @test AbstractQuasiArray{Float32}(Z) ≡ $Typ{Float32}(ax,ax)
                    @test AbstractQuasiArray{Float32,2}(Z) ≡ $Typ{Float32}(ax,ax)
                end
            end
        end

        @test QuasiFill(1) ≡ QuasiFill{Int}(1) ≡ QuasiFill{Int,0}(1) ≡ QuasiFill{Int,0,Tuple{}}(1,())
        ax = [1,3,4]
        @test QuasiFill(1.0,ax) isa AbstractQuasiVector{Float64}
        @test QuasiFill(1.0,ax,ax) isa AbstractQuasiMatrix{Float64}
        @test QuasiFill(1,ax) ≡ QuasiFill(1,(Inclusion(ax),))
        @test QuasiFill(1,ax,ax) ≡ QuasiFill(1,(Inclusion(ax),Inclusion(ax)))
        @test eltype(QuasiFill(1.0,ax,ax)) == Float64

        for T in (Int, Float64)
            F = QuasiFill{T}(one(T), ax)
            qF = QuasiArray(fill(one(T),length(ax)), (ax,))

            @test eltype(F) == T
            @test QuasiArray(F) == qF
            @test QuasiArray{T}(F) == qF
            @test QuasiArray{T,1}(F) == qF

            F = QuasiFill{T}(one(T), ax, ax)
            qF = QuasiArray(fill(one(T),length(ax),length(ax)), (ax,ax))
            @test eltype(F) == T
            @test QuasiArray(F) == qF
            @test QuasiArray{T}(F) == qF
            @test QuasiArray{T,2}(F) == qF

            @test convert(AbstractQuasiArray,F) ≡ F
            @test convert(AbstractQuasiArray{T},F) ≡ AbstractQuasiArray{T}(F) ≡ F
            @test convert(AbstractQuasiMatrix{T},F) ≡ AbstractQuasiMatrix{T}(F) ≡ F

            @test convert(AbstractQuasiArray{Float32},F) ≡ AbstractQuasiArray{Float32}(F) ≡
                    QuasiFill{Float32}(one(Float32),ax,ax)
            @test convert(AbstractQuasiMatrix{Float32},F) ≡ AbstractQuasiMatrix{Float32}(F) ≡
                    QuasiFill{Float32}(one(Float32),ax,ax)

            @test QuasiFill{T}(F) ≡ QuasiFill{T,2}(F) ≡ typeof(F)(F) ≡ F
        end

        @test QuasiEye(ax) isa QuasiDiagonal{Float64}
        @test QuasiEye(ax) == QuasiEye{Float64}(ax)
        @test eltype(QuasiEye(ax)) == Float64

        @test QuasiEye((Inclusion(ax),)) ≡ QuasiEye(ax)

        for T in (Int, Float64)
            E = QuasiEye{T}(ax)
            M = QuasiMatrix{T}(I, ax, ax)

            @test eltype(E) == T
            @test QuasiArray(E) == M
            @test QuasiArray{T}(E) == M
            @test QuasiArray{T,2}(E) == M

            @test convert(AbstractQuasiArray,E) === E
            @test convert(AbstractQuasiArray{T},E) === E
            @test convert(AbstractQuasiMatrix{T},E) === E

            @test AbstractQuasiArray{Float32}(E) == QuasiEye{Float32}(ax)
            @test QuasiEye{T}((Inclusion(ax),)) ≡ QuasiEye{T}(ax)
        end

        @testset "Bool should change type" begin
            ax = [1,3,4]
            x = QuasiFill(true,ax)
            y = x + x
            @test y isa QuasiFill{Int,1}
            @test y[1] == 2

            x = QuasiOnes{Bool}(ax)
            y = x + x
            @test y isa QuasiFill{Int,1}
            @test y[1] == 2
            @test x + QuasiZeros{Bool}(ax) ≡ x
            @test x - QuasiZeros{Bool}(ax) ≡ x
            @test QuasiZeros{Bool}(ax) + x ≡ x
            @test -x ≡ QuasiFill(-1,ax)
        end

        @testset "copy should return Fill" begin
            ax = [1,3,4]
            x = QuasiFill(1.0,ax)
            @test copy(x) ≡ x
            x = QuasiZeros(10)
            @test copy(x) ≡ x
            x = QuasiFill([1.,2.],ax)
            @test copy(x) == x
            @test copy(x) === x   # because isbits(x)
            @test copy(x) isa QuasiFill
        end

        @testset "in" begin
            ax = [1,3,4]
            for T in [QuasiZeros, QuasiOnes, QuasiFill]
                A = T(4, ax)
                @test FillArrays.getindex_value(A) in A
                @test !(FillArrays.getindex_value(A) + 1 in A)
            end
        end
    end

    @testset "indexing" begin
        ax = [1,3,4]
        A = QuasiFill(3.0,ax)
        @test A[[1,3]] ≡ Fill(3.0,2)
        @test A[Inclusion([1,3])] isa QuasiFill
        @test_throws BoundsError A[1:3]
        @test_throws BoundsError A[1:26]
        A = QuasiFill(3.0, ax, ax)
        @test A[[1,3], [3,4]] ≡ Fill(3.0,2,2)
        @test A[[1,3], Inclusion([3,4])] == QuasiFill(3.0,(Base.OneTo(2),Inclusion([3,4])))

        A = QuasiOnes{Int}(ax,ax)
        @test A[[1,3],1] ≡ Ones{Int}(2)
        @test A[[1,3],[3,4]] ≡ Ones{Int}(2,2)
        @test_throws BoundsError A[1:26]

        A = QuasiZeros{Int}(ax,ax)
        @test A[[1,3],1] ≡ Zeros{Int}(2)
        @test A[[1,3],[3,4]] ≡ Zeros{Int}(2,2)
        @test_throws BoundsError A[1:26]

        @testset "colon" begin
            @test QuasiOnes(ax)[:] == QuasiOnes(ax)
            @test QuasiZeros(ax)[:] == QuasiZeros(ax)
            @test QuasiFill(3.0,ax)[:] == QuasiFill(3.0,ax)

            @test QuasiOnes(ax,ax)[:,:] ≡ QuasiOnes(ax,ax)
        end

        @testset "mixed integer / vector /colon" begin
            ax = [1,3,4]
            a = QuasiFill(2.0,ax)
            z = QuasiZeros(ax)
            @test a[Inclusion(ax)] ≡ a[:] ≡ a
            @test z[Inclusion(ax)] ≡ z[:] ≡ z

            bx = 2:5
            A = QuasiFill(2.0,ax,bx)
            Z = QuasiZeros(ax,bx)
            @test A[:,2] ≡ A[Inclusion(ax),2] ≡ QuasiFill(2.0,ax)
            @test A[ax,2] ≡ Fill(2.0,3)
            @test A[1,:] ≡ A[1,Base.IdentityUnitRange(bx)] ≡ Fill(2.0,(Base.IdentityUnitRange(bx),))

            @test A[:,:] == A[Inclusion(ax),Base.IdentityUnitRange(bx)] == A[Inclusion(ax),:] == A[:,Base.IdentityUnitRange(bx)] == A
            @test Z[:,2] ≡ Z[Inclusion(ax),2] ≡ QuasiZeros(ax)
            @test Z[1,:] ≡ Z[1,Base.IdentityUnitRange(bx)] ≡ Zeros((Base.IdentityUnitRange(bx),))
            @test Z[:,:] == Z[Inclusion(ax),Base.IdentityUnitRange(bx)] == Z[Inclusion(ax),:] == Z[:,Base.IdentityUnitRange(bx)] == Z

            cx = 1:2
            A = QuasiFill(2.0,ax,bx,cx)
            Z = QuasiZeros(ax,bx,cx)
            @test A[:,2,1] ≡ A[Inclusion(ax),2,1] ≡ QuasiFill(2.0,ax)
            @test A[1,:,1] ≡ A[1,Base.IdentityUnitRange(bx),1] ≡ Fill(2.0,(Base.IdentityUnitRange(bx),))
            @test A[:,:,:] ≡ A[Inclusion(ax),Base.IdentityUnitRange(bx),Base.IdentityUnitRange(cx)] ≡ A[Inclusion(ax),:,Base.IdentityUnitRange(cx)] ≡ A[:,Base.IdentityUnitRange(bx),Base.IdentityUnitRange(cx)] ≡ A
        end
    end

    # Check that all pair-wise combinations of + / - elements of As and Bs yield the correct
    # type, and produce numerically correct results.
    function test_addition_and_subtraction(As, Bs, Tout::Type)
        for A in As, B in Bs
            @test A + B isa Tout{promote_type(eltype(A), eltype(B))}
            @test QuasiArray(A + B) == QuasiArray(A) + QuasiArray(B)

            @test A - B isa Tout{promote_type(eltype(A), eltype(B))}
            @test QuasiArray(A - B) == QuasiArray(A) - QuasiArray(B)

            @test B + A isa Tout{promote_type(eltype(B), eltype(A))}
            @test QuasiArray(B + A) == QuasiArray(B) + QuasiArray(A)

            @test B - A isa Tout{promote_type(eltype(B), eltype(A))}
            @test QuasiArray(B - A) == QuasiArray(B) - QuasiArray(A)
        end
    end

    # Check that all permutations of + / - throw a `DimensionMismatch` exception.
    function test_addition_and_subtraction_dim_mismatch(a, b)
        @test_throws DimensionMismatch a + b
        @test_throws DimensionMismatch a - b
        @test_throws DimensionMismatch b + a
        @test_throws DimensionMismatch b - a
    end

    @testset "FillArray addition and subtraction" begin
        ax = [1,3,4]
        bx = 2:5
        test_addition_and_subtraction_dim_mismatch(QuasiZeros(ax), QuasiZeros(bx))
        test_addition_and_subtraction_dim_mismatch(QuasiZeros(ax), QuasiZeros{Int}(bx))
        test_addition_and_subtraction_dim_mismatch(QuasiZeros(ax), QuasiZeros(bx,bx))
        test_addition_and_subtraction_dim_mismatch(QuasiZeros(ax), QuasiZeros{Int}(bx,ax))

        # Construct FillArray for repeated use.
        rng = MersenneTwister(123456)
        A_fill, B_fill = QuasiFill(randn(rng, Float64), ax), QuasiFill(4, ax)

        # Unary +/- constructs a new FillArray.
        @test +A_fill === A_fill
        @test -A_fill === QuasiFill(-A_fill.value, ax)

        # FillArray +/- FillArray should construct a new FillArray.
        test_addition_and_subtraction([A_fill, B_fill], [A_fill, B_fill], QuasiFill)
        test_addition_and_subtraction_dim_mismatch(A_fill, QuasiFill(randn(rng), 5, 2))

        # FillArray + Array (etc) should construct a new Array using `getindex`.
        A_dense, B_dense = QuasiArray(randn(rng, 3),ax), QuasiArray([5, 4, 3],ax)
        test_addition_and_subtraction([A_fill, B_fill], [A_dense, B_dense], QuasiArray)
        test_addition_and_subtraction_dim_mismatch(A_fill, QuasiArray(randn(rng, 3, 4),ax,bx))
    end

    @testset "Other matrix types" begin
        ax = [1,3,4]
        bx = 2:5
        z = QuasiZeros(ax)
        @test QuasiDiagonal(z) == QuasiDiagonal(QuasiArray(z))

        @test QuasiDiagonal(QuasiZeros(ax,bx)) == QuasiDiagonal(z)
        @test convert(QuasiDiagonal, QuasiZeros(ax,ax)) == QuasiDiagonal(z)
        @test_throws BoundsError convert(QuasiDiagonal, QuasiZeros(ax,bx))

        @test convert(QuasiDiagonal{Int}, QuasiZeros(ax,ax)) == QuasiDiagonal(z)
        @test_throws BoundsError convert(QuasiDiagonal{Int}, QuasiZeros(bx,ax))

        @test QuasiDiagonal(QuasiEye(ax)) == QuasiEye(ax)
        @test convert(QuasiDiagonal, QuasiEye(ax)) ==  QuasiEye(ax)
        @test convert(QuasiDiagonal{Int}, QuasiEye(ax)) == QuasiEye(ax)
    end

    @testset "==" begin
        ax,bx = [1,3,4],2:5
        @test QuasiZeros(ax,bx) == QuasiFill(0,ax,bx)
        @test QuasiZeros(ax,bx) ≠ QuasiZeros(3)
        @test QuasiOnes(ax,bx) == QuasiFill(1,ax,bx)
    end

    @testset "Rank" begin
        ax,bx = [1,3,4],2:5
        @test rank(QuasiZeros(ax,bx)) == 0
        @test rank(QuasiOnes(ax,bx)) == 1
        @test rank(QuasiFill(2,ax,bx)) == 1
        @test rank(QuasiFill(0,ax,bx)) == 0
    end

    @testset "Identities" begin
        ax,bx,cx = [1,3,4],2:5,1:5
        A = QuasiArray(randn(4,5),bx,cx)
        B = QuasiArray(randn(3,4),ax,bx)
        @test 1.0 .* QuasiZeros(ax,bx) ≡ QuasiZeros(ax,bx) .* 1.0 ≡ QuasiZeros(ax,bx)
        @test QuasiZeros(ax,bx) * QuasiZeros(bx,cx) == QuasiZeros(ax,bx) * A == B * QuasiZeros(bx,cx) == QuasiZeros(ax,cx)
        @test QuasiZeros(ax,bx) * A isa QuasiZeros
        @test B * QuasiZeros(bx,cx) isa QuasiZeros
        @test_throws DimensionMismatch B * QuasiZeros(ax, ax)

        # Check multiplication by Adjoint vectors works as expected.
        @test QuasiArray(randn(3, 4),ax,bx)' * QuasiZeros(ax) === QuasiZeros(bx)
        @test QuasiArray(randn(4),bx)' * QuasiZeros(bx) == zero(Float64)
        @test QuasiArray([1, 2, 3],ax)' * QuasiZeros{Int}(ax) == zero(Int)
        @test_broken QuasiArray([SVector(1,2)', SVector(2,3)', SVector(3,4)'],ax)' * QuasiZeros{Int}(ax) == SVector(0,0)

        @test +(QuasiZeros{Float64}(ax, bx)) === QuasiZeros{Float64}(ax, bx)
        @test -(QuasiZeros{Float32}(bx, cx)) === QuasiZeros{Float32}(bx, cx)

        # `Zeros` are closed under addition and subtraction (both unary and binary).
        z1, z2 = QuasiZeros{Float64}(ax), QuasiZeros{Int}(ax)
        @test +(z1) === z1
        @test -(z1) === z1

        test_addition_and_subtraction([z1, z2], [z1, z2], QuasiZeros)
        test_addition_and_subtraction_dim_mismatch(z1, QuasiZeros{Float64}(ax, bx))

        # `Zeros` +/- `Fill`s should yield `Fills`.
        fill1, fill2 = QuasiFill(5.0, ax), QuasiFill(5, ax)
        test_addition_and_subtraction([z1, z2], [fill1, fill2], QuasiFill)
        test_addition_and_subtraction_dim_mismatch(z1, QuasiFill(5, bx))

        X = QuasiArray(randn(3, 4), ax, bx)
        for op in [+, -]

            # Addition / subtraction with same eltypes.
            @test op(QuasiZeros(ax, bx), QuasiZeros(ax, bx)) === QuasiZeros(ax, bx)
            @test_throws DimensionMismatch op(X, QuasiZeros(ax, cx))
            @test eltype(op(QuasiZeros(ax, bx), X)) == Float64

            # Different eltypes, the other way around.
            @test op(X, QuasiZeros{Float32}(ax,bx)) isa QuasiMatrix{Float64}
            @test !(op(X, QuasiZeros{Float32}(ax,bx)) === X)
            @test op(X, QuasiZeros{Float32}(ax,bx)) == X
            @test !(op(X, QuasiZeros{ComplexF64}(ax,bx)) === X)
            @test op(X, QuasiZeros{ComplexF64}(ax,bx)) == X

            # Addition / subtraction of Zeros.
            @test eltype(op(QuasiZeros{Float64}(ax,bx), QuasiZeros{Int}(ax,bx))) == Float64
            @test eltype(op(QuasiZeros{Int}(bx,ax), QuasiZeros{Float32}(bx,ax))) == Float32
            @test op(QuasiZeros{Float64}(ax,bx), QuasiZeros{Int}(ax,bx)) isa QuasiZeros{Float64}
            @test op(QuasiZeros{Float64}(ax,bx), QuasiZeros{Int}(ax,bx)) === QuasiZeros{Float64}(ax,bx)
        end

        # Zeros +/- dense where + / - have different results.
        @test +(QuasiZeros(ax,bx), X) == X && +(X, QuasiZeros(ax,bx)) == X
        @test !(QuasiZeros(ax,bx) + X === X) && !(X + QuasiZeros(ax,bx) === X)
        @test -(QuasiZeros(ax,bx), X) == -X

        # Addition with different eltypes.
        @test +(QuasiZeros{Float32}(ax,bx), X) isa QuasiMatrix{Float64}
        @test !(+(QuasiZeros{Float32}(ax,bx), X) === X)
        @test +(QuasiZeros{Float32}(ax,bx), X) == X
        @test !(+(QuasiZeros{ComplexF64}(ax,bx), X) === X)
        @test +(QuasiZeros{ComplexF64}(ax,bx), X) == X

        # Subtraction with different eltypes.
        @test -(QuasiZeros{Float32}(ax,bx), X) isa QuasiMatrix{Float64}
        @test -(QuasiZeros{Float32}(ax,bx), X) == -X
        @test -(QuasiZeros{ComplexF64}(ax,bx), X) == -X

        # Tests for ranges.
        X = QuasiArray(randn(3),ax)
        @test !(QuasiZeros(ax) + X === X)

        # test Base.zero
        @test zero(QuasiZeros(ax)) == QuasiZeros(ax)
        @test zero(QuasiOnes(ax,ax)) == QuasiZeros(ax,ax)
        @test zero(QuasiFill(0.5, ax, ax)) == QuasiZeros(ax,ax)
    end

    @testset "maximum/minimum/svd/sort" begin
        ax = [1,3,4]
        @test maximum(QuasiFill(1, ax)) == minimum(QuasiFill(1, ax)) == 1
        @test sort(QuasiOnes(ax)) == sort!(QuasiOnes(ax))
    end

    @testset "Broadcast" begin
        ax = [1,3,4]
        x = QuasiFill(5,ax)
        @test (.+)(x) ≡ x
        @test (.-)(x) ≡ -x
        @test exp.(x) ≡ QuasiFill(exp(5),ax)
        @test x .+ 1 ≡ QuasiFill(6,ax)
        @test 1 .+ x ≡ QuasiFill(6,ax)
        @test x .+ x ≡ QuasiFill(10,ax)
        @test x .+ QuasiOnes(ax) ≡ QuasiFill(6.0,ax)
        f = (x,y) -> cos(x*y)
        @test f.(x, QuasiOnes(ax)) ≡ QuasiFill(f(5,1.0),ax)

        y = QuasiOnes(ax,ax)
        @test (.+)(y) ≡ QuasiOnes(ax,ax)
        @test (.-)(y) ≡ QuasiFill(-1.0,ax,ax)
        @test exp.(y) ≡ QuasiFill(exp(1),ax,ax)
        @test y .+ 1 ≡ QuasiFill(2.0,ax,ax)
        @test y .+ y ≡ QuasiFill(2.0,ax,ax)
        @test y .* y ≡ y ./ y ≡ y .\ y ≡ y

        @test QuasiZeros{Int}(5) .+ QuasiZeros(5) isa QuasiZeros{Float64}

        # Test for conj, real and imag with complex element types
        @test conj(QuasiZeros{ComplexF64}(ax)) isa QuasiZeros{ComplexF64}
        @test conj(QuasiZeros{ComplexF64}(ax,ax)) isa QuasiZeros{ComplexF64}
        @test conj(QuasiOnes{ComplexF64}(ax)) isa QuasiOnes{ComplexF64}
        @test conj(QuasiOnes{ComplexF64}(ax,ax)) isa QuasiOnes{ComplexF64}
        @test real(QuasiZeros{Float64}(ax)) isa QuasiZeros{Float64}
        @test real(QuasiZeros{Float64}(ax,ax)) isa QuasiZeros{Float64}
        @test real(QuasiZeros{ComplexF64}(ax)) isa QuasiZeros{Float64}
        @test real(QuasiZeros{ComplexF64}(ax,ax)) isa QuasiZeros{Float64}
        @test real(QuasiOnes{Float64}(ax)) isa QuasiOnes{Float64}
        @test real(QuasiOnes{Float64}(ax,ax)) isa QuasiOnes{Float64}
        @test real(QuasiOnes{ComplexF64}(ax)) isa QuasiOnes{Float64}
        @test real(QuasiOnes{ComplexF64}(ax,ax)) isa QuasiOnes{Float64}
        @test imag(QuasiZeros{Float64}(ax)) isa QuasiZeros{Float64}
        @test imag(QuasiZeros{Float64}(ax,ax)) isa QuasiZeros{Float64}
        @test imag(QuasiZeros{ComplexF64}(ax)) isa QuasiZeros{Float64}
        @test imag(QuasiZeros{ComplexF64}(ax,ax)) isa QuasiZeros{Float64}
        @test imag(QuasiOnes{Float64}(ax)) isa QuasiZeros{Float64}
        @test imag(QuasiOnes{Float64}(ax,ax)) isa QuasiZeros{Float64}
        @test imag(QuasiOnes{ComplexF64}(ax)) isa QuasiZeros{Float64}
        @test imag(QuasiOnes{ComplexF64}(ax,ax)) isa QuasiZeros{Float64}

        @testset "Special Zeros/Ones" begin
            @test broadcast(+,QuasiZeros(ax)) ≡ broadcast(-,QuasiZeros(ax)) ≡ QuasiZeros(ax)
            @test broadcast(+,QuasiOnes(ax)) ≡ QuasiOnes(ax)

            @test QuasiZeros(ax) .* QuasiOnes(ax) ≡ QuasiZeros(ax) .* 1 ≡ QuasiZeros(ax)
            @test QuasiZeros(ax) .* QuasiFill(5.0, ax) ≡ QuasiZeros(ax) .* 5.0 ≡ QuasiZeros(ax)
            @test QuasiOnes(ax) .* QuasiZeros(ax) ≡ 1 .* QuasiZeros(ax) ≡ QuasiZeros(ax)
            @test QuasiFill(5.0, ax) .* QuasiZeros(ax) ≡ 5.0 .* QuasiZeros(ax) ≡ QuasiZeros(ax)

            @test QuasiZeros(ax) ./ QuasiOnes(ax) ≡ QuasiZeros(ax) ./ 1 ≡ QuasiZeros(ax)
            @test QuasiZeros(ax) ./ QuasiFill(5.0, ax) ≡ QuasiZeros(ax) ./ 5.0 ≡ QuasiZeros(ax)
            @test QuasiOnes(ax) .\ QuasiZeros(ax) ≡ 1 .\ QuasiZeros(ax) ≡ QuasiZeros(ax)
            @test QuasiFill(5.0, ax) .\ QuasiZeros(ax) ≡ 5.0 .\ QuasiZeros(ax) ≡ QuasiZeros(ax)

            @test conj.(QuasiZeros(5)) ≡ QuasiZeros(5)
            @test conj.(QuasiZeros{ComplexF64}(5)) ≡ QuasiZeros{ComplexF64}(5)

            @test_throws DimensionMismatch broadcast(*, QuasiOnes(ax), 1:6)
            @test_throws DimensionMismatch broadcast(*, 1:6, QuasiOnes(ax))
            @test_throws DimensionMismatch broadcast(*, QuasiFill(1,ax), 1:6)
            @test_throws DimensionMismatch broadcast(*, 1:6, QuasiFill(1,ax))

            @testset "Number" begin
                @test broadcast(*, QuasiZeros(ax), 2) ≡ broadcast(*, 2, QuasiZeros(ax)) ≡ QuasiZeros(ax)
            end

            @testset "Nested" begin
                a = QuasiArray(randn(3), ax)
                b = QuasiArray(randn(3), ax)
                @test a .\ b .* QuasiZeros(ax) ≡ QuasiZeros(ax)
                @test broadcast(*, QuasiZeros(ax), Base.Broadcast.broadcasted(\, a, b)) ≡ QuasiZeros(ax)
            end

            @testset "array-valued" begin
                a = QuasiArray(1:3, ax)
                @test broadcast(*, QuasiFill([1,2],ax), a) == broadcast(*, a, QuasiFill([1,2],ax)) == QuasiArray((1:3) .* Fill([1,2],3), ax)
                @test broadcast(*, QuasiFill([1,2],ax), QuasiZeros(ax)) == broadcast(*, QuasiZeros(ax), QuasiFill([1,2],ax))
                @test broadcast(*, QuasiFill([1,2],ax), QuasiZeros(ax)) isa QuasiFill{Vector{Float64}}
                @test broadcast(*, QuasiArray([[1,2], [3,4,5], [1,2]], ax), QuasiZeros(ax)) == broadcast(*, QuasiZeros(ax), QuasiArray([[1,2], [3,4,5], [1,2]], ax))
            end

            @testset "NaN" begin
                @test QuasiZeros(ax) ./ QuasiZeros(ax) ≡ QuasiZeros(ax) .\ QuasiZeros(ax) ≡ QuasiFill(NaN,ax)
                @test QuasiZeros{Int}(ax,ax) ./ QuasiZeros{Int}(ax) ≡ QuasiZeros{Int}(ax) .\ QuasiZeros{Int}(ax,ax) ≡ QuasiFill(NaN,ax,ax)
            end

            @testset "Addition" begin
                a = QuasiArray(1:3,ax)
                @test QuasiZeros{Int}(ax) .+ a == a .+ QuasiZeros{Int}(ax) == a .- QuasiZeros{Int}(ax) == a
                @test QuasiZeros(ax) .+ a == a .+ QuasiZeros(ax) == a .- QuasiZeros(ax) == a
                @test QuasiZeros{Int}(ax) .+ QuasiFill(1,ax) ≡ QuasiFill(1,ax) .+ QuasiZeros{Int}(ax) ≡ QuasiFill(1,ax) .- QuasiZeros{Int}(ax) ≡ QuasiFill(1,ax)
                @test_throws DimensionMismatch QuasiZeros{Int}([1,2]) .+ a
                @test_throws DimensionMismatch a .+ QuasiZeros{Int}([1,2])
            end
        end

        @testset "support Ref" begin
            @test QuasiFill(1,ax) .- 1 ≡ QuasiFill(1,ax) .- Ref(1) ≡ QuasiFill(1,ax) .- Ref(1I)
            @test QuasiFill([1 2; 3 4],ax) .- Ref(1I) == QuasiFill([0 2; 3 3],ax)
            @test Ref(1I) .+ QuasiFill([1 2; 3 4],ax) == QuasiFill([2 2; 3 5],ax)
        end

        @testset "Special Ones" begin
            a = QuasiArray(1:3,ax)
            @test QuasiOnes{Int}(ax) .* a == a .* QuasiOnes{Int}(ax) == a
            @test QuasiOnes(ax) .* a == a .* QuasiOnes(ax) == a
            @test QuasiOnes{Int}(5) .* QuasiOnes{Int}(5) == QuasiOnes{Int}(5)
            @test_throws DimensionMismatch QuasiOnes{Int}([1,2]) .* a
            @test_throws DimensionMismatch a .* QuasiOnes{Int}([1,2])
            @test_throws DimensionMismatch QuasiOnes{Int}(ax) .* QuasiOnes{Int}([1,2])
        end

        @testset "Zeros -" begin
            @test QuasiZeros(ax) - QuasiZeros(ax) ≡ QuasiZeros(ax)
            @test QuasiOnes(ax) - QuasiZeros(ax) ≡ QuasiOnes(ax)
            @test QuasiOnes(ax) - QuasiOnes(ax) isa QuasiZeros
            @test QuasiFill(1,ax) - QuasiZeros(ax) ≡ QuasiFill(1.0,ax)

            @test QuasiZeros(ax) .- QuasiZeros(ax) ≡ QuasiZeros(ax)
            @test QuasiOnes(ax) .- QuasiZeros(ax) ≡ QuasiOnes(ax)
            @test QuasiOnes(ax) .- QuasiOnes(ax) ≡ QuasiZeros(ax)
            @test QuasiFill(1,ax) .- QuasiZeros(ax) ≡ QuasiFill(1.0,ax)
        end

        @testset "Zero .*" begin
            ax = [1,3,4]
            a = QuasiArray(randn(3),ax)
            b = QuasiArray(1:3,ax)
            @test QuasiZeros{Int}(ax) .* QuasiZeros{Int}(ax) ≡ QuasiZeros{Int}(ax)
            @test a .* QuasiZeros(ax) isa QuasiZeros
            @test QuasiZeros(ax) .* a isa QuasiZeros
            @test b .* QuasiZeros(ax) isa QuasiZeros
            @test QuasiZeros(ax) .* b isa QuasiZeros
            @test_throws DimensionMismatch (1:11) .* QuasiZeros(ax)
        end
    end

    @testset "map" begin
        ax = [1,3,4]
        x = QuasiOnes(ax)
        @test map(exp,x) === QuasiFill(exp(1.0),ax)
        @test map(isone,x) === QuasiFill(true,ax)

        x = QuasiZeros(ax)
        @test map(exp,x) === exp.(x)

        x = QuasiFill(2,ax,ax)
        @test map(exp,x) === QuasiFill(exp(2),ax,ax)
    end

    @testset "0-dimensional" begin
        A = QuasiFill{Int,0,Tuple{}}(3, ())

        @test A[] ≡ 3
        @test A ≡ QuasiFill{Int,0}(3, ()) ≡ QuasiFill(3, ()) ≡ QuasiFill(3)
        @test size(A) == ()
        @test axes(A) == ()

        A = QuasiOnes{Int,0,Tuple{}}(())
        @test A[] ≡ 1
        @test A ≡ QuasiOnes{Int,0}(()) ≡ QuasiOnes{Int}(()) ≡ QuasiOnes{Int}()

        A = QuasiZeros{Int,0,Tuple{}}(())
        @test A[] ≡ 0
        @test A ≡ QuasiZeros{Int,0}(()) ≡ QuasiZeros{Int}(()) ≡ QuasiZeros{Int}()
    end

    @testset "Eye identity ops" begin
        ax = [1,3,4]
        m = QuasiEye(ax)
        D = QuasiDiagonal(QuasiFill(2,ax))

        for op in (permutedims, inv)
            @test op(m) === m
        end
        @test permutedims(D) ≡ D
        @test inv(D) ≡ QuasiDiagonal(QuasiFill(1/2,ax))

        @test copy(m) ≡ m
        @test copy(D) ≡ D
        @test LinearAlgebra.copy_oftype(m, Int) ≡ QuasiEye{Int}(ax)
        @test LinearAlgebra.copy_oftype(D, Float64) ≡ QuasiDiagonal(QuasiFill(2.0,ax))
    end

    @testset "Adjoint/Transpose/permutedims" begin
        ax = [1,3,4]
        bx = 2:5
        @test QuasiOnes{ComplexF64}(ax,bx)' ≡ transpose(QuasiOnes{ComplexF64}(ax,bx)) ≡ QuasiOnes{ComplexF64}(bx,ax)
        @test QuasiZeros{ComplexF64}(ax,bx)' ≡ transpose(QuasiZeros{ComplexF64}(ax,bx)) ≡ QuasiZeros{ComplexF64}(bx,ax)
        @test QuasiFill(1+im, ax, bx)' ≡ QuasiFill(1-im, bx,ax)
        @test transpose(QuasiFill(1+im, ax, bx)) ≡ QuasiFill(1+im, bx,ax)
        @test QuasiOnes(ax)' isa QuasiAdjoint # Vectors still need special dot product
        @test QuasiFill([1+im 2; 3 4; 5 6], ax, bx)' == QuasiFill([1+im 2; 3 4; 5 6]',bx, ax)
        @test transpose(QuasiFill([1+im 2; 3 4; 5 6], ax, bx)) == QuasiFill(transpose([1+im 2; 3 4; 5 6]),bx, ax)

        @test permutedims(QuasiOnes(ax)) ≡ QuasiOnes(1,ax)
        @test permutedims(QuasiZeros(ax)) ≡ QuasiZeros(1,ax)
        @test permutedims(QuasiFill(2.0,ax)) ≡ QuasiFill(2.0,1,ax)
        @test permutedims(QuasiOnes(ax,bx)) ≡ QuasiOnes(bx,ax)
        @test permutedims(QuasiZeros(ax,bx)) ≡ QuasiZeros(bx,ax)
        @test permutedims(QuasiFill(2.0,ax,bx)) ≡ QuasiFill(2.0,bx,ax)
    end

    @testset "setindex!/fill!" begin
        ax = [1,3,4]
        bx = 2:5

        F = QuasiFill(1,ax)
        @test (F[1] = 1) == 1
        @test_throws BoundsError (F[11] = 1)
        @test_throws ArgumentError (F[3] = 2)

        F = QuasiFill(1,ax,bx)
        @test (F[3,3] = 1) == 1
        @test_throws BoundsError (F[51] = 1)
        @test_throws BoundsError (F[1,6] = 1)
        @test_throws ArgumentError (F[1,2] = 2)

        @test (F[:,2] .= 1) == QuasiFill(1,ax)
        @test_throws ArgumentError (F[:,2] .= 2)

        @test fill!(F,1) == F
        @test_throws ArgumentError fill!(F,2)
    end

    @testset "mult" begin
        ax = [1,3,4]
        bx = 2:5
        cx = 1:3

        @test QuasiFill(2,ax)*QuasiFill(3,Base.OneTo(1),bx) ≡ QuasiFill(6,ax,bx)
        @test QuasiFill(2,ax,bx)*QuasiFill(3,bx,cx) ≡ QuasiFill(6*length(bx),ax,cx)
        @test QuasiFill(2,ax,bx)*QuasiFill(3,bx) ≡ QuasiFill(6*length(bx),ax)
        @test_throws DimensionMismatch QuasiFill(2,bx)*QuasiFill(3,2,cx)
        @test_throws DimensionMismatch QuasiFill(2,3,bx)*QuasiFill(3,2,cx)

        @test QuasiOnes(bx)*QuasiFill(3,Base.OneTo(1),cx) ≡ QuasiFill(3.0,bx,cx)
        @test QuasiOnes(ax,bx)*QuasiFill(3,bx,cx) == QuasiFill(3length(bx),ax,cx)
        @test QuasiOnes(ax,bx)*QuasiFill(3,bx) == QuasiFill(3length(bx),ax)

        @test QuasiFill(2,ax)*QuasiOnes(Base.OneTo(1),bx) ≡ QuasiFill(2.0,ax,bx)
        @test QuasiFill(2,ax,bx)*QuasiOnes(bx,cx) == QuasiFill(2length(bx),ax,cx)
        @test QuasiFill(2,ax,bx)*QuasiOnes(bx) == QuasiFill(2length(bx),ax)

        @test QuasiOnes(ax)*QuasiOnes(Base.OneTo(1),bx) == QuasiOnes(ax,bx)
        @test QuasiOnes(ax,bx)*QuasiOnes(bx,cx) == QuasiFill(length(bx),ax,cx)
        @test QuasiOnes(ax,bx)*QuasiOnes(bx) == QuasiFill(length(bx),ax)

        @test QuasiZeros(ax)*QuasiFill(3,Base.OneTo(1),bx) ≡   QuasiZeros(ax,bx)
        @test QuasiZeros(ax,bx)*QuasiFill(3,bx,cx) ≡ QuasiZeros(ax,cx)
        @test QuasiZeros(ax,bx)*QuasiFill(3,bx) ≡    QuasiZeros(ax)

        @test QuasiFill(2,ax)*  QuasiZeros(Base.OneTo(1),bx) ≡  QuasiZeros(ax,bx)
        @test QuasiFill(2,ax,bx)*QuasiZeros(bx,cx) ≡ QuasiZeros(ax,cx)
        @test QuasiFill(2,ax,bx)*QuasiZeros(bx) ≡    QuasiZeros(ax)

        @test QuasiZeros(ax)*QuasiZeros(Base.OneTo(1),bx) ≡ QuasiZeros(ax,bx)
        @test QuasiZeros(ax,bx)*QuasiZeros(bx,cx) ≡ QuasiZeros(ax,cx)
        @test QuasiZeros(ax,bx)*QuasiZeros(bx) ≡ QuasiZeros(ax)

        a = QuasiArray(randn(4),bx)
        A = QuasiArray(randn(1,4),Base.OneTo(1),bx)

        @test QuasiFill(2,ax,Base.OneTo(1))*A ≈ QuasiMatrix(QuasiFill(2,ax,Base.OneTo(1)))*A
        @test QuasiFill(2,ax,bx)*a ≈ QuasiMatrix(QuasiFill(2,ax,bx))*a
        @test QuasiOnes(ax,Base.OneTo(1))*A ≈ QuasiMatrix(QuasiOnes(ax,Base.OneTo(1)))*A
        @test QuasiOnes(ax,bx)*a ≈ QuasiMatrix(QuasiOnes(ax,bx))*a
        @test QuasiZeros(ax,Base.OneTo(1))*A == QuasiZeros(ax,bx)
        @test QuasiZeros(ax,bx)*a == QuasiZeros(ax)

        @test A*QuasiFill(2,bx) ≈ A*QuasiVector(QuasiFill(2,bx))
        @test A*QuasiFill(2,bx,cx) ≈ A*QuasiMatrix(QuasiFill(2,bx,cx))
        @test A*QuasiOnes(bx) ≈ A*QuasiVector(QuasiOnes(bx))
        @test A*QuasiOnes(bx,cx) ≈ A*QuasiMatrix(QuasiOnes(bx,cx))
        @test A*QuasiZeros(bx)  ≡ Zeros((Base.OneTo(1),))
        @test A*QuasiZeros(bx,cx) ≡ QuasiZeros(Base.OneTo(1),cx)

        D = QuasiDiagonal(a)
        @test QuasiZeros(ax,bx)*D ≡ QuasiZeros(ax,bx)
        @test D*QuasiZeros(bx,cx) ≡ QuasiZeros(bx,cx)
        @test D*QuasiZeros(bx) ≡ QuasiZeros(bx)

        D = QuasiDiagonal(QuasiFill(2,bx))
        @test D * QuasiOnes(bx) == QuasiFill(2.0,bx)
        @test D * QuasiOnes(bx,cx) == QuasiFill(2.0,bx,cx)
        @test QuasiOnes(ax,bx) * D == QuasiFill(2.0,ax,bx)
    end

    @testset "zeros/ones/fill" begin
        ax = Inclusion([1,3,4])
        @test zero(ax) ≡ QuasiZeros{Int}((ax,))
        @test one(ax) ≡ QuasiOnes{Int}((ax,))
        @test zeros(ax) ≡ zeros(Float64,ax) ≡ QuasiZeros((ax,))
        @test zeros(Base.OneTo(3), ax) ≡ zeros(Float64,Base.OneTo(3),ax) ≡ QuasiZeros((Base.OneTo(3),ax))
        @test ones(ax) ≡ ones(Float64,ax) ≡ QuasiOnes((ax,))
        @test ones(Base.OneTo(3), ax) ≡ ones(Float64,Base.OneTo(3),ax) ≡ QuasiOnes((Base.OneTo(3),ax))
        @test fill(2,ax) ≡ QuasiFill(2,(ax,))
        @test fill(2,Base.OneTo(3), ax) ≡ QuasiFill(2,(Base.OneTo(3),ax))
    end

    @testset "Static eval" begin
        o = QuasiOnes([1,3,4])
        @test view(o,SVector(1,3)) ≡ o[SVector(1,3)] ≡ Ones((SOneTo(2),))
    end

    @testset "UniformScaling" begin
        A = QuasiArray(randn(3,3), 0:0.5:1,0:0.5:1)
        @test A+2I ≈ 2I+A ≈ QuasiArray(A.parent+2I, 0:0.5:1,0:0.5:1)
        @test A-2I ≈ -(2I-A) ≈ QuasiArray(A.parent-2I, 0:0.5:1,0:0.5:1)
        @test A*(2I) ≈ (2I)*A ≈ 2A
        @test A/(2I) ≈ (2I)\A ≈ A/2

        B = QuasiArray(randn(3,3), 0:0.5:1,1:0.5:2)
        @test_throws DimensionMismatch B+I
        @test_throws DimensionMismatch I+B
        @test B*I ≈ I*B ≈ B
    end

    @testset "show" begin
        @test stringmime("text/plain",ones(Inclusion([1,2,3]))) == "ones(Inclusion([1, 2, 3]))"
        @test stringmime("text/plain",zeros(Inclusion([1,2,3]))) == "zeros(Inclusion([1, 2, 3]))"
        @test stringmime("text/plain",fill(2,Inclusion([1,2,3]))) == "fill(2, Inclusion([1, 2, 3]))"
    end
end


@testset "thresholdexceedance.jl" begin
    n = 10000

    pd = GeneralizedPareto(1.0, 0.1)
    y = rand(pd, n)

    x = rand(n)

    ev = [Variable("x", x)]

    smodel = Extremes.ThresholdExceedance(Variable("y", y))
    nsmodel = Extremes.ThresholdExceedance(Variable("y", y), logscalecov = ev, shapecov = ev)

    @testset "ThresholdExceedance(exceedances; logscalecov, shapecov)" begin
        ev10 = Extremes.Variable("t", collect(1:n+10))

        # Build with logscalecov length != y length
        @test_throws AssertionError Extremes.ThresholdExceedance(Variable("y", y), logscalecov = [ev10])

        # Build with shapecov length != y length
        @test_throws AssertionError Extremes.ThresholdExceedance(Variable("y", y), shapecov = [ev10])

        # Build with all optional parameters set
        @test nsmodel.data.value == y
        @test nsmodel.logscale.covariate == ev
        @test nsmodel.shape.covariate == ev
    end

    @testset "paramindex(model)" begin
        # model with stationary and non-stationary parameters
        model = Extremes.ThresholdExceedance(Variable("y", y), logscalecov = ev)

        paramin = Extremes.paramindex(model)

        @test length(paramin) == 2
        @test paramin[:ϕ] == [1,2]
        @test paramin[:ξ] == [3]
    end

    @testset "getcovariatenumber(model)" begin
        # stationary
        @test Extremes.getcovariatenumber(smodel) == 0

        # non-stationary
        @test Extremes.getcovariatenumber(nsmodel) == 2
    end

    @testset "nparameter(model)" begin
        # stationary
        @test Extremes.nparameter(smodel) == 2

        # non-stationary
        @test Extremes.nparameter(nsmodel) == 4
    end

    @testset "getdistribution(model, θ)" begin
        # length(θ) != nparameter(model) throws
        @test_throws AssertionError Extremes.getdistribution(smodel, [1.0])

        # stationary
        n = 100

        σ = 1.0
        ξ = .1
        ϕ = log(σ)

        θ = [ϕ ; ξ]

        pd = GeneralizedPareto(σ, ξ)
        y = rand(pd,n)

        model = ThresholdExceedance(Variable("y", y))

        fd = Extremes.getdistribution(model, θ)[]

        @test fd == pd

        # non-stationary
        n = 100

        x₁ = randn(n)/3
        x₂ = randn(n)/3
        x₃ = randn(n)/10

        θ = [-.5 ; 1.0 ; 1.0 ; 0 ; 1.0]

        ϕ = θ[1] .+ θ[2] * x₁ .+ θ[3] * x₂
        ξ = θ[4] .+ θ[5] * x₃

        σ = exp.(ϕ)

        pd = GeneralizedPareto.(σ, ξ)
        y = rand.(pd)

        model = ThresholdExceedance(Variable("y", y), logscalecov = [Variable("x₁", x₁), Variable("x₂", x₂)], shapecov = [Variable("x₃", x₃)])

        fd = Extremes.getdistribution(model, θ)

        @test fd == pd

    end

    @testset "getinitialvalue(::Type{GeneralizedPareto},y)" begin
        # Test with valid pwm GPD estimates
        y = [0.0, 1.0, 2.0]
        ini = Extremes.getinitialvalue(GeneralizedPareto,y)
        @test !isapprox(ini[2],0)
        pd = GeneralizedPareto(exp(ini[1]), ini[2])
        @test all(insupport.(pd,y))

        # Test with invalid pwm GPD estimates
        y = [0.0 , 1.0, 1.0, 1.0, 3.0]
        ini = Extremes.getinitialvalue(GeneralizedPareto,y)
        @test ini[2] ≈ 0
        pd = GeneralizedPareto(exp(ini[1]), ini[2])
        @test all(insupport.(pd,y))

    end

    @testset "getinitialvalue(model)" begin
        # Test with valid pwm GPD estimates
        y = [0.0, 1.0, 2.0]
        model = ThresholdExceedance(Variable("y", y))
        ini = Extremes.getinitialvalue(model)
        @test ini[1] ≈ .-.693 atol = .001
        @test ini[2] ≈ .5 atol = .001

        # Test with invalid pwm GPD estimates
        y = [0.0 , 1.0, 1.0, 1.0, 3.0]
        model = ThresholdExceedance(Variable("y", y))
        ini = Extremes.getinitialvalue(model)
        @test ini[1] ≈ .182 atol = .001
        @test ini[2] ≈ 0 atol = .001

    end

    @testset "showEVA(io, obj; prefix)" begin
        # print does not throw
        buffer = IOBuffer()
        @test_logs Extremes.showEVA(buffer, smodel, prefix = "\t")

    end

end


### A Pluto.jl notebook ###
# v0.11.14

using Markdown
using InteractiveUtils

# ╔═╡ b1226c30-fdc8-11ea-1020-c1a5f67bc6aa
begin
	using Pkg
	Pkg.add("DataFramesMeta")
end

# ╔═╡ 3c45bc76-fdc5-11ea-21e4-d7973210099b
begin
	using DataFrames
	using CSV
	using HTTP
	using DataFramesMeta
	url = "https://raw.githubusercontent.com/OU-PhD-Econometrics/fall-2020/master/ProblemSets/PS5-ddc/busdataBeta0.csv"
	df = CSV.read(HTTP.get(url).body)
	df = @transform(df, bus_id = 1:size(df,1)) # Create bus id variable
end

# ╔═╡ 1ec897ae-fdc5-11ea-27b5-554b053bc087
md"""
# Introduction

In this problem set, we will explore a simplified version of the Rust (1987, \textit{Econometrica}) bus engine replacement model. Let's start by reading in the data.
"""

# ╔═╡ b2919210-fdc5-11ea-24d2-bba0c930d349
md"""
# Static estimation

Reshape the data into "long" panel format, calling your long dataset *df\_long*. I have included code on how to do this in the *PS5starter.jl*

1. Create bus id variable
"""

# ╔═╡ Cell order:
# ╟─1ec897ae-fdc5-11ea-27b5-554b053bc087
# ╠═b1226c30-fdc8-11ea-1020-c1a5f67bc6aa
# ╠═3c45bc76-fdc5-11ea-21e4-d7973210099b
# ╟─b2919210-fdc5-11ea-24d2-bba0c930d349


# ======================================================
# Testing continuous-time homogeneous system (no input)
# ======================================================
A = sparse([1, 1, 2, 3, 4], [1, 2, 2, 4, 3], [1., 2., 3., 4., 5.], 4, 4)
X0 = BallInf(zeros(4), 0.1)
cont_sys_homog = ContinuousSystem(A, X0)

# Check if the input is constant
@test inputdim(cont_sys_homog) == 0
# Check if the input is empty
#@test isa(next_set(cont_sys_homog.U), ZeroSet) # there is no input set
# Check data fields
@test cont_sys_homog.s.A == A
@test cont_sys_homog.x0.center == zeros(4) && cont_sys_homog.x0.radius == 0.1

# ===================================================
# Testing continuous-time system with constant input
# ===================================================
U = Ball2(ones(4), 0.5)
cont_sys = ContinuousSystem(A, X0, U)

# check initial state
@test cont_sys.x0.center ≈ zeros(4) && cont_sys.x0.radius ≈ 0.1

# recover input
inputs = next_set(cont_sys.s.U) #collect(nextinput(cont_sys.s.U))[1]

@test inputs.center ≈ ones(4) && inputs.radius ≈ 0.5

# ========================================================
# Testing continuous-time system with time-varying input
# ========================================================
Ui = [Ball2(0.01*i*ones(4), i*0.2) for i in 1:3]
cont_sys = ContinuousSystem(A, X0, Ui)

input_state = start(cont_sys.s.U)
inputs, input_state = next(cont_sys.s.U, input_state)
@test inputs.center ≈ 0.01*ones(4)
@test inputs.radius ≈ 0.2

inputs, input_state = next(cont_sys.s.U, input_state)
@test inputs.center ≈ 0.02*ones(4)
@test inputs.radius ≈ 0.4

inputs, input_state = next(cont_sys.s.U, input_state)
@test inputs.center ≈ 0.03*ones(4)
@test inputs.radius ≈ 0.6

# =========================================
# Testing discrete-time homogeneous system
# =========================================
δ = 0.01
discr_sys_homog = DiscreteSystem(A, X0)

# Check if the input is constant
#@test isa(discr_sys_homog.s.U, ConstantInput) # no field U
# Check if the input is empty
#@test isa(next_set(discr_sys_homog.s.U), ZeroSet)
# Check data fields
@test discr_sys_homog.s.A == A
@test discr_sys_homog.x0.center == zeros(4) && discr_sys_homog.x0.radius == 0.1

# =================================================
# Testing discrete-time system with constant input
# =================================================
U = Ball2(ones(4), 0.5)
discr_sys = DiscreteSystem(A, X0, U)

# check initial state
@test discr_sys.x0.center ≈ zeros(4) && discr_sys.x0.radius ≈ 0.1

# recover input
inputs = collect(nextinput(discr_sys.s.U))[1]

@test inputs.center ≈ ones(4) && inputs.radius ≈ 0.5

# =====================================================
# Testing discrete-time system with time-varying input
# =====================================================
Ui = [Ball2(0.01*i*ones(4), i*0.2) for i in 1:3]
discr_sys = DiscreteSystem(A, X0, Ui)
Uset = inputset(discr_sys.s)

input_state = start(Uset)
inputs, input_state = next(Uset, input_state)
@test inputs.center ≈ 0.01*ones(4)
@test inputs.radius ≈ 0.2

inputs, input_state = next(Uset, input_state)
@test inputs.center ≈ 0.02*ones(4)
@test inputs.radius ≈ 0.4

inputs, input_state = next(Uset, input_state)
@test inputs.center ≈ 0.03*ones(4)
@test inputs.radius ≈ 0.6


"""
    degrees(g[, T]; dir=:out)

Degree of each vertex. Return a vector which contains the degree of each vertex in graph `g`.

# Arguments

- `g`: should be a adjacency matrix, `SimpleGraph`, `SimpleDiGraph` (from Graphs) or `SimpleWeightedGraph`, `SimpleWeightedDiGraph` (from SimpleWeightedGraphs).
- `T`: result element type of degree vector; default is the element type of `g` (optional).
- `dir`: direction of degree; should be `:in`, `:out`, or `:both` (optional).

# Examples

```jldoctest
julia> using GraphLaplacians

julia> m = [0 1 1; 1 0 0; 1 0 0];

julia> GraphLaplacians.degrees(m)
3-element Vector{Int64}:
 2
 1
 1
```
"""
function degrees(g::AbstractGraph, T::DataType=eltype(g); dir::Symbol=:out)
    adj = Graphs.adjacency_matrix(g, T; dir=dir)
    degrees(adj, T; dir=dir)
end

function degree_matrix(g::AbstractGraph, T::DataType=eltype(g); dir::Symbol=:out)
    adj = Graphs.adjacency_matrix(g, T; dir=dir)
    degree_matrix(adj, T; dir=dir)
end

# Graphs.laplacian_matrix(g::AbstractGraph, T::DataType=eltype(g); dir::Symbol=:out)

function normalized_laplacian(g::AbstractGraph, T::DataType=eltype(g);
                              dir::Symbol=:both, selfloop::Bool=false)
    adj = Graphs.adjacency_matrix(g, T)
    normalized_laplacian(adj, T, dir=dir, selfloop=selfloop)
end

function scaled_laplacian(g::AbstractGraph, T::DataType=eltype(g))
    adj = adjacency_matrix(g, T)
    scaled_laplacian(adj, T)
end


function field_cache(v::Number,x)
  nx = length(x)
  c = zeros(typeof(v),nx)
  CachedArray(c)
end

function evaluate_field!(c,v::Number,x)
  nx = length(x)
  setsize!(c,(nx,))
  for i in eachindex(x)
    @inbounds c[i] = v
  end
  c
end

function field_gradient(v::Number)
  T = typeof(v)
  E = eltype(T)
  zero(E)
end

function field_cache(v::AbstractArray{<:Number},x)
  nx = length(x)
  sv = size(v)
  s = (nx,sv...)
  c = zeros(eltype(v),s)
  CachedArray(c)
end

function evaluate_field!(c,v::AbstractArray{<:Number},x)
  nx = length(x)
  sv = size(v)
  s = (nx,sv...)
  setsize!(c,s)
  cis = CartesianIndices(v)
  for i in eachindex(x)
    for ci in cis
      @inbounds c[i,ci] = v[ci]
    end
  end
  c
end

function field_gradient(v::AbstractArray{<:Number})
  T = eltype(v)
  E = eltype(T)
  Fill(zero(E),size(v))
end


# ---
# title: 949. Largest Time for Given Digits
# id: problem949
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Math
# link: <https://leetcode.com/problems/largest-time-for-given-digits/description/>
# hidden: true
# ---
#
# Given an array `arr` of 4 digits, find the latest 24-hour time that can be
# made using each digit **exactly once**.
#
# 24-hour times are formatted as `"HH:MM"`, where `HH` is between `00` and `23`,
# and `MM` is between `00` and `59`. The earliest 24-hour time is `00:00`, and
# the latest is `23:59`.
#
# Return _the latest 24-hour time  in `"HH:MM"` format_.  If no valid time can
# be made, return an empty string.
#
#
#
# **Example 1:**
#
#
#
#     Input: A = [1,2,3,4]
#     Output: "23:41"
#     Explanation:  The valid 24-hour times are "12:34", "12:43", "13:24", "13:42", "14:23", "14:32", "21:34", "21:43", "23:14", and "23:41". Of these times, "23:41" is the latest.
#
#
# **Example 2:**
#
#
#
#     Input: A = [5,5,5,5]
#     Output: ""
#     Explanation:  There are no valid 24-hour times as "55:55" is not valid.
#
#
# **Example 3:**
#
#
#
#     Input: A = [0,0,0,0]
#     Output: "00:00"
#
#
# **Example 4:**
#
#
#
#     Input: A = [0,0,1,0]
#     Output: "10:00"
#
#
#
#
# **Constraints:**
#
#   * `arr.length == 4`
#   * `0 <= arr[i] <= 9`
#
#
## @lc code=start
using LeetCode

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


function log_ticks(loglims)
    a = round(loglims[1], RoundDown)
    b = round(loglims[2], RoundUp)
    v = log10.(1:9)
    ticks = vcat([i .+ a .+ v for i in 0:b-a]..., [b])
    labels = [tick == round(tick) ? to_latex("10^{$(round(Int, tick))}") : " " for tick in ticks]
    return ticks, labels
end

function find_limits(vals; start=[first(vals), first(vals)], margins=0.05)
    vmin = start[1]
    vmax = start[2]
    for v in vals
        if v < vmin
            vmin = v
        elseif v > vmax
            vmax = v
        end
    end
    diff = vmax - vmin
    vmax += margins * diff
    vmin -= margins * diff
    return vmin, vmax
end

function print_tf(roots, gain)
    # Convert the numerator and denominator to strings
    num = []
    den = []

    for root in roots
        s = printroot(root.pos)
        if root.pole
            push!(den, s)
        else
            push!(num, s)
        end
    end

    if length(num) == 0
        numstr = "1.0"
    elseif length(num) == 1
        numstr = num[1]
    else
        numstr = "(" * join(num, ")(") * ")"
    end
    if length(den) == 0
        denstr = "1.0"
    elseif length(den) == 1
        denstr = den[1]
    else
        denstr = "(" * join(den, ")(") * ")"
    end

    gainstr = "$(round(gain, sigdigits=3)) "
    gainpre = "$(repeat(" ", length(gainstr)))"

    # Figure out the length of the separating line
    len_num = length(numstr)
    len_den = length(denstr)
    dashcount = max(len_num, len_den)

    # Center the numerator or denominator
    if len_num < dashcount
        numstr = "$(repeat(" ", div(dashcount - len_num, 2)))$numstr"
    else
        denstr = "$(repeat(" ", div(dashcount - len_den, 2)))$denstr"
    end
    return gainpre * to_latex(numstr) * "\n " * gainstr * repeat("-", dashcount) * "\n " * gainpre * to_latex(denstr)
end

function printroot(z)
    a = z[1]
    b = z[2]
    if b == 0
        return "s $(a < 0 ? "+" : "-") $(round(abs(a), sigdigits=3))"
    else
        return "s^2 $(a < 0 ? "+" : "-") $(round(2abs(a), sigdigits=3))s + $(round(a^2 + b^2, sigdigits=3))"
    end
end


import Ipopt, NLopt, Percival
using Nonconvex, LinearAlgebra, Test, Zygote, FiniteDifferences
const FDM = FiniteDifferences

@testset "OrderedDict of reals" begin
    f(x) = sqrt(x[:b])
    g(x, a, b) = (a*x[:a] + b)^3 - x[:b]

    m = DictModel(f)
    addvar!(m, :a, 0.0, 10.0)
    addvar!(m, :b, 0.0, 10.0)
    add_ineq_constraint!(m, x -> g(x, 2, 0))
    add_ineq_constraint!(m, x -> g(x, -1, 1))

    @test getmin(m) == OrderedDict(:a => 0.0, :b => 0.0)
    @test getmax(m) == OrderedDict(:a => 10.0, :b => 10.0)
    @test getmin(m, :a) == 0.0
    @test getmax(m, :a) == 10.0
    @test getmin(m, :b) == 0.0
    @test getmax(m, :b) == 10.0

    p = OrderedDict(:a => 1.234, :b => 2.345)

    vec_model, _, un = Nonconvex.tovecmodel(m)
    pvec = Nonconvex.flatten(p)[1]
    val0, grad0 = Nonconvex.value_gradient(Nonconvex.getobjective(vec_model), pvec)
    @test val0 == f(un(pvec))
    @test grad0 == Zygote.gradient(x -> f(un(x)), pvec)[1]

    val1, grad1 = Nonconvex.value_gradient(Nonconvex.getineqconstraint(vec_model, 1), pvec)
    @test val1 == g(un(pvec), 2, 0)
    @test grad1 == Zygote.gradient(pvec -> g(un(pvec), 2, 0), pvec)[1]

    val2, grad2 = Nonconvex.value_gradient(Nonconvex.getineqconstraint(vec_model, 2), pvec)
    @test val2 == g(un(pvec), -1, 1)
    @test grad2 == Zygote.gradient(pvec -> g(un(pvec), -1, 1), pvec)[1]

    vals, jac = Nonconvex.value_jacobian(Nonconvex.getineqconstraints(vec_model), pvec)
    @test [val1, val2] == vals
    @test [grad1 grad2]' == jac

    vals, jac = Nonconvex.value_jacobian(Nonconvex.getobjectiveconstraints(vec_model), pvec)
    @test [val0, val1, val2] == vals
    @test [grad0 grad1 grad2]' == jac

    for alg in [MMA87(), MMA02(), IpoptAlg(), NLoptAlg(:LD_MMA), PercivalAlg()]
        r = optimize(m, MMA02(), p)
        @test norm([r.minimizer[:a] - 1/3, r.minimizer[:b] - 8/27]) < 1e-6
    end
end

struct S
    a
    b
end
@testset "Vector of structs" begin
    f(x) = sqrt(x[1].b)
    g(x, a, b) = (a*x[1].a + b)^3 - x[1].b

    m = Model(f)
    addvar!(m, S(0.0, 0.0), S(10.0, 10.0))
    add_ineq_constraint!(m, x -> g(x, 2, 0))
    add_ineq_constraint!(m, x -> g(x, -1, 1))

    p = [S(1.234, 2.345)]

    vec_model, _, un = Nonconvex.tovecmodel(m)
    pvec, un = Nonconvex.flatten(p)
    val0, grad0 = Nonconvex.value_gradient(Nonconvex.getobjective(vec_model), pvec)
    @test val0 == f(un(pvec))
    @test grad0 == Zygote.gradient(x -> f(un(x)), pvec)[1]

    val1, grad1 = Nonconvex.value_gradient(Nonconvex.getineqconstraint(vec_model, 1), pvec)
    @test val1 == g(un(pvec), 2, 0)
    @test grad1 == Zygote.gradient(pvec -> g(un(pvec), 2, 0), pvec)[1]

    val2, grad2 = Nonconvex.value_gradient(Nonconvex.getineqconstraint(vec_model, 2), pvec)
    @test val2 == g(un(pvec), -1, 1)
    @test grad2 == Zygote.gradient(pvec -> g(un(pvec), -1, 1), pvec)[1]

    vals, jac = Nonconvex.value_jacobian(Nonconvex.getineqconstraints(vec_model), pvec)
    @test [val1, val2] == vals
    @test [grad1 grad2]' == jac

    vals, jac = Nonconvex.value_jacobian(Nonconvex.getobjectiveconstraints(vec_model), pvec)
    @test [val0, val1, val2] == vals
    @test [grad0 grad1 grad2]' == jac

    for alg in [MMA87(), MMA02(), IpoptAlg(), NLoptAlg(:LD_MMA), PercivalAlg()]
        r = optimize(m, MMA02(), p)
        @test norm([r.minimizer[1].a - 1/3, r.minimizer[1].b - 8/27]) < 1e-6
    end
end


function seed_clusters(xi,yi,n,nplus,Lx,Ly)

    κi = [ones(nplus); -ones(n-nplus)]

    # x and y distances
    xij = xi .- xi'
    yij = yi .- yi'

    # periodicity
   periodic_distances!(xij,yij,Lx,Ly)

    # distances: set self distance to much larger than box size
    rij = @. hypot(xij,yij)
    rij += diagm(0=>1e6*hypot(Lx,Ly)*ones(n))

    # get index of minimum separations
    _, nn_index = findmin(rij,dims=2)

    nn_mat = ones(n)*collect(1:n)' .|> Int
    nn_vec = nn_mat[nn_index][:,1]
    mutuals = (collect(1:n) .== nn_vec[nn_vec])

    is_seed = zeros(n)
    for i in eachindex(mutuals)
        is_seed[i] = (mutuals[i] == 1 && κi[i]*κi[nn_vec[i]] == 1.0)
    end

    seeds_index = findall(is_seed .> 0.0)
    seeds_index2= nn_vec[seeds_index]
    num_found_in_seeds = length(seeds_index)

    # seed coords
    xs = xi[seeds_index]
    ys = yi[seeds_index]
    xs2 = xi[seeds_index2]
    ys2 = yi[seeds_index2]

    xs = [xs; xs2]
    ys = [ys; ys2]
    seeds_index = [seeds_index; seeds_index2]

return xs,ys,is_seed,seeds_index,num_found_in_seeds
end