text:
"""
constraints.jl

Notes:
The jacobian! method is meant for Markovian constraints
while the jacobian_copy! method is intended for a direct solver
which accomodates more general constraints
"""

const NULL_MAT = Array{Float64,2}(undef, 0, 0)
const NULL_VEC = Array{Float64,1}(undef, 0)

# general

@enum ConstraintSense begin
    EQUALITY = 1
    INEQUALITY = 2
end

@enum ActionType begin
    STATE = 1
    CONTROL = 2
end

abstract type AbstractConstraint{S} end

@inline sense(con::AbstractConstraint{S}) where {S} = S

"Only a function of states and controls at a single knotpoint"
abstract type StageConstraint{S} <: AbstractConstraint{S} end
"Only a function of states at a single knotpoint"
abstract type StateConstraint{S} <: StageConstraint{S} end
"Only a function of controls at a single knotpoint"
abstract type ControlConstraint{S} <: StageConstraint{S} end
"Only a function of states and controls at two adjacent knotpoints"
abstract type CoupledConstraint{S} <: AbstractConstraint{S} end
"Only a function of states at adjacent knotpoints"
abstract type CoupledStateConstraint{S} <: CoupledConstraint{S} end
"Only a function of controls at adjacent knotpoints"
abstract type CoupledControlConstraint{S} <: CoupledConstraint{S} end

mutable struct ConstraintParams{T}
    # penalty scaling parameter
	ϕ::T
    # initial penalty parameter
	μ0::T
    # max penalty parameter
	μ_max::T
    # max Lagrange multiplier
	λ_max::T
    # active set tolerance
    a_tol::T
end

function ConstraintParams(;ϕ::T=10., μ0::T=1., μ_max::T=1e8, λ_max::T=1e8, a_tol::T=0.) where {T}
    return ConstraintParams{T}(ϕ, μ0, μ_max, λ_max, a_tol)
end

Base.eltype(::ConstraintParams{T}) where {T} = T

"""
	GoalConstraint{P,T}

Constraint of the form ``x_g = a``, where ``x_g`` can be only part of the state
vector.

# Constructors:
```julia
GoalConstraint(xf::AbstractVector)
GoalConstraint(xf::AbstractVector, inds)
```
where `xf` is an n-dimensional goal state. If `inds` is provided,
only `xf[inds]` will be used.
"""
struct GoalConstraint{S,Tx,Ti,Txp,Tup,Tp,Tpx,Tpu,T} <: StateConstraint{S}
    n::Int
    m::Int
    # constraint length
    p::Int
    # goal state vector
    xf::Tx
    # indices into state vector
    inds::Ti
    # tmp for cost_derivatives!
    XP_tmp::Txp
    UP_tmp::Tup
    p_tmp::Vector{Tp}
    # stores for jacobian!
    Cx::Tpx
    Cu::Tpu
    # misc
    const_jac::Bool
    state_expansion::Bool
    control_expansion::Bool
    coupled_expansion::Bool
    direct::Bool
    params::ConstraintParams{T}
end

# constructors
function GoalConstraint(xf::Tx, inds::Ti, n::Int, m::Int, M, V;
                        direct::Bool=false,
                        params::ConstraintParams{T}=ConstraintParams()) where {T,Tx,Ti}
    p = length(inds)
    XP_tmp = M(zeros(n, p))
    UP_tmp = M(zeros(n, p))
    p_tmp = [V(zeros(p)) for i = 1:2]
    Cx = M(zeros(p, n))
    Cu = M(zeros(p, m))
    Txp = typeof(XP_tmp)
    Tup = typeof(UP_tmp)
    Tp = typeof(p_tmp[1])
    Tpx = typeof(Cx)
    Tpu = typeof(Cu)
    const_jac = true
    state_expansion = true
    control_expansion = false
    coupled_expansion = false
    sense = EQUALITY
    con = GoalConstraint{sense,Tx,Ti,Txp,Tup,Tp,Tpx,Tpu,T}(
        n, m, p, xf, inds, XP_tmp, UP_tmp, p_tmp, Cx, Cu, const_jac,
        state_expansion, control_expansion, coupled_expansion, direct, params
    )
    jacobian!(con.Cx, con.Cu, con, NULL_VEC, NULL_VEC, 0)
    return con
end

# evaluation
function evaluate!(c::AbstractVector, con::GoalConstraint,
                   X::AbstractVector, U::AbstractVector, k::Int)
    for (i, j) in enumerate(con.inds)
        c[i] = X[k][j] - con.xf[j]
    end
    return nothing
end

function jacobian!(Cx::AbstractMatrix, Cu::AbstractMatrix, con::GoalConstraint, X::AbstractVector,
                   U::AbstractVector, k::Int)
    for (i, j) in enumerate(con.inds)
	    Cx[i, j] = 1.
    end
    return nothing
end

function jacobian_copy!(D::AbstractMatrix, con::GoalConstraint,
                        X::AbstractVector, U::AbstractVector, k::Int,
                        c_ginds::AbstractVector, x_ginds::AbstractVector,
                        u_ginds::AbstractVector)
    for (i, j) in enumerate(con.inds)
        D[c_ginds[i], x_ginds[k][j]] = 1.
    end
    return nothing
end

# methods
@inline Base.length(con::GoalConstraint) = con.p

function max_violation_info(con::GoalConstraint, c::AbstractVector, k::Int)
    max_viol = -Inf
    info_str = ""
    for (i, j) in enumerate(con.inds)
        viol = abs(c[i])
        if viol > max_viol
            info_str = "GoalConstraint x[$j] k=$k"
            max_viol = viol
        end
    end
    return max_viol, info_str
end

"""
DynamicsConstraint - constraint for explicit dynamics
"""
struct DynamicsConstraint{S,T,Tir,Tm,Tt,Tix,Tiu,Tx,Txx,Txu,Txz} <: CoupledConstraint{S}
    n::Int
    m::Int
    ir::Tir
    model::Tm
    ts::Tt
    ix::Tix
    iu::Tiu
    # store for evaluate!
    x_tmp::Tx
    # store for jacobian_copy!
    A::Txx
    B::Txu
    AB::Txz
    # misc
    const_jac::Bool
    direct::Bool
    params::ConstraintParams{T}
end

# constructors
function DynamicsConstraint(
    ir::Tir, model::Tm, ts::Tt, ix::Tix, iu::Tiu, n::Int, m::Int, M, V;
    direct::Bool=true, params::ConstraintParams{T}=ConstraintParams()) where {Tir,Tm,Tt,T,Tix,Tiu}
    x_tmp = V(zeros(n))
    A = M(zeros(n, n))
    B = M(zeros(n, m))
    AB = M(zeros(n, n+m))
    Tx = typeof(x_tmp)
    Txx = typeof(A)
    Txu = typeof(B)
    Txz = typeof(AB)
    const_jac = false
    sense = EQUALITY
    con = DynamicsConstraint{sense,T,Tir,Tm,Tt,Tix,Tiu,Tx,Txx,Txu,Txz}(
        n, m, ir, model, ts, ix, iu, x_tmp, A, B, AB, const_jac, direct, params
    )
    return con
end

# evaluation
function evaluate!(c::AbstractVector, con::DynamicsConstraint, X::AbstractVector,
                   U::AbstractVector, k::Int)
    discrete_dynamics!(con.x_tmp, con.ir, con.model, X[k - 1], U[k - 1], con.ts[k - 1],
                          con.ts[k] - con.ts[k - 1])
    for i = 1:con.n
        c[i] = con.x_tmp[i] - X[k][i]
    end
    return nothing
end

function jacobian!(Cx::AbstractMatrix, Cu::AbstractMatrix, con::DynamicsConstraint,
                   X::AbstractVector, U::AbstractVector, k::Int)
    throw("not implemented")
    return nothing
end

function jacobian_copy!(D::AbstractMatrix, con::DynamicsConstraint,
                        X::AbstractVector, U::AbstractVector, k::Int,
                        c_ginds::AbstractVector, x_ginds::AbstractVector,
                        u_ginds::AbstractVector)
    # ASSUMPTION: D[c_ginds, i] .= 0 for i ∉ x_ginds[k - 1] U u_ginds[k - 1] U x_ginds[k]
    discrete_jacobian!(con.A, con.B, con.ir, con.model,
                       X[k - 1], U[k - 1], con.ts[k - 1],
                       con.ts[k] - con.ts[k - 1])
    D[c_ginds, x_ginds[k - 1]] .= con.A
    D[c_ginds, u_ginds[k - 1]] .= con.B
    for (i, j) in enumerate(c_ginds)
        D[j, x_ginds[k][i]] = 1.
    end
end

# methods
@inline Base.length(con::DynamicsConstraint) = con.n

function max_violation_info(con::DynamicsConstraint, c::AbstractVector, k::Int)
    max_viol = -Inf
    info_str = ""
    for i = 1:con.n
        viol = abs(c[i])
        if viol > max_viol
            info_str = "DynamicsConstraint x[$i] k=$k"
            max_viol = viol
        end
    end
    return max_viol, info_str
end

"""
	BoundConstraint{Tz,Tiu,Til,Tinds}

Linear bound constraint on states and controls
# Constructors
```julia
BoundConstraint(n, m; x_min, x_max, u_min, u_max)
```
Any of the bounds can be ±∞. The bound can also be specifed as a single scalar, which applies the bound to all state/controls.
"""
struct BoundConstraint{S,Tx,Tu,Tixu,Tixl,Tiuu,Tiul,Txp,Tup,Tp,Tpx,Tpu,T} <: StageConstraint{S}
    n::Int
    m::Int
    # constraint length
    p::Int
    x_max::Tx
    x_min::Tx
    u_max::Tu
    u_min::Tu
    x_max_inds::Tixu
    x_min_inds::Tixl
    u_max_inds::Tiuu
    u_min_inds::Tiul
    XP_tmp::Txp
    UP_tmp::Tup
    p_tmp::Vector{Tp}
    Cx::Tpx
    Cu::Tpu
    const_jac::Bool
    state_expansion::Bool
    control_expansion::Bool
    coupled_expansion::Bool
    direct::Bool
    params::ConstraintParams{T}
end

# constructor
function BoundConstraint(
    x_max::Tx, x_min::Tx, u_max::Tu, u_min::Tu, n::Int, m::Int, M, V; direct::Bool=false,
    checks=true, params::ConstraintParams{T}=ConstraintParams()
) where {T,Tx<:AbstractVector,Tu<:AbstractVector}
    if checks
        @assert all(x_max .>= x_min)
        @assert all(u_max .>= u_min)
    end
    x_max_finite_inds = findall(isfinite, x_max)
    x_max_c_inds = 1:length(x_max_finite_inds)
    x_max_inds = V([(i, j) for (i, j) in zip(x_max_c_inds, x_max_finite_inds)])
    p = length(x_max_inds)
    x_min_finite_inds = findall(isfinite, x_min)
    x_min_c_inds = p .+ (1:length(x_min_finite_inds))
    x_min_inds = V([(i, j) for (i, j) in zip(x_min_c_inds, x_min_finite_inds)])
    p += length(x_min_inds)
    u_max_finite_inds = findall(isfinite, u_max)
    u_max_c_inds = p .+ (1:length(u_max_finite_inds))
    u_max_inds = V([(i, j) for (i, j) in zip(u_max_c_inds, u_max_finite_inds)])
    p += length(u_max_inds)
    u_min_finite_inds = findall(isfinite, u_min)
    u_min_c_inds = p .+ (1:length(u_min_finite_inds))
    u_min_inds = V([(i, j) for (i, j) in zip(u_min_c_inds, u_min_finite_inds)])
    p += length(u_min_inds)
    state_expansion = (length(x_max_inds) + length(x_min_inds)) > 0
    control_expansion = (length(u_max_inds) + length(u_min_inds)) > 0
    coupled_expansion = state_expansion && control_expansion
    # tmps for jacobian!
    XP_tmp = M(zeros(n, p))
    UP_tmp = M(zeros(m, p))
    p_tmp = [V(zeros(p)), V(zeros(p))]
    Cx = M(zeros(p, n))
    Cu = M(zeros(p, m))
    const_jac = true
    sense = INEQUALITY
    # types
    Tixu = typeof(x_max_inds)
    Tixl = typeof(x_min_inds)
    Tiuu = typeof(u_max_inds)
    Tiul = typeof(u_min_inds)
    Txp = typeof(XP_tmp)
    Tup = typeof(UP_tmp)
    Tp = typeof(p_tmp[1])
    Tpx = typeof(Cx)
    Tpu = typeof(Cu)
    # construct
    con = BoundConstraint{sense,Tx,Tu,Tixu,Tixl,Tiuu,Tiul,Txp,Tup,Tp,Tpx,Tpu,T}(
        n, m, p, x_max, x_min, u_max, u_min, x_max_inds, x_min_inds, u_max_inds,
        u_min_inds, XP_tmp, UP_tmp, p_tmp, Cx, Cu, const_jac, state_expansion,
        control_expansion, coupled_expansion, direct, params
    )
    # initialize
    jacobian!(Cx, Cu, con, x_max, x_max, 0)
    return con
end

# evaluation
function evaluate!(c::AbstractVector, con::BoundConstraint, X::AbstractVector,
                   U::AbstractVector, k::Int; log=false)
    for (i, j) in con.x_max_inds
        c[i] = X[k][j] - con.x_max[j]
    end
    for (i, j) in con.u_max_inds
        c[i] = U[k][j] - con.u_max[j]
    end
    for (i, j) in con.x_min_inds
        c[i] = con.x_min[j] - X[k][j]
    end
    for (i, j) in con.u_min_inds
        c[i] = con.u_min[j] - U[k][j]
    end
    return nothing
end

function jacobian!(Cx::AbstractMatrix, Cu::AbstractMatrix, con::BoundConstraint,
                   X::AbstractVector, U::AbstractVector, k::Int)
    for (i, j) in con.x_max_inds
        Cx[i, j] = 1.
    end
    for (i, j) in con.x_min_inds
        Cx[i, j] = -1.
    end
    for (i, j) in con.u_max_inds
        Cu[i, j] = 1.
    end
    for (i, j) in con.u_min_inds
        Cu[i, j] = -1.
    end
    return nothing
end

