text:
module ProgressMeter

using Printf: @sprintf
using Distributed

export Progress, ProgressThresh, ProgressUnknown, BarGlyphs, next!, update!, cancel, finish!, @showprogress, progress_map, progress_pmap, ijulia_behavior

"""
`ProgressMeter` contains a suite of utilities for displaying progress
in long-running computations. The major functions/types in this module
are:

- `@showprogress`: an easy interface for straightforward situations
- `Progress`: an object for managing progress updates with a predictable number of iterations
- `ProgressThresh`: an object for managing progress updates where termination is governed by a threshold
- `next!` and `update!`: report that progress has been made
- `cancel` and `finish!`: early termination
"""
ProgressMeter

abstract type AbstractProgress end

"""
Holds the five characters that will be used to generate the progress bar.
"""
mutable struct BarGlyphs
    leftend::Char
    fill::Char
    front::Union{Vector{Char}, Char}
    empty::Char
    rightend::Char
end

"""
String constructor for BarGlyphs - will split the string into 5 chars
"""
function BarGlyphs(s::AbstractString)
    glyphs = (s...,)
    if !isa(glyphs, NTuple{5,Char})
        error("""
            Invalid string in BarGlyphs constructor.
            You supplied "$s".
            Note: string argument must be exactly 5 characters long, e.g. "[=> ]".
        """)
    end
    return BarGlyphs(glyphs...)
end

"""
`prog = Progress(n; dt=0.1, desc="Progress: ", color=:green,
output=stderr, barlen=tty_width(desc), start=0)` creates a progress meter for a
task with `n` iterations or stages starting from `start`. Output will be
generated at intervals at least `dt` seconds apart, and perhaps longer if each
iteration takes longer than `dt`. `desc` is a description of
the current task.
"""
mutable struct Progress <: AbstractProgress
    n::Int
    reentrantlocker::Threads.ReentrantLock
    dt::Float64
    counter::Int
    tfirst::Float64
    tlast::Float64
    printed::Bool              # true if we have issued at least one status update
    desc::String               # prefix to the percentage, e.g.  "Computing..."
    barlen::Union{Int,Nothing} # progress bar size (default is available terminal width)
    barglyphs::BarGlyphs       # the characters to be used in the bar
    color::Symbol              # default to green
    output::IO                 # output stream into which the progress is written
    offset::Int                # position offset of progress bar (default is 0)
    numprintedvalues::Int      # num values printed below progress in last iteration
    start::Int                 # which iteration number to start from
    enabled::Bool              # is the output enabled

    function Progress(n::Integer;
                      dt::Real=0.1,
                      desc::AbstractString="Progress: ",
                      color::Symbol=:green,
                      output::IO=stderr,
                      barlen=nothing,
                      barglyphs::BarGlyphs=BarGlyphs('|','█', Sys.iswindows() ? '█' : ['▏','▎','▍','▌','▋','▊','▉'],' ','|',),
                      offset::Integer=0,
                      start::Integer=0,
                      enabled::Bool = true
                     )
        reentrantlocker = Threads.ReentrantLock()
        counter = start
        tfirst = tlast = time()
        printed = false
        new(n, reentrantlocker, dt, counter, tfirst, tlast, printed, desc, barlen, barglyphs, color, output, offset, 0, start, enabled)
    end
end

Progress(n::Integer, dt::Real, desc::AbstractString="Progress: ",
         barlen=nothing, color::Symbol=:green, output::IO=stderr;
         offset::Integer=0) =
    Progress(n, dt=dt, desc=desc, barlen=barlen, color=color, output=output, offset=offset)

Progress(n::Integer, desc::AbstractString, offset::Integer=0) = Progress(n, desc=desc, offset=offset)

"""
`prog = ProgressThresh(thresh; dt=0.1, desc="Progress: ",
color=:green, output=stderr)` creates a progress meter for a task
which will terminate once a value less than or equal to `thresh` is
reached. Output will be generated at intervals at least `dt` seconds
apart, and perhaps longer if each iteration takes longer than
`dt`. `desc` is a description of the current task.
"""
mutable struct ProgressThresh{T<:Real} <: AbstractProgress
    thresh::T
    reentrantlocker::Threads.ReentrantLock
    dt::Float64
    val::T
    counter::Int
    triggered::Bool
    tfirst::Float64
    tlast::Float64
    printed::Bool        # true if we have issued at least one status update
    desc::String         # prefix to the percentage, e.g.  "Computing..."
    color::Symbol        # default to green
    output::IO           # output stream into which the progress is written
    numprintedvalues::Int   # num values printed below progress in last iteration
    offset::Int             # position offset of progress bar (default is 0)
    enabled::Bool             # is the output a file or not

    function ProgressThresh{T}(thresh;
                               dt::Real=0.1,
                               desc::AbstractString="Progress: ",
                               color::Symbol=:green,
                               output::IO=stderr,
                               offset::Integer=0,
                               enabled = true) where T
        reentrantlocker = Threads.ReentrantLock()
        tfirst = tlast = time()
        printed = false
        new{T}(thresh, reentrantlocker, dt, typemax(T), 0, false, tfirst, tlast, printed, desc, color, output, 0, offset, enabled)
    end
end
ProgressThresh(thresh::Real; kwargs...) = ProgressThresh{typeof(thresh)}(thresh; kwargs...)

# Legacy constructor calls
ProgressThresh(thresh::Real, dt::Real, desc::AbstractString="Progress: ",
         color::Symbol=:green, output::IO=stderr;
         offset::Integer=0) =
    ProgressThresh(thresh; dt=dt, desc=desc, color=color, output=output, offset=offset)

ProgressThresh(thresh::Real, desc::AbstractString, offset::Integer=0) = ProgressThresh(thresh; desc=desc, offset=offset)

"""
`prog = ProgressUnknown(; dt=0.1, desc="Progress: ",
color=:green, output=stderr)` creates a progress meter for a task
which has a non-deterministic termination criterion.
Output will be generated at intervals at least `dt` seconds
apart, and perhaps longer if each iteration takes longer than
`dt`. `desc` is a description of the current task.
"""
mutable struct ProgressUnknown <: AbstractProgress
    done::Bool
    reentrantlocker::Threads.ReentrantLock
    dt::Float64
    counter::Int
    triggered::Bool
    tfirst::Float64
    tlast::Float64
    printed::Bool        # true if we have issued at least one status update
    desc::String         # prefix to the percentage, e.g.  "Computing..."
    color::Symbol        # default to green
    output::IO           # output stream into which the progress is written
    numprintedvalues::Int   # num values printed below progress in last iteration
    enabled::Bool
end

function ProgressUnknown(;dt::Real=0.1, desc::AbstractString="Progress: ", color::Symbol=:green, output::IO=stderr, enabled::Bool = true)
    reentrantlocker = Threads.ReentrantLock()
    tfirst = tlast = time()
    printed = false
    ProgressUnknown(false, reentrantlocker, dt, 0, false, tfirst, tlast, printed, desc, color, output, 0, enabled)
end

ProgressUnknown(dt::Real, desc::AbstractString="Progress: ",
         color::Symbol=:green, output::IO=stderr; kwargs...) =
    ProgressUnknown(dt=dt, desc=desc, color=color, output=output; kwargs...)

ProgressUnknown(desc::AbstractString) = ProgressUnknown(desc=desc)

#...length of percentage and ETA string with days is 29 characters
tty_width(desc, output) = max(0, (displaysize(output)::Tuple{Int,Int})[2] - (length(desc) + 29))

# Package level behavior of IJulia clear output
@enum IJuliaBehavior IJuliaWarned IJuliaClear IJuliaAppend

const IJULIABEHAVIOR = Ref(IJuliaWarned)

function ijulia_behavior(b)
    @assert b in [:warn, :clear, :append]
    b == :warn && (IJULIABEHAVIOR[] = IJuliaWarned)
    b == :clear && (IJULIABEHAVIOR[] = IJuliaClear)
    b == :append && (IJULIABEHAVIOR[] = IJuliaAppend)
end

# Whether or not to use IJulia.clear_output
running_ijulia_kernel() = isdefined(Main, :IJulia) && Main.IJulia.inited
clear_ijulia() = (IJULIABEHAVIOR[] != IJuliaAppend) && running_ijulia_kernel()

# update progress display
function updateProgress!(p::Progress; showvalues = (), truncate_lines = false, valuecolor = :blue, offset::Integer = p.offset, keep = (offset == 0), desc::Union{Nothing,AbstractString} = nothing)
    (!running_ijulia_kernel() & !p.enabled) && return
    if desc !== nothing
        if p.barlen !== nothing
            p.barlen += length(p.desc) - length(desc) #adjust bar length to accommodate new description
        end
        p.desc = desc
    end
    p.offset = offset
    t = time()
    if p.counter >= p.n
        if p.counter == p.n && p.printed
            barlen = p.barlen isa Nothing ? tty_width(p.desc, p.output) : p.barlen
            percentage_complete = 100.0 * p.counter / p.n
            bar = barstring(barlen, percentage_complete, barglyphs=p.barglyphs)
            dur = durationstring(t-p.tfirst)
            msg = @sprintf "%s%3u%%%s Time: %s" p.desc round(Int, percentage_complete) bar dur
            !clear_ijulia() && print(p.output, "\n" ^ (p.offset + p.numprintedvalues))
            move_cursor_up_while_clearing_lines(p.output, p.numprintedvalues)
            printover(p.output, msg, p.color)
            printvalues!(p, showvalues; color = valuecolor, truncate = truncate_lines)
            if keep
                println(p.output)
            else
                print(p.output, "\r\u1b[A" ^ (p.offset + p.numprintedvalues))
            end
            flush(p.output)
        end
        return nothing
    end

    if t > p.tlast+p.dt
        barlen = p.barlen isa Nothing ? tty_width(p.desc, p.output) : p.barlen
        percentage_complete = 100.0 * p.counter / p.n
        bar = barstring(barlen, percentage_complete, barglyphs=p.barglyphs)
        elapsed_time = t - p.tfirst
        est_total_time = elapsed_time * (p.n - p.start) / (p.counter - p.start)
        if 0 <= est_total_time <= typemax(Int)
            eta_sec = round(Int, est_total_time - elapsed_time )
            eta = durationstring(eta_sec)
        else
            eta = "N/A"
        end
        msg = @sprintf "%s%3u%%%s  ETA: %s" p.desc round(Int, percentage_complete) bar eta
        !clear_ijulia() && print(p.output, "\n" ^ (p.offset + p.numprintedvalues))
        move_cursor_up_while_clearing_lines(p.output, p.numprintedvalues)
        printover(p.output, msg, p.color)
        printvalues!(p, showvalues; color = valuecolor, truncate = truncate_lines)
        !clear_ijulia() && print(p.output, "\r\u1b[A" ^ (p.offset + p.numprintedvalues))
        flush(p.output)
        # Compensate for any overhead of printing. This can be
        # especially important if you're running over a slow network
        # connection.
        p.tlast = t + 2*(time()-t)
        p.printed = true
    end
    return nothing
end

function updateProgress!(p::ProgressThresh; showvalues = (), truncate_lines = false, valuecolor = :blue, offset::Integer = p.offset, keep = (offset == 0), desc = p.desc)
    (!running_ijulia_kernel() & !p.enabled) && return
    p.offset = offset
    p.desc = desc
    t = time()
    if p.val <= p.thresh && !p.triggered
        p.triggered = true
        if p.printed
            p.triggered = true
            dur = durationstring(t-p.tfirst)
            msg = @sprintf "%s Time: %s (%d iterations)" p.desc dur p.counter
            print(p.output, "\n" ^ (p.offset + p.numprintedvalues))
            move_cursor_up_while_clearing_lines(p.output, p.numprintedvalues)
            printover(p.output, msg, p.color)
            printvalues!(p, showvalues; color = valuecolor, truncate = truncate_lines)
            if keep
                println(p.output)
            else
                print(p.output, "\r\u1b[A" ^ (p.offset + p.numprintedvalues))
            end
            flush(p.output)
        end
        return
    end

    if t > p.tlast+p.dt && !p.triggered
        elapsed_time = t - p.tfirst
        msg = @sprintf "%s (thresh = %g, value = %g)" p.desc p.thresh p.val
        print(p.output, "\n" ^ (p.offset + p.numprintedvalues))
        move_cursor_up_while_clearing_lines(p.output, p.numprintedvalues)
        printover(p.output, msg, p.color)
        printvalues!(p, showvalues; color = valuecolor, truncate = truncate_lines)
        print(p.output, "\r\u1b[A" ^ (p.offset + p.numprintedvalues))
        flush(p.output)
        # Compensate for any overhead of printing. This can be
        # especially important if you're running over a slow network
        # connection.
        p.tlast = t + 2*(time()-t)
        p.printed = true
    end
end

function updateProgress!(p::ProgressUnknown; showvalues = (), truncate_lines = false, valuecolor = :blue, desc = p.desc)
    (!running_ijulia_kernel() & !p.enabled) && return
    p.desc = desc
    t = time()
    if p.done
        if p.printed
            dur = durationstring(t-p.tfirst)
            msg = @sprintf "%s %d \t Time: %s" p.desc p.counter dur
            move_cursor_up_while_clearing_lines(p.output, p.numprintedvalues)
            printover(p.output, msg, p.color)
            printvalues!(p, showvalues; color = valuecolor, truncate = truncate_lines)
            println(p.output)
            flush(p.output)
        end
        return
    end

    if t > p.tlast+p.dt
        dur = durationstring(t-p.tfirst)
        msg = @sprintf "%s %d \t Time: %s" p.desc p.counter dur
        move_cursor_up_while_clearing_lines(p.output, p.numprintedvalues)
        printover(p.output, msg, p.color)
        printvalues!(p, showvalues; color = valuecolor, truncate = truncate_lines)
        flush(p.output)
        # Compensate for any overhead of printing. This can be
        # especially important if you're running over a slow network
        # connection.
        p.tlast = t + 2*(time()-t)
        p.printed = true
        return
    end
