text:
@testset "Step" begin
    m = SquareRooter(4)
    IC.train!(m, 10)
    all_training_losses = m.training_losses

    m = SquareRooter(4)
    c = Step(n=2)
    state = IC.update!(c, m, 0, 1)
    @test state === (new_iterations = 2,)
    @test m.training_losses == all_training_losses[1:2]
    state = IC.update!(c, m, 0, 2, state)
    @test m.training_losses == all_training_losses[3:4]
    @test !IC.done(c, state)
    @test_logs((:info, r"A total of "),
               @test IC.takedown(c, 2, state) == (new_iterations = 4,))
end

@testset "Info" begin
    m = SquareRooter(4)
    c = Info(m->m.root)
    IC.train!(m, 1)
    @test_logs (:info, 2.5) IC.update!(c, m, 2, 1)
    @test_logs (:info, 2.5) IC.update!(c, m, 1, 1)
    state = @test_logs IC.update!(c, m, 0, 1)
    @test state === nothing
    @test_logs (:info, 2.5) IC.update!(c, m, 2, 2, state)
    @test !IC.done(c, state)
    @test IC.takedown(c, 10, state) == NamedTuple()
end

@testset "Warn" begin
    m = SquareRooter(4)
    c = Warn(m -> m.root > 2.4)

    IC.train!(m, 1)
    @test_logs (:warn, "") IC.update!(c, m, 1, 1)
    @test_logs (:warn, "") IC.update!(c, m, 0, 1)
    state = @test_logs IC.update!(c, m, -1, 1)
    @test state === (warnings=("", ),)

    IC.train!(m, 1)
    @test_logs  IC.update!(c, m, 1, 1)
    @test_logs  IC.update!(c, m, 0, 1)
    state = @test_logs IC.update!(c, m, -1, 1)
    @test state === (warnings=(),)

    m = SquareRooter(4)
    IC.train!(m, 1)
    state =  IC.update!(c, m, -1, 1)
    @test_logs (:warn, "") IC.update!(c, m, 1, 2, state)
    @test_logs (:warn, "") IC.update!(c, m, 0, 2, state)
    state = @test_logs IC.update!(c, m, -1, 2, state)
    @test state === (warnings=("", ""),)

    IC.train!(m, 1)
    @test_logs  IC.update!(c, m, 1, 3, state)
    @test_logs  IC.update!(c, m, 0, 3, state)
    state = @test_logs IC.update!(c, m, -1, 3, state)
    @test state === (warnings=("", ""),)

    m = SquareRooter(4)
    c = Warn(m -> m.root > 2.4, f = m->m.root)

    IC.train!(m, 1)
    @test_logs (:warn, 2.5) IC.update!(c, m, 1, 1)
    @test_logs (:warn, 2.5) IC.update!(c, m, 0, 1)
    state = @test_logs IC.update!(c, m, -1, 1)
    @test state === (warnings=(2.5, ),)

    @test_logs (:warn, 2.5) IC.update!(c, m, 1, 2, state)
    @test_logs (:warn, 2.5) IC.update!(c, m, 0, 2, state)
    state = @test_logs IC.update!(c, m, -1, 2, state)
    @test state === (warnings=(2.5, 2.5),)

    @test !IC.done(c, state)
    @test_logs((:warn, r"A `Warn`"),
               @test IC.takedown(c, 2, state) == (warnings = (2.5, 2.5),))
    @test_logs @test IC.takedown(c, 1, state) == (warnings = (2.5, 2.5),)

end

@testset "Error" begin
    m = SquareRooter(4)
    c = Error(m -> m.root > 2.4)

    IC.train!(m, 1)
    state = @test_logs (:error, "") IC.update!(c, m, 2, 1)
    @test state === (done=true, error="")

    IC.train!(m, 1)
    state = @test_logs  IC.update!(c, m, 2, 1)
    @test state === (done=false, error=())

    m = SquareRooter(4)
    IC.train!(m, 1)
    state = @test_logs (:error, "") IC.update!(c, m, 2, 1)
    state = @test_logs (:error, "") IC.update!(c, m, 2, 2, state)
    @test state === (done=true, error="")

    m = SquareRooter(4)
    c = Error(m -> m.root > 2.4, f = m->m.root)

    IC.train!(m, 1)
    state = @test_logs (:error, 2.5) IC.update!(c, m, 2, 1)
    @test state === (done=true, error=2.5)

    IC.train!(m, 1)
    state = @test_logs  IC.update!(c, m, 2, 1)
    @test state === (done=false, error=())

    m = SquareRooter(4)
    IC.train!(m, 1)
    state = @test_logs (:error, 2.5) IC.update!(c, m, 2, 1)
    state = @test_logs (:error, 2.5) IC.update!(c, m, 2, 2, state)
    @test state === (done=true, error=2.5)

    @test IC.done(c, state)
    @test IC.takedown(c, 10, state) == state
end

@testset "Callback" begin

    v = Float64[]
    f(model) = (push!(v, IC.loss(model)); last(v) < 0.02)

    c = Callback(f)
    m = SquareRooter(4)
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 1)
    @test !state.done
    @test v == [2.25, ]
    IC.train!(m, 2)
    state = IC.update!(c, m, 1, 2, state)
    @test !state.done
    @test v ≈ [2.25, (3281/1640)^2 - 4]
    @test IC.takedown(c, 0, state) == (done = false, log="")

    v = Float64[]
    f2(model) = (push!(v, IC.loss(model)); last(v) < 0.02)

    c = Callback(f2, stop_if_true=true)
    m = SquareRooter(4)
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 1)
    @test !state.done
    @test v == [2.25, ]
    IC.train!(m, 2)
    state = IC.update!(c, m, 1, 2, state)
    @test state.done
    @test v ≈ [2.25, (3281/1640)^2 - 4]
    @test IC.takedown(c, 0, state) ==
        (done = true,
         log="Stop triggered by a `Callback` control. ")

    v = Float64[]
    f3(model) = (push!(v, IC.loss(model)); last(v) < 0.02)

    c = Callback(f3, stop_if_true=true, stop_message="foo")
    m = SquareRooter(4)
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 1)
    @test !state.done
    @test v == [2.25, ]
    IC.train!(m, 2)
    state = IC.update!(c, m, 1, 2, state)
    @test state.done
    @test v ≈ [2.25, (3281/1640)^2 - 4]
    @test IC.takedown(c, 0, state) ==
        (done = true,
         log="foo")

end

@testset "WithLossDo" begin

    v = Float64[]
    f(loss) = (push!(v, loss); last(v) < 0.02)
    c = WithLossDo(f)
    m = SquareRooter(4)
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 1)
    @test !state.done
    @test v == [2.25, ]
    IC.train!(m, 2)
    state = IC.update!(c, m, 1, 2, state)
    @test !state.done
    @test v ≈ [2.25, (3281/1640)^2 - 4]
    @test IC.takedown(c, 1, state) ==
        (loss=v[end], done = false, log="")
    @test_logs((:info, r"final loss"),
               @test IC.takedown(c, 2, state) ==
               (loss=v[end], done = false, log=""))

    v = Float64[]
    f2(loss) = (push!(v, loss); last(v) < 0.02)
    c = WithLossDo(f2, stop_if_true=true)
    m = SquareRooter(4)
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 1)
    @test !state.done
    @test v == [2.25, ]
    IC.train!(m, 2)
    state = IC.update!(c, m, 1, 2, state)
    @test state.done
    @test v ≈ [2.25, (3281/1640)^2 - 4]
    @test IC.takedown(c, 0, state) ==
        (loss = v[end],
         done = true,
         log="Stop triggered by a `WithLossDo` control. ")
    @test_logs((:info, r"Stop triggered"),
               @test IC.takedown(c, 1, state) ==
               (loss = v[end],
                done = true,
                log="Stop triggered by a `WithLossDo` control. "))
    @test_logs((:info, r"final loss"),
               (:info, r"Stop triggered"),
               @test IC.takedown(c, 2, state) ==
               (loss = v[end],
                done = true,
                log="Stop triggered by a `WithLossDo` control. "))

    v = Float64[]
    f3(loss) = (push!(v, loss); last(v) < 0.02)
    c = WithLossDo(f3, stop_if_true=true, stop_message="foo")
    m = SquareRooter(4)
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 1)
    @test !state.done
    @test v == [2.25, ]
    IC.train!(m, 2)
    state = IC.update!(c, m, 1, 2, state)
    @test state.done
    @test v ≈ [2.25, (3281/1640)^2 - 4]
    @test IC.takedown(c, 0, state) ==
        (loss = v[end],
         done = true,
         log="foo")

end

@testset "WithTrainingLossesDo" begin

    v = Float64[]
    f(training_loss) = (push!(v, last(training_loss)); last(v) < 0.5)
    c = WithTrainingLossesDo(f)
    m = SquareRooter(4)
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 1)
    @test !state.done
    @test v ≈ [1.5, ]
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 2, state)
    @test !state.done
    @test v ≈ [1.5, 0.45]
    @test IC.takedown(c, 0, state) ==
        (latest_training_loss = v[end], done = false, log="")
    @test_logs((:info, r"final train"),
               @test IC.takedown(c, 2, state) ==
               (latest_training_loss=v[end], done = false, log=""))

    v = Float64[]
    f1(training_loss) = (push!(v, last(training_loss)); last(v) < 0.5)
    c = WithTrainingLossesDo(f1, stop_if_true=true)
    m = SquareRooter(4)
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 1)
    @test !state.done
    @test v == [1.5, ]
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 2, state)
    @test state.done
    @test v ≈ [1.5, 0.45]
    @test IC.takedown(c, 0, state) ==
        (latest_training_loss = v[end],
         done = true,
         log="Stop triggered by a `WithTrainingLossesDo` control. ")
    @test_logs((:info, r"Stop "),
               @test IC.takedown(c, 1, state) ==
               (latest_training_loss = v[end],
                done = true,
                log="Stop triggered by a `WithTrainingLossesDo` control. "))
    @test_logs((:info, r"final train"),
               (:info, r"Stop"),
               @test IC.takedown(c, 2, state) ==
               (latest_training_loss = v[end],
                done = true,
                log="Stop triggered by a `WithTrainingLossesDo` control. "))

    v = Float64[]
    f2(training_loss) = (push!(v, last(training_loss)); last(v) < 0.5)
    c = WithTrainingLossesDo(f2, stop_if_true=true, stop_message="foo")
    m = SquareRooter(4)
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 1)
    @test !state.done
    @test v == [1.5, ]
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 2, state)
    @test state.done
    @test v ≈ [1.5, 0.45]
    @test IC.takedown(c, 0, state) ==
        (latest_training_loss = v[end],
         done = true,
         log="foo")
end

@testset "WithNumberDo" begin
    v = Int[]
    f(n) = (push!(v, n); last(n) > 1)
    c = WithNumberDo(f)
    m = SquareRooter(4)
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 1)
    @test !state.done
    @test v == [1, ]
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 2, state)
    @test !state.done
    @test v == [1, 2]
    @test IC.takedown(c, 0, state) == (done = false, n = 2, log="")
    @test_logs((:info, r"final number"),
               @test IC.takedown(c, 2, state) == (done = false, n = 2, log=""))

    v = Int[]
    f2(n) = (push!(v, n); last(n) > 1)
    c = WithNumberDo(f2, stop_if_true=true)
    m = SquareRooter(4)
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 1)
    @test !state.done
    @test v == [1, ]
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 2, state)
    @test state.done
    @test v == [1, 2]
    @test IC.takedown(c, 0, state) ==
        (done = true,
         n= 2,
         log="Stop triggered by a `WithNumberDo` control. ")
    @test_logs((:info, r"Stop"),
               @test IC.takedown(c, 1, state) ==
               (done = true,
                n= 2,
                log="Stop triggered by a `WithNumberDo` control. "))
    @test_logs((:info, r"final number"),
               (:info, r"Stop"),
               @test IC.takedown(c, 2, state) ==
               (done = true,
                n= 2,
                log="Stop triggered by a `WithNumberDo` control. "))

    v = Int[]
    f3(n) = (push!(v, n); last(n) > 1)
    c = WithNumberDo(f3, stop_if_true=true, stop_message="foo")
    m = SquareRooter(4)
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 1)
    @test !state.done
    @test v == [1, ]
    IC.train!(m, 1)
    state = IC.update!(c, m, 1, 2, state)
    @test state.done
    @test v == [1, 2]
    @test IC.takedown(c, 0, state) ==
        (done = true,
         n = 2,
         log="foo")