function jacobian_copy!(D::AbstractMatrix, con::BoundConstraint,
                        X::AbstractVector, U::AbstractVector, k::Int,
                        c_ginds::AbstractVector, x_ginds::AbstractVector,
                        u_ginds::AbstractVector)
    for (i, j) in con.x_max_inds
        D[c_ginds[i], x_ginds[k][j]] = 1.
    end
    for (i, j) in con.x_min_inds
        D[c_ginds[i], x_ginds[k][j]] = -1.
    end
    for (i, j) in con.u_max_inds
        D[c_ginds[i], u_ginds[k][j]] = 1.
    end
    for (i, j) in con.u_min_inds
        D[c_ginds[i], u_ginds[k][j]] = -1.
    end
    return nothing
end

# methods
@inline Base.length(con::BoundConstraint) = con.p

function max_violation_info(con::BoundConstraint, c::AbstractVector, k::Int)
    max_viol = -Inf
    info_str = ""
    for (i, j) in con.x_max_inds
        viol = c[i]
        if viol > max_viol
            max_viol = viol
            info_str = "BoundConstraint x_max[$j] k=$k"
        end
    end
    for (i, j) in con.u_max_inds
        viol = c[i]
        if viol > max_viol
            max_viol = viol
            info_str = "BoundConstraint u_max[$j] k=$k"
        end
    end
    for (i, j) in con.x_min_inds
        viol = c[i]
        if viol > max_viol
            max_viol = viol
            info_str = "BoundConstraint x_min[$j] k=$k"
        end
    end
    for (i, j) in con.u_min_inds
        viol = c[i]
        if viol > max_viol
            max_viol = viol
            info_str = "BoundConstraint u_min[$j] k=$k"
        end
    end
    return max_viol, info_str
end

"""
NormConstraint
"""
struct NormConstraint{S,A,Ti,Txp,Tup,Tp,Tpx,Tpu,T} <: StageConstraint{S}
    n::Int
    m::Int
    inds::Ti
    n_max::T
    # constraint length
    p::Int
    XP_tmp::Txp
    UP_tmp::Tup
    p_tmp::Vector{Tp}
    Cx::Tpx
    Cu::Tpu
    const_jac::Bool
    state_expansion::Bool
    control_expansion::Bool
    coupled_expansion::Bool
    direct::Bool
    params::ConstraintParams{T}
end

# constructor
function NormConstraint(
    sense::ConstraintSense, action_type::ActionType, inds::Ti, n_max::T, n::Int, m::Int, M, V;
    direct::Bool=false, checks=true, params::ConstraintParams{T}=ConstraintParams()
) where {Ti,T}
    state_expansion = action_type == STATE ? true : false
    control_expansion = action_type == CONTROL ? true : false
    coupled_expansion = false
    if checks
        @assert n_max >= 0
        if action_type == STATE
            @assert all(inds .<= n)
        end
        if action_type == CONTROL
            @assert all(inds .<= m)
        end
    end
    # tmps for jacobian!
    p = 1
    XP_tmp = M(zeros(n, p))
    UP_tmp = M(zeros(m, p))
    p_tmp = [V(zeros(p)), V(zeros(p))]
    Cx = M(zeros(p, n))
    Cu = M(zeros(p, m))
    const_jac = false
    sense = EQUALITY
    # types
    Txp = typeof(XP_tmp)
    Tup = typeof(UP_tmp)
    Tp = typeof(p_tmp[1])
    Tpx = typeof(Cx)
    Tpu = typeof(Cu)
    # construct
    con = NormConstraint{sense, action_type,Ti,Txp,Tup,Tp,Tpx,Tpu,T}(
        n, m, inds, n_max, p, XP_tmp, UP_tmp, p_tmp, Cx, Cu, const_jac, state_expansion,
        control_expansion, coupled_expansion, direct, params
    )
    return con
end

# evaluation
function evaluate!(c::AbstractVector, con::NormConstraint{S,STATE}, X::AbstractVector,
                   U::AbstractVector, k::Int; log=false) where {S}
    c[1] = -con.n_max
    for i in con.inds
        c[1] += X[k][i]^2
    end
    return nothing
end

function evaluate!(c::AbstractVector, con::NormConstraint{S,CONTROL}, X::AbstractVector,
                   U::AbstractVector, k::Int; log=false) where {S}
    c[1] = -con.n_max
    for i in con.inds
        c[1] += U[k][i]^2
    end
    return nothing
end

function jacobian!(Cx::AbstractMatrix, Cu::AbstractMatrix, con::NormConstraint{S,STATE},
                   X::AbstractVector, U::AbstractVector, k::Int) where {S}
    for i in con.inds
        Cx[1, i] = 2 * X[k][i]
    end
    return nothing
end

function jacobian!(Cx::AbstractMatrix, Cu::AbstractMatrix, con::NormConstraint{S,CONTROL},
                   X::AbstractVector, U::AbstractVector, k::Int) where {S}
    for i in con.inds
        Cu[1, i] = 2 * U[k][i]
    end
    return nothing
end

function jacobian_copy!(D::AbstractMatrix, con::NormConstraint{S,STATE},
                        X::AbstractVector, U::AbstractVector, k::Int,
                        c_ginds::AbstractVector, x_ginds::AbstractVector,
                        u_ginds::AbstractVector) where {S}
    for i in con.inds
        D[c_ginds[1], x_ginds[k][i]] = 2 * X[k][i]
    end
    return nothing
end

function jacobian_copy!(D::AbstractMatrix, con::NormConstraint{S,CONTROL},
                        X::AbstractVector, U::AbstractVector, k::Int,
                        c_ginds::AbstractVector, x_ginds::AbstractVector,
                        u_ginds::AbstractVector) where {S}
    for i in con.inds
        D[c_ginds[1], u_ginds[k][i]] = 2 * U[k][i]
    end
    return nothing
end

# methods
@inline Base.length(con::NormConstraint) = con.p

function max_violation_info(con::NormConstraint{S,STATE}, c::AbstractVector, k::Int) where {S}
    max_viol = c[1]
    info_str = "NormConstraint x[$(con.inds)]"
    return max_viol, info_str
end

function max_violation_info(con::NormConstraint{CONTROL}, c::AbstractVector, k::Int)
    max_viol = c[1]
    info_str = "NormConstraint u[$(con.inds)]"
    return max_viol, info_str
end


text:
@discord_object struct DiscordChannel
    id::Snowflake
    type::ChannelType.ChannelTypeEnum
    guild_id::Snowflake
    position::Int
    permission_overwrites::Vector{Overwrite}
    name::String
    topic::String
    nsfw::Bool
    last_message_id::Snowflake
    bitrate::Int
    user_limit::Int
    rate_limit_per_user::Int
    recipients::Vector{User}
    icon::String
    owner_id::Snowflake
    application_id::Snowflake
    parent_id::Snowflake
    last_pin_timestamp::Union{String, DateTime}
end

ext: jl


text:
module Inside

import Gloria: onevent!, render!, update!
using Gloria: Gloria, AbstractObject, Audio, add!, kill!, play!, Scene, Texture,
    Window, isalive, isbutton, iskey, ispressed, getmousestate, tocoordinates,
    Event, Layer
using Gloria.Shapes: AbstractShape, circle, Polyline, Vertex, vertices,
    Polygon, intersects, inside, translate, rotate, centroid

using Colors: RGB, @colorant_str
using LinearAlgebra

##################################################
# Types
##################################################

mutable struct Controls <: AbstractObject end

abstract type InsideObject <: AbstractObject end

mutable struct Shape <: InsideObject
    shape::AbstractShape
    color::RGB
    x::Float64
    y::Float64
    θ::Float64
    active::Bool
end
Shape(shape, x, y, θ) = Shape(shape, colorant"#303030", x, y, θ, false)

##################################################
# Events
##################################################

function onevent!(::Controls, e::Event{:key_down})
    iskey(e, "escape") && Gloria.quit!(window, e)
end

function onevent!(self::Shape, e::Event{:mousemotion})
    if self.active
        if getmousestate().left
            self.x += e.rel_x
            self.y += e.rel_y
        end
    end
end

function onevent!(self::Shape, e::Event{:mousebutton_down})
    if isbutton(e, "left")
        # moved = self.shape |> rotate(self.θ) |> translate(self.x, self.y)
        # vs = vertices(moved)
        # xmin, xmax = extrema(v[1] for v in vs)
        # ymin, ymax = extrema(v[2] for v in vs)
        # c = centroid(moved)
        # mp = Vertex(tocoordinates(shape_layer, e.x, e.y)...)
        # v = normalize(c .- mp)
        # p2 = Vertex((mp .+ 1e6 * v)...)
        # @info "---------------------------"
        # @info typeof(self.shape)
        # @info "Shape center: $c"
        # @info "Boundary: [$xmin, $ymin] x [$xmax, $ymax]"
        # @info "Mouse position: $mp"
        # @info "Direction: $v"
        # @info "Ray target: $p2"
        # @info "---------------------------"
        if inside(
                self.shape |> rotate(self.θ) |> translate(self.x, self.y),
                Vertex(tocoordinates(shape_layer, e.x, e.y)...)
            )
            self.active = true
        end
    end
end

function onevent!(self::Shape, e::Event{:mousebutton_up})
    if isbutton(e, "left")
        self.active = false
    end
end

##################################################
# Update
##################################################

function update!(self::Shape, ::Gloria.AbstractLayer, t, dt)
    if any(
            shape -> any(
                v -> inside(
                    self.shape |> rotate(self.θ) |> translate(self.x, self.y), v
                ),
                vertices(
                    shape.shape |> rotate(shape.θ) |> translate(shape.x, shape.y)
                )
            ),
            filter(x->x ≢ self, shape_layer)
        )

        self.color = colorant"#FA00FA"
    else
        self.color = colorant"#303030"
    end
end

##################################################
# Render
##################################################

function render!(layer::Layer, self::Shape, frame::Int, fps::Float64)
    render!(layer, self.shape, self.x, self.y, self.θ, color=self.color)
end

###

##################################################
# Setup
##################################################

function main(; keepalive=true)
    @eval begin
        # const width, height = 1920, 1080
        const width, height = 800, 600
        const controls_layer = Layer([Controls()], show=false)

        const shape_layer = Layer(Shape[], width/2, height/2)

        const scene = Scene(shape_layer, controls_layer, color=colorant"#D0D0D0")
        const window = Window("Inside", width, height, scene, fullscreen=false)
        const keyboard = Gloria.KeyboardState()

        add!(shape_layer, Shape(circle(Vertex(0., 0.), 100), 0., 0., 0.))
        add!(shape_layer, Shape(Polygon(Vertex(0, 0), Vertex(100, 0), Vertex(0, 100)), 200., 0., 40.))
        add!(shape_layer, Shape(Polyline([Vertex(100(i - 5), randn()*50 + 200) for i in 0:10]...), 0., 0., -10.))
    end

    Gloria.run!(window)
    keepalive && wait(window)
end

end # module


text:
#! /bin/julia
# ~/.julia/dev/JuliaPlot/src/JuliaPlot.jl
using JuliaPlot
posstrings=readlines(`xrandr --listmonitors`)
pos = Tuple(
            parse.(Int64,
                   getindex.(
                             Ref(match(r".* (\d+)/\d+x(\d+)/\d+\+(\d+)\+(\d+) .*",posstring))
                             ,(3,4,1,2)
                            )
                  )
            for posstring in posstrings[2:end]
           )
multiMonitorJuliaPlot(;
                      pos = pos,
                     )


text:
# TODO: Move generic model tests from MOIU to here

struct UnknownScalarSet{T} <: MOI.AbstractScalarSet
    constant::T
end
MOI.constant(set::UnknownScalarSet) = set.constant
Base.copy(set::UnknownScalarSet) = UnknownScalarSet(copy(MOI.constant(set)))
function MOIU.shift_constant(set::UnknownScalarSet, offset)
    return UnknownScalarSet(MOI.constant(set) + offset)
end

struct UnknownVectorSet <: MOI.AbstractVectorSet end

function default_objective_test(model::MOI.ModelLike)
    @testset "Test default objective" begin
        MOI.empty!(model)
        MOI.get(model, MOI.ObjectiveSense()) == MOI.FEASIBILITY_SENSE
    end
end

function default_status_test(model::MOI.ModelLike)
    MOI.empty!(model)
    @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED
    @test MOI.get(model, MOI.PrimalStatus()) == MOI.NO_SOLUTION
    @test MOI.get(model, MOI.DualStatus()) == MOI.NO_SOLUTION
end