end

# update progress display
"""
`next!(prog, [color], step = 1)` reports that `step` units of progress have been
made. Depending on the time interval since the last update, this may
or may not result in a change to the display.

You may optionally change the color of the display. See also `update!`.
"""
function next!(p::Union{Progress, ProgressUnknown}; step::Int = 1, options...)
    lock(p.reentrantlocker) do
        p.counter += step
        updateProgress!(p; options...)
    end
end

function next!(p::Union{Progress, ProgressUnknown}, color::Symbol; step::Int = 1, options...)
    lock(p.reentrantlocker) do
        p.color = color
        p.counter += step
        updateProgress!(p; options...)
    end
end

"""
`update!(prog, counter, [color])` sets the progress counter to
`counter`, relative to the `n` units of progress specified when `prog`
was initialized.  Depending on the time interval since the last
update, this may or may not result in a change to the display.

If `prog` is a `ProgressThresh`, `update!(prog, val, [color])` specifies
the current value.

You may optionally change the color of the display. See also `next!`.
"""
function update!(p::Union{Progress, ProgressUnknown}, counter::Int=p.counter, color::Symbol=p.color; options...)
    lock(p.reentrantlocker) do
        p.counter = counter
        p.color = color
        updateProgress!(p; options...)
    end
end

function update!(p::ProgressThresh, val=p.val, color::Symbol=p.color; increment::Bool = true, options...)
    lock(p.reentrantlocker) do
        p.val = val
        if increment
            p.counter += 1
        end
        p.color = color
        updateProgress!(p; options...)
    end
end

"""
`cancel(prog, [msg], [color=:red])` cancels the progress display
before all tasks were completed. Optionally you can specify the
message printed and its color.

See also `finish!`.
"""
function cancel(p::AbstractProgress, msg::AbstractString = "Aborted before all tasks were completed", color = :red; showvalues = (), truncate_lines = false, valuecolor = :blue, offset = p.offset, keep = (offset == 0))
    lock(p.reentrantlocker) do
        p.offset = offset
        if p.printed
            print(p.output, "\n" ^ (p.offset + p.numprintedvalues))
            move_cursor_up_while_clearing_lines(p.output, p.numprintedvalues)
            printover(p.output, msg, color)
            printvalues!(p, showvalues; color = valuecolor, truncate = truncate_lines)
            if keep
                println(p.output)
            else
                print(p.output, "\r\u1b[A" ^ (p.offset + p.numprintedvalues))
            end
        end
    end
    return
end

"""
`finish!(prog)` indicates that all tasks have been completed.

See also `cancel`.
"""
function finish!(p::Progress; options...)
    while p.counter < p.n
        next!(p; options...)
    end
end

function finish!(p::ProgressThresh; options...)
    update!(p, p.thresh; options...)
end

function finish!(p::ProgressUnknown; options...)
    lock(p.reentrantlocker) do
        p.done = true
        updateProgress!(p; options...)
    end
end

# Internal method to print additional values below progress bar
function printvalues!(p::AbstractProgress, showvalues; color = :normal, truncate = false)
    length(showvalues) == 0 && return
    maxwidth = maximum(Int[length(string(name)) for (name, _) in showvalues])

    p.numprintedvalues = 0

    for (name, value) in showvalues
        msg = "\n  " * rpad(string(name) * ": ", maxwidth+2+1) * string(value)
        max_len = (displaysize(p.output)::Tuple{Int,Int})[2]
        # I don't understand why the minus 1 is necessary here, but empircally
        # it is needed.
        msg_lines = ceil(Int, (length(msg)-1) / max_len)
        if truncate && msg_lines >= 2
            # For multibyte characters, need to index with nextind.
            printover(p.output, msg[1:nextind(msg, 1, max_len-1)] * "…", color)
            p.numprintedvalues += 1
        else
            printover(p.output, msg, color)
            p.numprintedvalues += msg_lines
        end
    end
    p
end

# Internal method to print additional values below progress bar (lazy-showvalues version)
printvalues!(p::AbstractProgress, showvalues::Function; kwargs...) = printvalues!(p, showvalues(); kwargs...)

function move_cursor_up_while_clearing_lines(io, numlinesup)
    if numlinesup > 0 && clear_ijulia()
        Main.IJulia.clear_output(true)
        if IJULIABEHAVIOR[] == IJuliaWarned
            @warn "ProgressMeter by default refresh meters with additional information in IJulia via `IJulia.clear_output`, which clears all outputs in the cell. \n - To prevent this behaviour, do `ProgressMeter.ijulia_behavior(:append)`. \n - To disable this warning message, do `ProgressMeter.ijulia_behavior(:clear)`."
        end
    else
        for _ in 1:numlinesup
            print(io, "\r\u1b[K\u1b[A")
        end
    end
end

function printover(io::IO, s::AbstractString, color::Symbol = :color_normal)
    print(io, "\r")
    printstyled(io, s; color=color)
    if isdefined(Main, :IJulia)
        Main.IJulia.stdio_bytes[] = 0 # issue #76: circumvent IJulia I/O throttling
    elseif isdefined(Main, :ESS) || isdefined(Main, :Atom)
    else
        print(io, "\u1b[K")     # clear the rest of the line
    end
end

function compute_front(barglyphs::BarGlyphs, frac_solid::AbstractFloat)
    barglyphs.front isa Char && return barglyphs.front
    idx = round(Int, frac_solid * (length(barglyphs.front) + 1))
    return idx > length(barglyphs.front) ? barglyphs.fill :
           idx == 0 ? barglyphs.empty :
           barglyphs.front[idx]
end

function barstring(barlen, percentage_complete; barglyphs)
    bar = ""
    if barlen>0
        if percentage_complete == 100 # if we're done, don't use the "front" character
            bar = string(barglyphs.leftend, repeat(string(barglyphs.fill), barlen), barglyphs.rightend)
        else
            n_bars = barlen * percentage_complete / 100
            nsolid = trunc(Int, n_bars)
            frac_solid = n_bars - nsolid
            nempty = barlen - nsolid - 1
            bar = string(barglyphs.leftend,
                         repeat(string(barglyphs.fill), max(0,nsolid)),
                         compute_front(barglyphs, frac_solid),
                         repeat(string(barglyphs.empty), max(0, nempty)),
                         barglyphs.rightend)
        end
    end
    bar
end

function durationstring(nsec)
    days = div(nsec, 60*60*24)
    r = nsec - 60*60*24*days
    hours = div(r,60*60)
    r = r - 60*60*hours
    minutes = div(r, 60)
    seconds = floor(r - 60*minutes)

    hhmmss = @sprintf "%u:%02u:%02u" hours minutes seconds
    if days>9
        return @sprintf "%.2f days" nsec/(60*60*24)
    elseif days>0
        return @sprintf "%u days, %s" days hhmmss
    end
    hhmmss
end

function showprogress_process_expr(node, metersym)
    if !isa(node, Expr)
        node
    elseif node.head === :break || node.head === :return
        # special handling for break and return statements
        quote
            ($finish!)($metersym)
            $node
        end
    elseif node.head === :for || node.head === :while
        # do not process inner loops
        #
        # FIXME: do not process break and return statements in inner functions
        # either
        node
    else
        # process each subexpression recursively
        Expr(node.head, [showprogress_process_expr(a, metersym) for a in node.args]...)
    end
end

struct ProgressWrapper{T}
    obj::T
    meter::Progress
end

Base.length(wrap::ProgressWrapper) = Base.length(wrap.obj)

function Base.iterate(wrap::ProgressWrapper, state...)
    ir = iterate(wrap.obj, state...)

    if ir === nothing
        finish!(wrap.meter)
    elseif !isempty(state)
        next!(wrap.meter)
    end

    ir
end

"""
Equivalent of @showprogress for a distributed for loop.
```
result = @showprogress dt "Computing..." @distributed (+) for i = 1:50
    sleep(0.1)
    i^2
end
```
"""
function showprogressdistributed(args...)
    if length(args) < 1
        throw(ArgumentError("@showprogress @distributed requires at least 1 argument"))
    end
    progressargs = args[1:end-1]
    expr = Base.remove_linenums!(args[end])

    if expr.head != :macrocall || expr.args[1] != Symbol("@distributed")
        throw(ArgumentError("malformed @showprogress @distributed expression"))
    end

    distargs = filter(x -> !(x isa LineNumberNode), expr.args[2:end])
    na = length(distargs)
    if na == 1
        loop = distargs[1]
    elseif na == 2
        reducer = distargs[1]
        loop = distargs[2]
    else
        println("$distargs $na")
        throw(ArgumentError("wrong number of arguments to @distributed"))
    end
    if loop.head !== :for
        throw(ArgumentError("malformed @distributed loop"))
    end
    var = loop.args[1].args[1]
    r = loop.args[1].args[2]
    body = loop.args[2]

    setup = quote
        n = length($(esc(r)))
        p = Progress(n, $([esc(arg) for arg in progressargs]...))
        ch = RemoteChannel(() -> Channel{Bool}(n))
    end

    if na == 1
        # would be nice to do this with @sync @distributed but @sync is broken
        # https://github.com/JuliaLang/julia/issues/28979
        compute = quote
            display = @async let i = 0
                while i < n
                    take!(ch)
                    next!(p)
                    i += 1
                end
            end
            @distributed for $(esc(var)) = $(esc(r))
                $(esc(body))
                put!(ch, true)
            end
            nothing
        end
    else
        compute = quote
            display = @async while take!(ch) next!(p) end
            results = @distributed $(esc(reducer)) for $(esc(var)) = $(esc(r))
                x = $(esc(body))
                put!(ch, true)
                x
            end
            put!(ch, false)
            results
        end
    end

    quote
        $setup
        results = $compute
        wait(display)
        results
    end
end

"""
```
@showprogress dt "Computing..." for i = 1:50
    # computation goes here
end

@showprogress dt "Computing..." pmap(x->x^2, 1:50)
```
displays progress in performing a computation. `dt` is the minimum
interval between updates to the user. You may optionally supply a
custom message to be printed that specifies the computation being
performed.

`@showprogress` works for loops, comprehensions, map, reduce, and pmap.
"""
macro showprogress(args...)
    showprogress(args...)
end

function showprogress(args...)
    if length(args) < 1
        throw(ArgumentError("@showprogress requires at least one argument."))
    end
    progressargs = args[1:end-1]
    expr = args[end]
    if expr.head == :macrocall && expr.args[1] == Symbol("@distributed")
        return showprogressdistributed(args...)
    end
    orig = expr = copy(expr)
    if expr.args[1] == :|> # e.g. map(x->x^2) |> sum
        expr.args[2] = showprogress(progressargs..., expr.args[2])
        return expr
    end
    metersym = gensym("meter")
    mapfuns = (:map, :asyncmap, :reduce, :pmap)
    kind = :invalid # :invalid, :loop, or :map

    if isa(expr, Expr)
        if expr.head == :for
            outerassignidx = 1
            loopbodyidx = lastindex(expr.args)
            kind = :loop
        elseif expr.head == :comprehension
            outerassignidx = lastindex(expr.args)
            loopbodyidx = 1
            kind = :loop
        elseif expr.head == :typed_comprehension
            outerassignidx = lastindex(expr.args)
            loopbodyidx = 2
            kind = :loop
        elseif expr.head == :call && expr.args[1] in mapfuns
            kind = :map
        elseif expr.head == :do
            call = expr.args[1]
            if call.head == :call && call.args[1] in mapfuns
                kind = :map
            end
        end
    end

    if kind == :invalid
        throw(ArgumentError("Final argument to @showprogress must be a for loop, comprehension, map, reduce, or pmap; got $expr"))
    elseif kind == :loop
        # As of julia 0.5, a comprehension's "loop" is actually one level deeper in the syntax tree.
        if expr.head !== :for
            @assert length(expr.args) == loopbodyidx
            expr = expr.args[outerassignidx] = copy(expr.args[outerassignidx])
            @assert expr.head === :generator
            outerassignidx = lastindex(expr.args)
            loopbodyidx = 1
        end

        # Transform the first loop assignment
        loopassign = expr.args[outerassignidx] = copy(expr.args[outerassignidx])
        if loopassign.head === :block # this will happen in a for loop with multiple iteration variables
            for i in 2:length(loopassign.args)
                loopassign.args[i] = esc(loopassign.args[i])
            end
            loopassign = loopassign.args[1] = copy(loopassign.args[1])
        end
        @assert loopassign.head === :(=)
        @assert length(loopassign.args) == 2
        obj = loopassign.args[2]
        loopassign.args[1] = esc(loopassign.args[1])
        loopassign.args[2] = :(ProgressWrapper(iterable, $(esc(metersym))))

        # Transform the loop body break and return statements
        if expr.head === :for
            expr.args[loopbodyidx] = showprogress_process_expr(expr.args[loopbodyidx], metersym)
        end

        # Escape all args except the loop assignment, which was already appropriately escaped.
        for i in 1:length(expr.args)
            if i != outerassignidx
                expr.args[i] = esc(expr.args[i])
            end
        end
        if orig !== expr
            # We have additional escaping to do; this will occur for comprehensions with julia 0.5 or later.
            for i in 1:length(orig.args)-1
                orig.args[i] = esc(orig.args[i])
            end
        end

        setup = quote
            iterable = $(esc(obj))
            $(esc(metersym)) = Progress(length(iterable), $([esc(arg) for arg in progressargs]...))
        end

        if expr.head === :for
            return quote
                $setup
                $expr
            end
        else
            # We're dealing with a comprehension
            return quote
                begin
                    $setup
                    rv = $orig
                    next!($(esc(metersym)))
                    rv
                end
            end
        end
    else # kind == :map

        # isolate call to map
        if expr.head == :do
            call = expr.args[1]
        else
            call = expr
        end

        # get args to map to determine progress length
        mapargs = collect(Any, filter(call.args[2:end]) do a
            return isa(a, Symbol) || !(a.head in (:kw, :parameters))
        end)
        if expr.head == :do
            insert!(mapargs, 1, :nothing) # to make args for ncalls line up
        end

        # change call to progress_map
        mapfun = call.args[1]
        call.args[1] = :progress_map

        # escape args as appropriate
        for i in 2:length(call.args)
            call.args[i] = esc(call.args[i])
        end
        if expr.head == :do
            expr.args[2] = esc(expr.args[2])
        end

        # create appropriate Progress expression
        lenex = :(ncalls($(esc(mapfun)), ($([esc(a) for a in mapargs]...),)))
        progex = :(Progress($lenex, $([esc(a) for a in progressargs]...)))

        # insert progress and mapfun kwargs
        push!(call.args, Expr(:kw, :progress, progex))
        push!(call.args, Expr(:kw, :mapfun, esc(mapfun)))

        return expr
    end