end

@testset "Data" begin
    data = Float64[1.0, -0.9, 0]

    for option in [true, false]
        model = Particle(0.1)
        c = Data(data, stop_when_exhausted=option)

        state = IC.update!(c, model, 0, 1)
        IC.train!(model, 1)
        @test loss(model) ≈ 0.9

        state = IC.update!(c, model, 0, 2, state)
        IC.train!(model, 1)
        @test loss(model) ≈ 0.9

        state = IC.update!(c, model, 0, 3, state)
        IC.train!(model, 1)
        @test loss(model) ≈ 0.0

        @test !IC.done(c, state)

        if option
            state = IC.update!(c, model, 0, 4, state)
            @test IC.done(c, state)
            report = @test_logs (:info, IC.DATA_STOP) IC.takedown(c, 1, state)
            @test report == (done = true, log = IC.DATA_STOP)
        else
            state = @test_logs IC.update!(c, model, 0, 4, state)
            @test !IC.done(c, state)
            report = IC.takedown(c, 1, state)
            @test report == (done = false, log = "")
        end

    end
end

@testset "integration test" begin
    data = repeat([-1, 1], outer=5);

    model = Particle(0.1)
    losses = Float64[]
    callback!(model) = push!(losses, model.position)
    report = IC.train!(model,
                       Data(data),
                       Step(5),
                       Threshold(0.01),
                       TimeLimit(0.0005),
                       Info(loss),
                       Callback(callback!);
                       verbosity=-1)
    @test !report[1][2].done
    @test report[3][2].done
    @test loss(model) < 0.01

    model = Particle(0.1)
    losses = Float64[]
    noise = fill((:info, r""), 33)
    report = @test_logs(noise...,
                        IC.train!(model,
                                  Data(data, stop_when_exhausted=true),
                                  Step(5),
                                  WithNumberDo(),
                                  WithLossDo(),
                                  WithTrainingLossesDo(),
                                  Threshold(0.01),
                                  TimeLimit(0.0005),
                                  Info(loss),
                                  Callback(callback!),
                                  verbosity=-1))
    @test length(losses) == length(data) + 1
    @test loss(model) > 0.01
end


text:
# For visualization
import Graphs: attributes, vertex_index
# Export attributes, these are enumerates as properties for the variables and factors
# REF: http://www.graphviz.org/documentation/
function attributes(v::GraphsNode, g::T)::AttributeDict where T <:GenericIncidenceList
    AttributeDict(
        "label" => v.dfgNode.label,
        "color" => v.dfgNode isa DFGVariable ? "red" : "blue",
        "shape" => v.dfgNode isa DFGVariable ? "ellipse" : "box",
        "fillcolor" => v.dfgNode isa DFGVariable ? "red" : "blue"
        )
end

# This is insanely important - if we don't provide a valid index, the edges don't work correctly.
vertex_index(v::GraphsNode) = v.index

# Accessors
getLabelDict(dfg::GraphsDFG) = dfg.labelDict
getDescription(dfg::GraphsDFG) = dfg.description
setDescription(dfg::GraphsDFG, description::String) = dfg.description = description
getAddHistory(dfg::GraphsDFG) = dfg.addHistory
getSolverParams(dfg::GraphsDFG) = dfg.solverParams
function setSolverParams(dfg::GraphsDFG, solverParams::T) where T <: AbstractParams
    dfg.solverParams = solverParams
end

# Get user, robot, and session "small" data.
getUserData(dfg::GraphsDFG)::Dict{Symbol, String} = return dfg.userData
function setUserData(dfg::GraphsDFG, data::Dict{Symbol, String})::Bool
	dfg.userData = data
	return true
end
getRobotData(dfg::GraphsDFG)::Dict{Symbol, String} = return dfg.robotData
function setRobotData(dfg::GraphsDFG, data::Dict{Symbol, String})::Bool
	dfg.robotData = data
	return true
end
getSessionData(dfg::GraphsDFG)::Dict{Symbol, String} = return dfg.sessionData
function setSessionData(dfg::GraphsDFG, data::Dict{Symbol, String})::Bool
	dfg.sessionData = data
	return true
end

"""
    $(SIGNATURES)
Gets an empty and unique CloudGraphsDFG derived from an existing DFG.
"""
function _getDuplicatedEmptyDFG(dfg::GraphsDFG)::GraphsDFG
    newDfg = GraphsDFG{typeof(dfg.solverParams)}(;
		userId=dfg.userId, robotId=dfg.robotId, sessionId=dfg.sessionId,
		params=deepcopy(dfg.solverParams))
	newDfg.description ="(Copy of) $(dfg.description)"
	return newDfg
end

"""
    $(SIGNATURES)
True if the variable or factor exists in the graph.
"""
function exists(dfg::GraphsDFG, node::N) where N <: DFGNode
    return haskey(dfg.labelDict, node.label)
end
exists(dfg::GraphsDFG, nId::Symbol) = haskey(dfg.labelDict, nId)

"""
    $(SIGNATURES)
Add a DFGVariable to a DFG.
"""
function addVariable!(dfg::GraphsDFG, variable::DFGVariable)::Bool
    if haskey(dfg.labelDict, variable.label)
        error("Variable '$(variable.label)' already exists in the factor graph")
    end
    dfg.nodeCounter += 1
    variable._internalId = dfg.nodeCounter
    v = GraphsNode(dfg.nodeCounter, variable)
    Graphs.add_vertex!(dfg.g, v)
    push!(dfg.labelDict, variable.label=>variable._internalId)
    # Track insertion
    push!(dfg.addHistory, variable.label)

    return true
end

"""
    $(SIGNATURES)
Add a DFGFactor to a DFG.
"""
function addFactor!(dfg::GraphsDFG, variables::Vector{DFGVariable}, factor::DFGFactor)::Bool
    if haskey(dfg.labelDict, factor.label)
        error("Factor '$(factor.label)' already exists in the factor graph")
    end
    for v in variables
        if !(v.label in keys(dfg.labelDict))
            error("Variable '$(v.label)' not found in graph when creating Factor '$(factor.label)'")
        end
    end
    dfg.nodeCounter += 1
    factor._internalId = dfg.nodeCounter
    factor._variableOrderSymbols = map(v->v.label, variables)
    fNode = GraphsNode(dfg.nodeCounter, factor)
    f = Graphs.add_vertex!(dfg.g, fNode)
    # Add index
    push!(dfg.labelDict, factor.label=>factor._internalId)
    # Add the edges...
    for variable in variables
        v = dfg.g.vertices[variable._internalId]
        edge = Graphs.make_edge(dfg.g, v, f)
        Graphs.add_edge!(dfg.g, edge)
    end
    # Track insertion
    # push!(dfg.addHistory, factor.label)

    return true
end

"""
    $(SIGNATURES)
Add a DFGFactor to a DFG.
"""
function addFactor!(dfg::GraphsDFG, variableIds::Vector{Symbol}, factor::DFGFactor)::Bool
    variables = map(vId -> getVariable(dfg, vId), variableIds)
    return addFactor!(dfg, variables, factor)
end

"""
    $(SIGNATURES)
Get a DFGVariable from a DFG using its underlying integer ID.
"""
function getVariable(dfg::GraphsDFG, variableId::Int64)::DFGVariable
    @warn "This may be slow, rather use by getVariable(dfg, label)"
    #TODO: This may be slow (O(n)), can we make it better?
    if !(variableId in values(dfg.labelDict))
        error("Variable ID '$(variableId)' does not exist in the factor graph")
    end
    return dfg.g.vertices[variableId].dfgNode
end

"""
    $(SIGNATURES)
Get a DFGVariable from a DFG using its label.
"""
function getVariable(dfg::GraphsDFG, label::Union{Symbol, String})::DFGVariable
    if typeof(label) == String
        label = Symbol(label)
    end
    if !haskey(dfg.labelDict, label)
        error("Variable label '$(label)' does not exist in the factor graph")
    end
    return dfg.g.vertices[dfg.labelDict[label]].dfgNode
end

"""
    $(SIGNATURES)
Get a DFGFactor from a DFG using its underlying integer ID.
"""
function getFactor(dfg::GraphsDFG, factorId::Int64)::DFGFactor
    @warn "This may be slow, rather use by getFactor(dfg, label)"
    #TODO: This may be slow (O(n)), can we make it better?
    if !(factorId in values(dfg.labelDict))
        error("Factor ID '$(factorId)' does not exist in the factor graph")
    end
    return dfg.g.vertices[factorId].dfgNode
end

"""
    $(SIGNATURES)
Get a DFGFactor from a DFG using its label.
"""
function getFactor(dfg::GraphsDFG, label::Union{Symbol, String})::DFGFactor
    if typeof(label) == String
        label = Symbol(label)
    end
    if !haskey(dfg.labelDict, label)
        error("Factor label '$(label)' does not exist in the factor graph")
    end
    return dfg.g.vertices[dfg.labelDict[label]].dfgNode
end

"""
    $(SIGNATURES)
Update a complete DFGVariable in the DFG.
"""
function updateVariable!(dfg::GraphsDFG, variable::DFGVariable)::DFGVariable
    if !haskey(dfg.labelDict, variable.label)
        error("Variable label '$(variable.label)' does not exist in the factor graph")
    end
    dfg.g.vertices[dfg.labelDict[variable.label]].dfgNode = variable
    return variable
end

"""
    $(SIGNATURES)
Update a complete DFGFactor in the DFG.
"""
function updateFactor!(dfg::GraphsDFG, factor::DFGFactor)::DFGFactor
    if !haskey(dfg.labelDict, factor.label)
        error("Factor label '$(factor.label)' does not exist in the factor graph")
    end
    dfg.g.vertices[dfg.labelDict[factor.label]].dfgNode = factor
    return factor
end

"""
    $(SIGNATURES)
Delete a DFGVariable from the DFG using its label.
"""
function deleteVariable!(dfg::GraphsDFG, label::Symbol)::DFGVariable
    if !haskey(dfg.labelDict, label)
        error("Variable label '$(label)' does not exist in the factor graph")
    end
    variable = dfg.g.vertices[dfg.labelDict[label]].dfgNode
    delete_vertex!(dfg.g.vertices[dfg.labelDict[label]], dfg.g)
    delete!(dfg.labelDict, label)
    return variable
end

"""
    $(SIGNATURES)
Delete a DFGFactor from the DFG using its label.
"""
function deleteFactor!(dfg::GraphsDFG, label::Symbol)::DFGFactor
    if !haskey(dfg.labelDict, label)
        error("Factor label '$(label)' does not exist in the factor graph")
    end
    factor = dfg.g.vertices[dfg.labelDict[label]].dfgNode
    delete_vertex!(dfg.g.vertices[dfg.labelDict[label]], dfg.g)
    delete!(dfg.labelDict, label)
    return factor
end

"""
    $(SIGNATURES)
List the DFGVariables in the DFG.
Optionally specify a label regular expression to retrieves a subset of the variables.
"""
function getVariables(dfg::GraphsDFG, regexFilter::Union{Nothing, Regex}=nothing; tags::Vector{Symbol}=Symbol[])::Vector{DFGVariable}
    variables = map(v -> v.dfgNode, filter(n -> n.dfgNode isa DFGVariable, Graphs.vertices(dfg.g)))
    if regexFilter != nothing
        variables = filter(v -> occursin(regexFilter, String(v.label)), variables)
    end
	if length(tags) > 0
        mask = map(v -> length(intersect(v.tags, tags)) > 0, variables )
        return variables[mask]
    end
	return variables
end

"""
    $(SIGNATURES)
List the DFGFactors in the DFG.
Optionally specify a label regular expression to retrieves a subset of the factors.
"""
function getFactors(dfg::GraphsDFG, regexFilter::Union{Nothing, Regex}=nothing)::Vector{DFGFactor}
	factors = map(v -> v.dfgNode, filter(n -> n.dfgNode isa DFGFactor, Graphs.vertices(dfg.g)))
	if regexFilter != nothing
		factors = filter(f -> occursin(regexFilter, String(f.label)), factors)
	end
	return factors
end

"""
    $(SIGNATURES)
Checks if the graph is fully connected, returns true if so.
"""
function isFullyConnected(dfg::GraphsDFG)::Bool
    return length(Graphs.connected_components(dfg.g)) == 1
end