function nametest(model::MOI.ModelLike)
    @testset "Variables" begin
        MOI.empty!(model)
        x = MOI.add_variables(model, 2)
        MOI.set(model, MOI.VariableName(), x[1], "x1")
        @test MOI.get(model, MOI.VariableIndex, "x1") == x[1]
        MOI.set(model, MOI.VariableName(), x[1], "x2")
        @test MOI.get(model, MOI.VariableIndex, "x1") === nothing
        @test MOI.get(model, MOI.VariableIndex, "x2") == x[1]
        MOI.set(model, MOI.VariableName(), x[2], "x1")
        @test MOI.get(model, MOI.VariableIndex, "x1") == x[2]
        MOI.set(model, MOI.VariableName(), x[1], "x1")
        @test_throws ErrorException MOI.get(model, MOI.VariableIndex, "x1")
    end

    @testset "Variable bounds" begin
        MOI.empty!(model)
        x = MOI.add_variable(model)
        c1 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(0.0))
        c2 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(1.0))
        MOI.set(model, MOI.ConstraintName(), c1, "c1")
        @test MOI.get(model, MOI.ConstraintIndex, "c1") == c1
        MOI.set(model, MOI.ConstraintName(), c1, "c2")
        @test MOI.get(model, MOI.ConstraintIndex, "c1") === nothing
        @test MOI.get(model, MOI.ConstraintIndex, "c2") == c1
        MOI.set(model, MOI.ConstraintName(), c2, "c1")
        @test MOI.get(model, MOI.ConstraintIndex, "c1") == c2
        MOI.set(model, MOI.ConstraintName(), c1, "c1")
        @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "c1")
    end

    @testset "Affine constraints" begin
        MOI.empty!(model)
        x = MOI.add_variable(model)
        f = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0)
        c1 = MOI.add_constraint(model, f, MOI.GreaterThan(0.0))
        c2 = MOI.add_constraint(model, f, MOI.LessThan(1.0))
        MOI.set(model, MOI.ConstraintName(), c1, "c1")
        @test MOI.get(model, MOI.ConstraintIndex, "c1") == c1
        MOI.set(model, MOI.ConstraintName(), c1, "c2")
        @test MOI.get(model, MOI.ConstraintIndex, "c1") === nothing
        @test MOI.get(model, MOI.ConstraintIndex, "c2") == c1
        MOI.set(model, MOI.ConstraintName(), c2, "c1")
        @test MOI.get(model, MOI.ConstraintIndex, "c1") == c2
        MOI.set(model, MOI.ConstraintName(), c1, "c1")
        @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "c1")
    end

    @testset "Name test with $(typeof(model))" begin
        MOI.empty!(model)
        @test MOIU.supports_default_copy_to(model, #=copy_names=# true)
        @test MOI.supports(model, MOI.Name())
        @test !(MOI.Name() in MOI.get(model, MOI.ListOfModelAttributesSet()))
        @test MOI.get(model, MOI.Name()) == ""
        MOI.set(model, MOI.Name(), "Name1")
        @test MOI.Name() in MOI.get(model, MOI.ListOfModelAttributesSet())
        @test MOI.get(model, MOI.Name()) == "Name1"
        MOI.set(model, MOI.Name(), "Name2")
        @test MOI.Name() in MOI.get(model, MOI.ListOfModelAttributesSet())
        @test MOI.get(model, MOI.Name()) == "Name2"

        @test MOI.get(model, MOI.NumberOfVariables()) == 0
        @test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64},MOI.LessThan{Float64}}()) == 0

        @test MOI.supports(model, MOI.VariableName(), MOI.VariableIndex)
        v = MOI.add_variables(model, 2)
        @test MOI.get(model, MOI.VariableName(), v[1]) == ""
        x, cx = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0))
        @test MOI.get(model, MOI.VariableName(), x) == ""
        @test MOI.get(model, MOI.ConstraintName(), cx) == ""
        y, cy = MOI.add_constrained_variables(model, MOI.Nonpositives(4))
        for yi in y
            @test MOI.get(model, MOI.VariableName(), yi) == ""
        end
        @test MOI.get(model, MOI.ConstraintName(), cy) == ""

        MOI.set(model, MOI.VariableName(), v[1], "")
        MOI.set(model, MOI.VariableName(), v[2], "") # Shouldn't error with duplicate empty name
        MOI.set(model, MOI.VariableName(), x, "")
        for yi in y
            MOI.set(model, MOI.VariableName(), yi, "")
        end

        MOI.set(model, MOI.VariableName(), v[1], "Var1")
        MOI.set(model, MOI.VariableName(), v[2], "Var1")
        # Lookup must fail when there are multiple variables with the same name.
        @test_throws Exception MOI.get(model, MOI.VariableIndex, "Var1")

        MOI.set(model, MOI.VariableName(), v[2], "Var2")

        @test MOI.get(model, MOI.VariableIndex, "Var1") == v[1]
        @test MOI.get(model, MOI.VariableIndex, "Var2") == v[2]
        @test MOI.get(model, MOI.VariableIndex, "Var3") === nothing

        MOI.set(model, MOI.VariableName(), x, "Var1")
        @test_throws Exception MOI.get(model, MOI.VariableIndex, "Var1")

        MOI.set(model, MOI.VariableName(), x, "Varx")
        @test MOI.get(model, MOI.VariableIndex, "Var1") == v[1]
        @test MOI.get(model, MOI.VariableIndex, "Var2") == v[2]
        @test MOI.get(model, MOI.VariableIndex, "Varx") == x
        @test MOI.get(model, MOI.VariableIndex, "Var3") === nothing

        vynames = ["VarX", "Var2", "Vary1", "Vary2", "Vary3", "Vary4"]
        MOI.set(model, MOI.VariableName(), [v; y], vynames)
        @test MOI.get(model, MOI.VariableName(), v) == vynames[1:2]
        @test MOI.get(model, MOI.VariableName(), y) == vynames[3:6]
        @test MOI.get(model, MOI.VariableName(), [v; y]) == vynames

        @test MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64})
        c = MOI.add_constraint(model, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,1.0], v), 0.0), MOI.LessThan(1.0))
        @test MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64})
        c2 = MOI.add_constraint(model, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([-1.0,1.0], v), 0.0), MOI.EqualTo(0.0))
        @test MOI.get(model, MOI.ConstraintName(), c) == ""
        @test MOI.get(model, MOI.ConstraintName(), c2) == ""
        @test MOI.get(model, MOI.ConstraintName(), cx) == ""
        @test MOI.get(model, MOI.ConstraintName(), cy) == ""

        @test MOI.supports(model, MOI.ConstraintName(), typeof(c))
        MOI.set(model, MOI.ConstraintName(), c, "")
        @test MOI.supports(model, MOI.ConstraintName(), typeof(c2))
        MOI.set(model, MOI.ConstraintName(), c2, "") # Shouldn't error with duplicate empty name
        @test MOI.supports(model, MOI.ConstraintName(), typeof(cx))
        MOI.set(model, MOI.ConstraintName(), cx, "")
        @test MOI.supports(model, MOI.ConstraintName(), typeof(cy))
        MOI.set(model, MOI.ConstraintName(), cy, "")

        MOI.set(model, MOI.ConstraintName(), c, "Con0")
        @test MOI.get(model, MOI.ConstraintName(), c) == "Con0"
        MOI.set(model, MOI.ConstraintName(), c2, "Con0")
        # Lookup must fail when multiple constraints have the same name.
        @test_throws Exception MOI.get(model, MOI.ConstraintIndex, "Con0")
        @test_throws Exception MOI.get(model, typeof(c), "Con0")

        MOI.set(model, MOI.ConstraintName(), [c], ["Con1"])
        @test MOI.get(model, MOI.ConstraintName(), [c]) == ["Con1"]

        @test MOI.get(model, MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},MOI.LessThan{Float64}}, "Con1") == c
        @test MOI.get(model, MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},MOI.EqualTo{Float64}}, "Con1") === nothing
        @test MOI.get(model, MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},MOI.GreaterThan{Float64}}, "Con1") === nothing
        @test MOI.get(model, MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},MOI.LessThan{Float64}}, "Con2") === nothing
        @test MOI.get(model, MOI.ConstraintIndex, "Con1") == c
        @test MOI.get(model, MOI.ConstraintIndex, "Con2") === nothing

        MOI.set(model, MOI.ConstraintName(), [c], ["Con1"])

        MOI.set(model, MOI.ConstraintName(), [c2, cx], ["Con2", "Con2"])
        @test_throws Exception MOI.get(model, MOI.ConstraintIndex, "Con2")
        @test_throws Exception MOI.get(model, typeof(c2), "Con2")
        @test_throws Exception MOI.get(model, typeof(cx), "Con2")

        MOI.set(model, MOI.ConstraintName(), [cx, cy], ["Con3", "Con3"])
        @test_throws Exception MOI.get(model, MOI.ConstraintIndex, "Con3")
        @test_throws Exception MOI.get(model, typeof(cx), "Con3")
        @test_throws Exception MOI.get(model, typeof(cy), "Con3")

        MOI.set(model, MOI.ConstraintName(), cy, "Con4")

        for (i, ca) in enumerate([c, c2, cx, cy])
            namea = "Con$i"
            @test MOI.get(model, MOI.ConstraintName(), ca) == namea
            @test MOI.get(model, typeof(ca), namea) == ca
            @test MOI.get(model, MOI.ConstraintIndex, namea) == ca
            for cb in [c, c2, cx, cy]
                if ca === cb
                    continue
                end
                nameb = MOI.get(model, MOI.ConstraintName(), cb)
                @test MOI.get(model, typeof(cb), namea) == nothing
                @test MOI.get(model, typeof(ca), nameb) == nothing
            end
        end

        MOI.delete(model, v[2])
        @test MOI.get(model, MOI.VariableIndex, "Var2") === nothing

        MOI.delete(model, c)
        @test MOI.get(model, typeof(c), "Con1") === nothing
        @test MOI.get(model, MOI.ConstraintIndex, "Con1") === nothing

        MOI.set(model, MOI.ConstraintName(), cx, "Con2")
        @test_throws Exception MOI.get(model, MOI.ConstraintIndex, "Con2")
        @test_throws Exception MOI.get(model, typeof(c2), "Con2")
        @test_throws Exception MOI.get(model, typeof(cx), "Con2")

        MOI.delete(model, x)
        @test MOI.get(model, MOI.VariableIndex, "Varx") === nothing
        @test MOI.get(model, typeof(cx), "Con3") === nothing
        @test MOI.get(model, MOI.ConstraintIndex, "Con3") === nothing
        @test MOI.get(model, typeof(c2), "Con2") === c2
        @test MOI.get(model, MOI.ConstraintIndex, "Con2") === c2

        MOI.delete(model, y)
        @test MOI.get(model, typeof(cy), "Con4") === nothing
        @test MOI.get(model, MOI.ConstraintIndex, "Con4") === nothing
        for i in 1:4
            @test MOI.get(model, MOI.VariableIndex, "Vary$i") === nothing
        end
        MOI.set(model, MOI.ConstraintName(), c2, "Con4")
        @test MOI.get(model, typeof(c2), "Con4") === c2
        @test MOI.get(model, MOI.ConstraintIndex, "Con4") === c2
    end
    @testset "Duplicate names" begin
        @testset "Variables" begin
            MOI.empty!(model)
            (x, y, z) = MOI.add_variables(model, 3)
            MOI.set(model, MOI.VariableName(), x, "x")
            MOI.set(model, MOI.VariableName(), y, "x")
            MOI.set(model, MOI.VariableName(), z, "z")
            @test MOI.get(model, MOI.VariableIndex, "z") == z
            @test_throws ErrorException MOI.get(model, MOI.VariableIndex, "x")
            MOI.set(model, MOI.VariableName(), y, "y")
            @test MOI.get(model, MOI.VariableIndex, "x") == x
            @test MOI.get(model, MOI.VariableIndex, "y") == y
            MOI.set(model, MOI.VariableName(), z, "x")
            @test_throws ErrorException MOI.get(model, MOI.VariableIndex, "x")
            MOI.delete(model, x)
            @test MOI.get(model, MOI.VariableIndex, "x") == z
        end
        @testset "SingleVariable" begin
            MOI.empty!(model)
            x = MOI.add_variables(model, 3)
            c = MOI.add_constraints(model, MOI.SingleVariable.(x), MOI.GreaterThan(0.0))
            MOI.set(model, MOI.ConstraintName(), c[1], "x")
            MOI.set(model, MOI.ConstraintName(), c[2], "x")
            MOI.set(model, MOI.ConstraintName(), c[3], "z")
            @test MOI.get(model, MOI.ConstraintIndex, "z") == c[3]
            @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "x")
            MOI.set(model, MOI.ConstraintName(), c[2], "y")
            @test MOI.get(model, MOI.ConstraintIndex, "x") == c[1]
            @test MOI.get(model, MOI.ConstraintIndex, "y") == c[2]
            MOI.set(model, MOI.ConstraintName(), c[3], "x")
            @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "x")
            MOI.delete(model, c[1])
            @test MOI.get(model, MOI.ConstraintIndex, "x") == c[3]
            MOI.set(model, MOI.ConstraintName(), c[2], "x")
            @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "x")
            MOI.delete(model, x[3])
            @test MOI.get(model, MOI.ConstraintIndex, "x") == c[2]
        end
        @testset "ScalarAffineFunction" begin
            MOI.empty!(model)
            x = MOI.add_variables(model, 3)
            fs = [
                MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, xi)], 0.0)
                for xi in x
            ]
            c = MOI.add_constraints(model, fs, MOI.GreaterThan(0.0))
            MOI.set(model, MOI.ConstraintName(), c[1], "x")
            MOI.set(model, MOI.ConstraintName(), c[2], "x")
            MOI.set(model, MOI.ConstraintName(), c[3], "z")
            @test MOI.get(model, MOI.ConstraintIndex, "z") == c[3]
            @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "x")
            MOI.set(model, MOI.ConstraintName(), c[2], "y")
            @test MOI.get(model, MOI.ConstraintIndex, "x") == c[1]
            @test MOI.get(model, MOI.ConstraintIndex, "y") == c[2]
            MOI.set(model, MOI.ConstraintName(), c[3], "x")
            @test_throws ErrorException MOI.get(model, MOI.ConstraintIndex, "x")
            MOI.delete(model, c[1])
            @test MOI.get(model, MOI.ConstraintIndex, "x") == c[3]
        end
    end
end

# Taken from https://github.com/JuliaOpt/MathOptInterfaceUtilities.jl/issues/41
function validtest(model::MOI.ModelLike)
    MOI.empty!(model)
    @test MOIU.supports_default_copy_to(model, #=copy_names=# false)
    v = MOI.add_variables(model, 2)
    @test MOI.is_valid(model, v[1])
    @test MOI.is_valid(model, v[2])
    x = MOI.add_variable(model)
    @test MOI.is_valid(model, x)
    MOI.delete(model, x)
    @test !MOI.is_valid(model, x)
    cf = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,1.0], v), 0.0)
    @test MOI.supports_constraint(model, typeof(cf), MOI.LessThan{Float64})
    c = MOI.add_constraint(model, cf, MOI.LessThan(1.0))
    @test MOI.is_valid(model, c)
    @test !MOI.is_valid(model, MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float32},MOI.LessThan{Float32}}(1))
    @test !MOI.is_valid(model, MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float32},MOI.LessThan{Float64}}(1))
    @test !MOI.is_valid(model, MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},MOI.LessThan{Float32}}(1))
    @test !MOI.is_valid(model, MOI.ConstraintIndex{MOI.VectorQuadraticFunction{Float64},MOI.SecondOrderCone}(1))
end