end

"""
    progress_map(f, c...; mapfun=map, progress=Progress(...), kwargs...)

Run a `map`-like function while displaying progress.

`mapfun` can be any function, but it is only tested with `map`, `reduce` and `pmap`.
"""
function progress_map(args...; mapfun=map,
                               progress=Progress(ncalls(mapfun, args)),
                               channel_bufflen=min(1000, ncalls(mapfun, args)),
                               kwargs...)
    f = first(args)
    other_args = args[2:end]
    channel = RemoteChannel(()->Channel{Bool}(channel_bufflen), 1)
    local vals
    @sync begin
        # display task
        @async while take!(channel)
            next!(progress)
        end

        # map task
        @sync begin
            vals = mapfun(other_args...; kwargs...) do x...
                val = f(x...)
                put!(channel, true)
                yield()
                return val
            end
            put!(channel, false)
        end
    end
    return vals
end

"""
    progress_pmap(f, [::AbstractWorkerPool], c...; progress=Progress(...), kwargs...)

Run `pmap` while displaying progress.
"""
progress_pmap(args...; kwargs...) = progress_map(args...; mapfun=pmap, kwargs...)

"""
Infer the number of calls to the mapped function (i.e. the length of the returned array) given the input arguments to map, reduce or pmap.
"""
function ncalls(mapfun::Function, map_args)
    if mapfun == pmap && length(map_args) >= 2 && isa(map_args[2], AbstractWorkerPool)
        relevant = map_args[3:end]
    else
        relevant = map_args[2:end]
    end
    if isempty(relevant)
        error("Unable to determine number of calls in $mapfun. Too few arguments?")
    else
        return maximum(length(arg) for arg in relevant)
    end
end

end


text:
using Random
import Random: rand, srand

const RAND_MAX = typemax(Int32)

struct LibcRNG <: AbstractRNG end

srand(r::LibcRNG, seed::Integer) = ccall(:srand, Cvoid, (Cuint,), seed)
rand(r::LibcRNG, ::Type{Cint})   = ccall(:rand,  Cint, ())
rand(r::LibcRNG)                 = rand(r, Cint)

struct LibcRNG48 <: AbstractRNG end

srand(r::LibcRNG48, seed::Integer) = ccall(:srand48, Cvoid, (Clong,), seed)
rand(r::LibcRNG48, ::Type{Culong}) = ccall(:lrand48, Cint, ())
rand(r::LibcRNG48, ::Type{Clong})  = ccall(:mrand48, Cint, ())

ext: jl


text:
module VortexHelperVortexCustomBumper
using ..Ahorn, Maple

styles_options = String["Green", "Orange"]

@mapdef Entity "VortexHelper/VortexCustomBumper" VortexCustomBumper(
									x::Integer, y::Integer,
									style::String="Green",
									nodes::Array{Tuple{Integer, Integer}, 1}=Tuple{Integer, Integer}[],
									notCoreMore::Bool=false, wobble::Bool=true,
                                    sprite::String="")

const placements = Ahorn.PlacementDict(
    "Bumper ($style) (Vortex Helper)" => Ahorn.EntityPlacement(
        VortexCustomBumper,
		"point",
		Dict{String, Any}(
            "style" => style
        )
    ) for style in styles_options
)

Ahorn.editingOptions(entity::VortexCustomBumper) = Dict{String, Any}(
    "style" => styles_options
)

Ahorn.nodeLimits(entity::VortexCustomBumper) = 0, 1

function Ahorn.selection(entity::VortexCustomBumper)
    x, y = Ahorn.position(entity)
	rect = Ahorn.Rectangle(x - 11, y - 11, 22, 22)

	nodes = get(entity.data, "nodes", ())

	if !isempty(nodes)
		nx, ny = Int.(nodes[1])

		return [rect, Ahorn.Rectangle(nx - 11, ny - 11, 22, 22)]
	end

	return rect
end

function getSprite(entity::VortexCustomBumper)
	style = lowercase(get(entity, "style", "Green"))
	return "objects/VortexHelper/vortexCustomBumper/" * style * "22"
end

function Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::VortexCustomBumper, room::Maple.Room)
    Ahorn.drawSprite(ctx, getSprite(entity), 0, 0)
end

function Ahorn.renderSelectedAbs(ctx::Ahorn.Cairo.CairoContext, entity::VortexCustomBumper, room::Maple.Room)
	startX, startY = Int(entity.data["x"]), Int(entity.data["y"])
	nodes = get(entity.data, "nodes", ())

	if !isempty(nodes)
		nx, ny = Int.(nodes[1])

		Ahorn.drawSprite(ctx, getSprite(entity), nx, ny)
		Ahorn.drawArrow(ctx, startX, startY, nx, ny, Ahorn.colors.selection_selected_fc, headLength=6)
	end

end

end

max_stars_repo_name: catapillie/VortexHelper

max_issues_repo_name: catapillie/VortexHelper

max_forks_repo_name: catapillie/VortexHelper


text:
"""Linear interpolation of a variable u in the x-direction.
m,n = size(ux) must be m+1,n = size(u)."""
function Ix!(ux::Array{T,2},u::Array{T,2}) where {T<:AbstractFloat}
    m, n = size(ux)
    @boundscheck (m+1,n) == size(u) || throw(BoundsError())

    one_half = T(0.5)

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            ux[i,j] = one_half*(u[i+1,j] + u[i,j])
        end
    end
end

""" Linear interpolation a variable u in the y-direction.
    m,n = size(uy) must be m,n+1 = size(u)."""
function Iy!(uy::Array{T,2},u::Array{T,2}) where {T<:AbstractFloat}
    m,n = size(uy)
    @boundscheck (m,n+1) == size(u) || throw(BoundsError())

    one_half = T(0.5)

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            uy[i,j] = one_half*(u[i,j+1] + u[i,j])
        end
    end
end

""" Bilinear interpolation a variable u in x and y-direction.
m,n = size(uxy) must be m+1,n+1 = size(u). """
function Ixy!(uxy::Array{T,2},u::Array{T,2}) where {T<:AbstractFloat}
    m,n = size(uxy)
    @boundscheck (m+1,n+1) == size(u) || throw(BoundsError())

    one_quarter = T(0.25)

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            uxy[i,j] = one_quarter*(u[i,j] + u[i+1,j] + u[i,j+1] + u[i+1,j+1])
        end
    end
end

function Ix(u::Array{T,2}) where {T<:AbstractFloat}

    m,n = size(u)
    ux = Array{T,2}(undef,m-1,n)

    one_half = T(0.5)

    @inbounds for j ∈ 1:n
        for i ∈ 1:m-1
            ux[i,j] = one_half*(u[i+1,j] + u[i,j])
        end
    end

    return ux
end

function Iy(u::Array{T,2}) where {T<:AbstractFloat}

    m,n = size(u)
    uy = Array{T,2}(undef,m,n-1)

    one_half = T(0.5)

    @inbounds for j ∈ 1:n-1
        for i ∈ 1:m
            uy[i,j] = one_half*(u[i,j+1] + u[i,j])
        end
    end

    return uy
end


text:
# ViewDBI

module ViewDBI

import Base: show, contains, hash
import Base.Meta.isexpr
import ODBC
import DataFrames

export ViewODBC, View, has_one, has_many, many_to_many, contains, resolve

abstract ViewDB

type ViewODBC <: ViewDB
    db::ODBC.Connection
    schema::Symbol
end

show(io::IO, kodbc::ViewODBC) = print(io, "ViewODBC(",kodbc.schema,")")

ViewODBC(dsn::String) = (ODBC.connect(dsn); ViewODBC(ODBC.conn, symbol(ODBC.conn.dsn)))
disconnect(vdb::ViewODBC) = ODBC.disconnect(vdb.db)

function table_info(vdb::ViewODBC, table)
    colnames = ODBC.query("""select COLUMN_NAME,COLUMN_TYPE,IS_NULLABLE,COLUMN_DEFAULT
                             from INFORMATION_SCHEMA.COLUMNS ISC
                            where TABLE_SCHEMA=\"$(vdb.schema)\" and TABLE_NAME=\"$table\"
                         """, vdb.db)

    colnames[1] = [symbol(x)::Symbol for x in colnames[1]]

    keyinfo = ODBC.query("""select TABLE_SCHEMA,TABLE_NAME,CONSTRAINT_NAME,COLUMN_NAME,REFERENCED_TABLE_SCHEMA,REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME
                            from INFORMATION_SCHEMA.KEY_COLUMN_USAGE ISC
                            where (TABLE_SCHEMA=\"$(vdb.schema)\" and TABLE_NAME=\"$table\")
                         """, vdb.db)

    for i = 1:DataFrames.ncol(keyinfo)
        keyinfo[i] = [symbol(x)::Symbol for x in keyinfo[i]]
    end

    return (colnames, keyinfo)
    # rkeyinfo = ODBC.query("""select TABLE_SCHEMA,TABLE_NAME,CONSTRAINT_NAME,COLUMN_NAME,REFERENCED_TABLE_SCHEMA,REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME
    #                          from INFORMATION_SCHEMA.KEY_COLUMN_USAGE ISC
    #                          where (REFERENCED_TABLE_SCHEMA=\"$(vdb.schema)\" and REFERENCED_TABLE_NAME=\"$table\")
    #                       """, vdb.db)
    # return(colnames, keyinfo, rkeyinfo)

end

typealias Key Union(Symbol, Vector{Symbol})
contains(s::Vector{Symbol}, k::Vector{Symbol}) = issubset(k, s)

abstract DbView

type TableView <: DbView
    name::Symbol
    database::ViewDB
    schema::Symbol
    table::Symbol
    fields::Vector{Symbol}
    pk::Key
    prepare_fn#::Function
    transform_fn#::Function
    has_one::Dict{TableView,(Key,Key)}
    has_many::Dict{TableView,(Key,Key)}
    belongs_to::Dict{TableView,(Key,Key)}
    many_to_many::Dict{TableView,(TableView,Key,Key,Key,Key)}

    cols
    keyinfo
    #rkeyinfo
end

const VIEW_HASH=hash(TableView)

hash(en::TableView) = bitmix(VIEW_HASH, hash(en.name))

typealias ViewRelation Dict{TableView,(Key,Key)}

function TableView(name::Symbol, vdb::ViewDB; schema=vdb.schema, table=name, fields=Symbol[], args...)
    table = symbol(table)

    # TODO: handle case where db doesn't yet exist in vdb

    cols, keyinfo = table_info(vdb, table)

    if isempty(fields)
        fields = [symbol(x)::Symbol for x in cols["COLUMN_NAME"]]
    end

    pk = [symbol(x)::Symbol for x in keyinfo[keyinfo["CONSTRAINT_NAME"] .== :PRIMARY, "COLUMN_NAME"]]

    TableView(name, vdb, schema, table, fields, pk,
           nothing, nothing,
           (TableView=>(Key,Key))[],
           (TableView=>(Key,Key))[],
           (TableView=>(Key,Key))[],
           (TableView=>(TableView,Key,Key,Key,Key))[],
           cols, keyinfo)
end

function show(io::IO, v::TableView)
    print(io, "TableView(")
    print(io, v.name)
    print(io, ", ", v.database)
    print(io, ", ", v.table)
    print(io, ", ", "fields=", v.fields)
    print(io, ", ", "pk=", v.pk)
    print(io, ", ", "prepare_fn=", v.prepare_fn == nothing ? "nothing" : v.prepare_fn.env.name)
    print(io, ", ", "transform_fn=", v.transform_fn == nothing ? "nothing" : v.transform_fn.env.name)
    print(io, ", ", "has_one=",{x.name for x in keys(v.has_one)})
    print(io, ", ", "has_many=",{x.name for x in keys(v.has_many)})
    print(io, ", ", "belongs_to=",{x.name for x in keys(v.belongs_to)})
    print(io, ", ", "many_to_many=",{x.name for x in keys(v.many_to_many)}, ")")
end

function get_joinkey(main::TableView, other::TableView, mainkey::Key=main.pk)
    if contains(other.fields, mainkey);  return mainkey;  end
    for testkey in [join([main.table, :id], ""), join([main.table, :id], "_")]
        if contains(other.fields, testkey)
            return testkey
        end
    end
    error("get_joinkey: no key found to join Entities")