"""
    $(SIGNATURES)
Retrieve a list of labels of the immediate neighbors around a given variable or factor.
"""
function getNeighbors(dfg::GraphsDFG, node::T; ready::Union{Nothing, Int}=nothing, backendset::Union{Nothing, Int}=nothing)::Vector{Symbol}  where T <: DFGNode
    if !haskey(dfg.labelDict, node.label)
        error("Variable/factor with label '$(node.label)' does not exist in the factor graph")
    end
    vert = dfg.g.vertices[dfg.labelDict[node.label]]
    neighbors = in_neighbors(vert, dfg.g) #Don't use out_neighbors! It enforces directiveness even if we don't want it
    # Additional filtering
    neighbors = ready != nothing ? filter(v -> v.dfgNode.ready == ready, neighbors) : neighbors
    neighbors = backendset != nothing ? filter(v -> v.dfgNode.backendset == backendset, neighbors) : neighbors
    # Variable sorting (order is important)
    if node isa DFGFactor
        order = intersect(node._variableOrderSymbols, map(v->v.dfgNode.label, neighbors))
        return order
    end

    return map(n -> n.dfgNode.label, neighbors)
end
"""
    $(SIGNATURES)
Retrieve a list of labels of the immediate neighbors around a given variable or factor specified by its label.
"""
function getNeighbors(dfg::GraphsDFG, label::Symbol; ready::Union{Nothing, Int}=nothing, backendset::Union{Nothing, Int}=nothing)::Vector{Symbol}  where T <: DFGNode
    if !haskey(dfg.labelDict, label)
        error("Variable/factor with label '$(label)' does not exist in the factor graph")
    end
    vert = dfg.g.vertices[dfg.labelDict[label]]
    neighbors = in_neighbors(vert, dfg.g) #Don't use out_neighbors! It enforces directiveness even if we don't want it
    # Additional filtering
    neighbors = ready != nothing ? filter(v -> v.dfgNode.ready == ready, neighbors) : neighbors
    neighbors = backendset != nothing ? filter(v -> v.dfgNode.backendset == backendset, neighbors) : neighbors
    # Variable sorting when using a factor (function order is important)
    if vert.dfgNode isa DFGFactor
        vert.dfgNode._variableOrderSymbols
        order = intersect(vert.dfgNode._variableOrderSymbols, map(v->v.dfgNode.label, neighbors))
        return order
    end

    return map(n -> n.dfgNode.label, neighbors)
end

# function _copyIntoGraph!(sourceDFG::GraphsDFG, destDFG::GraphsDFG, variableFactorLabels::Vector{Symbol}, includeOrphanFactors::Bool=false)::Nothing
#     # Split into variables and factors
#     verts = map(id -> sourceDFG.g.vertices[sourceDFG.labelDict[id]], variableFactorLabels)
#     sourceVariables = filter(n -> n.dfgNode isa DFGVariable, verts)
#     sourceFactors = filter(n -> n.dfgNode isa DFGFactor, verts)
#
#     # Now we have to add all variables first,
#     for variable in sourceVariables
#         if !haskey(destDFG.labelDict, variable.dfgNode.label)
#             addVariable!(destDFG, deepcopy(variable.dfgNode))
#         end
#     end
#     # And then all factors to the destDFG.
#     for factor in sourceFactors
#         if !haskey(destDFG.labelDict, factor.dfgNode.label)
#             # Get the original factor variables (we need them to create it)
#             neighVarIds = getNeighbors(sourceDFG, factor.dfgNode.label) #OLD: in_neighbors(factor, sourceDFG.g)
#             # Find the labels and associated neighVarIds in our new subgraph
#             factVariables = DFGVariable[]
#             for neighVarId in neighVarIds
#                 if haskey(destDFG.labelDict, neighVarId)
#                     push!(factVariables, getVariable(destDFG, neighVarId))
#                     #otherwise ignore
#                 end
#             end
#
#             # Only if we have all of them should we add it (otherwise strange things may happen on evaluation)
#             if includeOrphanFactors || length(factVariables) == length(neighVarIds)
#                 addFactor!(destDFG, factVariables, deepcopy(factor.dfgNode))
#             end
#         end
#     end
#     return nothing
# end

"""
    $(SIGNATURES)
Retrieve a deep subgraph copy around a given variable or factor.
Optionally provide a distance to specify the number of edges should be followed.
Optionally provide an existing subgraph addToDFG, the extracted nodes will be copied into this graph. By default a new subgraph will be created.
Note: By default orphaned factors (where the subgraph does not contain all the related variables) are not returned. Set includeOrphanFactors to return the orphans irrespective of whether the subgraph contains all the variables.
"""
function getSubgraphAroundNode(dfg::GraphsDFG{P}, node::T, distance::Int64=1, includeOrphanFactors::Bool=false, addToDFG::GraphsDFG=GraphsDFG{P}())::GraphsDFG where {P <: AbstractParams, T <: DFGNode}
    if !haskey(dfg.labelDict, node.label)
        error("Variable/factor with label '$(node.label)' does not exist in the factor graph")
    end

    # Build a list of all unique neighbors inside 'distance'
    neighborList = Dict{Symbol, Any}()
    push!(neighborList, node.label => dfg.g.vertices[dfg.labelDict[node.label]])
    curList = Dict{Symbol, Any}(node.label => dfg.g.vertices[dfg.labelDict[node.label]])
    for dist in 1:distance
        newNeighbors = Dict{Symbol, Any}()
        for (key, node) in curList
            neighbors = in_neighbors(node, dfg.g) #Don't use out_neighbors! It enforces directiveness even if we don't want it
            for neighbor in neighbors
                if !haskey(neighborList, neighbor.dfgNode.label)
                    push!(neighborList, neighbor.dfgNode.label => neighbor)
                    push!(newNeighbors, neighbor.dfgNode.label => neighbor)
                end
            end
        end
        curList = newNeighbors
    end

    # Copy the section of graph we want
    _copyIntoGraph!(dfg, addToDFG, collect(keys(neighborList)), includeOrphanFactors)
    return addToDFG
end

"""
    $(SIGNATURES)
Produces a dot-format of the graph for visualization.
"""
function toDot(dfg::GraphsDFG)::String
	return Graphs.to_dot(dfg.g)
end

ext: jl


text:
using VSL
import Random: rand, rand!
import Test: @test, @test_throws
import Printf: @printf

brng_types = [
    VSL_BRNG_MCG31, VSL_BRNG_R250, VSL_BRNG_MRG32K3A, VSL_BRNG_MCG59, VSL_BRNG_WH, VSL_BRNG_SOBOL, VSL_BRNG_NIEDERR,
    VSL_BRNG_MT19937, VSL_BRNG_MT2203, VSL_BRNG_SFMT19937, VSL_BRNG_NONDETERM, VSL_BRNG_ARS5, VSL_BRNG_PHILOX4X32X10
]

macro test_rand(expr)
    values = @eval $expr
    if isa(values, Array)
        for value in values
            @printf "%.6f\n" value
        end
    else
        @printf "%.6f\n" values
    end
end

test_folder = joinpath(dirname(pathof(VSL)), "..", "test")


text:
module ensp