function emptytest(model::MOI.ModelLike)
    MOI.empty!(model)
    @test MOIU.supports_default_copy_to(model, #=copy_names=# false)
    # Taken from LIN1
    v = MOI.add_variables(model, 3)
    @test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Nonnegatives)
    vc = MOI.add_constraint(model, MOI.VectorOfVariables(v), MOI.Nonnegatives(3))
    @test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.Zeros)
    c = MOI.add_constraint(model, MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1,1,1,2,2], MOI.ScalarAffineTerm.(1.0, [v;v[2];v[3]])), [-3.0, -2.0]), MOI.Zeros(2))
    MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([-3.0, -2.0, -4.0], v), 0.0))
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    @test !MOI.is_empty(model)

    MOI.empty!(model)

    @test MOI.is_empty(model)

    @test MOI.get(model, MOI.NumberOfVariables()) == 0
    @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorOfVariables,MOI.Nonnegatives}()) == 0
    @test MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.Zeros}()) == 0
    @test isempty(MOI.get(model, MOI.ListOfConstraints()))

    @test !MOI.is_valid(model, v[1])
    @test !MOI.is_valid(model, vc)
    @test !MOI.is_valid(model, c)
end

abstract type BadModel <: MOI.ModelLike end
MOI.get(::BadModel, ::MOI.ListOfModelAttributesSet) = MOI.AbstractModelAttribute[]
MOI.get(::BadModel, ::MOI.NumberOfVariables) = 1
MOI.get(::BadModel, ::MOI.ListOfVariableIndices) = [MOI.VariableIndex(1)]
MOI.get(::BadModel, ::MOI.ListOfVariableAttributesSet) = MOI.AbstractVariableAttribute[]
MOI.get(::BadModel, ::MOI.ListOfConstraints) = [(MOI.SingleVariable, MOI.EqualTo{Float64})]
MOI.get(::BadModel, ::MOI.ListOfConstraintIndices{F,S}) where {F,S} = [MOI.ConstraintIndex{F,S}(1)]
MOI.get(::BadModel, ::MOI.ConstraintFunction, ::MOI.ConstraintIndex{MOI.SingleVariable,MOI.EqualTo{Float64}}) = MOI.SingleVariable(MOI.VariableIndex(1))
MOI.get(::BadModel, ::MOI.ConstraintSet, ::MOI.ConstraintIndex{MOI.SingleVariable,MOI.EqualTo{Float64}}) = MOI.EqualTo(0.0)
MOI.get(::BadModel, ::MOI.ListOfConstraintAttributesSet) = MOI.AbstractConstraintAttribute[]

struct BadConstraintModel <: BadModel end
MOI.get(::BadConstraintModel, ::MOI.ListOfConstraints) = [(MOI.SingleVariable, MOI.EqualTo{Float64}), (MOI.SingleVariable, UnknownScalarSet{Float64})]
MOI.get(::BadModel, ::MOI.ConstraintFunction, ::MOI.ConstraintIndex{MOI.SingleVariable, <:UnknownScalarSet}) = MOI.SingleVariable(MOI.VariableIndex(1))
MOI.get(::BadModel, ::MOI.ConstraintSet, ::MOI.ConstraintIndex{MOI.SingleVariable, UnknownScalarSet{T}}) where T = UnknownScalarSet(one(T))

struct UnknownModelAttribute <: MOI.AbstractModelAttribute end
struct BadModelAttributeModel <: BadModel end
MOI.get(src::BadModelAttributeModel, ::UnknownModelAttribute) = 0
MOI.get(::BadModelAttributeModel, ::MOI.ListOfModelAttributesSet) = MOI.AbstractModelAttribute[UnknownModelAttribute()]

struct UnknownVariableAttribute <: MOI.AbstractVariableAttribute end
struct BadVariableAttributeModel <: BadModel end
MOI.get(::BadVariableAttributeModel, ::UnknownVariableAttribute, ::MOI.VariableIndex) = 0
MOI.get(::BadVariableAttributeModel, ::MOI.ListOfVariableAttributesSet) = MOI.AbstractVariableAttribute[UnknownVariableAttribute()]

struct UnknownConstraintAttribute <: MOI.AbstractConstraintAttribute end
struct BadConstraintAttributeModel <: BadModel end
MOI.get(::BadConstraintAttributeModel, ::UnknownConstraintAttribute, ::MOI.ConstraintIndex) = 0
MOI.get(::BadConstraintAttributeModel, ::MOI.ListOfConstraintAttributesSet) = MOI.AbstractConstraintAttribute[UnknownConstraintAttribute()]

function failcopytestc(dest::MOI.ModelLike)
    @test !MOI.supports_constraint(dest, MOI.SingleVariable, UnknownScalarSet{Float64})
    @test_throws MOI.UnsupportedConstraint MOI.copy_to(dest, BadConstraintModel())
end
function failcopytestia(dest::MOI.ModelLike)
    @test !MOI.supports(dest, UnknownModelAttribute())
    @test_throws MOI.UnsupportedAttribute MOI.copy_to(dest, BadModelAttributeModel())
end
function failcopytestva(dest::MOI.ModelLike)
    @test !MOI.supports(dest, UnknownVariableAttribute(), MOI.VariableIndex)
    @test_throws MOI.UnsupportedAttribute MOI.copy_to(dest, BadVariableAttributeModel())
end
function failcopytestca(dest::MOI.ModelLike)
    @test !MOI.supports(dest, UnknownConstraintAttribute(), MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{Float64}})
    @test_throws MOI.UnsupportedAttribute MOI.copy_to(dest, BadConstraintAttributeModel())
end

function start_values_test(dest::MOI.ModelLike, src::MOI.ModelLike)
    MOI.empty!(dest)
    @test MOIU.supports_default_copy_to(src, #=copy_names=# false)
    x, y, z = MOI.add_variables(src, 3)
    a = MOI.add_constraint(src, x, MOI.EqualTo(1.0))
    b = MOI.add_constraint(src, y, MOI.EqualTo(2.0))
    c = MOI.add_constraint(src, z, MOI.EqualTo(3.0))
    F = MOI.SingleVariable
    S = MOI.EqualTo{Float64}

    vpattr = MOI.VariablePrimalStart()
    cpattr = MOI.ConstraintPrimalStart()
    cdattr = MOI.ConstraintDualStart()

    @testset "Supports" begin
        @test MOI.supports(dest, vpattr, MOI.VariableIndex)
        @test MOI.supports(dest, cpattr, MOI.ConstraintIndex{F, S})
        @test MOI.supports(dest, cdattr, MOI.ConstraintIndex{F, S})
    end

    @testset "Attribute set to no indices" begin
        dict = MOI.copy_to(dest, src, copy_names=false)

        @test !(vpattr in MOI.get(dest, MOI.ListOfVariableAttributesSet()))
        @test MOI.get(dest, vpattr, dict[x]) === nothing
        @test MOI.get(dest, vpattr, dict[y]) === nothing
        @test MOI.get(dest, vpattr, dict[z]) === nothing
        @test !(cpattr in MOI.get(dest, MOI.ListOfConstraintAttributesSet{F, S}()))
        @test MOI.get(dest, cpattr, dict[a]) === nothing
        @test MOI.get(dest, cpattr, dict[b]) === nothing
        @test MOI.get(dest, cpattr, dict[c]) === nothing
        @test !(cdattr in MOI.get(dest, MOI.ListOfConstraintAttributesSet{F, S}()))
        @test MOI.get(dest, cdattr, dict[a]) === nothing
        @test MOI.get(dest, cdattr, dict[b]) === nothing
        @test MOI.get(dest, cdattr, dict[c]) === nothing
    end

    @testset "Attribute set to some indices" begin
        MOI.set(src, vpattr, x, 1.0)
        MOI.set(src, vpattr, z, 3.0)
        MOI.set(src, cpattr, a, 1.0)
        MOI.set(src, cpattr, b, 2.0)
        MOI.set(src, cdattr, b, 2.0)
        MOI.set(src, cdattr, c, 3.0)

        dict = MOI.copy_to(dest, src, copy_names=false)

        @test vpattr in MOI.get(dest, MOI.ListOfVariableAttributesSet())
        @test MOI.get(dest, vpattr, dict[x]) == 1.0
        @test MOI.get(dest, vpattr, dict[y]) === nothing
        @test MOI.get(dest, vpattr, dict[z]) == 3.0
        @test cpattr in MOI.get(dest, MOI.ListOfConstraintAttributesSet{F, S}())
        @test MOI.get(dest, cpattr, dict[a]) == 1.0
        @test MOI.get(dest, cpattr, dict[b]) == 2.0
        @test MOI.get(dest, cpattr, dict[c]) === nothing
        @test cdattr in MOI.get(dest, MOI.ListOfConstraintAttributesSet{F, S}())
        @test MOI.get(dest, cdattr, dict[a]) === nothing
        @test MOI.get(dest, cdattr, dict[b]) == 2.0
        @test MOI.get(dest, cdattr, dict[c]) == 3.0
    end
end

function copytest(dest::MOI.ModelLike, src::MOI.ModelLike)
    @test MOIU.supports_default_copy_to(src, #=copy_names=# true)
    MOI.empty!(src)
    MOI.empty!(dest)
    MOI.set(src, MOI.Name(), "ModelName")
    v = MOI.add_variables(src, 3)
    w = MOI.add_variable(src)
    MOI.set(src, MOI.VariableName(), v, ["var1", "var2", "var3"])
    csv = MOI.add_constraint(src, MOI.SingleVariable(w), MOI.EqualTo(2.))
    # We test this after the creation of every `SingleVariable` constraint
    # to ensure a good coverage of corner cases.
    @test csv.value == w.value
    MOI.set(src, MOI.ConstraintName(), csv, "csv")
    cvv = MOI.add_constraint(src, MOI.VectorOfVariables(v), MOI.Nonnegatives(3))
    MOI.set(src, MOI.ConstraintName(), cvv, "cvv")
    csa = MOI.add_constraint(
        src,
        MOI.ScalarAffineFunction(
            MOI.ScalarAffineTerm.([1.0, 3.0], [v[3], v[1]]), 0.0),
        MOI.LessThan(2.))
    MOI.set(src, MOI.ConstraintName(), csa, "csa")
    cva = MOI.add_constraint(
        src,
        MOI.VectorAffineFunction(
            MOI.VectorAffineTerm.(
                [1, 2],
                MOI.ScalarAffineTerm.(1.0, [v[3], v[2]])),
            [-3.0, -2.0]),
        MOI.Zeros(2))
    MOI.set(src, MOI.ConstraintName(), cva, "cva")
    MOI.set(src, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
            MOI.ScalarAffineFunction(
                MOI.ScalarAffineTerm.([-3.0, -2.0, -4.0], v), 2.0))
    MOI.set(src, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    @test MOI.supports(dest, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
    @test MOI.supports_constraint(dest, MOI.SingleVariable, MOI.EqualTo{Float64})
    @test MOI.supports_constraint(dest, MOI.VectorOfVariables, MOI.Nonnegatives)
    @test MOI.supports_constraint(dest, MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64})
    @test MOI.supports_constraint(dest, MOI.VectorAffineFunction{Float64}, MOI.Zeros)

    dict = MOI.copy_to(dest, src, copy_names=false)

    @test !MOI.supports(dest, MOI.Name()) || MOI.get(dest, MOI.Name()) == ""
    @test MOI.get(dest, MOI.NumberOfVariables()) == 4
    if MOI.supports(dest, MOI.VariableName(), MOI.VariableIndex)
        for vi in v
            MOI.get(dest, MOI.VariableName(), dict[vi]) == ""
        end
    end
    @test MOI.get(dest, MOI.NumberOfConstraints{MOI.SingleVariable,MOI.EqualTo{Float64}}()) == 1
    @test MOI.get(dest, MOI.ListOfConstraintIndices{MOI.SingleVariable,MOI.EqualTo{Float64}}()) == [dict[csv]]
    @test MOI.get(dest, MOI.NumberOfConstraints{MOI.VectorOfVariables,MOI.Nonnegatives}()) == 1
    @test MOI.get(dest, MOI.ListOfConstraintIndices{MOI.VectorOfVariables,MOI.Nonnegatives}()) == [dict[cvv]]
    @test MOI.get(dest, MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64},MOI.LessThan{Float64}}()) == 1
    @test MOI.get(dest, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64},MOI.LessThan{Float64}}()) == [dict[csa]]
    @test MOI.get(dest, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Float64},MOI.Zeros}()) == 1
    @test MOI.get(dest, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64},MOI.Zeros}()) == [dict[cva]]
    loc = MOI.get(dest, MOI.ListOfConstraints())
    @test length(loc) == 4
    @test (MOI.SingleVariable,MOI.EqualTo{Float64}) in loc
    @test (MOI.VectorOfVariables,MOI.Nonnegatives) in loc
    @test (MOI.ScalarAffineFunction{Float64},MOI.LessThan{Float64}) in loc
    @test (MOI.VectorAffineFunction{Float64},MOI.Zeros) in loc

    @test !MOI.supports(dest, MOI.ConstraintName(), typeof(csv)) || MOI.get(dest, MOI.ConstraintName(), dict[csv]) == ""
    @test MOI.get(dest, MOI.ConstraintFunction(), dict[csv]) == MOI.SingleVariable(dict[w])
    @test MOI.get(dest, MOI.ConstraintSet(), dict[csv]) == MOI.EqualTo(2.)
    @test !MOI.supports(dest, MOI.ConstraintName(), typeof(cvv)) || MOI.get(dest, MOI.ConstraintName(), dict[cvv]) == ""
    @test MOI.get(dest, MOI.ConstraintFunction(), dict[cvv]) == MOI.VectorOfVariables(getindex.(Ref(dict), v))
    @test MOI.get(dest, MOI.ConstraintSet(), dict[cvv]) == MOI.Nonnegatives(3)
    @test !MOI.supports(dest, MOI.ConstraintName(), typeof(csa)) || MOI.get(dest, MOI.ConstraintName(), dict[csa]) == ""
    @test MOI.get(dest, MOI.ConstraintFunction(), dict[csa]) ≈ MOI.ScalarAffineFunction(
        MOI.ScalarAffineTerm.([1., 3.], [dict[v[3]], dict[v[1]]]), 0.0)
    @test MOI.get(dest, MOI.ConstraintSet(), dict[csa]) == MOI.LessThan(2.)
    @test !MOI.supports(dest, MOI.ConstraintName(), typeof(cva)) || MOI.get(dest, MOI.ConstraintName(), dict[cva]) == ""
    @test MOI.get(dest, MOI.ConstraintFunction(), dict[cva]) ≈ MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1, 2], MOI.ScalarAffineTerm.(1.0, [dict[v[3]], dict[v[2]]])), [-3.0, -2.0])
    @test MOI.get(dest, MOI.ConstraintSet(), dict[cva]) == MOI.Zeros(2)

    @test MOI.get(dest, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) ≈ MOI.ScalarAffineFunction(
        MOI.ScalarAffineTerm.([-3.0, -2.0, -4.0],
                              [dict[v[1]], dict[v[2]], dict[v[3]]]), 2.0)
    @test MOI.get(dest, MOI.ObjectiveFunctionType()) == MOI.ScalarAffineFunction{Float64}
    @test MOI.get(dest, MOI.ObjectiveSense()) == MOI.MIN_SENSE