end

function linkup(main::TableView, relmain::ViewRelation,
                sub::TableView, relsub::ViewRelation;
                mainkey=main.pk, kwargs...)
    k = find(x->x[1] == :key, kwargs)
    key = isempty(k) ? get_joinkey(main, sub, mainkey) : kwargs[k[1]][2]

    @assert(contains(main.fields, mainkey))
    @assert(contains(sub.fields, key))
    @assert(typeof(mainkey) == typeof(key))
    @assert(!isa(mainkey, Vector) || length(mainkey) == length(key))

    relmain[sub] = (mainkey, key)
    relsub[main] = (key, mainkey)

    nothing
end

has_one(main::TableView, sub::TableView; mainkey=main.pk, kwargs...) =
    linkup(main, main.has_one, sub, sub.belongs_to; mainkey=mainkey, kwargs...)

has_many(main::TableView, sub::TableView; mainkey=main.pk, kwargs...) =
    linkup(main, main.has_many, sub, sub.belongs_to; mainkey=mainkey, kwargs...)

function many_to_many(ent1::TableView, ent2::TableView, joinent::TableView; entkey1=ent1.pk, entkey2=ent2.pk, kwargs...)
    k = find(x->x[1] == :joinkey1, kwargs)
    joinkey1 = isempty(k) ? get_joinkey(ent1, joinent, entkey1) : kwargs[k[1]][2]
    k = find(x->x[1] == :joinkey2, kwargs)
    joinkey2 = isempty(k) ? get_joinkey(ent2, joinent, entkey2) : kwargs[k[1]][2]

    @assert(contains(ent1.fields, entkey1))
    @assert(contains(ent2.fields, entkey2))
    @assert(contains(joinent.fields, joinkey1))
    @assert(contains(joinent.fields, joinkey2))
    @assert(!isa(entkey1, Vector) || length(entkey1) == length(joinkey1))
    @assert(!isa(entkey2, Vector) || length(entkey2) == length(joinkey2))

    ent1.many_to_many[entkey1] = (joinent, joinkey1, joinkey2, ent2, entkey2)
    ent2.many_to_many[entkey2] = (joinent, joinkey2, joinkey1, ent1, entkey1)

    nothing
end

function resolve(ent1::TableView, ent2::TableView)
    ref1 = ent1.keyinfo[((ent1.keyinfo["REFERENCED_TABLE_SCHEMA"] .== ent2.schema) &
                         (ent1.keyinfo["REFERENCED_TABLE_NAME"] .== ent2.table)),:]

    for r in DataFrames.EachRow(ref1)
        ent1.belongs_to[ent2] = (r[1,"COLUMN_NAME"], r[1,"REFERENCED_COLUMN_NAME"])
        ent2.has_many[ent1] = (r[1,"REFERENCED_COLUMN_NAME"], r[1,"COLUMN_NAME"])
    end

    ref2 = ent2.keyinfo[((ent2.keyinfo["REFERENCED_TABLE_SCHEMA"] .== ent1.schema) &
                         (ent2.keyinfo["REFERENCED_TABLE_NAME"] .== ent1.table)),:]

    for r in DataFrames.EachRow(ref2)
        ent2.belongs_to[ent1] = (r[1,"COLUMN_NAME"], r[1,"REFERENCED_COLUMN_NAME"])
        ent1.has_many[ent2] = (r[1,"REFERENCED_COLUMN_NAME"], r[1,"COLUMN_NAME"])
    end

    nothing
end

end


text:
# Autogenerated wrapper script for Ogg_jll for x86_64-w64-mingw32
export libogg