function ensemble()
    csvs = map(x->joinpath("data",x), readdir("data"))

    ids = Array{ASCIIString,1}[]
    for csv in csvs
        data = readcsv(csv,ASCIIString)
        id = data[2:end,1]
        push!(ids,id)
        info(csv)
    end

    #=
    for i in 1:length(ids)-1
        @assert ids[i] == ids[i+1]
        info("check ids $i")
    end
    =#

    targets = Array{Float64,1}[]
    for csv in csvs
        data = readcsv(csv,ASCIIString)
        target = map(x->parse(Float64,x), data[2:end,2])
        push!(targets, target)
    end

    header = Any["id", "pred"]

    @show size(targets)
    #target = targets .*
    target = zeros(targets[1])
    weights = [0.2 for i=1:5]
    for i in 1:5
        target += targets[i] * weights[i]
    end

    data = vcat(header', hcat(ids[1],target))
    @show data[1:3,:]
    name = "ensemble_5.csv"
    writecsv(name, data)
    run(`gzip -9 $name`)
    info("done")
end

end


text:
for i in ("1", "01", "10", "11", "12", "20", "21")
    f = "cuda$(i)test.jl"
    info(f)
    include("../src/$f")
end

# Sun Aug 28 18:50:55 EEST 2016
#
# julia> Pkg.test("Knet")
# INFO: Testing Knet
# sqrt
#   1.198008 seconds (306.34 k allocations: 6.399 MB)
#   1.488229 seconds (305.40 k allocations: 6.363 MB)
# exp
#   0.765560 seconds (298.98 k allocations: 6.088 MB, 0.36% gc time)
#   1.539859 seconds (298.98 k allocations: 6.088 MB)
# log
#   0.740429 seconds (298.98 k allocations: 6.088 MB, 0.32% gc time)
#   1.654432 seconds (298.98 k allocations: 6.088 MB)
# sin
#   0.782896 seconds (298.98 k allocations: 6.088 MB)
#   1.974086 seconds (298.98 k allocations: 6.088 MB, 0.10% gc time)
# tanh
#   0.949269 seconds (298.98 k allocations: 6.088 MB)
#   1.781415 seconds (298.98 k allocations: 6.088 MB)
# neg
#   0.783159 seconds (298.98 k allocations: 6.088 MB)
#   1.411126 seconds (298.98 k allocations: 6.088 MB)
# inv
#   0.733850 seconds (298.98 k allocations: 6.088 MB, 0.23% gc time)
#   1.469844 seconds (298.98 k allocations: 6.088 MB)
# relu
#   0.734503 seconds (298.98 k allocations: 6.088 MB)
#   1.428948 seconds (298.98 k allocations: 6.088 MB)
# sigm
#   0.782921 seconds (298.98 k allocations: 6.088 MB)
#   1.572042 seconds (298.98 k allocations: 6.088 MB, 0.11% gc time)
# abs
#   0.754620 seconds (298.98 k allocations: 6.088 MB)
#   1.433652 seconds (298.98 k allocations: 6.088 MB, 0.11% gc time)
# abs2
#   0.734661 seconds (298.98 k allocations: 6.088 MB)
#   1.411047 seconds (298.98 k allocations: 6.088 MB)
# add
#   0.742177 seconds (305.10 k allocations: 6.339 MB)
#   1.407227 seconds (305.10 k allocations: 6.339 MB)
# sub
#   0.735742 seconds (298.98 k allocations: 6.088 MB, 0.25% gc time)
#   1.399725 seconds (298.98 k allocations: 6.088 MB)
# mul
#   0.735076 seconds (298.98 k allocations: 6.088 MB)
#   1.399998 seconds (298.98 k allocations: 6.088 MB)
# div
#   0.740939 seconds (298.98 k allocations: 6.088 MB)
#   1.534365 seconds (298.98 k allocations: 6.088 MB, 0.11% gc time)
# pow
#   0.745232 seconds (298.98 k allocations: 6.088 MB)
#   2.975677 seconds (298.98 k allocations: 6.088 MB)
# max
#   0.744763 seconds (298.98 k allocations: 6.088 MB)
#   1.408032 seconds (298.98 k allocations: 6.088 MB)
# min
#   0.743997 seconds (298.98 k allocations: 6.088 MB, 0.22% gc time)
#   1.406233 seconds (298.98 k allocations: 6.088 MB)
# pow
#   0.772437 seconds (305.10 k allocations: 6.339 MB, 0.23% gc time)
#   2.863349 seconds (305.11 k allocations: 6.339 MB)
# pow
#   0.765591 seconds (298.98 k allocations: 6.088 MB)
#   2.856764 seconds (298.98 k allocations: 6.088 MB, 0.06% gc time)
# pow
#   0.766089 seconds (298.98 k allocations: 6.088 MB)
#   2.856661 seconds (298.98 k allocations: 6.088 MB)
# add
#   0.840574 seconds (305.31 k allocations: 6.350 MB)
#   2.068473 seconds (305.32 k allocations: 6.350 MB)
# sub
#   0.833583 seconds (298.98 k allocations: 6.088 MB, 0.30% gc time)
#   2.061315 seconds (298.98 k allocations: 6.088 MB)
# mul
#   0.833596 seconds (298.98 k allocations: 6.088 MB, 0.27% gc time)
#   2.060891 seconds (298.98 k allocations: 6.088 MB)
# div
#   0.837831 seconds (298.98 k allocations: 6.088 MB)
#   2.096731 seconds (298.98 k allocations: 6.088 MB, 0.09% gc time)
# pow
#   0.937980 seconds (298.98 k allocations: 6.088 MB)
#   3.113095 seconds (298.98 k allocations: 6.088 MB, 0.06% gc time)
# max
#   0.836112 seconds (298.98 k allocations: 6.088 MB)
#   2.060026 seconds (298.98 k allocations: 6.088 MB)
# min
#   0.836224 seconds (298.98 k allocations: 6.088 MB)
#   2.059889 seconds (298.98 k allocations: 6.088 MB)
# add
#   0.918067 seconds (310.71 k allocations: 6.592 MB)
#   1.105991 seconds (309.96 k allocations: 6.552 MB)
#   1.283519 seconds (298.98 k allocations: 6.088 MB)
#   1.520820 seconds (298.98 k allocations: 6.088 MB)
#   2.091047 seconds (310.71 k allocations: 6.590 MB, 0.12% gc time)
#   1.444449 seconds (309.95 k allocations: 6.552 MB)
#   1.479492 seconds (298.98 k allocations: 6.088 MB)
#   1.599586 seconds (298.98 k allocations: 6.088 MB)
# sub
#   0.897188 seconds (298.98 k allocations: 6.088 MB, 0.25% gc time)
#   1.091974 seconds (298.98 k allocations: 6.088 MB)
#   1.310290 seconds (298.98 k allocations: 6.088 MB, 0.16% gc time)
#   1.576266 seconds (298.98 k allocations: 6.088 MB)
#   2.078716 seconds (298.98 k allocations: 6.088 MB)
#   1.434182 seconds (298.98 k allocations: 6.088 MB, 0.14% gc time)
#   1.482647 seconds (298.98 k allocations: 6.088 MB)
#   1.600311 seconds (298.98 k allocations: 6.088 MB, 0.11% gc time)
# mul
#   0.899609 seconds (298.98 k allocations: 6.088 MB)
#   1.091918 seconds (298.98 k allocations: 6.088 MB)
#   1.310454 seconds (298.98 k allocations: 6.088 MB, 0.14% gc time)
#   1.573036 seconds (298.98 k allocations: 6.088 MB)
#   2.078040 seconds (298.98 k allocations: 6.088 MB)
#   1.433069 seconds (298.98 k allocations: 6.088 MB)
#   1.478995 seconds (298.98 k allocations: 6.088 MB)
#   1.599840 seconds (298.98 k allocations: 6.088 MB, 0.15% gc time)
# div
#   0.898756 seconds (298.98 k allocations: 6.088 MB)
#   1.076243 seconds (298.98 k allocations: 6.088 MB)
#   1.273588 seconds (298.98 k allocations: 6.088 MB)
#   1.535823 seconds (298.98 k allocations: 6.088 MB)
#   2.122483 seconds (298.98 k allocations: 6.088 MB, 0.09% gc time)
#   1.561032 seconds (298.98 k allocations: 6.088 MB)
#   1.649356 seconds (298.98 k allocations: 6.088 MB)
#   1.834667 seconds (298.98 k allocations: 6.088 MB)
# pow
#   0.977069 seconds (298.98 k allocations: 6.088 MB)
#   1.097480 seconds (298.98 k allocations: 6.088 MB, 0.15% gc time)
#   1.308324 seconds (298.98 k allocations: 6.088 MB)
#   1.570295 seconds (298.98 k allocations: 6.088 MB)
#   3.174292 seconds (298.98 k allocations: 6.088 MB, 0.06% gc time)
#   3.131706 seconds (298.98 k allocations: 6.088 MB)
#   3.351452 seconds (298.98 k allocations: 6.088 MB, 0.05% gc time)
#   3.524023 seconds (298.98 k allocations: 6.088 MB)
# max
#   0.904631 seconds (298.98 k allocations: 6.088 MB)
#   1.078452 seconds (298.98 k allocations: 6.088 MB)
#   1.278753 seconds (298.98 k allocations: 6.088 MB)
#   1.542877 seconds (298.98 k allocations: 6.088 MB, 0.11% gc time)
#   2.083428 seconds (298.98 k allocations: 6.088 MB)
#   1.434031 seconds (298.98 k allocations: 6.088 MB)
#   1.477127 seconds (298.98 k allocations: 6.088 MB)
#   1.599171 seconds (298.98 k allocations: 6.088 MB)
# min
#   0.904480 seconds (298.98 k allocations: 6.088 MB, 0.18% gc time)
#   1.078259 seconds (298.98 k allocations: 6.088 MB)
#   1.278503 seconds (298.98 k allocations: 6.088 MB)
#   1.542717 seconds (298.98 k allocations: 6.088 MB, 0.12% gc time)
#   2.083427 seconds (298.98 k allocations: 6.088 MB)
#   1.434334 seconds (298.98 k allocations: 6.088 MB, 0.12% gc time)
#   1.478011 seconds (298.98 k allocations: 6.088 MB)
#   1.599201 seconds (298.98 k allocations: 6.088 MB)
# add
#   2.964730 seconds (404.17 k allocations: 7.825 MB)
#   3.059696 seconds (404.17 k allocations: 7.825 MB)
# add
#   2.958643 seconds (398.98 k allocations: 7.614 MB, 0.07% gc time)
#   3.053202 seconds (398.98 k allocations: 7.614 MB)
# mul
#   2.956758 seconds (398.98 k allocations: 7.614 MB, 0.07% gc time)
#   3.041163 seconds (398.98 k allocations: 7.614 MB)
# mul
#   2.954647 seconds (398.98 k allocations: 7.614 MB)
#   3.045259 seconds (398.98 k allocations: 7.614 MB, 0.07% gc time)
# max
#   2.989635 seconds (398.98 k allocations: 7.614 MB)
#   3.085604 seconds (398.98 k allocations: 7.614 MB, 0.07% gc time)
# max
#   2.981864 seconds (398.98 k allocations: 7.614 MB)
#   3.084174 seconds (398.98 k allocations: 7.614 MB)
# min
#   2.985036 seconds (398.98 k allocations: 7.614 MB, 0.07% gc time)
#   3.081865 seconds (398.98 k allocations: 7.614 MB)
# min
#   2.989373 seconds (398.98 k allocations: 7.614 MB, 0.07% gc time)
#   3.082634 seconds (398.98 k allocations: 7.614 MB)
# sum1
#   2.953099 seconds (398.98 k allocations: 7.614 MB)
#   3.045103 seconds (398.98 k allocations: 7.614 MB, 0.06% gc time)
# sum1
#   2.950990 seconds (398.98 k allocations: 7.614 MB)
#   3.051839 seconds (398.98 k allocations: 7.614 MB)
# sum2
#   2.952941 seconds (398.98 k allocations: 7.614 MB, 0.07% gc time)
#   3.045060 seconds (398.98 k allocations: 7.614 MB)
# sum2
#   2.960478 seconds (398.98 k allocations: 7.614 MB, 0.06% gc time)
#   3.046865 seconds (398.98 k allocations: 7.614 MB)
# nnz
#   2.977525 seconds (398.98 k allocations: 7.614 MB)
#   3.085898 seconds (398.98 k allocations: 7.614 MB, 0.06% gc time)
# nnz
#   2.984819 seconds (398.98 k allocations: 7.614 MB)
#   3.083199 seconds (398.98 k allocations: 7.614 MB)
# INFO: Knet tests passed


text:
const _active_kernels = IdDict{HSAQueue,Vector{AMDGPU.HSAStatusSignal}}()

"""
    active_kernels(queue) -> Vector{AMDGPU.HSAStatusSignal}

Returns the set of actively-executing kernels on `queue`.
"""
function active_kernels(queue::HSAQueue=get_default_queue())
    lock(RT_LOCK) do
        copy(_active_kernels[queue])
    end
end
active_kernels(queue::RuntimeQueue) = active_kernels(queue.queue)


text:
"""
Module ParallelRandomFields

Enables to sample spatial realisations of 2D and 3D Gaussian random fields with given power spectrum using following covariance functions:
- anisotropic exponential
- isotropic Gaussian

ParallelRandomFields can be deployed on both (multi-) GPUs and CPUs.

# General overview and examples
https://github.com/luraess/ParallelRandomFields.jl

Functions are defined in the submodules.

# Submodules
- [`ParallelRandomFields.grf2D_Threads`](@ref)
- [`ParallelRandomFields.grf3D_Threads`](@ref)
- [`ParallelRandomFields.grf2D_CUDA`](@ref)
- [`ParallelRandomFields.grf3D_CUDA`](@ref)

To see a description of a function type `?<functionname>`.
"""
module ParallelRandomFields

using ParallelStencil

# include submodules and reset ParallelStencil prior
include("grf2D_Threads.jl")

ParallelStencil.@reset_parallel_stencil()
include("grf3D_Threads.jl")

ParallelStencil.@reset_parallel_stencil()
include("grf2D_CUDA.jl")

ParallelStencil.@reset_parallel_stencil()
include("grf3D_CUDA.jl")

ParallelStencil.@reset_parallel_stencil()

end # Module ParallelRandomFields


text:
int
main()
{
  printDouble(power(3.14, 7));
  printDouble(power(2.46, 4));
  return 0;
}

/* complexity: O(log(b)) */
double power(double a, int b)
{
  double ta;
  double tb;

  if (b != 0)
  {
      ta = a;
      tb = 1.0;
      while (b > 1)
      {
         if ((b % 2) != 0)
         {
            tb = tb * ta;
         }
         ta = ta * ta;
         b = b / 2;
      }
      return ta * tb;
   }
   else
   {
      return 1.0;
   }
}

max_stars_repo_name: lukaszcz/javalette

max_issues_repo_name: lukaszcz/javalette

max_forks_repo_name: lukaszcz/javalette


text:
# Plot birthweight vs body length for CPP data (collaborative perinatal project data)

module CPPdata
using PyPlot
draw() = (pause(0.001); get_current_fig_manager()[:window][:raise_]())

target_name = "V_BWGT"
predictor_name = "V_BLENG"

# Load data
data = readdlm("datasets/CPP/finaldde.csv",',')
names = vec(data[1,:])
values = data[2:end,:]
target_index = findfirst(names.==target_name)
predictor_index = findfirst(names.==predictor_name)
missing = vec(maximum(values[:,[predictor_index,target_index]].==".", 2))
y = convert(Array{Float64,1}, values[!missing,target_index])
x = convert(Array{Float64,1}, values[!missing,predictor_index])
n = length(y)
println("Removed $(sum(missing)) records with missing entries.")

# Plot
figure(1,figsize=(8,3.2)); clf(); hold(true)
subplots_adjust(bottom=0.2)
plot(x,y/1000,"b.",markersize=3)
xlabel("\$\\mathrm{body\\,length\\,(cm)}\$",fontsize=17)
ylabel("\$\\mathrm{birthweight\\,(kg)}\$",fontsize=16)
xlim(35,60)
draw()
savefig("cpp-length-vs-weight.png",dpi=150)

end # module

max_stars_repo_name: jwmi/CoarsenedPosterior

max_issues_repo_name: jwmi/CoarsenedPosterior

max_forks_repo_name: jwmi/CoarsenedPosterior


text:
# This file was generated, do not modify it. # hide
boston3 = coerce(boston, autotype(boston, :few_to_finite))
schema(boston3)


text:
module aws_cloudfront
include("AWSCorePrototypeServices.jl")
using .AWSCorePrototypeServices: cloudfront

"""
List CloudFront distributions.
"""
ListDistributions2019_03_26() = cloudfront("GET", "/2019-03-26/distribution")

"""
Add tags to a CloudFront resource.
"""
TagResource2019_03_26(Resource, Tags) = cloudfront("POST", "/2019-03-26/tagging?Operation=Tag")

"""
Get the field-level encryption profile configuration information.
"""
GetFieldLevelEncryptionProfileConfig2019_03_26(Id) = cloudfront("GET", "/2019-03-26/field-level-encryption-profile/$Id/config")

"""
Get the information about an origin access identity.
"""
GetCloudFrontOriginAccessIdentity2019_03_26(Id) = cloudfront("GET", "/2019-03-26/origin-access-identity/cloudfront/$Id")

"""
Get the configuration information about a distribution.
"""
GetDistributionConfig2019_03_26(Id) = cloudfront("GET", "/2019-03-26/distribution/$Id/config")

"""
Get the information about a distribution.
"""
GetDistribution2019_03_26(Id) = cloudfront("GET", "/2019-03-26/distribution/$Id")

"""
Return public key configuration informaation
"""
GetPublicKeyConfig2019_03_26(Id) = cloudfront("GET", "/2019-03-26/public-key/$Id/config")

"""
Update an origin access identity.
"""
UpdateCloudFrontOriginAccessIdentity2019_03_26(CloudFrontOriginAccessIdentityConfig, Id) = cloudfront("PUT", "/2019-03-26/origin-access-identity/cloudfront/$Id/config")

"""
Create a new distribution with tags.
"""
CreateDistributionWithTags2019_03_26(DistributionConfigWithTags) = cloudfront("POST", "/2019-03-26/distribution?WithTags")

"""
Creates a new web distribution. You create a CloudFront distribution to tell CloudFront where you want content to be delivered from, and the details about how to track and manage content delivery. Send a POST request to the /CloudFront API version/distribution/distribution ID resource.  When you update a distribution, there are more required fields than when you create a distribution. When you update your distribution by using UpdateDistribution, follow the steps included in the documentation to get the current configuration and then make your updates. This helps to make sure that you include all of the required fields. To view a summary, see Required Fields for Create Distribution and Update Distribution in the Amazon CloudFront Developer Guide.
"""
CreateDistribution2019_03_26(DistributionConfig) = cloudfront("POST", "/2019-03-26/distribution")

"""
Get the public key information.
"""
GetPublicKey2019_03_26(Id) = cloudfront("GET", "/2019-03-26/public-key/$Id")

"""
Create a new field-level encryption configuration.
"""
CreateFieldLevelEncryptionConfig2019_03_26(FieldLevelEncryptionConfig) = cloudfront("POST", "/2019-03-26/field-level-encryption")

"""
List streaming distributions.
"""
ListStreamingDistributions2019_03_26() = cloudfront("GET", "/2019-03-26/streaming-distribution")

"""
Get the configuration information about a streaming distribution.
"""
GetStreamingDistributionConfig2019_03_26(Id) = cloudfront("GET", "/2019-03-26/streaming-distribution/$Id/config")

"""
Update a field-level encryption configuration.
"""
UpdateFieldLevelEncryptionConfig2019_03_26(FieldLevelEncryptionConfig, Id) = cloudfront("PUT", "/2019-03-26/field-level-encryption/$Id/config")

"""
Gets information about a specified RTMP distribution, including the distribution configuration.
"""
GetStreamingDistribution2019_03_26(Id) = cloudfront("GET", "/2019-03-26/streaming-distribution/$Id")

"""
Remove a field-level encryption profile.
"""
DeleteFieldLevelEncryptionProfile2019_03_26(Id) = cloudfront("DELETE", "/2019-03-26/field-level-encryption-profile/$Id")

"""
Get the configuration information about an origin access identity.
"""
GetCloudFrontOriginAccessIdentityConfig2019_03_26(Id) = cloudfront("GET", "/2019-03-26/origin-access-identity/cloudfront/$Id/config")

"""
Get the field-level encryption configuration information.
"""
GetFieldLevelEncryptionConfig2019_03_26(Id) = cloudfront("GET", "/2019-03-26/field-level-encryption/$Id/config")

"""
Remove tags from a CloudFront resource.
"""
UntagResource2019_03_26(Resource, TagKeys) = cloudfront("POST", "/2019-03-26/tagging?Operation=Untag")

"""
Creates a new RTMP distribution. An RTMP distribution is similar to a web distribution, but an RTMP distribution streams media files using the Adobe Real-Time Messaging Protocol (RTMP) instead of serving files using HTTP.  To create a new distribution, submit a POST request to the CloudFront API version/distribution resource. The request body must include a document with a StreamingDistributionConfig element. The response echoes the StreamingDistributionConfig element and returns other information about the RTMP distribution. To get the status of your request, use the GET StreamingDistribution API action. When the value of Enabled is true and the value of Status is Deployed, your distribution is ready. A distribution usually deploys in less than 15 minutes. For more information about web distributions, see Working with RTMP Distributions in the Amazon CloudFront Developer Guide.  Beginning with the 2012-05-05 version of the CloudFront API, we made substantial changes to the format of the XML document that you include in the request body when you create or update a web distribution or an RTMP distribution, and when you invalidate objects. With previous versions of the API, we discovered that it was too easy to accidentally delete one or more values for an element that accepts multiple values, for example, CNAMEs and trusted signers. Our changes for the 2012-05-05 release are intended to prevent these accidental deletions and to notify you when there's a mismatch between the number of values you say you're specifying in the Quantity element and the number of values specified.
"""
CreateStreamingDistribution2019_03_26(StreamingDistributionConfig) = cloudfront("POST", "/2019-03-26/streaming-distribution")

"""
Remove a field-level encryption configuration.
"""
DeleteFieldLevelEncryptionConfig2019_03_26(Id) = cloudfront("DELETE", "/2019-03-26/field-level-encryption/$Id")

"""
Creates a new origin access identity. If you're using Amazon S3 for your origin, you can use an origin access identity to require users to access your content using a CloudFront URL instead of the Amazon S3 URL. For more information about how to use origin access identities, see Serving Private Content through CloudFront in the Amazon CloudFront Developer Guide.
"""
CreateCloudFrontOriginAccessIdentity2019_03_26(CloudFrontOriginAccessIdentityConfig) = cloudfront("POST", "/2019-03-26/origin-access-identity/cloudfront")

"""
Get the field-level encryption configuration information.
"""
GetFieldLevelEncryption2019_03_26(Id) = cloudfront("GET", "/2019-03-26/field-level-encryption/$Id")

"""
Lists origin access identities.
"""
ListCloudFrontOriginAccessIdentities2019_03_26() = cloudfront("GET", "/2019-03-26/origin-access-identity/cloudfront")

"""
Delete an origin access identity.
"""
DeleteCloudFrontOriginAccessIdentity2019_03_26(Id) = cloudfront("DELETE", "/2019-03-26/origin-access-identity/cloudfront/$Id")

"""
List all field-level encryption configurations that have been created in CloudFront for this account.
"""
ListFieldLevelEncryptionConfigs2019_03_26() = cloudfront("GET", "/2019-03-26/field-level-encryption")

"""
Create a new streaming distribution with tags.
"""
CreateStreamingDistributionWithTags2019_03_26(StreamingDistributionConfigWithTags) = cloudfront("POST", "/2019-03-26/streaming-distribution?WithTags")

"""
List tags for a CloudFront resource.
"""
ListTagsForResource2019_03_26(Resource) = cloudfront("GET", "/2019-03-26/tagging")

"""
List all public keys that have been added to CloudFront for this account.
"""
ListPublicKeys2019_03_26() = cloudfront("GET", "/2019-03-26/public-key")

"""
Get the information about an invalidation.
"""
GetInvalidation2019_03_26(DistributionId, Id) = cloudfront("GET", "/2019-03-26/distribution/$DistributionId/invalidation/$Id")

"""
Lists invalidation batches.
"""
ListInvalidations2019_03_26(DistributionId) = cloudfront("GET", "/2019-03-26/distribution/$DistributionId/invalidation")

"""
Add a new public key to CloudFront to use, for example, for field-level encryption. You can add a maximum of 10 public keys with one AWS account.
"""
CreatePublicKey2019_03_26(PublicKeyConfig) = cloudfront("POST", "/2019-03-26/public-key")

"""
Delete a distribution.
"""
DeleteDistribution2019_03_26(Id) = cloudfront("DELETE", "/2019-03-26/distribution/$Id")

"""
List the distributions that are associated with a specified AWS WAF web ACL.
"""
ListDistributionsByWebACLId2019_03_26(WebACLId) = cloudfront("GET", "/2019-03-26/distributionsByWebACLId/$WebACLId")

"""
Update a streaming distribution.
"""
UpdateStreamingDistribution2019_03_26(StreamingDistributionConfig, Id) = cloudfront("PUT", "/2019-03-26/streaming-distribution/$Id/config")

"""
Remove a public key you previously added to CloudFront.
"""
DeletePublicKey2019_03_26(Id) = cloudfront("DELETE", "/2019-03-26/public-key/$Id")

"""
Create a new invalidation.
"""
CreateInvalidation2019_03_26(DistributionId, InvalidationBatch) = cloudfront("POST", "/2019-03-26/distribution/$DistributionId/invalidation")

"""
Update a field-level encryption profile.
"""
UpdateFieldLevelEncryptionProfile2019_03_26(FieldLevelEncryptionProfileConfig, Id) = cloudfront("PUT", "/2019-03-26/field-level-encryption-profile/$Id/config")

"""
Get the field-level encryption profile information.
"""
GetFieldLevelEncryptionProfile2019_03_26(Id) = cloudfront("GET", "/2019-03-26/field-level-encryption-profile/$Id")

"""
Request a list of field-level encryption profiles that have been created in CloudFront for this account.
"""
ListFieldLevelEncryptionProfiles2019_03_26() = cloudfront("GET", "/2019-03-26/field-level-encryption-profile")

"""
Update public key information. Note that the only value you can change is the comment.
"""
UpdatePublicKey2019_03_26(PublicKeyConfig, Id) = cloudfront("PUT", "/2019-03-26/public-key/$Id/config")

"""
Updates the configuration for a web distribution.   When you update a distribution, there are more required fields than when you create a distribution. When you update your distribution by using this API action, follow the steps here to get the current configuration and then make your updates, to make sure that you include all of the required fields. To view a summary, see Required Fields for Create Distribution and Update Distribution in the Amazon CloudFront Developer Guide.  The update process includes getting the current distribution configuration, updating the XML document that is returned to make your changes, and then submitting an UpdateDistribution request to make the updates. For information about updating a distribution using the CloudFront console instead, see Creating a Distribution in the Amazon CloudFront Developer Guide.  To update a web distribution using the CloudFront API    Submit a GetDistributionConfig request to get the current configuration and an Etag header for the distribution.  If you update the distribution again, you must get a new Etag header.    Update the XML document that was returned in the response to your GetDistributionConfig request to include your changes.   When you edit the XML file, be aware of the following:   You must strip out the ETag parameter that is returned.   Additional fields are required when you update a distribution. There may be fields included in the XML file for features that you haven't configured for your distribution. This is expected and required to successfully update the distribution.   You can't change the value of CallerReference. If you try to change this value, CloudFront returns an IllegalUpdate error.    The new configuration replaces the existing configuration; the values that you specify in an UpdateDistribution request are not merged into your existing configuration. When you add, delete, or replace values in an element that allows multiple values (for example, CNAME), you must specify all of the values that you want to appear in the updated distribution. In addition, you must update the corresponding Quantity element.      Submit an UpdateDistribution request to update the configuration for your distribution:   In the request body, include the XML document that you updated in Step 2. The request body must include an XML document with a DistributionConfig element.   Set the value of the HTTP If-Match header to the value of the ETag header that CloudFront returned when you submitted the GetDistributionConfig request in Step 1.     Review the response to the UpdateDistribution request to confirm that the configuration was successfully updated.   Optional: Submit a GetDistribution request to confirm that your changes have propagated. When propagation is complete, the value of Status is Deployed.
"""
UpdateDistribution2019_03_26(DistributionConfig, Id) = cloudfront("PUT", "/2019-03-26/distribution/$Id/config")

"""
Delete a streaming distribution. To delete an RTMP distribution using the CloudFront API, perform the following steps.  To delete an RTMP distribution using the CloudFront API:   Disable the RTMP distribution.   Submit a GET Streaming Distribution Config request to get the current configuration and the Etag header for the distribution.    Update the XML document that was returned in the response to your GET Streaming Distribution Config request to change the value of Enabled to false.   Submit a PUT Streaming Distribution Config request to update the configuration for your distribution. In the request body, include the XML document that you updated in Step 3. Then set the value of the HTTP If-Match header to the value of the ETag header that CloudFront returned when you submitted the GET Streaming Distribution Config request in Step 2.   Review the response to the PUT Streaming Distribution Config request to confirm that the distribution was successfully disabled.   Submit a GET Streaming Distribution Config request to confirm that your changes have propagated. When propagation is complete, the value of Status is Deployed.   Submit a DELETE Streaming Distribution request. Set the value of the HTTP If-Match header to the value of the ETag header that CloudFront returned when you submitted the GET Streaming Distribution Config request in Step 2.   Review the response to your DELETE Streaming Distribution request to confirm that the distribution was successfully deleted.   For information about deleting a distribution using the CloudFront console, see Deleting a Distribution in the Amazon CloudFront Developer Guide.
"""
DeleteStreamingDistribution2019_03_26(Id) = cloudfront("DELETE", "/2019-03-26/streaming-distribution/$Id")

"""
Create a field-level encryption profile.
"""
CreateFieldLevelEncryptionProfile2019_03_26(FieldLevelEncryptionProfileConfig) = cloudfront("POST", "/2019-03-26/field-level-encryption-profile")
end

max_stars_repo_name: nicoleepp/JuliaCloud-AWS-State

max_issues_repo_name: nicoleepp/JuliaCloud-AWS-State

max_forks_repo_name: nicoleepp/JuliaCloud-AWS-State


text:
include(normpath(joinpath(@__DIR__, "EMOM", "src", "models", "EMOM", "EMOM.jl")))

using NCDatasets

println("Processing data...")

domain_file = "domain.ocn_aqua.fv4x5_gx3v7.091218.nc"
zdomain_file = ""
topo_file = ""
output_file = "ocn_init.nc"
Dataset(domain_file, "r") do ds
    global Nx = ds.dim["ni"]
    global Ny = ds.dim["nj"]
    global mask = convert(Array{Float64}, replace(ds["mask"][:], missing=>NaN))
end

mask .= 1.0
mask[:, 1]   .= 0.0
mask[:, end] .= 0.0

if zdomain_file != ""
    Dataset(zdomain_file, "r") do ds
        global zs  = replace(ds["zs"][:], missing=>NaN)
    end
else
    zs = collect(Float64, 0:-50:-1000)
end

if topo_file != ""
    Dataset(topo_file, "r") do ds
        global topo  = - replace(ds["depth"][:], missing=>NaN)
    end
else
    global topo = nothing
end

ocn = EMOM.Ocean(
    gridinfo_file = domain_file,
    Nx       = Nx,
    Ny       = Ny,
    zs_bone  = zs,
    Ts       = 15.0,
    Ss       = 35.0,
    T_ML     = 15.0,
    S_ML     = 35.0,
    h_ML     = 10.0,
    h_ML_min = 10.0,
    h_ML_max = 1e5,             # make it unrestricted
    topo     = topo,
    Ts_clim_relax_time = 86400.0 * 10,
    Ts_clim            = nothing, #Ts_clim,
    Ss_clim_relax_time = 86400.0 * 10,
    Ss_clim            = nothing, #Ss_clim,
    arrange  = :xyz,
    do_convective_adjustment = true,
)

EMOM.takeSnapshot(ocn, output_file)

max_stars_repo_name: meteorologytoday/IOM

max_issues_repo_name: meteorologytoday/IOM

max_forks_repo_name: meteorologytoday/IOM


text:
# check Thikonov proximal map
function testL2Prox(N=1024; numPeaks=5, λ=0.01)
  @info "test L2-regularization"
  Random.seed!(1234)
  x = zeros(N)
  for i=1:numPeaks
    x[rand(1:N)] = rand()
  end

  # x_l2 = 1. / (1. + 2. *λ)*x
  x_l2 = copy(x)
  proxL2!(x_l2,λ)
  @test norm(x_l2 - 1.0/(1.0+2.0*λ)*x) / norm(1.0/(1.0+2.0*λ)*x) ≈ 0 atol=0.001
  # check decrease of objective function
  @test 0.5*norm(x-x_l2)^2 + normL2(x_l2,λ) <= normL2(x,λ)
end

# denoise a signal consisting of a number of delta peaks
function testL1Prox(N=1024; numPeaks=5, σ=0.03)
  @info "test L1-regularization"
  Random.seed!(1234)
  x = zeros(N)
  for i=1:numPeaks
    x[rand(1:N)] = (1-2*σ)*rand()+2*σ
  end

  σ = sum(abs.(x))/length(x)*σ
  xNoisy = x .+ σ/sqrt(2.0)*(randn(N)+1im*randn(N))

  x_l1 = copy(xNoisy)
  proxL1!(x_l1, 2*σ)

  # solution should be better then without denoising
  @info "rel. L1 error : $(norm(x - x_l1)/ norm(x)) vs $(norm(x - xNoisy)/ norm(x))"
  @test norm(x - x_l1) <= norm(x - xNoisy)
  @test norm(x - x_l1) / norm(x) ≈ 0 atol=0.1
  # check decrease of ojective function
  @test 0.5*norm(xNoisy-x_l1)^2+normL1(x_l1,2*σ) <= normL1(xNoisy,2*σ)
end

# denoise a signal consisting  of multiple slices with delta peaks at the same locations
# only the last slices are noisy.
# Thus, the first slices serve as a reference to inhance denoising
function testL21Prox(N=1024; numPeaks=5, numSlices=8, noisySlices=2, σ=0.05)
  @info "test L21-regularization"
  Random.seed!(1234)
  x = zeros(ComplexF64,N,numSlices)
  for i=1:numPeaks
    x[rand(1:N),:] = (1-2*σ)*rand(numSlices) .+ 2*σ
  end
  x = vec(x)

  xNoisy = copy(x)
  noise = randn(N*noisySlices)
  σ = sum(abs.(x))/length(x)*σ
  xNoisy[(numSlices-noisySlices)*N+1:end] .+= σ/sqrt(2.0)*(randn(N*noisySlices)+1im*randn(N*noisySlices)) #noise

  x_l1 = copy(xNoisy)
  proxL1!(x_l1, 2*σ)

  x_l21 = copy(xNoisy)
  proxL21!(x_l21, 2*σ,slices=numSlices)

  # solution should be better then without denoising and with l1-denoising
  @info "rel. L21 error : $(norm(x - x_l21)/ norm(x)) vs $(norm(x - xNoisy)/ norm(x))"
  @test norm(x - x_l21) <= norm(x - xNoisy)
  @test norm(x - x_l21) <= norm(x - x_l1)
  @test norm(x - x_l21) / norm(x) ≈ 0 atol=0.05
  # check decrease of objective function
  @test 0.5*norm(xNoisy-x_l21)^2+normL21(x_l21,2*σ,slices=numSlices) <= normL21(xNoisy,2*σ,slices=numSlices)
  @test 0.5*norm(xNoisy-x_l21)^2+normL21(x_l21,2*σ,slices=numSlices) <= 0.5*norm(xNoisy-x_l1)^2+normL21(x_l1,2*σ,slices=numSlices)
end

# denoise a piece-wise constant signal using TV regularization
function testTVprox(N=1024; numEdges=5, σ=0.05)
  @info "test TV-regularization"
  Random.seed!(1234)
  x = zeros(ComplexF64,N,N)
  for i=1:numEdges
    idx1 = rand(0:N-1)
    idx2 = rand(0:N-1)
    x[idx1+1:end, idx2+1:end] .+= randn()
  end
  x= vec(x)

  xNoisy = copy(x)
  σ = sum(abs.(x))/length(x)*σ
  xNoisy[:] += σ/sqrt(2.0)*(randn(N*N)+1im*randn(N*N))

  x_l1 = copy(xNoisy)
  proxL1!(x_l1, 2*σ)

  x_tv = copy(xNoisy)
  proxTV!(x_tv, 2*σ, shape=(N,N))

  @info "rel. TV error : $(norm(x - x_tv)/ norm(x)) vs $(norm(x - xNoisy)/ norm(x))"
  @test norm(x - x_tv) <= norm(x - xNoisy)
  @test norm(x - x_tv) <= norm(x - x_l1)
  @test norm(x - x_tv) / norm(x) ≈ 0 atol=0.05
  # check decrease of objective function
  @test 0.5*norm(xNoisy-x_tv)^2+normTV(x_tv,2*σ,shape=(N,N)) <= normTV(xNoisy,2*σ,shape=(N,N))
  @test 0.5*norm(xNoisy-x_tv)^2+normTV(x_tv,2*σ,shape=(N,N)) <= 0.5*norm(xNoisy-x_l1)^2+normTV(x_l1,2*σ,shape=(N,N))
end

# test enforcement of positivity constraint
function testPositive(N=1024)
  @info "test positivity-constraint"
  Random.seed!(1234)
  x = randn(N) .+ 1im*randn(N)
  xPos = real.(x)
  xPos[findall(x->x<0,xPos)] .= 0
  xProj = copy(x)
  proxPositive!(xProj)

  @test norm(xProj-xPos)/norm(xPos) ≈ 0 atol=1.e-4
  # check decrease of objective function
  @test 0.5*norm(x-xProj)^2+normPositive(xProj) <= normPositive(x)
end

# test enforcement of "realness"-constraint
function testProj(N=1012)
  @info "test realness-constraint"
  Random.seed!(1234)
  x = randn(N) .+ 1im*randn(N)
  xReal = real.(x)
  xProj = copy(x)
  proxProj!(xProj,0.0,projFunc=x->real(x))
  @test norm(xProj-xReal)/norm(xReal) ≈ 0 atol=1.e-4
  # check decrease of objective function
  @test 0.5*norm(x-xProj)^2+normProj(xProj,projFunc=x->real(x)) <= normProj(x,projFunc=x->real(x))
end

# test denoising of a low-rank matrix
function testNuclear(N=32,rank=2;σ=0.05)
  @info "test nuclear norm regularization"
  Random.seed!(1234)
  x = zeros(ComplexF64,N,N);
  for i=1:rank
    x[:,i] = (0.3+0.7*randn())*cos.(2*pi/N*rand(1:div(N,4))*collect(1:N));
  end
  for i=rank+1:N
    for j=1:rank
      x[:,i] .+= rand()*x[:,j];
    end
  end
  x = vec(x)

  σ = sum(abs.(x))/length(x)*σ
  xNoisy = copy(x)
  xNoisy[:] += σ/sqrt(2.0)*(randn(N*N)+1im*randn(N*N))

  x_lr = copy(xNoisy)
  proxNuclear!(x_lr,5*σ,svtShape=(32,32))
  @test norm(x - x_lr) <= norm(x - xNoisy)
  @test norm(x - x_lr) / norm(x) ≈ 0 atol=0.05
  @info "rel. LR error : $(norm(x - x_lr)/ norm(x)) vs $(norm(x - xNoisy)/ norm(x))"
  # check decreas of objective function
  @test 0.5*norm(xNoisy-x_lr)^2+normNuclear(x_lr,5*σ,svtShape=(N,N)) <= normNuclear(xNoisy,5*σ,svtShape=(N,N))
end

function testLLR(shape=(32,32,80),blockSize=[4,4];σ=0.05)
  @info "test LLR regularization"
  Random.seed!(1234)
  x = zeros(ComplexF64,shape);
  for j=1:div(shape[2],blockSize[2])
    for i=1:div(shape[1],blockSize[1])
      ampl = rand()
      r = rand()
      for t=1:shape[3]
        x[(i-1)*blockSize[1]+1:i*blockSize[1],(j-1)*blockSize[2]+1:j*blockSize[2],t] .= ampl*exp.(-r*t)
      end
    end
  end
  x = vec(x)

  xNoisy = copy(x)
  σ = sum(abs.(x))/length(x)*σ
  xNoisy[:] += σ/sqrt(2.0)*(randn(prod(shape))+1im*randn(prod(shape)))

  x_llr = copy(xNoisy)
  proxLLR!(x_llr,10*σ,shape=shape[1:2],blockSize=blockSize,randshift=false)
  @test norm(x - x_llr) <= norm(x - xNoisy)
  @test norm(x - x_llr) / norm(x) ≈ 0 atol=0.05
  @info "rel. LLR error : $(norm(x - x_llr)/ norm(x)) vs $(norm(x - xNoisy)/ norm(x))"
  # check decreas of objective function
  @test 0.5*norm(xNoisy-x_llr)^2+normLLR(x_llr,10*σ,shape=shape[1:2],blockSize=blockSize,randshift=false) <= normLLR(xNoisy,10*σ,shape=shape[1:2],blockSize=blockSize,randshift=false)
end

@testset "Proximal Maps" begin
  testL2Prox()
  testL1Prox()
  testL21Prox()
  testTVprox()
  testPositive()
  testProj()
  testNuclear()
  testLLR()
end


text:
using Luxor
using UUIDs

##
# No padding: Padding can be achived using margins of the inner element
##

##
# Everything visual is an aggregation of
# Label, grid lines, and Cell primitives.
##

##
# Grid is a simpler structure than Table.
# - All rows sizes are equal to all column sizes.
# - Row/col size is predetermined (i.e., doesn't depend on any cell content).
# - The entire grid size is predetermined (base on the aggregate of the cell size, plus the margins).
##

struct Grid <: Drawable
    cell_fn::Function
    num_rows::Integer
    num_cols::Integer
    cell_size::Integer
    line_color::Color
    line_thickness::Integer
    title::Union{Nothing,Label}
    title_right::Union{Nothing,Label}
    header::Union{Nothing,Label}
    header_right::Union{Nothing,Label}
    margins::Margins
    font_size::Integer
end

# @todo make "cell_fn" optional (i.e.), Grid(3,3)
function Grid(
    num_rows::Integer,
    num_cols::Integer,
    cell_fn::Function;
    cell_size=50,
    line_thickness=1,
    line_color="#333",
    title=nothing,
    title_right=nothing,
    header=nothing,
    header_right=nothing,
    margin=nothing,
    margin_top=nothing,
    margin_bottom=nothing,
    margin_left=nothing,
    margin_right=nothing,
    font_size=22,
    )
    if !isnothing(margin)
        if isnothing(margin_top)
            margin_top = margin
        end
        if isnothing(margin_bottom)
            margin_bottom = margin
        end
        if isnothing(margin_left)
            margin_left = margin
        end
        if isnothing(margin_right)
            margin_right = margin
        end
    end
    if isnothing(margin_top)
        if !isnothing(title) || !isnothing(title_right)
            margin_top = cell_size
        end
    end
    if isnothing(margin_bottom)
        if !isnothing(header) || !isnothing(header_right)
            margin_bottom = cell_size
        end
    end
    Grid(
        cell_fn,
        num_rows,
        num_cols,
        cell_size,
        Color(line_color),
        line_thickness,
        Label(title),
        Label(title_right),
        Label(header),
        Label(header_right),
        Margins(
            top=margin_top,
            bottom=margin_bottom,
            left=margin_left,
            right=margin_right,
        ),
        font_size
    )
end

function Grid(grids::Vector{<:Drawable}; kwargs...)
    Grid(grids, :, 3; kwargs...)
end

function Grid(grids::Vector{<:Drawable}, num_rows::Integer, ::Colon; kwargs...)
    num_cols = ((length(grids)-1) ÷ num_rows) + 1
    Grid(grids, num_rows, num_cols; kwargs...)
end

function Grid(grids::Vector{<:Drawable}, ::Colon, num_cols::Integer; kwargs...)
    num_rows = ((length(grids)-1) ÷ num_cols) + 1
    Grid(grids, num_rows, num_cols; kwargs...)
end

function Grid(grids::Vector{<:Drawable}, num_rows::Integer, num_cols::Integer; kwargs...)
    function cellfn(row,col)
        grids_idx = num_cols*(row-1) + col
        if grids_idx > length(grids)
            return nothing
        end
        grids[grids_idx]
    end
    Table(num_rows, num_cols, cellfn; kwargs...)
end

getcellsize(g::Grid) = (g.cell_size, g.cell_size)
getnumrows(g::Grid) = g.num_rows
getnumcols(g::Grid) = g.num_cols
getlinecolor(g::Grid) = string(g.line_color.name)

function getwidth(g::Grid)
    sum([
        (g.cell_size * g.num_cols),
        g.margins.left,
        g.margins.right,
    ])
end

function getheight(g::Grid)
    sum([
        (g.cell_size * g.num_rows),
        g.margins.top,
        g.margins.bottom,
    ])
end

usetitlerow(g::Grid) = !isnothing(g.title) || !isnothing(g.title_right)
useheaderrow(g::Grid) = !isnothing(g.header) || !isnothing(g.header_right)

getdrawable(x::Drawable; kwargs...) = x
getdrawable(x::Label; kwargs...) = Cell(Label(x); kwargs...)
getdrawable(::Nothing; kwargs...) = Cell(nothing; kwargs...)
getdrawable(x; kwargs...) = Cell(Label(x); kwargs...)

draw(::Nothing, pos, cell_size; halign=nothing) = nothing

function drawtitle(g::Grid, cell_center)
    pos = sum([
        cell_center,
        (-1*(g.cell_size/2), 0),
        #title offset
    ])
    draw(
        g.title,
        pos,
        g.cell_size;
        halign=:left
    )
end
function drawtitleright(g::Grid, cell_center)
    pos = sum([
        cell_center,
        (g.cell_size/2, 0),
        #titleright offset
    ])
    draw(
        g.title_right,
        pos,
        g.cell_size;
        halign=:right
    )
end
function drawheader(g::Grid, cell_center)
    pos = sum([
        cell_center,
        (-1*(g.cell_size/2), 0),
        #header offset
    ])
    draw(
        g.header,
        pos,
        g.cell_size;
        halign=:left
    )
end

function drawheaderright(g::Grid, cell_center)
    pos = sum([
        cell_center,
        (g.cell_size/2, 0),
        #headerright offset
    ])
    draw(
        g.header_right,
        pos,
        g.cell_size;
        halign=:right
    )
end

function draw(g::Grid)
    sethue("#fff")
    box(O, getwidth(g), getheight(g), :fill)
    num_rows = getnumrows(g) + 2
    num_cols = getnumcols(g)
    A = rand(1:99, num_rows, num_cols)
    #@show "Grid.draw.translate"
    #translate(0, (g.margins.top - g.margins.bottom) / 2.0)
    sethue("black")
    fontsize(30)
    setline(0.5)
    sethue("white")
    table = Luxor.Table(size(A)..., getcellsize(g)...)
    after_effects = []
    for r in 1:num_rows, c in 1:num_cols
        cell_center = table[r, c]
        if r == 1
            if c == 1
                drawtitle(g, cell_center)
            elseif c == num_cols
                drawtitleright(g, cell_center)
            end
            continue
        end
        if r == num_rows
            if c == 1
                drawheader(g, cell_center)
            elseif c == num_cols
                drawheaderright(g, cell_center)
            end
            continue
        end
        grid_row = r - 1
        grid_col = c
        sethue("#333")
        fontsize(g.font_size)
        x = g.cell_fn(grid_row,grid_col)
        d = getdrawable(x; line_thickness=g.line_thickness)
        draw(
            d,
            cell_center,
            g.cell_size
        )
        if hasaftereffect(d)
            push!(after_effects, (d,cell_center))
        end
    end
    for after_effect in after_effects
        (drawable, position) = after_effect
        drawaftereffect(
            drawable,
            position,
            g.cell_size
        )
    end
end

function savesvg(g::Grid, path::String)
    ensurepathtoexists(path)
    Drawing(
        getwidth(g),
        getheight(g),
        path
    )
    origin()
    draw(g)
    finish()
    preview()
end

function Base.show(io::IO, m::MIME"image/svg+xml", g::Grid)
    path = "./output/grid.svg"
    savesvg(g, path)
    print(io, read(path, String))
end


text:
using AutoHashEquals

Headline{T} = NamedTuple{(:level, :title), Tuple{Int, T}}
Base.show(io::IO, v::Type{Headline{T}}) where T = print(io, "Headline{", T, "}")

function Base.show(io::IO, v::Headline{T}) where T
    print(io,repeat("=", v.level), " ")
    print(io, v.title)
    print(io," ", repeat("=", v.level))
end

Title{S,T} = NamedTuple{(:title, :text), Tuple{S, T}}
Base.show(io::IO, v::Type{Title{S,T}}) where {S,T} = print(io, "Title{", T, "}")
function Base.show(io::IO, v::Title{S,T}) where {S,T}
    println(io,v.title)
    print(io, v.text)
end

export substnothing
substnothing(default, x) =
    x === nothing ? default : x

export WikiLink
@auto_hash_equals struct WikiLink <: AbstractToken ## todo: make Tokens String, in token_lines make tokens with name=field
    namespace_page::Token
    anchor::Token
    label::Token
    function WikiLink(namespace::AbstractString, page::AbstractString, anchor::AbstractString, label::AbstractString, addlabel="")
        new(Token(intern(namespace), intern(page)), Token(:anchor,intern(anchor)),
            Token(:label, intern(label=="" ? page : label) * addlabel))
    end
    function WikiLink(namespace::Token, page::Token, anchor::Token, label::Token)
        new(namespace, page, anchor, label)
    end
end

Base.show(io::IO, ::MIME"text/x-wiki", x::WikiLink) =
    print(io, "[[", value(x.namespace) == "" ? "" : variable(x.namespace_page) * ":",
          value(x.namespace_page),
          x.anchor == "" ? "" : "#" * x.anchor,
          x.label == "" ? "" : "|" * x.label,
          "]]")

Base.show(io::IO, x::WikiLink) =
          printstyled(io, value(x.label)=="" ? value(x.namespace_page) : value(x.label); bold=true,
                      color=36)

export @substringorempty
macro substringorempty(x,range)
    :( $(esc(x)) === nothing ? "" : $(esc(x))[$(esc(range))] )
end

export TemplateArgument, Template, LineContent
"""
Wikitext template arguments are stored with `ta=name => value`.
An unnamed template argument has `t.first==""`.
The value is
"""
TemplateArgument{I,T} = Pair{String,Vector{Line{I,T}}}

"""
A wikitext template struct
"""
@auto_hash_equals struct Template{I,T} <: AbstractToken
    template::String
    arguments::Vector{TemplateArgument{I,T}}
    Template(t,a::Vector{TemplateArgument{I,T}}) where {I,T} =
        new{I,T}(t,[ k => v for (k,v) in a])
    Template{I,T}(t,a::Vector) where {I,T} =
        new{I,T}(t,[ k => convert(Vector{Line{I,T}},v) for (k,v) in a])
    Template(t,a::Vector) =
        new{Any,Any}(t,[ k => v for (k,v) in a])
end

import CombinedParserTools.Tokens: isinformative
CombinedParserTools.Tokens.isinformative(i::Template)  = true

Template(a::String) = Template(a,TemplateArgument{NamedString,LineContent}[])

function Base.show(io::IO, x::Template) where T
    print(io, "{{")
    print(io, x.template)
    for a in x.arguments
        print(io, "|")
        if a isa Pair
            if !isempty(a.first)
                print(io, a.first,"=",a.second)
            else
                print(io, a.second)
            end
        else
            print(io, a)
        end
    end
    print(io, "}}")
end

export TemplateParameter
TemplateParameter = TokenPair{Vector{LineContent}, Vector{Line{NamedString,LineContent}}}


text:
d = readdlm("input.txt", ':', Int)
d = Dict(mapslices(x -> (x[1], x[2]), d, 2))
# Part 1
severity = 0
for i in keys(d)
    if i % (2 * d[i] - 2) == 0
        severity += i * d[i]
    end
end
println(severity)

# Part 2
cleanRun = false
delay = 1
while !cleanRun
    cleanRun = true
    for i in keys(d)
        if (i + delay) % (2 * d[i] - 2) == 0
            cleanRun = false
            delay += 1
            break
        end
    end
end
println(delay)


text:
using DiffEqBase, OrdinaryDiffEq, Sundials, ApproxFun, DiffEqApproxFun
using Base.Test

S=Fourier()
u0=Fun(θ->cos(cos(θ-0.1))-cos(cos(0-0.1)),S)
c=Fun(cos,S)

ode_prob = ODEProblem((u,p,t)->u''+(c+1)*u',u0,(0.,1.))
prob = ApproxFunProblem(ode_prob)
@time sol=solve(prob,Euler(),dt=1/1000)
@time sol=solve(prob,Tsit5())
sol(0.5)
sol(0.5,0.2)

@time sol=solve(prob,CVODE_BDF())

sol[1]
sol[5](0.2)

function bc(t,u)
  C=eye(S)[3:end,:]
  tmp = [Evaluation(0);
         Evaluation(2π);
         C]\[0.;0.;u]
end

u0=Fun(θ->cos(cos(θ)) - cos(cos(0)),S)
u0 = bc(0.0,u0)
c=Fun(cos,S)
ode_prob = ODEProblem((u,p,t)->u''+(c+1)*u',u0,(0.,1.))

prob = ApproxFunProblem(ode_prob)

condition(t,u,integrator) = true
function affect!(integrator)
  S=Fourier()
  u = Fun(S,integrator.u)
  B=Dirichlet()
  C=eye(S)[3:end,:]
  tmp = [Evaluation(0);
         Evaluation(2π);
         C]\[0.;0.;u]
  integrator.u=pad!(tmp.coefficients,length(integrator.u))
end
boundary_projection = DiscreteCallback(condition,affect!)

@time sol=solve(prob,Tsit5())
@test sol(0.0,0.0) ≈ 0.0 atol=1e-12
@test sol(0.0,2π) ≈ 0.0 atol=1e-12
@test sol(0.5,0.0) > 0.3
@test sol(0.5,2π) > 0.3
@test sol(1.0,0.0) > 0.3
@test sol(1.0,2π) > 0.3

@time sol=solve(prob,Tsit5(),callback=boundary_projection)
@test sol(0.0,0.0) ≈ 0.0 atol=1e-12
@test sol(0.0,2π) ≈ 0.0 atol=1e-12
@test sol(0.5,0.0) ≈ 0.0 atol=1e-3
@test sol(0.5,2π) ≈ 0.0 atol=1e-3
@test sol(1.0,0.0) ≈ 0.0 atol=1e-3
@test sol(1.0,2π) ≈ 0.0 atol=1e-3

boundary_projection2 = BoundaryCallback(bc)

@time sol=solve(prob,Tsit5(),callback=boundary_projection2)
@test sol(0.0,0.0) ≈ 0.0 atol=1e-12
@test sol(0.0,2π) ≈ 0.0 atol=1e-12
@test sol(0.5,0.0) ≈ 0.0 atol=1e-3
@test sol(0.5,2π) ≈ 0.0 atol=1e-3
@test sol(1.0,0.0) ≈ 0.0 atol=1e-3
@test sol(1.0,2π) ≈ 0.0 atol=1e-3


text:
module JuliaRunClient

using HTTP
using JSON
using ClusterManagers
using Base64
using Sockets
using Distributed

import Base: show
export Context, JuliaParBatch, JuliaParBatchWorkers, Notebook, JuliaBatch, PkgBuilder, Webserver, MessageQ, Generic
export getSystemStatus, listJobs, getAllJobInfo, getJobStatus, getJobScale, setJobScale, getJobEndpoint, deleteJob, tailJob, submitJob, updateJob, initParallel, self, waitForWorkers, @result, initializeCluster, releaseCluster, getEncodedURL

"""
Types of Jobs:
- JuliaParBatch
- JuliaParBatchWorkers
- Notebook
- JuliaBatch
- PkgBuilder
- Webserver
- MessageQ
- Generic
"""
abstract type JRunClientJob end

const JOBTYPE_LABELS = Vector{String}()
const JOBTYPE = Vector{Any}()
_JuliaClusterManager = nothing

as_label(::Type{T}) where {T<:JRunClientJob} = String(rsplit(string(T), '.'; limit=2)[end])

"""
Returns a reference to the current job.
"""
function self()
    jtype = ENV["JRUN_TYPE"]
    jname = ENV["JRUN_NAME"]
    jultype = JOBTYPE[something(findfirst(isequal(jtype), JOBTYPE_LABELS), 0)]
    jultype(jname)
end

for T in (:JuliaParBatch, :JuliaParBatchWorkers, :Notebook, :JuliaBatch, :PkgBuilder, :Webserver, :MessageQ, :Generic)
    @eval begin
        struct $T <: JRunClientJob
            name::String
        end
        push!(JOBTYPE, $T)
        push!(JOBTYPE_LABELS, as_label($T))
    end
end

struct ApiException <: Exception
    status::Int
    reason::String
    resp::HTTP.Response

    function ApiException(resp::HTTP.Response; reason::String="")
        isempty(reason) && (reason = get(HTTP.Messages.STATUS_MESSAGES, resp.status, reason))
        new(resp.status, reason, resp)
    end
end

"""
A JuliaRun client context.

Consists of:
- URL of the JuliaRun remote server
- an authentication token
- namespace to operate in

Default values of all parameters are set to match those inside a JuliaRun cluster.
- connects to a service endpoint at "juliarunremote-svc.juliarun"
- reads the namespace from the default secret
- presents the namespace service token (also read from the default secret) for authentication
"""
struct Context
    root::String
    token::String
    namespace::String
    readtimeout::Int
    connecttimeout::Int

    function Context(root::String="http://juliarunremote-svc.juliarun:80",
            token::String="/var/run/secrets/kubernetes.io/serviceaccount/token",
            namespace::String="/var/run/secrets/kubernetes.io/serviceaccount/namespace";
            readtimeout::Int=60, connecttimeout=60)
        _isfile(token) && (token = base64encode(read(token, String)))
        _isfile(namespace) && (namespace = read(namespace, String))
        new(root, token, namespace)
    end
end

show(io::IO, ctx::Context) = print(io, "JuliaRunClient for ", ctx.namespace, " @ ", ctx.root)

"""
Verifies if JuliaRun is running and is connected to a compute cluster.

Returns:
- boolean: true/false indicating success/failure
"""
getSystemStatus(ctx::Context) = _simple_query(ctx, "/getSystemStatus/")

"""
List all submitted jobs.

Returns:
- dictionary: of the form `{"jobname": { "type": "JuliaBatch" }...}`
"""
listJobs(ctx::Context) = _simple_query(ctx, "/listJobs/")

"""
List all submitted jobs.

Returns:
- dictionary: of the form `{"jobname": { "type": "JuliaBatch", "status": [], "scale": [], "endpoint": [] }...}`
"""
getAllJobInfo(ctx::Context) = _simple_query(ctx, "/getAllJobInfo/")

"""
Get encoded URL to job port when using router.

Returns:
- The encoded URL as a String
"""
getEncodedURL(ctx::Context, job, port) = _type_name_query(ctx, "/getEncodedURL/", job, Dict("port"=>port))

"""
Fetch current status of a Job.

Parameters:
- job: A JRunClientJob of appropriate type

Returns tuple/array with:
- boolean: whether the job completed
- integer: for a parallel job, number of workers that completed successfully
- integer: for a parallel job, number of workers started
- boolean: whether the job has been created (vs. scheduled)
- boolean: whether this is a notebook (legacy, likely to be removed in future)
"""
getJobStatus(ctx::Context, job::JRunClientJob) = _type_name_query(ctx, "/getJobStatus/", job)

"""
Get the current scale of a job.

Parameters:
- job: A JRunClientJob of appropriate type

Returns tuple/array with:
- integer: number of workers running
- integer: number of workers requested
"""
getJobScale(ctx::Context, job::JRunClientJob) = _type_name_query(ctx, "/getJobScale/", job)

"""
Request to scale the job up or down to the level of parallelism requested.

Parameters:
- job: A JRunClientJob of appropriate type
- parallelism: number of workers to scale to

Returns:
- boolean: true/false indicating success/failure
"""
setJobScale(ctx::Context, job::JRunClientJob, parallelism::Int) = _type_name_query(ctx, "/setJobScale/", job, Dict("parallelism" => string(parallelism)))

"""
Get the endpoint exposed by the job/service.

Parameters:
- job: A JRunClientJob of appropriate type

Returns tuple/array of endpoints as URLs or IP and ports
"""
getJobEndpoint(ctx::Context, job::JRunClientJob) = _type_name_query(ctx, "/getJobEndpoint/", job)

"""
Removes the job entry from the queue.

Parameters:
- job: A JRunClientJob of appropriate type
- force: whether to remove an incomplete job (optional, default: false)

Returns:
- boolean: true/false indicating success/failure
"""
deleteJob(ctx::Context, job::JRunClientJob; force=false) = _type_name_query(ctx, "/deleteJob/", job, Dict("force"=>string(force)))

"""
Tail logs from the job.

Parameters:
- job: A JRunClientJob of appropriate type
- stream: the stream to read from ("stdout"/"stdin"), all streams are read if not specified.
- count: number of log entries to return (50 by default)

Returns a string of log entries separated by new line.
"""
function tailJob(ctx::Context, job::JRunClientJob; stream=nothing, count=50)
    query = Dict("count"=>string(count))
    (stream === nothing) || (query["stream"] = string(stream))
    _type_name_query(ctx, "/tailJob/", job, query)
end

"""
Submit a job definition to execute on the cluster.

Parameters:
- job: A JRunClientJob of appropriate type
- job specific parameters, with names as documented for the JobType constructor

Returns nothing.
"""
function submitJob(ctx::Context, job::JRunClientJob; kwargs...)
    query = Dict{String,String}()
    for (k,v) in kwargs
        query[string(k)] = string(v)
    end
    _type_name_query(ctx, "/submitJob/", job, query)
end

"""
Update a job definition to execute on the cluster.

Parameters:
- job: A JRunClientJob of appropriate type
- job specific parameters, with names as documented for the JobType constructor

Returns nothing.
"""
function updateJob(ctx::Context, job::JRunClientJob; kwargs...)
    query = Dict{String,String}()
    for (k,v) in kwargs
        query[string(k)] = string(v)
    end
    _type_name_query(ctx, "/updateJob/", job, query)
end

"""
Initialize the cluster manager for parallel mode.
"""
function initParallel(; topology=:master_worker)
    if haskey(ENV, "JULIABOX")
        v = "$(VERSION.major).$(VERSION.minor)"
        open(joinpath(ENV["HOME"], ".julia_master_version"), "w") do f
            write(f, v)
        end
    end

    global _JuliaClusterManager
    COOKIE = ENV["JRUN_CLUSTER_COOKIE"]
    if _JuliaClusterManager === nothing
        try
            _JuliaClusterManager = ElasticManager(;addr=IPv4("0.0.0.0"), port=9009, cookie=COOKIE, topology=topology)
        catch ex
            (isa(ex, Base.IOError) && (ex.code == -98)) || rethrow(ex)
            error("Parallel mode is already being used in a different Julia instance")  # "listen: address already in use (EADDRINUSE)"
        end
    else
        @info("Parallel mode was already initialized for this Julia instance")
    end
    _JuliaClusterManager
end

"""
Wait for a certain number of workers to join.
"""
function waitForWorkers(min_workers)
    @info("waiting for $min_workers...")
    t1 = time()
    if min_workers > 0
        while (1 in workers()) || (nworkers() < min_workers)
            sleep(1)
        end
    end
    @info("workers started in $(time()-t1) seconds")
end

macro result(req)
    quote
        _server_exception = nothing
        try
            res = $(esc(req))
            (res["code"] == 0) ? res["data"] : throw(ApiException(res["code"], res["data"], res))
        catch x
            if isa(x, ApiException)
                println(stderr, "Error: ", x.reason)
                isempty(x.resp.body) || println(stderr, "Caused by: ", String(x.resp.body))
            elseif isa(x, Base.IOError)
                println(stderr, "Error: ", x.msg, "(", x.code, ")")
            end
            rethrow(x)
        end
    end
end

# ---------------------------------------------------
# Utility methods
# ---------------------------------------------------
_jobtype(j::T) where {T<:JRunClientJob} = _jobtype(T)
_jobtype(::Type{T}) where {T} = rsplit(string(T), '.'; limit=2)[end]

# assuming PATH_MAX is 256
_isfile(val) = (length(val) < 256) && isfile(val)

function make_query(ctx::Context)
    Dict{String,String}(
        "jruntok" => ctx.token,
        "jrunns" => ctx.namespace
    )
end

function parse_resp(resp)
    (200 <= resp.status <= 206) || throw(ApiException(resp))
    #@info("response " * String(resp.body))
    JSON.parse(String(resp.body))
end

function _http_opts(ctx::Context)
    Dict(
            :retries            => 0,
            :readtimeout        => ctx.readtimeout,
            :connecttimeout     => ctx.connecttimeout,
            :status_exception   => false
        )
end

function _simple_query(ctx, path; q::Dict{String,String}=Dict{String,String}())
    query = merge(make_query(ctx), q)
    url = merge(HTTP.URIs.URI(ctx.root * path); query=query)
    #@info("requesting", ctx.root, path, query, url)
    resp = HTTP.request("GET", url; _http_opts(ctx)...)
    parse_resp(resp)
end

function _type_name_query(ctx::Context, path::String, job::JRunClientJob, query::Dict{String,String}=Dict{String,String}())
    query = merge(make_query(ctx), query)
    query["name"] = job.name
    jt = _jobtype(job)
    url = merge(HTTP.URIs.URI(ctx.root * path * jt * "/"); query=query)
    #@info("requesting", ctx.root, path, jt, query, url)
    resp = HTTP.request("GET", url; _http_opts(ctx)...)
    parse_resp(resp)
end

function initializeCluster(num_workers, ctx=Context())
    initParallel()
    job = self()
    res = setJobScale(ctx, job, num_workers)
    if (res["code"] == 0) && res["data"]
        waitForWorkers(num_workers)
        res = Dict{String,Any}("code"=>0, "data"=>ctx)
    end
    res
end

function releaseCluster(ctx=Context())
    job = self()
    ret = setJobScale(ctx, job, 0)
    while !(1 in workers())
        sleep(1)
    end
    ret
end

include("docs.jl")

end # module