end

function supports_constrainttest(model::MOI.ModelLike, ::Type{GoodT}, ::Type{BadT}) where {GoodT, BadT}
    v = MOI.add_variable(model)
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.EqualTo{GoodT})
    @test MOI.supports_constraint(model, MOI.ScalarAffineFunction{GoodT}, MOI.EqualTo{GoodT})
    # Bad type
    @test !MOI.supports_constraint(model, MOI.ScalarAffineFunction{BadT}, MOI.EqualTo{GoodT})
    @test !MOI.supports_constraint(model, MOI.ScalarAffineFunction{BadT}, MOI.EqualTo{BadT})
    @test !MOI.supports_constraint(model, MOI.SingleVariable, MOI.EqualTo{BadT})

    @test MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.Zeros)
    @test !MOI.supports_constraint(model, MOI.VectorOfVariables, MOI.EqualTo{GoodT}) # vector in scalar
    @test !MOI.supports_constraint(model, MOI.SingleVariable, MOI.Zeros) # scalar in vector
    @test !MOI.supports_constraint(model, MOI.VectorOfVariables, UnknownVectorSet) # set not supported
end

"""
    orderedindicestest(model::MOI.ModelLike)

Test whether the model returns ListOfVariableIndices and ListOfConstraintIndices
sorted by creation time.
"""
function orderedindicestest(model::MOI.ModelLike)
    @test MOIU.supports_default_copy_to(model, #=copy_names=# false)
    MOI.empty!(model)
    v1 = MOI.add_variable(model)
    @test MOI.get(model, MOI.ListOfVariableIndices()) == [v1]
    v2 = MOI.add_variable(model)
    @test MOI.get(model, MOI.ListOfVariableIndices()) == [v1, v2]
    MOI.delete(model, v1)
    @test MOI.get(model, MOI.ListOfVariableIndices()) == [v2]
    v3 = MOI.add_variable(model)
    @test MOI.get(model, MOI.ListOfVariableIndices()) == [v2, v3]
    v4 = MOI.add_variable(model)
    @test MOI.get(model, MOI.ListOfVariableIndices()) == [v2, v3, v4]

    # Note: there are too many combinations to test, so we're just going to
    # check SingleVariable-in-LessThan and hope it works for the rest
    c1 = MOI.add_constraint(model, MOI.SingleVariable(v2), MOI.LessThan(1.0))
    @test c1.value == v2.value
    @test MOI.get(model, MOI.ListOfConstraintIndices{MOI.SingleVariable, MOI.LessThan{Float64}}()) == [c1]
    c2 = MOI.add_constraint(model, MOI.SingleVariable(v3), MOI.LessThan(2.0))
    @test c2.value == v3.value
    @test MOI.get(model, MOI.ListOfConstraintIndices{MOI.SingleVariable, MOI.LessThan{Float64}}()) == [c1, c2]
    MOI.delete(model, c1)
    @test MOI.get(model, MOI.ListOfConstraintIndices{MOI.SingleVariable, MOI.LessThan{Float64}}()) == [c2]
    c3 = MOI.add_constraint(model, MOI.SingleVariable(v4), MOI.LessThan(3.0))
    @test c3.value == v4.value
    @test MOI.get(model, MOI.ListOfConstraintIndices{MOI.SingleVariable, MOI.LessThan{Float64}}()) == [c2, c3]
end

# Test that `MOI.ScalarFunctionConstantNotZero` is thrown when a constraint with
# a function with nonzero constant is added. This test is optional because
# solvers could choose to support scalar functions with nonzero constants.
function scalar_function_constant_not_zero(model::MOI.ModelLike)
    @testset "Constraint with nonzero function constant" begin
        err = MOI.ScalarFunctionConstantNotZero{Float64,
               MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}}(1.0)
        @test_throws err begin
            MOI.add_constraint(model,
                 MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}[], 1.0),
                 MOI.EqualTo(2.0))
        end
        err = MOI.ScalarFunctionConstantNotZero{Float64,
               MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}(2.0)
        @test_throws err begin
            MOI.add_constraint(model,
                 MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}[], 2.0),
                 MOI.GreaterThan(1.0))
        end
    end
end

function set_lower_bound_twice(model::MOI.ModelLike, T::Type)
    MOI.empty!(model)
    @test MOI.is_empty(model)
    x = MOI.add_variable(model)
    f = MOI.SingleVariable(x)
    lb = zero(T)
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{T})
    sets = [MOI.EqualTo(lb), MOI.Interval(lb, lb),
             MOI.Semicontinuous(lb, lb), MOI.Semiinteger(lb, lb)]
    set2 = MOI.GreaterThan(lb)
    for set1 in sets
        if !MOI.supports_constraint(model, MOI.SingleVariable, typeof(set1))
            continue
        end
        ci = MOI.add_constraint(model, f, set1)
        err = MOI.LowerBoundAlreadySet{typeof(set1), typeof(set2)}(x)
        @test_throws err MOI.add_constraint(model, f, set2)
        MOI.delete(model, ci)
        ci = MOI.add_constraint(model, f, set2)
        err = MOI.LowerBoundAlreadySet{typeof(set2), typeof(set1)}(x)
        @test_throws err MOI.add_constraint(model, f, set1)
        MOI.delete(model, ci)
    end
end

function set_upper_bound_twice(model::MOI.ModelLike, T::Type)
    MOI.empty!(model)
    @test MOI.is_empty(model)
    x = MOI.add_variable(model)
    f = MOI.SingleVariable(x)
    ub = zero(T)
    @test MOI.supports_constraint(model, MOI.SingleVariable, MOI.LessThan{T})
    sets = [MOI.EqualTo(ub), MOI.Interval(ub, ub),
             MOI.Semicontinuous(ub, ub), MOI.Semiinteger(ub, ub)]
    set2 = MOI.LessThan(ub)
    for set1 in sets
        if !MOI.supports_constraint(model, MOI.SingleVariable, typeof(set1))
            continue
        end
        ci = MOI.add_constraint(model, f, set1)
        err = MOI.UpperBoundAlreadySet{typeof(set1), typeof(set2)}(x)
        @test_throws err MOI.add_constraint(model, f, set2)
        MOI.delete(model, ci)
        ci = MOI.add_constraint(model, f, set2)
        err = MOI.UpperBoundAlreadySet{typeof(set2), typeof(set1)}(x)
        @test_throws err MOI.add_constraint(model, f, set1)
        MOI.delete(model, ci)
    end
end