JLLWrappers.@generate_wrapper_header("Ogg")
JLLWrappers.@declare_library_product(libogg, "libogg-0.dll")
function __init__()
    JLLWrappers.@generate_init_header()
    JLLWrappers.@init_library_product(
        libogg,
        "bin\\libogg-0.dll",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@generate_init_footer()
end  # __init__()


text:
# Use baremodule to shave off a few KB from the serialized `.ji` file
baremodule Graphviz_jll
using Base
using Base: UUID
import JLLWrappers

JLLWrappers.@generate_main_file_header("Graphviz")
JLLWrappers.@generate_main_file("Graphviz", UUID("3c863552-8265-54e4-a6dc-903eb78fde85"))
end  # module Graphviz_jll


text:
using MPI
using Test

using Compat
import Compat.String
import Compat.Sys: BINDIR

# Code coverage command line options; must correspond to src/julia.h
# and src/ui/repl.c
const JL_LOG_NONE = 0
const JL_LOG_USER = 1
const JL_LOG_ALL = 2
const coverage_opts =
    Dict{Int, String}(JL_LOG_NONE => "none",
                      JL_LOG_USER => "user",
                      JL_LOG_ALL => "all")

# Files to run without mpiexec
juliafiles = ["test_cman_julia.jl"]
# Files to run with mpiexec -n 1
singlefiles = ["test_spawn.jl"]

excludedfiles = []
if Sys.iswindows()
    push!(excludedfiles, "test_info.jl")
    push!(excludedfiles, "test_onesided.jl")
    push!(excludedfiles, "test_finalize_atexit.jl")
    if Sys.WORD_SIZE == 32
        push!(excludedfiles, "test_spawn.jl")
    end
end

function runtests()
    nprocs = clamp(Sys.CPU_THREADS, 2, 4)
    exename = joinpath(BINDIR, Base.julia_exename())
    testdir = dirname(@__FILE__)
    istest(f) = endswith(f, ".jl") && startswith(f, "test_")
    testfiles = sort(filter(istest, readdir(testdir)))

    extra_args = []
    @static if !Compat.Sys.iswindows()
      if Compat.occursin( "OpenRTE", Compat.open(f->read(f, String),`mpiexec --version`))
            push!(extra_args,"--oversubscribe")
        end
    end

    nfail = 0
    Compat.printstyled("Running MPI.jl tests\n"; color=:white)
    for f in testfiles
        if f ∈ excludedfiles
            println("Skipping disabled test $f")
            continue
        end
        try
            coverage_opt = coverage_opts[Base.JLOptions().code_coverage]
            if f ∈ singlefiles
                run(`mpiexec $extra_args -n 1 $exename --code-coverage=$coverage_opt $(joinpath(testdir, f))`)
            elseif f ∈ juliafiles
                run(`$exename --code-coverage=$coverage_opt $(joinpath(testdir, f))`)
            else
                run(`mpiexec $extra_args -n $nprocs $exename --code-coverage=$coverage_opt $(joinpath(testdir, f))`)
            end
            Base.with_output_color(:green,stdout) do io
                println(io,"\tSUCCESS: $f")
            end
        catch ex
            Base.with_output_color(:red,stderr) do io
                println(io,"\tError: $(joinpath(testdir, f))")
                showerror(io,ex,backtrace())
            end
            nfail += 1
        end
    end
    return nfail
end

exit(runtests())


text:
"""
    nonherm(nbOne::Ti, size::Ti, diag_l::Ti, diag_u::Ti, spectrum::AbstractVector{Tv}) where {Tv<:Complex, Ti<:Integer}

Generate a non hermitian matrix with default nilpotent matrix and default initialization of matrix.

## Examples
```julia
julia> spec=[1+im, 2, 4, 6, 7-2im, 4+3im, 7, 8, 9, 10.2-im]
10-element Vector{ComplexF64}:
  1.0 + 1.0im
  2.0 + 0.0im
  4.0 + 0.0im
  6.0 + 0.0im
  7.0 - 2.0im
  4.0 + 3.0im
  7.0 + 0.0im
  8.0 + 0.0im
  9.0 + 0.0im
 10.2 - 1.0im

julia> nonherm(3, 10, -8, -2, spec)
10×10 SparseMatrixCSC{ComplexF64, Int64} with 66 stored entries:
   1.04657+1.0im    -0.4959+0.5im  0.0833701+0.0833333im  0.00347223+0.00347222im            ⋅                ⋅               ⋅                     ⋅                ⋅           ⋅
 -0.231523+0.0im     1.9567+0.0im   -1.00012+0.0im        3.02285e-5+0.0im                   ⋅                ⋅               ⋅                     ⋅                ⋅           ⋅
  0.207446+0.0im  -0.115496+0.0im    3.94688+0.0im          -1.00394+0.0im                   ⋅                ⋅               ⋅                     ⋅                ⋅           ⋅
  0.766797+0.0im   0.949636+0.0im   0.347018+0.0im           6.04985+0.0im                   ⋅                ⋅               ⋅                     ⋅                ⋅           ⋅
  0.413475+0.0im   0.847118+0.0im    1.11204+0.0im          0.240384+0.0im           7.05096-2.0im    1.50334-2.5im  0.500002-0.666667im  0.0277777-0.0381944im      ⋅           ⋅
 -0.114481+0.0im   0.314934+0.0im   0.463301+0.0im          0.257056+0.0im         -0.253119+0.0im     3.9802+3.0im  -1.49669+1.5im       -0.166576+0.25im           ⋅           ⋅
  0.592639+0.0im   0.167683+0.0im  -0.318934+0.0im          0.847507+0.0im          0.225334+0.0im  -0.292356+0.0im    6.8867+0.0im       -0.507753+0.0im            ⋅           ⋅
  0.112486+0.0im   0.332264+0.0im   0.799049+0.0im          0.349224+0.0im          0.842714+0.0im    1.37186+0.0im  0.545475+0.0im         8.08213+0.0im            ⋅           ⋅
   0.96467+0.0im    1.36421+0.0im    1.13479+0.0im          0.769023+0.0im         -0.219469+0.0im  -0.196588+0.0im   0.26921+0.0im        0.154841+0.0im        9.0+0.0im  -0.6+0.5im
           ⋅        0.12781+0.0im   0.438092+0.0im           1.12886+0.0im          0.438939+0.0im   0.466332+0.0im  0.160009+0.0im        0.022096+0.0im            ⋅      10.2-1.0im
```

"""
function nonherm(
    nbOne::Ti,
    size::Ti,
    diag_l::Ti,
    diag_u::Ti,
    spectrum::AbstractVector{Tv}
) where {Tv<:Complex, Ti<:Integer}

    Am = spzeros(Tv, size, size)
    initMat!(Am, diag_l, diag_u, size)

    nilp = Nilp(nbOne, size)
    return nonherm(size, diag_l, diag_u, spectrum, Am, nilp)

end

"""
    nonherm(nbOne::Ti, size::Ti, diag_l::Ti, diag_u::Ti, spectrum::AbstractVector{Tv}, Am::SparseMatrixCSC{Tv, Ti}) where {Tv<:Complex, Ti<:Integer}

Generate a non hermitian matrix with default nilpotent matrix and user-provided initialization of matrix.

## Examples
```julia
julia> spec=[1+im, 2, 4, 6, 7-2im, 4+3im, 7, 8, 9, 10.2-im]
10-element Vector{ComplexF64}:
  1.0 + 1.0im
  2.0 + 0.0im
  4.0 + 0.0im
  6.0 + 0.0im
  7.0 - 2.0im
  4.0 + 3.0im
  7.0 + 0.0im
  8.0 + 0.0im
  9.0 + 0.0im
 10.2 - 1.0im

julia> Am = spzeros(ComplexF64, 10, 10);
julia> initMat!(Am, -8, -2, 10; scale=0.1, sparsity=0.005)
julia> nonherm(3, 10, -8, -2, spec, Am)
10×10 SparseMatrixCSC{ComplexF64, Int64} with 22 stored entries:
 1.0+1.0im  -0.5+0.5im  0.0833333+0.0833333im  0.00347222+0.00347222im      ⋅          ⋅           ⋅                     ⋅                ⋅           ⋅
     ⋅       2.0+0.0im       -1.0+0.0im                   ⋅                 ⋅          ⋅           ⋅                     ⋅                ⋅           ⋅
     ⋅           ⋅            4.0+0.0im              -1.0+0.0im             ⋅          ⋅           ⋅                     ⋅                ⋅           ⋅
     ⋅           ⋅                ⋅                   6.0+0.0im             ⋅          ⋅           ⋅                     ⋅                ⋅           ⋅
     ⋅           ⋅                ⋅                       ⋅             7.0-2.0im  1.5-2.5im   0.5-0.666667im  0.0277778-0.0381944im      ⋅           ⋅
     ⋅           ⋅                ⋅                       ⋅                 ⋅      4.0+3.0im  -1.5+1.5im       -0.166667+0.25im           ⋅           ⋅
     ⋅           ⋅                ⋅                       ⋅                 ⋅          ⋅       7.0+0.0im            -0.5+0.0im            ⋅           ⋅
     ⋅           ⋅                ⋅                       ⋅                 ⋅          ⋅           ⋅                 8.0+0.0im            ⋅           ⋅
     ⋅           ⋅                ⋅                       ⋅                 ⋅          ⋅           ⋅                     ⋅            9.0+0.0im  -0.6+0.5im
     ⋅           ⋅                ⋅                       ⋅                 ⋅          ⋅           ⋅                     ⋅                ⋅      10.2-1.0im
```

"""
function nonherm(
    nbOne::Ti,
    size::Ti,
    diag_l::Ti,
    diag_u::Ti,
    spectrum::AbstractVector{Tv},
    Am::SparseMatrixCSC{Tv, Ti}
) where {Tv<:Complex, Ti<:Integer}

    Tv2 = eltype(real(spectrum[1]))

    nilp = Nilp(nbOne, size)

    return nonherm(size, diag_l, diag_u, spectrum, Am, nilp)

end

"""
    nonherm(size::Ti, diag_l::Ti, diag_u::Ti, spectrum::AbstractVector{Tv}, Am::SparseMatrixCSC{Tv, Ti}, nilp::Nilpotent{Ti}) where {Tv<:Complex, Ti<:Integer}

Generate a non hermitian matrix with user-provided nilpotent matrix and initialization of matrix.

## Examples
```julia
julia> spec=[1+im, 2, 4, 6, 7-2im, 4+3im, 7, 8, 9, 10.2-im]
10-element Vector{ComplexF64}:
  1.0 + 1.0im
  2.0 + 0.0im
  4.0 + 0.0im
  6.0 + 0.0im
  7.0 - 2.0im
  4.0 + 3.0im
  7.0 + 0.0im
  8.0 + 0.0im
  9.0 + 0.0im
 10.2 - 1.0im

julia> Am = spzeros(ComplexF64, 10, 10);
julia> initMat!(Am, -8, -2, 10; scale=0.1, sparsity=0.005)
julia> nilp=SMG2S.Nilp(4, 3, 10)
┌ Info: the degree of given nilpotent matrix is:
└   degree = 2
Nilpotent{Int64}(1, 10, 2,
 ⋅  ⋅  ⋅  1  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅
 ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅
 ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅
 ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅
 ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  1  ⋅  ⋅
 ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅
 ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  1
 ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅
 ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅
 ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅)

julia> nonherm(10, -8, -2, spec, Am, nilp)
10×10 SparseMatrixCSC{ComplexF64, Int64} with 13 stored entries:
 1.0+1.0im      ⋅          ⋅      -2.5+0.5im      ⋅          ⋅          ⋅           ⋅          ⋅           ⋅
     ⋅      2.0+0.0im      ⋅           ⋅          ⋅          ⋅          ⋅           ⋅          ⋅           ⋅
     ⋅          ⋅      4.0+0.0im       ⋅          ⋅          ⋅          ⋅           ⋅          ⋅           ⋅
     ⋅          ⋅          ⋅       6.0+0.0im      ⋅          ⋅          ⋅           ⋅          ⋅           ⋅
     ⋅          ⋅          ⋅           ⋅      7.0-2.0im      ⋅          ⋅      -0.5-1.0im      ⋅           ⋅
     ⋅          ⋅          ⋅           ⋅          ⋅      4.0+3.0im      ⋅           ⋅          ⋅           ⋅
     ⋅          ⋅          ⋅           ⋅          ⋅          ⋅      7.0+0.0im       ⋅          ⋅      -1.6+0.5im
     ⋅          ⋅          ⋅           ⋅          ⋅          ⋅          ⋅       8.0+0.0im      ⋅           ⋅
     ⋅          ⋅          ⋅           ⋅          ⋅          ⋅          ⋅           ⋅      9.0+0.0im       ⋅
     ⋅          ⋅          ⋅           ⋅          ⋅          ⋅          ⋅           ⋅          ⋅      10.2-1.0im
```
"""
function nonherm(
    size::Ti,
    diag_l::Ti,
    diag_u::Ti,
    spectrum::AbstractVector{Tv},
    Am::SparseMatrixCSC{Tv, Ti},
    nilp::Nilpotent{Ti}
) where {Tv<:Complex, Ti<:Integer}

    Tv2 = eltype(real(spectrum[1]))
    #nbOne = nilp.nbOne
    for i = 1:size
        Am[i, i] = spectrum[i]
    end

    matAop = Am

    fact = 1

    for k = 1 : 2 * (nilp.degree-1)
        nilpMat = NilpMat(nilp)
        matAop = matAop * nilpMat - nilpMat * matAop
        fact /= factorial(k,k)
        Am += fact * matAop
    end
    return Am
end


text:
struct NumberOfObjectives <: MOI.AbstractModelAttribute end

function MOI.set(model::Optimizer, ::NumberOfObjectives, n::Integer)
    set_intattr!(model.inner, "NumObj", n)
    return
end

function MOI.get(model::Optimizer, ::NumberOfObjectives)
    return get_intattr(model.inner, "NumObj")
end

struct MultiObjectiveFunction <: MOI.AbstractModelAttribute
    index::Int
end

function c_api_setobjectiven(
    model,
    index::Cint,
    priority::Cint,
    weight::Cdouble,
    abstol::Cdouble,
    reltol::Cdouble,
    name::String,
    constant::Cdouble,
    lnz::Cint,
    lind::Vector{Cint},
    lval::Vector{Cdouble}
)
    ret = @grb_ccall(
        setobjectiven,
        Cint,
        (
            Ptr{Cvoid}, Cint, Cint, Cdouble, Cdouble, Cdouble, Ptr{Cchar},
            Cdouble, Cint, Ptr{Cint}, Ptr{Cdouble}
        ),
        model, index, priority, weight, abstol, reltol, name, constant, lnz, lind, lval
    )
    if ret != 0
        throw(GurobiError(model.env, ret))
    end
end

function MOI.set(
    model::Optimizer,
    attr::MultiObjectiveFunction,
    f::MOI.ScalarAffineFunction
)
    num_vars = length(model.variable_info)
    obj = zeros(Float64, num_vars)
    for term in f.terms
        column = _info(model, term.variable_index).column
        obj[column] += term.coefficient
    end
    indices, coefficients = _indices_and_coefficients(model, f)
    _update_if_necessary(model)
    c_api_setobjectiven(
        model.inner,
        Cint(attr.index - 1),
        Cint(0),
        1.0,
        0.0,
        0.0,
        "",
        f.constant,
        Cint(length(indices)),
        Cint.(indices) .- Cint(1),
        coefficients
    )
    _require_update(model)
    return
end

struct MultiObjectivePriority <: MOI.AbstractModelAttribute
    index::Int
end

function MOI.set(
    model::Gurobi.Optimizer, attr::MultiObjectivePriority, priority::Int
)
    set_int_param!(model.inner, "ObjNumber", attr.index - 1)
    set_intattr!(model.inner, "ObjNPriority", priority)
    _require_update(model)
    return
end

function MOI.get(
    model::Gurobi.Optimizer, attr::MultiObjectivePriority
)
    _update_if_necessary(model)
    set_int_param!(model.inner, "ObjNumber", attr.index - 1)
    return get_intattr(model.inner, "ObjNPriority")
end

struct MultiObjectiveWeight <: MOI.AbstractModelAttribute
    index::Int
end

function MOI.set(
    model::Gurobi.Optimizer, attr::MultiObjectiveWeight, weight::Float64
)
    set_int_param!(model.inner, "ObjNumber", attr.index - 1)
    set_dblattr!(model.inner, "ObjNWeight", weight)
    _require_update(model)
    return
end

function MOI.get(
    model::Gurobi.Optimizer, attr::MultiObjectiveWeight
)
    _update_if_necessary(model)
    set_int_param!(model.inner, "ObjNumber", attr.index - 1)
    return get_dblattr(model.inner, "ObjNWeight")
end

struct MultiObjectiveValue <: MOI.AbstractModelAttribute
    index::Int
end

function MOI.get(
    model::Gurobi.Optimizer, attr::MultiObjectiveValue
)
    set_int_param!(model.inner, "ObjNumber", attr.index - 1)
    return get_dblattr(model.inner, "ObjNVal")
end


text:
#  Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors
#  This Source Code Form is subject to the terms of the Mozilla Public
#  License, v. 2.0. If a copy of the MPL was not distributed with this
#  file, You can obtain one at http://mozilla.org/MPL/2.0/.
#############################################################################
# JuMP
# An algebraic modeling language for Julia
# See http://github.com/jump-dev/JuMP.jl
#############################################################################
# test/variable.jl
# Testing for VariableRef
#############################################################################

using JuMP
using EMP

import LinearAlgebra: Symmetric
using Test

include(joinpath(@__DIR__, "utilities.jl"))
#@static if !(:JuMPExtension in names(Main))
#    include("JuMPExtension.jl")
#end

VariableRefType = JuMP.VariableRef

function test_variable_name(variable, name)
    @test name == @inferred JuMP.name(variable)
    @test variable == JuMP.variable_by_name(JuMP.owner_model(variable), name)
end

# Slices three-dimensional DenseAxisArray x[I,J,K]
# I,J,K can be singletons, ranges, colons, etc.
function _sliceof_util(VariableRefType, x, I, J, K)
    y = Array{VariableRefType}(undef, length(I), length(J), length(K))

    ii = 1
    jj = 1
    kk = 1
    for i in I
        for j in J
            for k in K
                y[ii,jj,kk] = x[i,j,k]
                kk += 1
            end
            jj += 1
            kk = 1
        end
        ii += 1
        jj = 1
    end
    idx = [length(I)==1, length(J)==1, length(K)==1]
    dropdims(y, dims=tuple(findall(idx)...))
end

function test_variable_no_bound(ModelType, VariableRefType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, nobounds)
    @test !JuMP.has_lower_bound(nobounds)
    @test !JuMP.has_upper_bound(nobounds)
    @test !JuMP.is_fixed(nobounds)
    test_variable_name(nobounds, "nobounds")
    @test zero(nobounds) isa JuMP.GenericAffExpr{Float64, VariableRefType}
    @test one(nobounds) isa JuMP.GenericAffExpr{Float64, VariableRefType}
end

function test_variable_lower_bound_rhs(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, lbonly >= 0, Bin)
    @test JuMP.has_lower_bound(lbonly)
    @test 0.0 == @inferred JuMP.lower_bound(lbonly)
    @test !JuMP.is_fixed(lbonly)
    @test JuMP.is_binary(lbonly)
    @test !JuMP.is_integer(lbonly)
    @test isequal(mp[:lbonly], lbonly)
    JuMP.delete_lower_bound(lbonly)
    @test !JuMP.has_lower_bound(lbonly)
    # Name already used
    @test_throws ErrorException @variable(mp, lbonly)
end

function test_variable_lower_bound_lhs(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, 0 <= lblhs, Bin)
    @test JuMP.has_lower_bound(lblhs)
    @test 0.0 == @inferred JuMP.lower_bound(lblhs)
    @test !JuMP.is_fixed(lblhs)
    @test JuMP.is_binary(lblhs)
    @test !JuMP.is_integer(lblhs)
    @test isequal(mp[:lblhs], lblhs)
end

function test_variable_upper_bound_rhs(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, ubonly <= 1, Int)
    @test !JuMP.has_lower_bound(ubonly)
    @test JuMP.has_upper_bound(ubonly)
    @test 1.0 == @inferred JuMP.upper_bound(ubonly)
    @test !JuMP.is_fixed(ubonly)
    @test !JuMP.is_binary(ubonly)
    @test JuMP.is_integer(ubonly)
    @test isequal(mp[:ubonly], ubonly)
    JuMP.delete_upper_bound(ubonly)
    @test !JuMP.has_upper_bound(ubonly)
end

function test_variable_upper_bound_lhs(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, 1 >= ublhs, Int)
    @test !JuMP.has_lower_bound(ublhs)
    @test JuMP.has_upper_bound(ublhs)
    @test 1.0 == @inferred JuMP.upper_bound(ublhs)
    @test !JuMP.is_fixed(ublhs)
    @test !JuMP.is_binary(ublhs)
    @test JuMP.is_integer(ublhs)
    @test isequal(mp[:ublhs],ublhs)
end

function test_variable_interval(ModelType)
    function has_bounds(var, lb, ub)
        @test JuMP.has_lower_bound(var)
        @test lb == @inferred JuMP.lower_bound(var)
        @test JuMP.has_upper_bound(var)
        @test ub == @inferred JuMP.upper_bound(var)
        @test !JuMP.is_fixed(var)
    end
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, 0 <= bothb1 <= 1)
    has_bounds(bothb1, 0.0, 1.0)
    @variable(mp, 0 ≤  bothb2 ≤  1)
    has_bounds(bothb2, 0.0, 1.0)
    @variable(mp, 1 >= bothb3 >= 0)
    has_bounds(bothb3, 0.0, 1.0)
    @variable(mp, 1 ≥  bothb4 ≥  0)
    has_bounds(bothb4, 0.0, 1.0)
    @test_macro_throws ErrorException @variable(mp, 1 ≥ bothb5 ≤ 0)
    @test_macro_throws ErrorException @variable(mp, 1 ≤ bothb6 ≥ 0)
end

function test_variable_fix(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, fixed == 1.0)
    @test !JuMP.has_lower_bound(fixed)
    @test !JuMP.has_upper_bound(fixed)
    @test JuMP.is_fixed(fixed)
    @test 1.0 == @inferred JuMP.fix_value(fixed)
    JuMP.unfix(fixed)
    @test !JuMP.is_fixed(fixed)
    JuMP.set_lower_bound(fixed, 0.0)
    @test_throws Exception JuMP.fix(fixed, 1.0)
    JuMP.fix(fixed, 1.0; force = true)
    @test !JuMP.has_lower_bound(fixed)
    @test !JuMP.has_upper_bound(fixed)
    @test JuMP.is_fixed(fixed)
    @test 1.0 == @inferred JuMP.fix_value(fixed)
end

function test_variable_custom_index_sets(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, onerangeub[-7:1] <= 10, Int)
    @variable(mp, manyrangelb[0:1, 10:20, 1:1] >= 2)
    @test JuMP.has_lower_bound(manyrangelb[0, 15, 1])
    @test 2 == @inferred JuMP.lower_bound(manyrangelb[0, 15, 1])
    @test !JuMP.has_upper_bound(manyrangelb[0, 15, 1])

    s = ["Green","Blue"]
    @variable(mp, x[i=-10:10, s] <= 5.5, Int, start=i+1)
    @test 5 == @inferred JuMP.upper_bound(x[-4, "Green"])
    test_variable_name(x[-10, "Green"], "x[-10,Green]")
    @test JuMP.start_value(x[-3, "Blue"]) == -2
    @test isequal(mp[:onerangeub][-7], onerangeub[-7])
    @test_throws KeyError mp[:foo]
end

function test_variable_anonymous(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @test_throws ErrorException @variable(mp, [(0, 0)])  # #922
    x = @variable(mp, [(0, 2)])
    @test "" == @inferred JuMP.name(x[0])
    @test "" == @inferred JuMP.name(x[2])
end

function test_variable_is_valid_delete(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, x)
    @test JuMP.is_valid(mp, x)
    JuMP.delete(mp, x)
    @test !JuMP.is_valid(mp, x)
    second_m = EMPmaster(); mp = MathPrgm(m)
    @test_throws Exception JuMP.delete(second_mp, x)
end

function test_variable_bounds_set_get(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, 0 <= x <= 2)
    @test 0 == @inferred JuMP.lower_bound(x)
    @test 2 == @inferred JuMP.upper_bound(x)
    set_lower_bound(x, 1)
    @test 1 == @inferred JuMP.lower_bound(x)
    set_upper_bound(x, 3)
    @test 3 == @inferred JuMP.upper_bound(x)
    @variable(mp, q, Bin)

    @variable(mp, 0 <= y <= 1, Bin)
    @test 0 == @inferred JuMP.lower_bound(y)
    @test 1 == @inferred JuMP.upper_bound(y)

    @variable(mp, fixedvar == 2)
    @test 2.0 == @inferred JuMP.fix_value(fixedvar)
    JuMP.fix(fixedvar, 5)
    @test 5 == @inferred JuMP.fix_value(fixedvar)
    @test_throws Exception JuMP.lower_bound(fixedvar)
    @test_throws Exception JuMP.upper_bound(fixedvar)
end

function test_variable_starts_set_get(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, x[1:3])
    x0 = collect(1:3)
    JuMP.set_start_value.(x, x0)
    @test JuMP.start_value.(x) == x0
    @test JuMP.start_value.([x[1],x[2],x[3]]) == x0

    @variable(mp, y[1:3,1:2])
    @test_throws DimensionMismatch JuMP.set_start_value.(y, collect(1:6))
end

function test_variable_integrality_set_get(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, x[1:3])

    JuMP.set_integer(x[2])
    JuMP.set_integer(x[2])  # test duplicated call
    @test JuMP.is_integer(x[2])
    JuMP.unset_integer(x[2])
    @test !JuMP.is_integer(x[2])

    JuMP.set_binary(x[1])
    JuMP.set_binary(x[1])  # test duplicated call
    @test JuMP.is_binary(x[1])
    @test_throws Exception JuMP.set_integer(x[1])
    JuMP.unset_binary(x[1])
    @test !JuMP.is_binary(x[1])

    @variable(mp, y, binary = true)
    @test JuMP.is_binary(y)
    @test_throws Exception JuMP.set_integer(y)
    JuMP.unset_binary(y)
    @test !JuMP.is_binary(y)

    @variable(mp, z, integer = true)
    @test JuMP.is_integer(z)
    @test_throws Exception JuMP.set_binary(z)
    JuMP.unset_integer(z)
    @test !JuMP.is_integer(z)
end

function test_variable_repeated_elements(ModelType)
    # Tests repeated elements in index set throw error (JuMP issue #199).
    m = EMPmaster(); mp = MathPrgm(m)
    index_set = [:x,:x,:y]
    @test_throws ErrorException (
        @variable(mp, unused_variable[index_set], container=DenseAxisArray))
    @test_throws ErrorException (
        @variable(mp, unused_variable[index_set], container=SparseAxisArray))
    @test_throws ErrorException (
        @variable(mp, unused_variable[index_set, [1]], container=DenseAxisArray))
    @test_throws ErrorException (
        @variable(mp, unused_variable[index_set, [1]], container=SparseAxisArray))
end

function test_variable_oneto_index_set(ModelType, VariableRefType)
    # Tests that Base.OneTo can be used in index set (JuMP issue #933).
    m = EMPmaster(); mp = MathPrgm(m)
    auto_var = @variable(mp, [Base.OneTo(3), 1:2], container=Auto)
    @test auto_var isa Matrix{VariableRefType}
    @test (3, 2) == @inferred size(auto_var)
    array_var = @variable(mp, [Base.OneTo(3), 1:2], container=Array)
    @test array_var isa Matrix{VariableRefType}
    @test (3, 2) == @inferred size(array_var)
    denseaxisarray_var = @variable(mp, [Base.OneTo(3), 1:2], container=DenseAxisArray)
    @test denseaxisarray_var isa JuMP.Containers.DenseAxisArray{VariableRefType}
    @test length.(axes(denseaxisarray_var)) == (3, 2)
end

function test_variable_base_name_in_macro(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, normal_var)
    test_variable_name(normal_var, "normal_var")
    no_indices = @variable(mp, base_name="foo")
    test_variable_name(no_indices, "foo")
    # Note that `z` will be ignored in name.
    indices = @variable(mp, z[i=2:3], base_name="t")
    test_variable_name(indices[2], "t[2]")
    test_variable_name(indices[3], "t[3]")
end

function test_variable_name(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, x)
    test_variable_name(x, "x")
    JuMP.set_name(x, "y")
    @test JuMP.variable_by_name(mp, "x") isa Nothing
    test_variable_name(x, "y")
    y = @variable(mp, base_name="y")
    err(name) = ErrorException("Multiple variables have the name $name.")
    @test_throws err("y") JuMP.variable_by_name(mp, "y")
    JuMP.set_name(y, "x")
    test_variable_name(x, "y")
    test_variable_name(y, "x")
    JuMP.set_name(x, "x")
    @test_throws err("x") JuMP.variable_by_name(mp, "x")
    @test JuMP.variable_by_name(mp, "y") isa Nothing
    JuMP.set_name(y, "y")
    test_variable_name(x, "x")
    test_variable_name(y, "y")
end

function test_variable_condition_in_indexing(ModelType)
    function test_one_dim(x)
        @test 5 == @inferred length(x)
        for i in 1:10
            if iseven(i)
                @test haskey(x, i)
            else
                @test !haskey(x, i)
            end
        end
    end

    function test_two_dim(y)
        @test 15 == @inferred length(y)
        for j in 1:10, k in 3:2:9
            if isodd(j+k) && k <= 8
                @test haskey(y, (j,k))
            else
                @test !haskey(y, (j,k))
            end
        end
    end

    m = EMPmaster(); mp = MathPrgm(m)
    # Parses as ref on 0.7.
    @variable(mp, named_one_dim[i=1:10; iseven(i)])
    test_one_dim(named_one_dim)
    # Parses as vcat on 0.7.
    anon_one_dim = @variable(mp, [i=1:10; iseven(i)])
    test_one_dim(anon_one_dim)
    # Parses as typed_vcat on 0.7.
    @variable(mp, named_two_dim[j=1:10, k=3:2:9; isodd(j + k) && k <= 8])
    test_two_dim(named_two_dim)
    # Parses as vect on 0.7.
    anon_two_dim = @variable(mp, [j=1:10, k=3:2:9; isodd(j + k) && k <= 8])
    test_two_dim(anon_two_dim)
end

function test_variable_macro_return_type(ModelType, VariableRefType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, x[1:3, 1:4, 1:2], start=0.0)
    @test typeof(x) == Array{VariableRefType,3}
    @test typeof(JuMP.start_value.(x)) == Array{Float64,3}

    @variable(mp, y[1:0], start=0.0)
    @test typeof(y) == Vector{VariableRefType}
    # No type to infer for an empty collection.
    @test typeof(JuMP.start_value.(y)) == Vector{Union{Nothing, Float64}}

    @variable(mp, z[1:4], start = 0.0)
    @test typeof(z) == Vector{VariableRefType}
    @test typeof(JuMP.start_value.(z)) == Vector{Float64}
end

function test_variable_start_value_on_empty(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, x[1:4,  1:0,1:3], start = 0)  # Array{VariableRef}
    @variable(mp, y[1:4,  2:1,1:3], start = 0)  # DenseAxisArray
    @variable(mp, z[1:4,Set(),1:3], start = 0)  # SparseAxisArray

    @test JuMP.start_value.(x) == Array{Float64}(undef, 4, 0, 3)
    # TODO: Decide what to do here. I don't know if we still need to test this
    #       given broadcast syntax.
    # @test typeof(JuMP.start_value(y)) <: JuMP.DenseAxisArray{Float64}
    # @test JuMP.size(JuMP.start_value(y)) == (4,0,3)
    # @test typeof(JuMP.start_value(z)) ==
    #   JuMP.DenseAxisArray{Float64,3,Tuple{UnitRange{Int},Set{Any},UnitRange{Int}}}
    # @test length(JuMP.start_value(z)) == 0
end

function test_variable_denseaxisarray_slices(ModelType, VariableRefType)
    # Test slicing DenseAxisArrays (JuMP issue #684).
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, x[1:3, 1:4, 1:2], container=DenseAxisArray)
    @variable(mp, y[1:3, -1:2, 3:4])
    @variable(mp, z[1:3, -1:2:4, 3:4])
    @variable(mp, w[1:3, -1:2,[:red, "blue"]])

    #@test x[:] == vec(_sliceof_util(VariableRefType, x, 1:3, 1:4, 1:2))
    @test x isa JuMP.Containers.DenseAxisArray
    @test x[:, :, :].data == _sliceof_util(VariableRefType, x, 1:3, 1:4, 1:2)
    @test x[1, :, :].data == _sliceof_util(VariableRefType, x, 1, 1:4, 1:2)
    @test x[1, :, 2].data == _sliceof_util(VariableRefType, x, 1, 1:4, 2)
    @test_throws KeyError x[1, :, 3]
    #@test x[1:2,:,:].data == _sliceof_util(VariableRefType, x, 1:2, 1:4, 1:2)
    #@test x[1:2,:,2].data == _sliceof_util(VariableRefType, x, 1:2, 1:4, 2)
    #@test x[1:2,:,1:2].data == _sliceof_util(VariableRefType, x, 1:2, 1:4, 1:2)
    @test_throws KeyError x[1:2, :, 1:3]

    #@test y[:] == vec(_sliceof_util(VariableRefType, y, 1:3, -1:2, 3:4))
    @test y[:, :, :].data == _sliceof_util(VariableRefType, y, 1:3, -1:2, 3:4)
    @test y[1, :, :].data == _sliceof_util(VariableRefType, y, 1, -1:2, 3:4)
    @test y[1, :, 4].data == _sliceof_util(VariableRefType, y, 1, -1:2, 4)
    @test_throws KeyError y[1, :, 5]
    # @test y[1:2,:,:] == _sliceof_util(VariableRefType, y, 1:2, -1:2, 3:4)
    # @test y[1:2,:,4] == _sliceof_util(VariableRefType, y, 1:2, -1:2, 4)
    # @test y[1:2,:,3:4] == _sliceof_util(VariableRefType, y, 1:2, -1:2, 3:4)
    # @test_throws BoundsError y[1:2,:,1:3]

    #@test z[:] == vec(_sliceof_util(VariableRefType, z, 1:3, -1:2:4, 3:4))
    @test z[:, 1, :].data == _sliceof_util(VariableRefType, z, 1:3, 1, 3:4)
    @test z[1, 1, :].data == _sliceof_util(VariableRefType, z, 1, 1, 3:4)
    @test_throws KeyError z[:, 5, 3]
    # @test z[1:2,1,:] == _sliceof_util(VariableRefType, z, 1:2, 1, 3:4)
    # @test z[1:2,1,4] == _sliceof_util(VariableRefType, z, 1:2, 1, 4)
    # @test z[1:2,1,3:4] == _sliceof_util(VariableRefType, z, 1:2, 1, 3:4)
    # @test_throws BoundsError z[1:2,1,1:3]

    #@test w[:] == vec(_sliceof_util(VariableRefType, w, 1:3, -1:2, [:red,"blue"]))
    @test w[:, :, :] == w
    @test w[1, :, "blue"].data == _sliceof_util(VariableRefType, w, 1, -1:2, ["blue"])
    @test w[1, :, :red].data == _sliceof_util(VariableRefType, w, 1, -1:2, [:red])
    @test_throws KeyError w[1, :, "green"]
    # @test w[1:2,:,"blue"] == _sliceof_util(VariableRefType, w, 1:2, -1:2, ["blue"])
    # @test_throws ErrorException w[1:2,:,[:red,"blue"]]
end

function test_variable_end_indexing(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, x[0:2, 1:4])
    @variable(mp, z[0:2])
    @test x[end,1] == x[2, 1]
    @test x[0, end-1] == x[0, 3]
    @test z[end] == z[2]
    # TODO: It is redirected to x[11] as it is the 11th element but linear
    #       indexing is not supported
    @test_throws BoundsError x[end-1]
end

function test_variable_unsigned_index(ModelType)
    # Tests unsigned int can be used to construct index set (JuMP issue #857).
    m = EMPmaster(); mp = MathPrgm(m)
    t = UInt(4)
    @variable(mp, x[1:t])
    @test 4 == @inferred num_variables(mp)
end

function test_variable_symmetric(ModelType)
    m = EMPmaster(); mp = MathPrgm(m)

    @variable mp x[1:2, 1:2] Symmetric
    @test x isa Symmetric
    @test x[1, 2] === x[2, 1]
    @test mp[:x] === x

    y = @variable mp [1:2, 1:2] Symmetric
    @test y isa Symmetric
    @test y[1, 2] === y[2, 1]
end