function delete_test(model::MOI.ModelLike)
    x = MOI.add_variable(model)
    cx = MOI.add_constraint(model, x, MOI.GreaterThan(0.0))
    y = MOI.add_variables(model, 4)
    cy = MOI.add_constraint(model, y, MOI.Nonpositives(4))
    @test MOI.is_valid(model, x)
    @test MOI.is_valid(model, y[1])
    @test MOI.is_valid(model, y[2])
    @test MOI.is_valid(model, y[3])
    @test MOI.is_valid(model, y[4])
    @test MOI.is_valid(model, cx)
    @test MOI.is_valid(model, cy)
    @test MOI.get(model, MOI.ConstraintFunction(), cx) == MOI.SingleVariable(x)
    @test MOI.get(model, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0)
    @test MOI.get(model, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y)
    @test MOI.get(model, MOI.ConstraintSet(), cy) == MOI.Nonpositives(4)
    @test Set(MOI.get(model, MOI.ListOfConstraints())) ==
        Set([(MOI.SingleVariable, MOI.GreaterThan{Float64}),
             (MOI.VectorOfVariables, MOI.Nonpositives)])
    @test MOI.get(model, MOI.ListOfConstraintIndices{
        MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == [cx]
    @test MOI.get(model, MOI.ListOfConstraintIndices{
        MOI.VectorOfVariables, MOI.Nonpositives}()) == [cy]
    MOI.delete(model, y[3])
    @test MOI.is_valid(model, x)
    @test MOI.is_valid(model, y[1])
    @test MOI.is_valid(model, y[2])
    @test !MOI.is_valid(model, y[3])
    @test MOI.is_valid(model, y[4])
    @test MOI.is_valid(model, cx)
    @test MOI.is_valid(model, cy)
    @test MOI.get(model, MOI.ConstraintFunction(), cx) == MOI.SingleVariable(x)
    @test MOI.get(model, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0)
    @test MOI.get(model, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[1, 2, 4]])
    @test MOI.get(model, MOI.ConstraintSet(), cy) == MOI.Nonpositives(3)
    @test Set(MOI.get(model, MOI.ListOfConstraints())) ==
        Set([(MOI.SingleVariable, MOI.GreaterThan{Float64}),
             (MOI.VectorOfVariables, MOI.Nonpositives)])
    @test MOI.get(model, MOI.ListOfConstraintIndices{
        MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == [cx]
    @test MOI.get(model, MOI.ListOfConstraintIndices{
        MOI.VectorOfVariables, MOI.Nonpositives}()) == [cy]
    MOI.delete(model, y[1])
    @test MOI.is_valid(model, x)
    @test !MOI.is_valid(model, y[1])
    @test MOI.is_valid(model, y[2])
    @test !MOI.is_valid(model, y[3])
    @test MOI.is_valid(model, y[4])
    @test MOI.is_valid(model, cx)
    @test MOI.is_valid(model, cy)
    @test MOI.get(model, MOI.ConstraintFunction(), cx) == MOI.SingleVariable(x)
    @test MOI.get(model, MOI.ConstraintSet(), cx) == MOI.GreaterThan(0.0)
    @test MOI.get(model, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[2, 4]])
    @test MOI.get(model, MOI.ConstraintSet(), cy) == MOI.Nonpositives(2)
    @test Set(MOI.get(model, MOI.ListOfConstraints())) ==
        Set([(MOI.SingleVariable, MOI.GreaterThan{Float64}),
             (MOI.VectorOfVariables, MOI.Nonpositives)])
    @test MOI.get(model, MOI.ListOfConstraintIndices{
        MOI.SingleVariable, MOI.GreaterThan{Float64}}()) == [cx]
    @test MOI.get(model, MOI.ListOfConstraintIndices{
        MOI.VectorOfVariables, MOI.Nonpositives}()) == [cy]
    MOI.delete(model, x)
    @test !MOI.is_valid(model, x)
    @test !MOI.is_valid(model, y[1])
    @test MOI.is_valid(model, y[2])
    @test !MOI.is_valid(model, y[3])
    @test MOI.is_valid(model, y[4])
    @test !MOI.is_valid(model, cx)
    @test MOI.is_valid(model, cy)
    @test MOI.get(model, MOI.ConstraintFunction(), cy) == MOI.VectorOfVariables(y[[2, 4]])
    @test MOI.get(model, MOI.ConstraintSet(), cy) == MOI.Nonpositives(2)
    @test MOI.get(model, MOI.ListOfConstraints()) ==
        [(MOI.VectorOfVariables, MOI.Nonpositives)]
    @test isempty(MOI.get(model, MOI.ListOfConstraintIndices{
        MOI.SingleVariable, MOI.GreaterThan{Float64}}()))
    @test MOI.get(model, MOI.ListOfConstraintIndices{
        MOI.VectorOfVariables, MOI.Nonpositives}()) == [cy]
    MOI.delete(model, y[[2, 4]])
    @test !MOI.is_valid(model, x)
    @test !MOI.is_valid(model, y[1])
    @test !MOI.is_valid(model, y[2])
    @test !MOI.is_valid(model, y[3])
    @test !MOI.is_valid(model, y[4])
    @test !MOI.is_valid(model, cx)
    @test !MOI.is_valid(model, cy)
    @test isempty(MOI.get(model, MOI.ListOfConstraints()))
    @test isempty(MOI.get(model, MOI.ListOfConstraintIndices{
        MOI.SingleVariable, MOI.GreaterThan{Float64}}()))
    @test isempty(MOI.get(model, MOI.ListOfConstraintIndices{
        MOI.VectorOfVariables, MOI.Nonpositives}()))
end


text:
function OrderedComplexEigenDecomposition(arg0::RealMatrix)
    return OrderedComplexEigenDecomposition((RealMatrix,), arg0)
end

function get_vt(obj::OrderedComplexEigenDecomposition)
    return jcall(obj, "getVT", FieldMatrix, ())
end


text:
module CategoricalTimeSeries

using IntegerIB
using SerialDependence
using SpectralEnvelope

export IB, search_optima!, brute_optimize!, IB_optimize!, calc_metrics, get_IB_curve, get_y, print_results
export cramer_coefficient, cohen_coefficient, conditional_entropy, LaggedBivariateProbability, H, theils_u, rate_evolution
export spectral_envelope, get_mappings, detrend, smooth, power_spectrum

end


text:
using SymbolicUtils: PolyForm, Term, symtype
using Test, SymbolicUtils

include("utils.jl")

@testset "div and polyform" begin
    @syms x y z
    @test repr(PolyForm(x-y)) == "x - y"
    @test repr(x/y*x/z) == "(x^2) / (y*z)"
    @test repr(simplify_fractions(((x-y+z)*(x+4z+1)) /
                                  (y*(2x - 3y + 3z) +
                                   x*(x + z)))) == repr(simplify_fractions((1 + x + 4z) / (x + 3.0y)))
    @test simplify_fractions(x/(x+3) + 3/(x+3)) == 1
    @test repr(simplify(simplify_fractions(cos(x)/sin(x) + sin(x)/cos(x)))) == "2 / sin(2x)"
end

@testset "expand" begin
    @syms a b c d

    @test expand(term(*, 0, a)) == 0
    @test expand(a * (b + -1 * c) + -1 * (b * a + -1 * c * a)) == 0
    @eqtest simplify(expand(sin((a+b)^2)^2)) == simplify(sin(a^2+2*(b*a)+b^2)^2)
    @test simplify(expand(sin((a+b)^2)^2 + cos((a+b)^2)^2)) == 1
    @syms x1::Real f(::Real)::Real

    # issue 193
    #@test isequal(expand(f(2.0 + x1)), f(2.0 + x1))
    @test symtype(expand(f(x1 + 2.0))) == Real

    # cleanup rules
    # I don't think we need this anymore?
   #@test expand(Term{Number}(identity, 0)) == 0
   #@test expand(Term{Number}(one, 0)) == 1
   #@test expand(Term{Number}(zero, 0)) == 0
   #@test expand(identity(a * b) - b * a) == 0
    @test expand(a * b - b * a) == 0

    @syms A::Vector{Real}
    # test that the following works
    expand(Term{Real}(getindex, [A, 3]) - 3)
end

@testset "simplify_fractions with quick-cancel" begin
    @syms x y
    @test simplify_fractions(x/2x) == 1//2
    @test simplify_fractions(2x//x) == 2
    @eqtest simplify_fractions((x+y) * (x-y) / (x+y)) == (x-y)
    @eqtest simplify_fractions(x^3 * y / x) == y*x^2
    @eqtest simplify_fractions(2x^3 * y / x) == 2y*x^2
    @eqtest simplify_fractions(x / (3(x^3)*y)) == simplify_fractions(1/(3*(y*x^2)))
    @eqtest simplify_fractions(2x / (3(x^3)*y)) == simplify_fractions(2/(3*(y*x^2)))
    @eqtest simplify_fractions(x^2 / (3(x^3)*y)) == simplify_fractions(1/(3*(y*x)))
    @eqtest simplify_fractions((3(x^3)*y) / x^2) == simplify_fractions(3*(y*x))
    @eqtest simplify_fractions(x^2/x^4) == (1/x^2)
    @eqtest simplify_fractions(x^2/x^3) == 1/x
    @eqtest simplify_fractions(x^3/x^2) == x
    @eqtest simplify_fractions(x^2/x^2) == 1
    @eqtest simplify_fractions(3x^2/x^3) == 3/x
    @eqtest simplify_fractions(3*(x^2)*(y^3)/(3*(x^3)*(y^2))) == y/x
    @eqtest simplify_fractions(3*(x^x)/x*y) == 3*(x^x)/x*y

    ##404#issuecomment-939404030
    a = 1 / (x - (2//1)) + ((-5//1) - x) / ((x - (2//1))^2)
    @test isequal(simplify_fractions(a), -7/(x-2)^2)
end

@testset "isone iszero" begin
    @syms a b c d e f g h i
    x = (f + ((((g*(c^2)*(e^2)) / d - e*h*(c^2)) / b + (-c*e*f*g) / d + c*e*i) /
              (i + ((c*e*g) / d - c*h) / b + (-f*g) / d) - c*e) / b +
         ((g*(f^2)) / d + ((-c*e*f*g) / d + c*f*h) / b - f*i) /
         (i + ((c*e*g) / d - c*h) / b + (-f*g) / d)) / d

    o = (d + (e*((c*(g + (-d*g) / d)) / (i + (-c*(h + (-e*g) / d)) / b + (-f*g) / d))) / b + (-f*(g + (-d*g) / d)) / (i + (-c*(h + (-e*g) / d)) / b + (-f*g) / d)) / d
    @test SymbolicUtils.fraction_iszero(x)
    @test !SymbolicUtils.fraction_isone(x)
    @test SymbolicUtils.fraction_isone(o)
end


text:
# ---
# title: 1566. Detect Pattern of Length M Repeated K or More Times
# id: problem1566
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Array
# link: <https://leetcode.com/problems/detect-pattern-of-length-m-repeated-k-or-more-times/description/>
# hidden: true
# ---
#
# Given an array of positive integers `arr`,  find a pattern of length `m` that
# is repeated `k` or more times.
#
# A **pattern** is a subarray (consecutive sub-sequence) that consists of one or
# more values, repeated multiple times **consecutively** without overlapping. A
# pattern is defined by its length and the number of repetitions.
#
# Return `true`  _if there exists a pattern of length_  `m`  _that is repeated_
# `k`  _or more times, otherwise return_  `false`.
#
#
#
# **Example 1:**
#
#
#
#     Input: arr = [1,2,4,4,4,4], m = 1, k = 3
#     Output: true
#     Explanation: The pattern **(4)** of length 1 is repeated 4 consecutive times. Notice that pattern can be repeated k or more times but not less.
#
#
# **Example 2:**
#
#
#
#     Input: arr = [1,2,1,2,1,1,1,3], m = 2, k = 2
#     Output: true
#     Explanation: The pattern **(1,2)** of length 2 is repeated 2 consecutive times. Another valid pattern **(2,1) is** also repeated 2 times.
#
#
# **Example 3:**
#
#
#
#     Input: arr = [1,2,1,2,1,3], m = 2, k = 3
#     Output: false
#     Explanation: The pattern (1,2) is of length 2 but is repeated only 2 times. There is no pattern of length 2 that is repeated 3 or more times.
#
#
# **Example 4:**
#
#
#
#     Input: arr = [1,2,3,1,2], m = 2, k = 2
#     Output: false
#     Explanation: Notice that the pattern (1,2) exists twice but not consecutively, so it doesn't count.
#
#
# **Example 5:**
#
#
#
#     Input: arr = [2,2,2,2], m = 2, k = 3
#     Output: false
#     Explanation: The only pattern of length 2 is (2,2) however it's repeated only twice. Notice that we do not count overlapping repetitions.
#
#
#
#
# **Constraints:**
#
#   * `2 <= arr.length <= 100`
#   * `1 <= arr[i] <= 100`
#   * `1 <= m <= 100`
#   * `2 <= k <= 100`
#
#
## @lc code=start
using LeetCode

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


text:
import YAML

abstract type IO_Object end

type GPIO <: IO_Object
    ID::Int32
    pins::Array{String, 1}
    InOut::String
    file_handles::Array{IOStream}
end
GPIO(ID, pins, InOut; file_handles=[])=GPIO(ID, pins, InOut, file_handles)

# The PWM type
type PWM <: IO_Object
    ID::Int32
    pins::Array{String, 1}
    file_handles::Array{IOStream}
end
PWM(ID, pins; file_handles=[])=PWM(ID, pins, file_handles)

# The analog to digital converter type
type ADC <: IO_Object
    ID::Int32
    pins::Array{String, 1}
    channel::String
    file_handles::Array{IOStream,}
end
ADC(ID, pins, channel; file_handles=[])=ADC(ID, pins, channel, file_handles)

# The quadrature encoder type
type QEP <: IO_Object
    ID::Int32
    pins::Array{String, 1}
    file_handles::Array{IOStream}
end
QEP(ID, pins; file_handles=[])=QEP(ID, pins, file_handles)

function to_log(log::String, entry)
    return string(log, " * ", entry, "\n")
end

"""
    This function loads a specified configuration. If the configuration
    passes the assetion, an array of IO objects are ccreated. In doing so,
    each object in the configuration is

        (1) Exported - such that the necessary filesystem is created. This is
            done using the setup(device) method, which needs to be written for
            each component independently.
        (2) Opened - The file objects are opened for each object, allowing
            fast reding and writing by simply finding the object in an array
            where it is identitfied by an internal identifier.

    Once the IO_Objects list has been created, each component may be accessed by
    direct reference with

        write(IO_Objects[ID].file_handles[INDEX], ENTRY)

    or

        read(IO_Objects[ID].file_handles[INDEX])

    which may be done directly from the stream
"""
function load_configuration(configuration::Dict)

    # Check if the configuration is consistent, if not return empty list
    flag, log = assert_configuration(configuration)
    if !flag
        return []
    end

    # Iterate over all configured objects and find the maximum id
    maxID = 0
    for component in keys(configuration)
        for element in configuration[component]
            if element["ID_internal"] > maxID
                maxID = element["ID_internal"]
            end
        end
    end

    # This list contains objects with open files for fast IO manipulation
    IO_Objects = Array{IO_Object}(1,maxID)

    # Populate IO_Object array
    for component in keys(configuration)
        for element in configuration[component]
            id = element["ID_internal"]
            pins = element["Location"]

            if component == "gpio"
                InOut = element["Setting"]
                object = GPIO(id, pins, InOut)
            elseif component == "pwm"
                object = PWM(id, pins)
            elseif component == "adc"
                channel = element["Setting"]
                object = ADC(id, pins, channel)
            elseif component == "qep"
                object = QEP(id, pins)
            end

            # This sets up the specific object, expoting the filesystem
            setup(object)

            # Adds the object to the IO_Objects array
            IO_Objects[id] = object
        end
    end
    return IO_Objects
end

function assert_configuration(configuration::Dict)
    log = ""
    interface = YAML.load(open("pins.yml"))

    # Check that the file contains keys
    if isempty(keys(configuration))
        return false, log
    end

    # Check validity of keys
    taken_pins = []
    taken_SPI_pins = []
    taken_internal_ids = []
    for component in keys(configuration)
        if component in ["gpio", "pwm", "qep"]
            ############# Check GPIOs #############
            for element in configuration[component]
                # Check thet the
                pins = element["Location"]
                id = element["ID_internal"]
                for pin in pins
                    if !(pin in keys(interface["available_pins"]["gpio"]))
                        log = to_log(log, "The pin $pin is not supported for $component functionality")
                    end
                    if pin in taken_pins
                        log = to_log(log, "The pin $pin has been defined for multiple uses")
                    end
                end
                if id in taken_internal_ids
                    log = to_log(log, "The internal ID $id has been defined for multiple uses")
                end
                append!(taken_pins, pins)
                append!(taken_internal_ids, [id])
            end
        elseif component == "adc"
            #Check ADCs
        else
            log = to_log("The componenet $component is not supported")
        end
    end
    return isempty(log), log
end

function setup(object::GPIO)
    id = object.ID
    print("Setup GPIO with ID=$id\n")

    # TODO: Export filesystem

    # TODO: Find the correct directory, currently opens dummy file
    filename = "testfile.txt"

    # Open IOStream object
    print("Open IOStream A")
    object.file_handles = [open(filename, "r+")]
end

function setup(object::PWM)
    id = object.ID
    print("Setup PWM with ID=$id\n")

    # TODO: Export filesystem

    # TODO: Find the correct directory, currently opens dummy file
    filename = "testfile.txt"

    # Open IOStream object
    print("Open IOStream for PWM with ID=$id\n")
    object.file_handles = [open(filename, "r+")]
end

function setup(object::ADC)
    id = object.ID
    print("Setup ADC with ID=$id\n")

    # TODO: Export filesystem

    # TODO: Find the correct directory, currently opens dummy file
    filename = "testfile.txt"

    # Open IOStream object
    print("Open IOStream for ADC with ID=$id\n")
    object.file_handles = [open(filename, "r+")]
end

function setup(object::QEP)
    id = object.ID
    print("Setup QEP with ID=$id\n")

    # TODO: Export filesystem

    # TODO: Find the correct directory, currently opens dummy file
    filename = "testfile.txt"

    # Open IOStream object
    print("Open IOStream for QEP with ID=$id\n")
    object.file_handles = [open(filename, "r+")]
end

# Command line consistency functionality can be enabled
if false
    if !isempty(ARGS)
        filename = ARGS[1]
        if isfile(filename)
            print("\nLoading configuration file...\n")
            data = YAML.load(open(filename))
            print("Running consistency checks...\n")
            flag, log = assert_configuration(data)
            if flag
                print("\nAll checks passed!\n")
            else
                print("\n", log)
                print("\nSome checks failed!\n")
            end
        else
            error("Invalid filename provided")
        end
    else
        error("No input file provided")
    end
end


text:
using BenchmarkTools, ResumableFunctions
using ResumableFunctions

const n = 93

function direct_fib(n)
  a = zero(Int)
  b = a + one(a)
  for i in 1:n-1
    a, b = b, a+b
  end#for
  a
end#function

@resumable function fibonnaci_resumable(n)
  a = zero(Int)
  b = a + one(a)
  for i in 1:n
    @yield a
    a, b = b, a + b
  end
end

function test_resumable(n)
  a = 0
  for i in fibonnaci_resumable(n)
    a = i
  end#for
  a
end

function fibonnaci_channel(n, ch::Channel)
  a = zero(Int)
  b = a + one(a)
  for i in 1:n
    put!(ch, a)
    a, b = b, a + b
  end
end

function test_channel(n, csize::Int)
  fib_channel = Channel(c -> fibonnaci_channel(n, c); ctype=Int, csize=csize)
  a = 0
  for i in fib_channel
    a = i
  end#for
  a
end

function fibonnaci_closure()
  a = zero(Int)
  b = a + one(Int)
  function()
    tmp = a
    a, b = b, a + b
    tmp
  end
end

function test_closure(n)
  fib_closure = fibonnaci_closure()
  a = 0
  for i in 1:n
    a = fib_closure()
  end
  a
end

function fibonnaci_closure_opt()
  a = Ref(zero(Int))
  b = Ref(a[] + one(Int))
  @noinline function()
    tmp = a[]
    a[], b[] = b[], a[] + b[]
    tmp
  end
end

function test_closure_opt(n)
  fib_closure = fibonnaci_closure_opt()
  a = 0
  for i in 1:n
    a = fib_closure()
  end
  a
end

function fibonnaci_closure_stm()
  a = Ref(zero(Int))
  b = Ref(a[] + one(Int))
  _state = Ref(zero(UInt8))
  function(_arg::Any=nothing)
    _state[] == 0x00 && @goto _STATE_0
    _state[] == 0x01 && @goto _STATE_1
    error("@resumable function has stopped!")
    @label _STATE_0
    _state[] = 0xff
    _arg isa Exception && throw(_arg)
    while true
      _state[] = 0x01
      return a[]
      @label _STATE_1
      _state[] = 0xff
      _arg isa Exception && throw(_arg)
      a[], b[] = b[], a[] + b[]
    end
  end
end

function test_closure_stm(n)
  fib_closure = fibonnaci_closure_stm()
  a = 0
  for i in 1:n
    a = fib_closure()
  end
  a
end

struct FibN
  n::Int
end

function Base.iterate(f::FibN, state::NTuple{3,Int}=(0, 1, 1))
  @inbounds state[3] >= f.n && return nothing
  a, b, iters = state
  @inbounds b, (b, a + b, iters + 1)
end

function test_iteration_protocol(n)
  fib = FibN(n)
  a = 0
  for i in fib
    a = i
  end
  a
end

isinteractive() || begin
  println("Direct: ")
  @btime direct_fib($n)
  @assert direct_fib(n) == 7540113804746346429

  println("ResumableFunctions: ")
  @btime test_resumable($n)
  @assert direct_fib(n) == 7540113804746346429

  println("Channels csize=0: ")
  @btime test_channel($n, $0)
  @assert test_channel(n, 0) == 7540113804746346429

  println("Channels csize=1: ")
  @btime test_channel($n, $1)
  @assert test_channel(n, 1) == 7540113804746346429

  println("Channels csize=20: ")
  @btime test_channel($n, $20)
  @assert test_channel(n, 20) == 7540113804746346429

  println("Channels csize=100: ")
  @btime test_channel($n, $100)
  @assert test_channel(n, 100) == 7540113804746346429

  println("Closure: ")
  @btime test_closure($n)
  @assert test_closure(n) == 7540113804746346429

  println("Closure optimised: ")
  @btime test_closure_opt($n)
  @assert test_closure_opt(n) == 7540113804746346429

  println("Closure statemachine: ")
  @btime test_closure_stm($n)
  @assert test_closure_stm(n) == 7540113804746346429

  println("Iteration protocol: ")
  @btime test_iteration_protocol($n)
  @assert test_iteration_protocol(n) == 7540113804746346429
end


text:
module KTOptical
    using Parameters
# Write your package code here.

    # l,p: LG のモードパラメータ
    # x,y: 座標(使用する際はブロードキャストする)
    # ω,λ: モードフィールド径、波長

    # ラゲール倍多項式
    @with_kw struct struct_beamparam
        ω::Float64
        z::Float64
        λ::Float64
        k::Float64
        zr::Float64
        ωz::Float64
        Rz::Float64
    end

    struct power
        W
    end
    struct energy
        J
    end

    export LG_E
    export LG_I
    export HG_E
    export HG_I
    export gauss_mode
    export vortex_mode
    struct gauss_mode
        m::Int8
        n::Int8
    end
    struct vortex_mode
        # userはl,pで代入するが、
        # m,nのフィールドも持っておき、コードライティング中では、m,nでやらせる。
        l::Int8
        p::Int8
    end

    function setParam(ω_,z_,λ_)
        # 計算結果を使ってパラメータセットするときは、
        # 引数内の宣言内のスコープが引き継がれない。
        # よってω、ｚ、λを使った計算はk,zr,wz,rzに入れられない。
        # ω_、ｚ_、λ_を使う必要がある。
        k = 2π / λ_
        zr = k * ω_^2 / 2
        ωz = ω_ * √(1 + (z_^2 / zr^2))
        Rz = k * ω_^2 / 2

        global bp = struct_beamparam(ω_,z_,λ_,k,zr,ωz,Rz)
    end

    function __Llp__(l,p,u)
        function Llp_setinit(l,p,u,m)
            if m < 0 #再起関数の終了条件
                return 1
            else
                return Llp_setinit(l,p,u,m-1) + (-1)^m*(2p+abs(l)-m)* u^m / factorial(m)
            end
        end
        return Llp_setinit(l,p,u,p)
    end

    function LG_E(l,p,x,y)
        R = √(x^2 + y^2)
        Θ = atan(y/x)
        E = @. √(2 * factorial(p) / (π * factorial(p + abs(l)))) *
            (√2R / bp.ω)^abs(l) *
            __Llp__(l, p, 2 * R^2 / bp.ω^2) *
            (bp.ω / bp.ωz) *
            exp(-R^2 / bp.ω^2) *
            exp(1im * l * Θ) *
            exp(-1im * (1 + 2p + abs(l)) * atan(bp.z / bp.zr)) *
            exp(-1im * bp.k * R^2 / (2bp.Rz))
        return E
    end

    function LG_E(l,p,x)
        R = √(x^2)
        Θ = atan(0)
        E = @. √(2 * factorial(p) / (π * factorial(p + abs(l)))) *
            (√2R / bp.ω)^abs(l) *
            __Llp__(l, p, 2 * R^2 / bp.ω^2) *
            (bp.ω / bp.ωz) *
            exp(-R^2 / bp.ω^2) *
            exp(1im * l * Θ) *
            exp(-1im * (1 + 2p + abs(l)) * atan(bp.z / bp.zr)) *
            exp(-1im * bp.k * R^2 / (2bp.Rz))
        return E
    end

    function LG_I(l,p,x,y)
        return abs(LG_E(l,p,x,y))^2
    end

    function LG_I(l,p,x)
        return abs(HG_E(l,p,x))^2
    end

# m,n: HG のモードパラメータ
# x,y: 座標(使用する際はブロードキャストする)

# エルミート倍多項式
    function __Hn__(n,u)
        function Hn_setinit(n,u,m)
            if m > floor(n/2) #再起関数の終了条件
                return 0
            else
                return Hn_setinit(n, u, m + 1) + (-1)^m/(factorial(m)*factorial(n-2m))*(2u)^(n-2m)
            end
        end
        return factorial(n) * Hn_setinit(n,u,0)
    end

    function HG_E(m,n,x,y)
        E = __Hn__(n, √2 * y / bp.ω) *
        __Hn__(m, √2 * x / bp.ω) *
        exp(-(x^2 + y^2) / bp.ω^2) *
        exp(-1im * (1 + m + n) * atan(bp.z / bp.zr)) *
        exp(-1im * bp.k * (x^2 + y^2) / (2bp.Rz))
        return E
    end

    function HG_E(m,n,x)
        E = __Hn__(m, √2 * x / bp.ω) *
        exp(-(x^2) / bp.ω^2) *
        exp(-1im * (1 + m + n) * atan(bp.z / bp.zr)) *
        exp(-1im * bp.k * (x^2) / (2bp.Rz))
        return E
    end

    function HG_I(m,n,x,y)
        return abs(HG_E(m,n,x,y))^2
    end

    function HG_I(m,n,x)
        return abs(HG_E(m,n,x))^2
    end

    function E(mode::vortex_mode ,x,y)
        l = mode.l
        p = mode.p
        R = √(x^2 + y^2)
        Θ = atan(y/x)
        E = @. √(2 * factorial(p) / (π * factorial(p + abs(l)))) *
            (√2R / bp.ω)^abs(l) *
            __Llp__(l, p, 2 * R^2 / bp.ω^2) *
            (bp.ω / bp.ωz) *
            exp(-R^2 / bp.ω^2) *
            exp(1im * l * Θ) *
            exp(-1im * (1 + 2p + abs(l)) * atan(bp.z / bp.zr)) *
            exp(-1im * bp.k * R^2 / (2bp.Rz))
        return E
    end

    function E(mode::vortex_mode ,x)
        l = mode.l
        p = mode.p
        R = √(x^2)
        Θ = atan(0)
        E = @. √(2 * factorial(p) / (π * factorial(p + abs(l)))) *
            (√2R / bp.ω)^abs(l) *
            __Llp__(l, p, 2 * R^2 / bp.ω^2) *
            (bp.ω / bp.ωz) *
            exp(-R^2 / bp.ω^2) *
            exp(1im * l * Θ) *
            exp(-1im * (1 + 2p + abs(l)) * atan(bp.z / bp.zr)) *
            exp(-1im * bp.k * R^2 / (2bp.Rz))
        return E
    end

    function E(mode::gauss_mode,x,y)
        m = mode.m
        n = mode.n
        E = __Hn__(n, √2 * y / bp.ω) *
        __Hn__(m, √2 * x / bp.ω) *
        exp(-(x^2 + y^2) / bp.ω^2) *
        exp(-1im * (1 + m + n) * atan(bp.z / bp.zr)) *
        exp(-1im * bp.k * (x^2 + y^2) / (2bp.Rz))
        return E
    end

    function E(mode::gauss_mode,x)
        m = mode.m
        n = mode.n
        E = __Hn__(m, √2 * x / bp.ω) *
        exp(-(x^2) / bp.ω^2) *
        exp(-1im * (1 + m + n) * atan(bp.z / bp.zr)) *
        exp(-1im * bp.k * (x^2) / (2bp.Rz))
        return E
    end

    function I(mode::vortex_mode,x)
        l = mode.l
        p = mode.p
        return abs(HG_E(l,p,x))^2
    end

    function I(mode::vortex_mode,x,y)
        l = mode.l
        p = mode.p
        return abs(LG_E(l,p,x,y))^2
    end

    function I(mode::gauss_mode,x)
        m = mode.m
        n = mode.n
        return abs(HG_E(m,n,x))^2
    end

    function I(mode::gauss_mode,x,y)
        m = mode.m
        n = mode.n
        return abs(HG_E(m,n,x,y))^2
    end

    function set_I(I,I0,step_t)
        total_input_I = sum(I)
        ratio = (I0*step_t)/total_input_I
        return ratio .* I , ratio
    end

end


text:
#=
Feature Pyramid Network
=#
struct FPN
    C1
    C2
    C3
    C4
    C5
    P6
    P5_conv1
    P5_conv2
    P4_conv1
    P4_conv2
    P3_conv1
    P3_conv2
    P2_conv1
    P2_conv2
    P1_conv1
    P1_conv2
end


text:
include("types.jl")
rayTracerProperties = Dict(  "lightSource" => Vector(0, 0, -120),
		"cameraPos" => Vector(0, 0, 30),
		"objects" => [ Sphere( Vector(6,4,-10), 3, Vector(200,255,0)),
		               Sphere( Vector(2,8,-10), 1, Vector(220,0,100)),
			             Sphere( Vector(10,4,-10), 3, Vector(0,100,205)) ],
		"imageWidth" => 500,
		"imageHeight" => 500,
		"pixelWidth" => 45,
		"ambient" => 0.1,
		"specular" => Vector(0, 80, 50))


text:
#-----------------------------------------------------------------------
#   FULL TEST OF BROYDEN CODE
#-----------------------------------------------------------------------

d = 12

r0 = [0.6027037403222177, 0.6823081703161471, 0.4969968192879606,
      0.9455425492979632, 0.09666423716951855, 0.5533229648934559,
      0.6257838591529667, 0.3219114195167574, 0.9465987429640241,
      0.9543249417929738, 0.1987862235853426, 0.050519233798469454]

r1 = [0.45667659772446867, 0.29604904127839915, 0.36276182662693257,
      0.05020123630607376, 0.5045105043961433, 0.21437469923464825,
      0.9063213634872238, 0.6585306236631778, 0.14703614205907733,
      0.5187343751664399, 0.137009564745886, 0.7477554965317557]

r2 = [0.8559101941413021, 0.8407795618867213, 0.9935645925564422,
      0.24371648277714364, 0.5445775437567715, 0.05095659932958019,
      0.788215773526395, 0.9845642037746567, 0.5623887100264071,
      0.11735622784087019, 0.07719560594089825, 0.5954214792591306]

x0 = 2*r0/1000
x1 = r1
x2 = -π*r2/10

f0 = x->2*π*(x-x0)
Jac0 = ForwardDiff.jacobian(f0,zeros(d));

@test (bsolve(f0,Jac0,f0(zeros(d)),zeros(d),2*d)-x0)./x0 ≈ zeros(d)

f1 = x->(x-x0).*(x-x1)
Jac1 = ForwardDiff.jacobian(f1,zeros(d));

@test (bsolve(f1,Jac1,f1(zeros(d)),zeros(d),2*d)-x0)./x0 ≈ zeros(d)

f2 = x->(x-x0).*(x-x1).*(x-x2)
Jac2 = ForwardDiff.jacobian(f2,zeros(d));

@test (bsolve(f2,Jac2,f2(zeros(d)),zeros(d),2*d)-x0)./x0 ≈ zeros(d)


text:
using Random
using VisualRegressionTests
using LinearAlgebra
using SparseArrays
using ImageMagick

istravis = "TRAVIS" ∈ keys(ENV)

default(show=false, reuse=true)

cd(@__DIR__)
cd("../assets")

@testset "README" begin
    n = 15
    Random.seed!(1)
    A = Float64[ rand() < 0.5 ? 0 : rand() for i=1:n, j=1:n]
    for i=1:n
        A[i, 1:i-1] = A[1:i-1, i]
    end
    x = rand(n)
    y = rand(n)
    z = rand(n)
    @plottest graphplot(A,
                        node_weights = 1:n,
                        nodecolor = range(colorant"yellow", stop=colorant"red", length=n),
                        names = 1:n,
                        linecolor = :darkgrey,
                        layout_kw = Dict(:x => x, :y => y),
                        ) "random_labelled_graph.png" popup=!istravis tol=0.03

    @plottest graphplot(A,
                        node_weights = 1:n,
                        markercolor = :darkgray,
                        dim = 3,
                        markersize = 5,
                        linecolor = :darkgrey,
                        linealpha = 0.5,
                        layout_kw = Dict(:x => x, :y => y, :z => z),
                        ) "random_3d_graph.png" popup=!istravis tol=0.02
    adjmat = Symmetric(sparse(rand(0:1,8,8)))

    adjmat = Symmetric(sparse(rand(0:1,8,8)))
    @plottest plot(
                   graphplot(adjmat,
                             method=:chorddiagram,
                             names=[text(string(i), 8) for i in 1:8],
                             linecolor=:black,
                             fillcolor=:lightgray),

                   graphplot(adjmat,
                             method=:arcdiagram,
                             markersize=3,
                             linecolor=:black,
                             markercolor=:black)
                  ) "arc_chord_diagrams.png" popup=!istravis tol=0.02

    code = :(
    function mysum(list)
        out = 0
        for value in list
            out += value
        end
        out
    end
    )

    default(size=(1000, 1000))
    @plottest plot(code, fontsize=10, shorten=0.01, axis_buffer=0.15, nodeshape=:rect) "AST_example.png" popup=!istravis tol=0.02
    @plottest plot(AbstractFloat, method=:tree, fontsize=10, nodeshape=:ellipse) "julia_type_tree.png" popup=!istravis tol=0.2
end


text:
# The AD generates fairly large backtraces that are unhelpful if you interrupt
# while training; this just cleans that up.
macro interrupts(ex)
  :(try $(esc(ex))
    catch e
      e isa InterruptException || rethrow()
      throw(e)
    end)
end

# In-place gradients

init_grad(x) = zero(x)
zero_grad!(x) = zero(x)
zero_grad!(x::AbstractArray) = (x .= 0)

scan(c::Call) = foreach(scan, c.args)

function scan(x::Tracked)
  x.isleaf && return
  ref = x.ref += 1
  if ref == 1
    scan(x.f)
    isdefined(x, :grad) && (x.grad = zero_grad!(x.grad))
  end
  return
end

function scan(x)
  istracked(x) && scan(tracker(x))
  return
end

function back_(c::Call, Δ, once)
  Δs = c.func(Δ)
  (Δs isa Tuple && length(Δs) >= length(c.args)) ||
    error("Gradient is not a tuple of length $(length(c.args))")
  foreach((x, d) -> back(x, d, once), c.args, data.(Δs))
end

back_(::Call{Nothing}, Δ, once) = nothing
back_(::Call{Missing}, Δ, once) = error("`back!` was already used")

accum!(x, Δ) = x .+ Δ
accum!(x::AbstractArray, Δ) = (x .+= Δ)

function back(x::Tracked, Δ, once)
  x.isleaf && (x.grad = accum!(x.grad, Δ); return)
  ref = x.ref -= 1
  grad = if isdefined(x, :grad)
    x.grad = accum!(x.grad, Δ)
  elseif ref > 0
    x.grad = Δ
  else
    Δ
  end
  if ref == 0
    back_(x.f, grad, once)
    once && !x.isleaf && (x.f = Call(missing, ()))
  end
  return
end

back(::Nothing, Δ, once) = return

# Interface methods

# TODO: if an error occurs in `back` the refcounts will be broken
# and `back` will silently fail to update.
# (but only if you re-use intermediate values between passes)
# Refcounts are also probably not safe in some situations (e.g. back called
# from within a backpropagator)

function back!(x, Δ; once = true)
  istracked(x) || return
  scan(x)
  back(tracker(x), Δ, once)
  return
end

function extract_grad!(x)
  x̄ = copy(grad(x))
  x̄ = nobacksies("Use `gradient(...; nest = true)` for nested derivatives", x̄)
  tracker(x).grad = zero_grad!(grad(x))
  return x̄
end

function gradient_(f, xs...)
  xs = param.(data.(xs))
  l = f(xs...)
  losscheck(l)
  @interrupts back!(l)
  extract_grad!.(xs)
end

function gradient_(f, xs::Params)
  l = f()
  losscheck(l)
  @interrupts back!(l)
  gs = Grads()
  for x in xs
    gs[tracker(x)] = extract_grad!(x)
  end
  return gs
end

# Out-of-place gradients

function back_(g::Grads, c::Call, Δ)
  Δs = c.func(Δ)
  (Δs isa Tuple && length(Δs) >= length(c.args)) ||
    error("Gradient is not a tuple of length $(length(c.args))")
  foreach((x, Δ) -> back(g, x, Δ), c.args, Δs)
end

back_(g::Grads, ::Call{Nothing}, Δ) = nothing

function back(g::Grads, x::Tracked, Δ)
  x.isleaf && (accum!(g, x, Δ); return)
  ref = x.ref -= 1
  if ref > 0 || haskey(g, x)
    accum!(g, x, Δ)
    ref == 0 && back_(g, x.f, g[x])
  else
    ref == 0 && back_(g, x.f, Δ)
  end
  return
end

back(::Grads, ::Nothing, _) = return

collectmemaybe(xs) = xs

function forward(f, ps::Params)
  y = collectmemaybe(f())
  y, function (Δ)
    g = Grads(ps)
    if istracked(y)
      scan(y)
      back(g, tracker(y), Δ)
    end
    return g
  end
end

function forward(f, args...)
  args = param.(args)
  y, back = forward(() -> f(args...), Params(args))
  y, Δ -> getindex.(Ref(back(Δ)), args)
end

function losscheck(x)
  x isa Real || error("Function output is not scalar")
  isinf(x) && error("Loss is infinite")
  isnan(x) && error("Loss is NaN")
end

function gradient_nested(f, args...)
  y, back = forward(f, args...)
  losscheck(y)
  return back(1)
end

gradient(f, xs...; nest = false) =
  nest ? gradient_nested(f, xs...) : gradient_(f, xs...)

# Jacobians and Hessians

"""
    J = jacobian(m,x)

Calculate the output jacobian `J = d/dx m(x)` such that each row `i` of `J` corresponds to the gradient `J[i,:] = ∇ₓ(m(x)[i])`
"""
function jacobian(f, x::AbstractVector)
  y::AbstractVector, back = forward(f, x)
  ȳ(i) = [i == j for j = 1:length(y)]
  vcat([transpose(back(ȳ(i))[1]) for i = 1:length(y)]...)
end

hessian(f, x) = jacobian(x -> gradient(f, x, nest=true)[1], x)


text:
@inline function issquare(m::AbstractArray{T,2}) where {T<:Number}
    r, c = size(m)
    return r === c
end

@inline function issquare(m::Array{DoubleFloat{T},2}) where {T<:IEEEFloat}
    r, c = size(m)
    return r === c
end

function norm(v::Array{DoubleFloat{T},N}) where {N, T<:IEEEFloat}
    v = v .* v
    return sqrt(sum(v))
end

function norm(v::Array{DoubleFloat{T},N}, p::R) where {N, T<:IEEEFloat, R<:Real}
    v = v .* v
    r = inv(DoubleFloat{T}(p))
    return (sum(v))^r
end


text:
# Note that this script can accept some limited command-line arguments, run
# `julia build_tarballs.jl --help` to see a usage message.
using BinaryBuilder

name = "MariaDB_Connector_ODBC"
version = v"3.1.9"

# Collection of sources required to build MariaDB_Connector_ODBC
sources = [
    ArchiveSource("https://downloads.mariadb.com/Connectors/odbc/connector-odbc-$(version)/mariadb-connector-odbc-$(version)-ga-src.tar.gz",
                  "5ead3f69ccde539fd7e3de6f4b8f0e6f9f6c32b4b4f082adf0e2ff110971fe1e"),
    DirectorySource("./bundled"),
    FileSource("https://downloads.mariadb.com/Connectors/odbc/connector-odbc-$(version)/mariadb-connector-odbc-$(version)-win64.msi",
               "c526714f4a65d672b86bf9c24962d55bec70661b37863d891f007f680dae71f7"; filename = "x86_64-w64-mingw32.msi"),
    FileSource("https://downloads.mariadb.com/Connectors/odbc/connector-odbc-$(version)/mariadb-connector-odbc-$(version)-win32.msi",
               "5ef098aefee1d30e6e349bf2363a601007e1376508e6b4a0e23e1eb3ed5a51e2"; filename = "i686-w64-mingw32.msi"),
    ## Keep the patches just in case some day we decide to build for Windows
    ## from source
    # DirectorySource("./bundled"),
]

# Bash recipe for building across all platforms
script = raw"""
install_license $WORKSPACE/srcdir/mariadb-connector*/COPYING

if [[ "${target}" == *-mingw* ]]; then
    # For Windows just use the prebuilt library
    cd $WORKSPACE/srcdir
    apk add p7zip
    7z x "${target}.msi"
    install -Dvm 755 mariadb_odbc_dll "${libdir}/mariadb/mariadb_odbc.dll"
    exit
fi

cd $WORKSPACE/srcdir/mariadb-connector*/

# Skip building of macOS package
sed -i 's/ADD_SUBDIRECTORY(osxinstall)/# ADD_SUBDIRECTORY(osxinstall)/' CMakeLists.txt

# They want to run a script which changes the name of the required library
# `libiodbcinst` from `libiodbcinst.2.dylib` to `libiodbcinst.dylib` which has the only
# effect of making `libmaodbc` not loadable, as `libiodbcinst` is dlopened as... guess
# what... `libiodbcinst.2.dylib`.
atomic_patch -p1 ../patches/do-not-change-name-of-iodbcinst.patch

## Keep this for reference in case we decide to build for Windows from source
# if [[ "${target}" == *-mingw* ]]; then
#     # Disable dialog plugin
#     sed -i 's/-DPLUGINS_LIB_DIR.*:dialog.*//' CMakeLists.txt
#     atomic_patch -p1 "${WORKSPACE}/srcdir/patches/0001-fix-case-headers-windows.patch"
#     # The following is not really a patch: Microsoft Visual C++ thinks that it's
#     # a good idea to put some stray characters in the first bits of the file,
#     # making it hard to digest for MinGW.  We can't diff the file as the
#     # original one appears to be binary, because of the stray character.
#     cp "${WORKSPACE}/srcdir/patches/resource.h" dsn/
# fi

mkdir build && cd build

export CFLAGS="-I${includedir}/mariadb"
# Find the MariaDB lib and link to the right OpenSSL and Zlib libraries
export LDFLAGS="-L${libdir}/mariadb ${libdir}/libssl.${dlext} ${libdir}/libcrypto.${dlext} -lz"
cmake -DCMAKE_INSTALL_PREFIX=${prefix} \
    -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TARGET_TOOLCHAIN} \
    -DCMAKE_BUILD_TYPE=Release \
    -DWITH_OPENSSL=ON \
    -DWITH_SSL=OPENSSL \
    -DOPENSSL_FOUND=ON \
    -DOPENSSL_CRYPTO_LIBRARY=${libdir}/libcrypto.${dlext} \
    -DOPENSSL_SSL_LIBRARY=${libdir}/libssl.${dlext} \
    -DICONV_LIBRARIES=${libdir}/libiconv.${dlext} \
    -DICONV_INCLUDE_DIR=${includedir} \
    ..
make -j${nproc}
make install
"""

platforms = [
    Platform("i686", "linux"; libc="glibc"),
    Platform("x86_64", "linux"; libc="glibc"),
    # Platform("aarch64", "linux"; libc="glibc"),
    # Platform("armv7l", "linux"; libc="glibc"),
    # Platform("powerpc64le", "linux"; libc="glibc"),
    Platform("i686", "linux"; libc="musl"),
    Platform("x86_64", "linux"; libc="musl"),
    # Platform("aarch64", "linux"; libc="musl"),
    # Platform("armv7l", "linux"; libc="musl"),
    Platform("x86_64", "macos"),
    Platform("aarch64", "macos"),
    # Platform("x86_64", "freebsd"),
    Platform("i686", "windows"),
    Platform("x86_64", "windows"),
]

# The products that we will ensure are always built
products = [
    LibraryProduct(["libmaodbc", "mariadb_odbc"], :libmaodbc,
                   raw"${libdir}/mariadb"),
]

# Dependencies that must be installed before this package can be built
dependencies = [
    BuildDependency("MariaDB_Connector_C_jll"),
    Dependency("iODBC_jll"),
    Dependency("Libiconv_jll"),
    Dependency("unixODBC_jll"),
    Dependency("OpenSSL_jll"),
    Dependency("Zlib_jll"),
]

# Build the tarballs, and possibly a `build.jl` as well.
build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies; julia_compat="1.6")


text:
module TestStaleDeps

include("preamble.jl")
using Aqua: PkgId, UUID, _analyze_stale_deps_2, ispass, ⊜

@testset "_analyze_stale_deps_2" begin
    pkg = PkgId(UUID(42), "TargetPkg")

    dep1 = PkgId(UUID(1), "Dep1")
    dep2 = PkgId(UUID(2), "Dep2")
    dep3 = PkgId(UUID(3), "Dep3")

    @testset "pass" begin
        @test _analyze_stale_deps_2(;
            pkg = pkg,
            deps = PkgId[],
            loaded_uuids = UUID[],
            ignore = Symbol[],
        ) ⊜ true
        @test _analyze_stale_deps_2(;
            pkg = pkg,
            deps = PkgId[dep1],
            loaded_uuids = UUID[dep1.uuid, dep2.uuid, dep3.uuid],
            ignore = Symbol[],
        ) ⊜ true
        @test _analyze_stale_deps_2(;
            pkg = pkg,
            deps = PkgId[dep1],
            loaded_uuids = UUID[dep2.uuid, dep3.uuid],
            ignore = Symbol[:Dep1],
        ) ⊜ true
    end
    @testset "failure" begin
        @test _analyze_stale_deps_2(;
            pkg = pkg,
            deps = PkgId[dep1],
            loaded_uuids = UUID[],
            ignore = Symbol[],
        ) ⊜ false
        @test _analyze_stale_deps_2(;
            pkg = pkg,
            deps = PkgId[dep1],
            loaded_uuids = UUID[dep2.uuid, dep3.uuid],
            ignore = Symbol[],
        ) ⊜ false
        @test _analyze_stale_deps_2(;
            pkg = pkg,
            deps = PkgId[dep1, dep2],
            loaded_uuids = UUID[dep3.uuid],
            ignore = Symbol[:Dep1],
        ) ⊜ false
    end
end

with_sample_pkgs() do
    @testset "Package without `deps`" begin
        pkg = AquaTesting.SAMPLE_PKG_BY_NAME["PkgWithoutTestProject"]
        results = Aqua.analyze_stale_deps(pkg)
        @test length(results) == 1
        r, = results
        @test ispass(r)
        @test r ⊜ true
        msg = sprint(show, "text/plain", r)
        @test occursin("No `deps` table in", msg)
    end
    @testset "PkgWithoutProject" begin
        pkg = AquaTesting.SAMPLE_PKG_BY_NAME["PkgWithoutProject"]
        results = Aqua.analyze_stale_deps(pkg)
        @test length(results) == 1
        r, = results
        @test !ispass(r)
        @test r ⊜ false
        msg = sprint(show, "text/plain", r)
        @test occursin("Project.toml file at project directory does not exist", msg)
    end
end

end  # module


text:
!(VERSION.minor > 3) && println("Required Julia 0.4")

module JuliaIntro

try
  Pkg.installed("PageDown")
catch
 Pkg.clone("https://github.com/wookay/PageDown.jl")
end
using PageDown

push!(LOAD_PATH, ".")
import Slide: pages

export page
export clear
export @page, @current, @next, @prev, @go, @first, @last, @step, @open

page = Page(pages, :IMG=>"짤")

function __init__()
  Base.Markdown.term(STDOUT, @first)
end

end # module