function variables_test(ModelType::Type{<:JuMP.AbstractModel},
                        VariableRefType::Type{<:JuMP.AbstractVariableRef})
    @testset "Variable name" begin
        test_variable_name(ModelType)
    end

    @testset "Constructors" begin
        test_variable_no_bound(ModelType, VariableRefType)
        test_variable_lower_bound_rhs(ModelType)
        test_variable_lower_bound_lhs(ModelType)
        test_variable_upper_bound_rhs(ModelType)
        test_variable_upper_bound_lhs(ModelType)
        test_variable_interval(ModelType)
        test_variable_fix(ModelType)
        test_variable_custom_index_sets(ModelType)
        test_variable_anonymous(ModelType)
    end

    @testset "isvalid and delete variable" begin
        test_variable_is_valid_delete(ModelType)
    end

    @testset "get and set bounds" begin
        test_variable_bounds_set_get(ModelType)
    end

    @testset "get and set start" begin
        test_variable_starts_set_get(ModelType)
    end

    @testset "get and set integer/binary" begin
        test_variable_integrality_set_get(ModelType)
    end

    @testset "repeated elements in index set (issue #199)" begin
        test_variable_repeated_elements(ModelType)
    end

    @testset "Base.OneTo as index set (#933)" begin
        test_variable_oneto_index_set(ModelType, VariableRefType)
    end

    @testset "base_name= in @variable" begin
        test_variable_base_name_in_macro(ModelType)
    end

    @testset "condition in indexing" begin
        test_variable_condition_in_indexing(ModelType)
    end

    @testset "@variable returning Array{VariableRef}" begin
        test_variable_macro_return_type(ModelType, VariableRefType)
    end

    @testset "start_value on empty things" begin
        test_variable_start_value_on_empty(ModelType)
    end

    @testset "Slices of DenseAxisArray (#684)" begin
        test_variable_denseaxisarray_slices(ModelType, VariableRefType)
    end

    @testset "end for indexing a DenseAxisArray" begin
        test_variable_end_indexing(ModelType)
    end

    @testset "Unsigned dimension lengths (#857)" begin
        test_variable_unsigned_index(ModelType)
    end

end

@testset "Variables for JuMP.Model" begin
    variables_test(Model, VariableRef)
    @testset "all_variables" begin
        m = EMPmaster(); mp = MathPrgm(m)
        @variable(mp, x)
        @variable(mp, y)
        @test [x, y] == @inferred JuMP.all_variables(mp)
    end
#    @testset "@variables" begin
#        m = EMPmaster(); mp = MathPrgm(m)
#        @variables model begin
#            0 ≤ x[i=1:2] ≤ i
#            y ≥ 2, Int, (start = 0.7)
#            z ≤ 3, (start = 10)
#            q, (Bin, start = 0.5)
#        end
#
#        @test "x[1]" == @inferred JuMP.name(x[1])
#        @test 0 == @inferred JuMP.lower_bound(x[1])
#        @test 1 == @inferred JuMP.upper_bound(x[1])
#        @test !JuMP.is_binary(x[1])
#        @test !JuMP.is_integer(x[1])
#        @test JuMP.start_value(x[1]) === nothing
#
#        @test "x[2]" == @inferred JuMP.name(x[2])
#        @test 0 == @inferred JuMP.lower_bound(x[2])
#        @test 2 == @inferred JuMP.upper_bound(x[2])
#        @test !JuMP.is_binary(x[2])
#        @test !JuMP.is_integer(x[2])
#        @test JuMP.start_value(x[2]) === nothing
#
#        @test "y" == @inferred JuMP.name(y)
#        @test 2 == @inferred JuMP.lower_bound(y)
#        @test !JuMP.has_upper_bound(y)
#        @test !JuMP.is_binary(y)
#        @test JuMP.is_integer(y)
#        @test JuMP.start_value(y) === 0.7
#
#        @test "z" == @inferred JuMP.name(z)
#        @test !JuMP.has_lower_bound(z)
#        @test 3 == @inferred JuMP.upper_bound(z)
#        @test !JuMP.is_binary(z)
#        @test !JuMP.is_integer(z)
#        @test JuMP.start_value(z) === 10.0
#
#        @test "q" == @inferred JuMP.name(q)
#        @test !JuMP.has_lower_bound(q)
#        @test !JuMP.has_upper_bound(q)
#        @test JuMP.is_binary(q)
#        @test !JuMP.is_integer(q)
#        @test JuMP.start_value(q) === 0.5
#    end
end

#@testset "Variables for JuMPExtension.MyModel" begin
#    variables_test(JuMPExtension.MyModel, JuMPExtension.MyVariableRef)
#end

@testset "Dual from Variable" begin
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, x == 0)
    exception = ErrorException(
        "To query the dual variables associated with a variable bound, first " *
        "obtain a constraint reference using one of `UpperBoundRef`, `LowerBoundRef`, " *
        "or `FixRef`, and then call `dual` on the returned constraint reference.\nFor " *
        "example, if `x <= 1`, instead of `dual(x)`, call `dual(UpperBoundRef(x))`.")
    @test_throws exception JuMP.dual(x)
end

@testset "value on containers" begin
    m = EMPmaster(); mp = MathPrgm(m)
    @variable(mp, x[1:2])
    exception = ErrorException(
        "`JuMP.value` is not defined for collections of JuMP types. Use " *
        "Julia's broadcast syntax instead: `JuMP.value.(x)`.")
    @test_throws exception JuMP.value(x)
end


text:
using RealNeuralNetworks
using RealNeuralNetworks.NodeNets.PointArrays

using Test

@testset "test PointArray add_offset! operation" begin
    # build fake data
    a = [   0xc95f39a7  0x21a2a09c  0xc9e447a2
            0xa8e1555e  0x7fbeb45b  0xc786dc67
            0x940649cd  0x20b148bc  0xc15152cb
            0xc91b9db1  0x39fee982  0x7019e841]
    offset = (UInt32(1), UInt32(2), UInt32(3))
    PointArrays.add_offset!(a, offset)
    b = [0xc95f39a8  0x21a2a09e  0xc9e447a5
         0xa8e1555f  0x7fbeb45d  0xc786dc6a
         0x940649ce  0x20b148be  0xc15152ce
         0xc91b9db2  0x39fee984  0x7019e844]
    @test all(a.==b)
end


text:
# Autogenerated wrapper script for OpenLSTO_jll for aarch64-apple-darwin-cxx03
export m2do_3d_lsm, m2do_fea, m2do_lsm

using CompilerSupportLibraries_jll
JLLWrappers.@generate_wrapper_header("OpenLSTO")
JLLWrappers.@declare_library_product(m2do_3d_lsm, "@rpath/m2do_3d_lsm.dylib")
JLLWrappers.@declare_library_product(m2do_fea, "@rpath/m2do_fea.dylib")
JLLWrappers.@declare_library_product(m2do_lsm, "@rpath/m2do_lsm.dylib")
function __init__()
    JLLWrappers.@generate_init_header(CompilerSupportLibraries_jll)
    JLLWrappers.@init_library_product(
        m2do_3d_lsm,
        "lib/m2do_3d_lsm.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        m2do_fea,
        "lib/m2do_fea.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        m2do_lsm,
        "lib/m2do_lsm.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@generate_init_footer()
end  # __init__()


text:
# This file is a part of ForwardDiffPullbacks.jl, licensed under the MIT License (MIT).

import Test

Test.@testset "Package ForwardDiffPullbacks" begin
    include("test_fwd_back.jl")
    include("test_rrules.jl")
    include("test_zygote.jl")
end # testset


text:
@testset "967.numbers-with-same-consecutive-differences.jl" begin
    @test nums_same_consec_diff(3, 7) |> Set == [181,292,707,818,929] |> Set
    @test nums_same_consec_diff(2, 1) |> Set == [10,12,21,23,32,34,43,45,54,56,65,67,76,78,87,89,98] |> Set
    @test nums_same_consec_diff(2, 0) |> Set == [11,22,33,44,55,66,77,88,99] |> Set
    @test nums_same_consec_diff(2, 2) |> Set == [13,20,24,31,35,42,46,53,57,64,68,75,79,86,97] |> Set
end


text:
coordinates(node::Node) = [node.location.lon, node.location.lat]

function node_gdf(g::OSMGraph)
    ids = collect(keys(g.nodes))
    geom = map(ids) do id
       coordinates(g.nodes[id])
    end
    return DataFrame(;id=ids, geom=Point.(geom))
end

function highway_gdf(g::OSMGraph)
    ids = collect(keys(g.highways))
    _way_coordinates(way) = map(way.nodes) do id
        coordinates(g.nodes[id])
    end
    geom = map(id -> _way_coordinates(g.highways[id]), ids)
    return DataFrame(;id=ids, geom=LineString.(geom))
end

function node_gdf(sg::SimplifiedOSMGraph)
    ids = collect(keys(sg.node_to_index))
    geom = map(ids) do id
        coordinates(sg.parent.nodes[id])
    end
    return DataFrame(;id=ids, geom=Point.(geom))
end

highway_gdf(sg::SimplifiedOSMGraph) = highway_gdf(sg.parent)

function edge_gdf(sg::SimplifiedOSMGraph)
    edge_ids = collect(keys(sg.edges))
    geom = map(edge_ids) do edge
        path = sg.edges[edge]
        reverse.(sg.parent.node_coordinates[path])
    end
    u, v, key = map(i -> getindex.(edge_ids, i), 1:3)
    return DataFrame(;u, v, key, geom=LineString.(geom))
end


text:
# %% Carbon cycle extinction events
# model of Rothman, 10.1073/pnas.1905164116
# main dynamic rule, eqs.(11, 12) of paper
using DrWatson
@quickactivate "NonlinearDynamicsTextbook"
include(srcdir("style.jl"))
using DynamicalSystems, PyPlot, Statistics, DelimitedFiles
function excitable_carbon(u, p, t)
    c, w = u
    μ, b, θ, cₓ, cₚ, ν, w₀, γ, f₀, c_f, β = p
    s = excitable_carbon_s(c, cₚ, γ)
    sbar = 1 - excitable_carbon_s(c, cₓ, γ)
    f = excitable_carbon_f(c, f₀, β, c_f)
    cdot = μ*(1 - b*s - θ*sbar - ν) + w - w₀
    cdot *= f
    wdot = μ*(1 - b*s + θ*sbar + ν) - w + w₀
    return SVector(cdot, wdot)
end
# functions used in equations of motion
excitable_carbon_f(c, f₀, β, c_f) = f₀*(c^β / (c^β + c_f^β))
excitable_carbon_s(c, cₚ, γ) = c^γ/(c^γ + cₚ^γ)

u0 = [100.0, 2800.0] # random, from Fig. 4

# Parameter default values in Table 1 of appendix, but cₓ same as Fig. 5
p0 = [250, 4, 5, 55, 110, 0, 2000, 4, 0.694, 43.9, 1.7]
# μ, b, θ, cₓ, cₚ, ν, w₀, γ, f₀, c_f, β = p0

ec = ContinuousDynamicalSystem(excitable_carbon, u0, p0)

# streamplot:
xgrid = 0:5:190
ygrid = 2000:20:3500
ux = zeros(length(xgrid), length(ygrid))
uy = copy(ux)
for (i, x) in enumerate(xgrid)
    for (j, y) in enumerate(ygrid)
        ux[i, j], uy[i, j] = ec.f(SVector(x, y), ec.p, 0)
    end
end

fig, axs = subplots(1,2; figsize = (0.66figx, figy))

axs[1].streamplot(Vector(xgrid), Vector(ygrid) ./1000 , ux', uy' ./ 1000;
    linewidth = 1.0, density = 0.25, color = "C4", arrowsize = 1.0
)

# plot trajectories
c_stable = 83.58 # approximate value
for (i, u) in enumerate(([c_stable, 2075.0], [c_stable, 2102.0], ))
    tr = trajectory(ec, 100.0, u)
    c, w = columns(tr)
    w ./= 1000
    for ax in axs
        ax.plot(c, w; color = "C$(i-1)")
    end
end
ax = axs[1]
ax.set_xlabel("\$c\$"; labelpad = -15)
ax.set_ylabel("\$w \\times 10^{-3}\$")
ax.set_xticks(0:60:180)

zbox = ((65, 2.05), (100, 2.25))

# Plot fixed point and arrows
fp = trajectory(ec, 10.0, [83.56, 2075]; Ttr = 1000)[end]
axs[2].plot([fp[1]], [fp[2]/1000]; marker = "o", mew=4, ms = 15, mec="C2", zorder = 99, mfc="white")

# axs[2].arrow(
#     fp[1], fp[2]/1000, 0,  2.1003 - fp[2]/1000,
#     zorder = 99, width = 0.01,
# )

# axs[2].arrow(
#     fp[1], 2.102, 0, fp[2]/1000 - 2.102;
#     zorder = 99, width = 0.01, length_includes_head = true,
#     # head_width = 2,
#     head_starts_at_zero = true
# )

axis_zoomin!(axs[2], axs[1], zbox, zbox, "C3"; α = 0.75)
axs[2].axis("off")
fig.tight_layout(pad = 0.3)
fig.subplots_adjust(wspace = 0.1)

# add_identifiers!(fig)
wsave(plotsdir("12", "carboncycle"), fig)

max_stars_repo_name: JuliaDynamics/NonlinearDynamicsTextbook

max_issues_repo_name: JuliaDynamics/NonlinearDynamicsTextbook

max_forks_repo_name: JuliaDynamics/NonlinearDynamicsTextbook


text:
function encode(s)
    all_matches = eachmatch(r"(.)\1*", s)
    format(m) = (x = m.match; length(x) == 1 ? string(first(x)) : string(length(x), first(x)))
    mapfoldl(format, string, all_matches; init = "")
end

function decode(s)
    all_matches = eachmatch(r"(\d*).", s)
    format(m) = (x = m.match; length(x) > 1 ? repeat(string(last(x)), parse(Int, x[1:end-1])) : x)
    mapfoldl(format, string, all_matches; init = "")
end

max_stars_repo_name: koladilip/julia-exercism

max_issues_repo_name: koladilip/julia-exercism

max_forks_repo_name: koladilip/julia-exercism


text:
int main() {
  1.1++; // this should only work for int
  return 1;
}

max_stars_repo_name: fredefox/javalette

max_issues_repo_name: fredefox/javalette

max_forks_repo_name: fredefox/javalette


text:
abstract type TuningStrategy <: MLJ.MLJType end
const ParameterName=Union{Symbol,Expr}

"""
    Grid(resolution=10, acceleration=DEFAULT_RESOURCE[])

Define a grid-based hyperparameter tuning strategy, using the
specified `resolution` for numeric hyperparameters. For use with a
`TunedModel` object.

Individual hyperparameter resolutions can also be specified, as in

    Grid(resolution=[:n => r1, :(atom.max_depth) => r2])

where `r1` and `r2` are `NumericRange` objects.

The `acceleration` keyword argument is used to specify the compute resource (a
subtype of `ComputationalResources.AbstractResource`) that will be used to
accelerate/parallelize the resampling operation.

See also [TunedModel](@ref), [range](@ref).

"""
mutable struct Grid <: TuningStrategy
    resolution::Union{Int,Vector{<:Pair{<:ParameterName,Int}}}
    acceleration::AbstractResource
end

# Constructor with keywords
Grid(; resolution=10, acceleration::AbstractResource=DEFAULT_RESOURCE[]) =
    Grid(resolution, acceleration)

MLJBase.show_as_constructed(::Type{<:Grid}) = true

"""
$TYPEDEF

Container for a deterministic tuning strategy.
"""
mutable struct DeterministicTunedModel{T,M<:Deterministic} <: MLJ.Deterministic
    model::M
    tuning::T  # tuning strategy
    resampling # resampling strategy
    measure
    weights::Union{Nothing,Vector{<:Real}}
    operation
    ranges::Union{Vector{<:ParamRange},ParamRange}
    full_report::Bool
    train_best::Bool
end

"""
$TYPEDEF

Container for a probabilistic tuning strategy.
"""
mutable struct ProbabilisticTunedModel{T,M<:Probabilistic} <: MLJ.Probabilistic
    model::M
    tuning::T  # tuning strategy
    resampling # resampling strategy
    measure
    weights::Union{Nothing,AbstractVector{<:Real}}
    operation
    ranges::Union{Vector{<:ParamRange},ParamRange}
    full_report::Bool
    train_best::Bool
end

const EitherTunedModel{T,M} =
    Union{DeterministicTunedModel{T,M},ProbabilisticTunedModel{T,M}}

MLJBase.is_wrapper(::Type{<:EitherTunedModel}) = true

"""
    tuned_model = TunedModel(; model=nothing,
                             tuning=Grid(),
                             resampling=Holdout(),
                             measure=nothing,
                             weights=nothing,
                             operation=predict,
                             ranges=ParamRange[],
                             full_report=true,
                             train_best=true)

Construct a model wrapper for hyperparameter optimization of a
supervised learner.

Calling `fit!(mach)` on a machine `mach=machine(tuned_model, X, y)` or
`mach=machine(tuned_model, X, y, w)` will:

- Instigate a search, over clones of `model`, with the hyperparameter
  mutations specified by `ranges`, for a model optimizing the
  specified `measure`, using performance evaluations carried out using
  the specified `tuning` strategy and `resampling` strategy. If
  `measure` supports weights (`supports_weights(measure) == true`)
  then any `weights` specified will be passed to the measure.

- Fit an internal machine, based on the optimal model
  `fitted_params(mach).best_model`, wrapping the optimal `model`
  object in *all* the provided data `X, y` (or in `task`). Calling
  `predict(mach, Xnew)` then returns predictions on `Xnew` of this
  internal machine. The final train can be supressed by setting
  `train_best=false`.

*Important.* If a custom measure `measure` is used, and the measure is
a score, rather than a loss, be sure to check that
`MLJ.orientation(measure) == :score` to ensure maximization of the
measure, rather than minimization. Override an incorrect value with
`MLJ.orientation(::typeof(measure)) = :score`.

*Important:* If `weights` are left unspecified, and `measure` supports
sample weights, then any weight vector `w` used in constructing a
corresponding tuning machine, as in `tuning_machine =
machine(tuned_model, X, y, w)` (which is then used in *training* each
model in the search) will also be passed to `measure` for evaluation.

In the case of two-parameter tuning, a Plots.jl plot of performance
estimates is returned by `plot(mach)` or `heatmap(mach)`.

Once a tuning machine `mach` has bee trained as above, one can access
the learned parameters of the best model, using
`fitted_params(mach).best_fitted_params`. Similarly, the report of
training the best model is accessed via `report(mach).best_report`.

"""
function TunedModel(;model=nothing,
                    tuning=Grid(),
                    resampling=Holdout(),
                    measures=nothing,
                    measure=measures,
                    weights=nothing,
                    operation=predict,
                    range=ParamRange[],
                    ranges=range,
                    minimize=true,
                    full_report=true,
                    train_best=true)

    !isempty(ranges) || error("You need to specify ranges=... ")
    model !== nothing || error("You need to specify model=... ")
    model isa Supervised || error("model must be a SupervisedModel. ")

    message = clean!(model)
    isempty(message) || @info message

    if model isa Deterministic
        return DeterministicTunedModel(model, tuning, resampling,
           measure, weights, operation, ranges, full_report, train_best)
    elseif model isa Probabilistic
        return ProbabilisticTunedModel(model, tuning, resampling,
           measure, weights, operation, ranges, full_report, train_best)
    end
    error("$model does not appear to be a Supervised model.")
end

function MLJBase.clean!(model::EitherTunedModel)
    message = ""
    if model.measure === nothing
        model.measure = default_measure(model)
        message *= "No measure specified. Setting measure=$(model.measure). "
    end
    return message
end

## GRID SEARCH

function MLJBase.fit(tuned_model::EitherTunedModel{Grid,M},
                     verbosity::Integer, args...) where M

    if tuned_model.ranges isa AbstractVector
        ranges = tuned_model.ranges
    else
        ranges = [tuned_model.ranges,]
    end

    ranges isa AbstractVector{<:ParamRange} ||
        error("ranges must be a ParamRange object or a vector of " *
              "ParamRange objects. ")

    # Build a vector of resolutions, one element per range. In case of
    # OrdinalRange provide a dummy value of 5. In case of a dictionary
    # with missing keys for the NumericRange`s, use fallback of 5.
    resolution = tuned_model.tuning.resolution
    if resolution isa Vector
        val_given_field = Dict(resolution...)
        fields = keys(val_given_field)
        resolutions = map(ranges) do range
            if range.field in fields
                return val_given_field[range.field]
            else
                if range isa MLJ.NumericRange && verbosity > 0
                    @warn "No resolution specified for "*
                    "$(range.field). Will use a value of 5. "
                end
                return 5
            end
        end
    else
        resolutions = fill(resolution, length(ranges))
    end

    if tuned_model.measure isa AbstractVector
        measure = tuned_model.measure[1]
        verbosity >=0 &&
            @warn "Provided `meausure` is a vector. Using first element only. "
    else
        measure = tuned_model.measure
    end

    minimize = ifelse(orientation(measure) == :loss, true, false)

    if verbosity > 0 && tuned_model.train_best
        if minimize
            @info "Mimimizing $measure. "
        else
            @info "Maximizing $measure. "
        end
    end

    parameter_names = [string(r.field) for r in ranges]
    scales = [scale(r) for r in ranges]

    # We mutate a clone of the provided model but with any :rng field
    # passed to the clone:
    clone = deepcopy(tuned_model.model)
    if isdefined(clone, :rng)
        clone.rng = tuned_model.model.rng
    end

    resampler = Resampler(model=clone,
                          resampling=tuned_model.resampling,
                          measure=measure,
                          weights=tuned_model.weights,
                          operation=tuned_model.operation)

    resampling_machine = machine(resampler, args...)

    # tuple of iterators over hyper-parameter values:
    iterators = map(eachindex(ranges)) do j
        range = ranges[j]
        if range isa MLJ.NominalRange
            MLJ.iterator(range)
        elseif range isa MLJ.NumericRange
            MLJ.iterator(range, resolutions[j])
        else
            throw(TypeError(:iterator, "", MLJ.ParamRange, range))
        end
    end

#    nested_iterators = copy(tuned_model.ranges, iterators)

    n_iterators = length(iterators) # same as number of ranges
    A = MLJ.unwind(iterators...)
    N = size(A, 1)

    if tuned_model.full_report
        measurements = Vector{Float64}(undef, N)
    end

    # initialize search for best model:
    best_model = deepcopy(tuned_model.model)
    best_measurement = ifelse(minimize, Inf, -Inf)
    s = ifelse(minimize, 1, -1)

    # evaluate all the models using specified resampling:
    # TODO: parallelize!

    meter = Progress(N+1, dt=0, desc="Iterating over a $N-point grid: ",
                     barglyphs=BarGlyphs("[=> ]"), barlen=25, color=:yellow)
    verbosity != 1 || next!(meter)

    for i in 1:N
        verbosity != 1 || next!(meter)

        A_row = Tuple(A[i,:])

 #       new_params = copy(nested_iterators, A_row)

        # mutate `clone` (the model to which `resampler` points):
        for k in 1:n_iterators
            field = ranges[k].field
            recursive_setproperty!(clone, field, A_row[k])
        end

        if verbosity == 2
            fit!(resampling_machine, verbosity=0)
        else
            fit!(resampling_machine, verbosity=verbosity-1)
        end
        e = evaluate(resampling_machine).measurement[1]

        if verbosity > 1
            text = prod("$(parameter_names[j])=$(A_row[j]) \t" for j in 1:length(A_row))
            text *= "measurement=$e"
            println(text)
        end

        if s*(best_measurement - e) > 0
            best_model = deepcopy(clone)
            best_measurement = e
        end

        if tuned_model.full_report
 #           models[i] = deepcopy(clone)
            measurements[i] = e
        end

    end

    fitresult = machine(best_model, args...)
    if tuned_model.train_best
        verbosity < 1 || @info "Training best model on all supplied data."

        # train best model on all the data:
        # TODO: maybe avoid using machines here and use model fit/predict?
        fit!(fitresult, verbosity=verbosity-1)
        best_report = fitresult.report
    else
        verbosity < 1 || @info "Training of best model suppressed.\n "*
        "To train tuning machine `mach` on all supplied data, call "*
        "`fit!(mach.fitresult)`."
        fitresult = tuned_model.model
        best_report = missing
    end

    pre_report = (parameter_names= permutedims(parameter_names), # row vector
                  parameter_scales=permutedims(scales),   # row vector
                  best_measurement=best_measurement,
                  best_report=best_report)

    if tuned_model.full_report
        report = merge(pre_report,
                       (parameter_values=A,
                        measurements=measurements,))
    else
        report = merge(pre_report,
                       (parameter_values=missing,
                        measurements=missing,))
    end

    cache = nothing

    return fitresult, cache, report

end

function MLJBase.fitted_params(tuned_model::EitherTunedModel, fitresult)
    if tuned_model.train_best
        return (best_model=fitresult.model,
                best_fitted_params=fitted_params(fitresult))
    else
        return (best_model=fitresult.model,
                best_fitted_params=missing)
    end
end

MLJBase.predict(tuned_model::EitherTunedModel, fitresult, Xnew) = predict(fitresult, Xnew)
MLJBase.best(model::EitherTunedModel, fitresult) = fitresult.model

## METADATA

MLJBase.supports_weights(::Type{<:EitherTunedModel{<:Any,M}}) where M =
    MLJBase.supports_weights(M)

MLJBase.load_path(::Type{<:DeterministicTunedModel}) =
    "MLJ.DeterministicTunedModel"
MLJBase.package_name(::Type{<:DeterministicTunedModel}) = "MLJ"
MLJBase.package_uuid(::Type{<:DeterministicTunedModel}) = ""
MLJBase.package_url(::Type{<:DeterministicTunedModel}) =
    "https://github.com/alan-turing-institute/MLJ.jl"
MLJBase.is_pure_julia(::Type{<:DeterministicTunedModel{T,M}}) where {T,M} =
    MLJBase.is_pure_julia(M)
MLJBase.input_scitype(::Type{<:DeterministicTunedModel{T,M}}) where {T,M} =
    MLJBase.input_scitype(M)
MLJBase.target_scitype(::Type{<:DeterministicTunedModel{T,M}}) where {T,M} =
    MLJBase.target_scitype(M)

MLJBase.load_path(::Type{<:ProbabilisticTunedModel}) =
    "MLJ.ProbabilisticTunedModel"
MLJBase.package_name(::Type{<:ProbabilisticTunedModel}) = "MLJ"
MLJBase.package_uuid(::Type{<:ProbabilisticTunedModel}) = ""
MLJBase.package_url(::Type{<:ProbabilisticTunedModel}) =
    "https://github.com/alan-turing-institute/MLJ.jl"
MLJBase.is_pure_julia(::Type{<:ProbabilisticTunedModel{T,M}}) where {T,M} =
    MLJBase.is_pure_julia(M)
MLJBase.input_scitype(::Type{<:ProbabilisticTunedModel{T,M}}) where {T,M} =
    MLJBase.input_scitype(M)
MLJBase.target_scitype(::Type{<:ProbabilisticTunedModel{T,M}}) where {T,M} =
    MLJBase.target_scitype(M)