text:
using Test
using Dates
using Forecast

@testset "stl" begin
    @test_throws AssertionError stl(rand(100),10; ns=5)

    x = stl(rand(100),10)
    @test x isa STL
    @test x.decomposition isa DataFrame
    @test x.call isa String

    x = stl(co2(),365; robust=true, spm=true)
    @test Array(x.decomposition[1:5,:]) ==
    Any[Date("1974-05-17") 3.3336092113804625 330.03520225817715 0.011188530442382216; Date("1974-05-18") 3.2974595453530924 330.0378212346287 -0.22528077998180152; Date("1974-05-19") 3.2594500979279815 330.0404399841902 0.16010991788181173; Date("1974-05-20") 3.219714340570647 330.043058508077 0.3772271513523151; Date("1974-05-21") 3.178405197122059 330.0456768071629 0.1859179957150463]

end


text:
# matrix of ones

export joOnes
"""
    julia> op = joOnes(m[,n];[DDT=...,][RDT=...,][name=...])

Operator equivalent to matrix of zerso

# Signature

    joOnes(m::Integer,n::Integer=m;
        DDT::DataType=joFloat,RDT::DataType=DDT,name::String="joOnes")

# Arguments

- `m`: size
- optional
    - `n`: 2nd dimension if not square
- keywords
    - `DDT`: domain data type
    - `RDT`: range data type
    - `name`: custom name

# Examples

    joOnes(m)
    joOnes(m,n)

examples with DDT/RDT

    joOnes(m; DDT=Float32)
    joOnes(m; DDT=Float32,RDT=Float64)

"""
joOnes(m::Integer,n::Integer=m;DDT::DataType=joFloat,RDT::DataType=DDT,name::String="joOnes") =
    joMatrix{DDT,RDT}(name,m,n,
        v1->jo_convert(RDT,ones(eltype(v1),m,1)*sum(v1,dims=1),false),
        v2->jo_convert(DDT,ones(eltype(v2),n,1)*sum(v2,dims=1),false),
        v3->jo_convert(DDT,ones(eltype(v3),n,1)*sum(v3,dims=1),false),
        v4->jo_convert(RDT,ones(eltype(v4),m,1)*sum(v4,dims=1),false),
        @joNF, @joNF, @joNF, @joNF
        )

ext: jl


text:
### A Pluto.jl notebook ###
# v0.14.7

using Markdown
using InteractiveUtils

# ╔═╡ 22364cc8-c792-11eb-3458-75afd80f5a03
using PlutoPkgTestA

# ╔═╡ ca0765b8-ce3f-4869-bd65-855905d49a2d
using Dates

# ╔═╡ 5cbe4ac1-1bc5-4ef1-95ce-e09749343088
domath(20)

# ╔═╡ Cell order:
# ╠═22364cc8-c792-11eb-3458-75afd80f5a03
# ╠═ca0765b8-ce3f-4869-bd65-855905d49a2d
# ╠═5cbe4ac1-1bc5-4ef1-95ce-e09749343088


text:
#  Let's build tree structure (borrowed from AbstractTrees.jl)
@with_kw mutable struct BinaryNode{T}
    idx::T
    parent::Union{BinaryNode{T}, Nothing} = nothing
    left::Union{BinaryNode{T}, Nothing} = nothing
    right::Union{BinaryNode{T}, Nothing} = nothing
    a::Union{Array, Nothing} = nothing
    b::Union{Real, Nothing} = nothing
    label::Any = nothing
end
BinaryNode(idx) = BinaryNode{typeof(idx)}(idx = idx)
BinaryNode(idx, parent::BinaryNode) = BinaryNode{typeof(idx)}(idx = idx, parent = parent)

function Base.show(io::IO, bn::BinaryNode)
    if is_leaf(bn)
        print("Leaf BinaryNode $(bn.idx) with label $(string(bn.label)).")
    else
        print("Branching BinaryNode $(bn.idx).")
    end
    return
end

"""
    $(TYPEDSIGNATURES)

Adds a left (less-than) child to BinaryNode.
"""
function leftchild(parent::BinaryNode, child::BinaryNode)
    isnothing(parent.left) || error("Left child of node $(parent.idx) is already assigned.")
    isnothing(child.parent) || error("Parent of node $(child.idx) is already assigned.")
    parent.left = child
    child.parent = parent
    return
end

"""
    $(TYPEDSIGNATURES)

Adds a right (greater-than) child to BinaryNode.
"""
function rightchild(parent::BinaryNode, child::BinaryNode)
    isnothing(parent.right) || error("Right child of node $(parent.idx) is already assigned.")
    isnothing(child.parent) || error("Parent of node $(child.idx) is already assigned.")
    parent.right = child
    child.parent = parent
    return
end

"""
    children(node::BinaryNode)

Returns (left, right) children of a BinaryNode.
"""
function children(node::BinaryNode)
    if !isnothing(node.left)
        if !isnothing(node.right)
            return (node.left, node.right)
        end
        return (node.left,)
    end
    !isnothing(node.right) && return (node.right,)
    return ()
end

""" Returns all "younger" relatives of a BinaryNode. """
function alloffspring(nd::BinaryNode)
    offspr = [children(nd)...]
    queue = [children(nd)...]
    while !isempty(queue)
        nextnode = popfirst!(queue)
        if !is_leaf(nextnode)
            for child in children(nextnode)
                push!(offspr, child)
                push!(queue, child)
            end
        else
            for child in children(nextnode)
                push!(offspr, child)
            end
        end
    end
    return offspr
end

""" Returns lineage of BinaryNode to the tree root. """
function lineage(nd::BinaryNode)
    parents = []
    bn = nd
    while !isnothing(bn.parent)
        push!(parents, bn.parent)
        bn = bn.parent
    end
    return parents
end

"""
    delete_children!(bn::BinaryNode)

"Deletes" the children of BinaryNode by pruning and returning the nodes.
"""
function delete_children!(bn::BinaryNode)
    allchildren = children(bn)
    bn.left = nothing
    bn.right = nothing
    for child in allchildren
        child.parent = nothing
    end
end

""" Better node printing."""
printnode(io::IO, node::BinaryNode) = print(io, node.idx)

"""
Returns a-coefficients and b-value of the split on BinaryNode.
"""
function get_split_values(bn::BinaryNode)
    is_leaf(bn) && throw(ErrorException("Cannot get split values of leaf node $(bn.idx)."))
    return bn.a, bn.b
end

""" Returns b-value of the split on BinaryNode. """
function get_split_threshold(bn::BinaryNode)
    is_leaf(bn) && throw(ErrorException("Cannot get split threshold of leaf node $(bn.idx)."))
    return bn.b
end

""" Returns a-coefficients of the split on BinaryNode. """
function get_split_weights(bn::BinaryNode)
    is_leaf(bn) && throw(ErrorException("Cannot get split weights of leaf node $(bn.idx)."))
    return bn.a
end

"""
Sets a-coefficients and b-value of a split on BinaryNode.
"""
function set_split_values!(bn::BinaryNode, a, b)
    is_leaf(bn) && throw(ErrorException("Cannot set split values for leaf node $(bn.idx)."))
    bn.a = a
    bn.b = b
    bn.label = nothing
    return
end

"""
    is_leaf(bn::BinaryNode)

Checks whether a BinaryNode is a leaf of the tree.
"""
function is_leaf(bn::BinaryNode)
    return isnothing(bn.left) && isnothing(bn.right)
end

"""
    depth(bn::BinaryNode)

Finds the depth of current BinaryNode from the tree root.
"""
function depth(bn::BinaryNode)
    d = 0
    nd = bn
    while !isnothing(nd.parent)
        d +=1
        nd = nd.parent
    end
    return d
end

"""
    $(TYPEDSIGNATURES)

Returns the classification label of a leaf BinaryNode.
"""
function get_classification_label(bn::BinaryNode)
    is_leaf(bn) || throw(ErrorException("Cannot get the classification label of node $(bn.idx), " *
                        "since it is not a leaf node."))
    return bn.label
end

""" Returns the regression constant of a leaf BinaryNode. """
function get_regression_constant(bn::BinaryNode)
    is_leaf(bn) || throw(ErrorException("Cannot get the regression constant of node $(bn.idx), since it is not a leaf node."))
    bn.label isa Tuple || throw(ErrorException("Node $(nd.dx) has not been properly labeled. Are you sure that the MIO tree is a regression tree?"))
    return bn.label[1]
end

""" Returns the regression weights of a leaf BinaryNode. """
function get_regression_weights(bn::BinaryNode)
    is_leaf(bn) || throw(ErrorException("Cannot get the regression weights of node $(bn.idx), since it is not a leaf node."))
    bn.label isa Tuple || throw(ErrorException("Node $(nd.dx) has not been properly labeled. Are you sure that the MIO tree is a regression tree?"))
    return bn.label[2]
end

"""
    set_classification_label!(bn::BinaryNode)

Sets the classification label of a leaf BinaryNode.
"""
function set_classification_label!(bn::BinaryNode, label)
    is_leaf(bn) || throw(ErrorException("Cannot set the classification label of node $(bn.idx), " *
    "since it is not a leaf node."))
    bn.label = label
    bn.a = nothing
    bn.b = nothing
    return
end

""" Returns left child of BinaryNode (less than side)."""
function get_lower_child(bn::BinaryNode)
    !is_leaf(bn) || throw(ErrorException("Cannot get lower child of leaf node $(bn.idx)."))
    return bn.left
end

""" Returns right child of BinaryNode (greater than side)."""
function get_upper_child(bn::BinaryNode)
    !is_leaf(bn) || throw(ErrorException("Cannot get upper child of leaf node $(bn.idx)."))
    return bn.right
end

""" Returns the parent of BinaryNode. """
function get_parent(bn::BinaryNode)
    !isnothing(bn.parent) || throw(ErrorException("BinaryNode $(bn.idx) has no parent."))
    return bn.parent
end


text:
test = """
    ..##.......
    #...#...#..
    .#....#..#.
    ..#.#...#.#
    .#...##..#.
    ..#.##.....
    .#.#.#....#
    .#........#
    #.##...#...
    #...##....#
    .#..#...#.#
    """

function parse_forest(rows::AbstractVector{<: AbstractString})::BitMatrix
    hcat([split(row, "") .== "#" for row in rows]...)'
end

function path_sum(forest::AbstractMatrix;
                  right::Integer,
                  down::Integer
                 )::Integer
    total = 0
    (r, c) = (1, 1)
    while r <= size(forest, 1)
        total += forest[r, c]
        c = (c + right - 1) % size(forest, 2) + 1
        r += down
    end
    return total
end

const SLOPES = [(right = 1, down = 1),
                (right = 3, down = 1),
                (right = 5, down = 1),
                (right = 7, down = 1),
                (right = 1, down = 2)]

let forest = IOBuffer(test) |> readlines |> parse_forest
    @assert path_sum(forest; right = 3, down = 1) == 7
    @assert prod([path_sum(forest; slope...) for slope in SLOPES]) == 336
end

forest = readlines("day03.in") |> parse_forest
println("Q1: ", path_sum(forest,; right = 3, down = 1))
println("Q2: ", prod([path_sum(forest; slope...) for slope in SLOPES]))

max_stars_repo_name: dhanak/competitive-coding

max_issues_repo_name: dhanak/competitive-coding

max_forks_repo_name: dhanak/competitive-coding


text:
#####################################################
# Download data from UF Sparse Matrix Collection
#####################################################

# check line for remote identification and store in global variable
function remotetype(line::AbstractString)
    global uf_remote
    TAMUID = TA_REMOTE.params.indexgrep
    UFID = UF_REMOTE.params.indexgrep
    MMID = MM_REMOTE.params.indexgrep
    if occursin(TAMUID, line)
        uf_remote = TA_REMOTE
        uf_remote
    elseif occursin(UFID, line)
        uf_remote = UF_REMOTE
        uf_remote
    elseif occursin(MMID, line)
        MM_REMOTE
    else
        nothing
    end
end

function parse_headerinfo(akku::Dict{AbstractString,AbstractString}, count::Integer)
    toint(id::String) = parse(Int, replace(get(akku, id, "0"), ','=>""))
    id = toint("id")
    id == 0 && (id = count)
    m = toint("num_rows")
    n = toint("num_cols")
    nnz = toint("nonzeros")
    kind = get(akku, "kind", "")
    datestr = get(akku, "date", "0")
    date = match(r"^\d+$", datestr) != nothing ? parse(Int, datestr) : 0
    id, MetaInfo(m, n, nnz, 0, kind, date, "", "", "", "", "")
end

# extract loading url base and matrix names from index file
function extract_names(db::MatrixDatabase, matrices::AbstractString)
    remote = nothing
    matrixdata = RemoteMatrixData[]
    count = 0
    open(matrices) do f
        akku = Dict{AbstractString,AbstractString}()
        for line in readlines(f)
            if remote === nothing
                remote = remotetype(line)
                continue
            end
            _, grepex, spquote, ending, parts, regexinf = remote.params.scan
            m = regexinf === nothing ? nothing : match(regexinf, line)
            if m != nothing
                pname = length(m.captures) == 2 ? m.captures[1] : "id"
                akku[pname] = m.captures[end]
            end
            if occursin(grepex, line)
                murl = split(line, '"')[spquote]
                if endswith(murl, ending)
                    list = rsplit(murl[1:end-length(ending)], '/', limit=parts+1)[2:end]
                    count += 1
                    name = join(list, '/')
                    id, info = parse_headerinfo(akku, count)
                    data = RemoteMatrixData{typeof(remote)}(name, id, info)
                    addmetadata!(data)
                    push!(db, data)
                    count = id
                end
            end
        end
    end
    matrixdata
end

# read indexfile
function downloadindex(remote::RemoteType)
    file = localindex(remote)
    url = indexurl(remote)
    if !isfile(file)
        println("dowloading index file $url")
        download(url, file)
    end
    nothing
end

# collect the keys from local database (MATRIXDICT od USERMATRIXDICT)
# provide a numerical id counting from 1 for either database.
function insertlocal(db::MatrixDatabase, T::Type{<:GeneratedMatrixData},
                     ldb::Dict{<:AbstractString,Function})

    cnt = 0
    ks = sort!(collect(keys(ldb)))
    for k in ks
        cnt += 1
        db.data[k] = T(k, cnt, ldb[k])
    end
end

dbpath(db::MatrixDatabase) = abspath(DATA_DIR, "db.data")
function readdb(db::MatrixDatabase)
    cachedb = dbpath(db)
    open(cachedb, "r") do io
        dbx = deserialize(io)
        merge!(db.data, dbx.data)
        merge!(db.aliases, dbx.aliases)
    end
end

function writedb(db::MatrixDatabase)
    cachedb = dbpath(db)
    open(cachedb, "w") do io
        serialize(io, db)
    end
end

"""
    MatrixDepot.downloadindices(db)
download html files and store matrix data in `db`.
additionally generate aliases for local and loaded matrices.
Serialize db object in file `db.data`.
If a file `db.data` is present in the data directory, deserialize
the data instead of downloading and collection data.
"""
function downloadindices(db::MatrixDatabase; ignoredb=false)
    added = 0
    if !ignoredb && isfile(dbpath(db))
        readdb(db)
    else
        _downloadindices(db)
        added = 1
    end
    # println("populating internal database...")
    added += addmetadata!(db)
    if added > 0
        writedb(db)
    end
    nothing
end

function _downloadindices(db::MatrixDatabase)
    empty!(db)
    insertlocal(db, GeneratedMatrixData{:B}, MATRIXDICT)
    insertlocal(db, GeneratedMatrixData{:U}, USERMATRIXDICT)

    try
        downloadindex(preferred(TURemoteType))
        downloadindex(preferred(MMRemoteType))

        extract_names(db, localindex(preferred(TURemoteType)))
        extract_names(db, localindex(preferred(MMRemoteType)))
    finally
        for data in values(db.data)
            db.aliases[aliasname(data)] = data.name
        end
    end
    nothing
end

"""
    MatrixDepot.update()
update database index from the websites
"""
function update(db::MatrixDatabase=MATRIX_DB)
    cachedb = abspath(DATA_DIR, "db.data")
    isfile(cachedb) && rm(cachedb)
    uf_matrices = localindex(preferred(TURemoteType))
    isfile(uf_matrices) && rm(uf_matrices)
    mm_matrices = localindex(preferred(MMRemoteType))
    isfile(mm_matrices) && rm(mm_matrices)
    downloadindices(db, ignoredb=true)
end

function gunzip(fname)
    endswith(fname, ".gz") || error("gunzip: $fname: unknown suffix")

    destname = rsplit(fname, ".gz", limit=2)[1]
    BUFFSIZE = 1000000
    open(destname, "w") do f
        open(GzipDecompressorStream, fname) do g
            buffer = read(g, BUFFSIZE)
            while length(buffer) > 0
                write(f, buffer)
                buffer = read(g, BUFFSIZE)
            end
        end
    end
    destname
end

"""
    loadmatrix(data::RemoteMatrixData)

Download the files backing the data from a remote repository. That is currently
the TAMU site for the UFl collection and the NIST site for the MatrixMarket
collection. The files are uncompressed and un-tar-ed if necessary.
The data files containing the matrix data have to be in MatrixMarket format in
both cases. Note, that some of the files of the MM collection are not available
in MatrixMarket format. An error message results, if tried to load them.
"""
function loadmatrix(data::RemoteMatrixData)
    file = matrixfile(data)
    if isfile(file)
        addmetadata!(data)
        return 0
    end
    dirfn = localfile(data)
    dir = dirname(localdir(data))
    url = dataurl(data)

    isdir(dir) || mkpath(dir)

    try
        println("downloading: ", url)
        download(url, dirfn)
        tarfile = gunzip(dirfn)
        if endswith(tarfile, ".tar")
            run(`tar -vxf $tarfile -C $dir`)
            rm(tarfile; force=true)
        end
    finally
        rm(dirfn, force=true)
    end
    addmetadata!(data)
    1
end
loadmatrix(data::GeneratedMatrixData) = 0

"""
    loadinfo(data::RemoteDate)
Download the first part of the data file. Stop reading, as soon as the initial
comment an the size values of the main matrix have been finished. Stor this in
a file with extension `.info` in the same directory, where the `.mtx` file is.
If the complete file is already availble, the download is not performed, because
the head of the `.mtx` file contains the same lines.
"""
function loadinfo(data::RemoteMatrixData)
    filemtx = matrixfile(data)
    file = matrixinfofile(data)
    if isfile(filemtx) || isfile(file)
        return 0
    end
    url = dataurl(data)
    urls = rsplit(url, '.', limit=3)
    cmd = []
    push!(cmd, `sh -c 'curl "'$url'" -o - 2>/dev/null'`)
    if urls[end] == "gz"
        push!(cmd, `gunzip`)
        resize!(urls, length(urls)-1)
    end
    if urls[end] == "tar"
        push!(cmd, `tar -xOf -`)
    end

    out = IOBuffer()
    s = try
        println("downloading head of $url")
        open(pipeline(cmd...), "r") do io
            while ( s = readline(io) ) != ""
                println(out, s)
                s[1] != '%' && break
            end;
            close(io)
        end
        String(take!(out))
    catch ex
        ex isa InterruptException && rethrow()
        String(take!(out))
    finally
        close(out)
    end
    if !isempty(s)
        mkpath(dirname(file))
        write(file, s)
        1
    else
        0
    end
end
loadinfo(data::MatrixData) = 0

function data_warn(data::RemoteMatrixData, dn, i1, i2)
    @warn "$(data.name): header $dn = '$i1' file $dn = '$i2' $(data.properties[])"
    i1
end

function extremnnz(data::RemoteMatrixData, m, n, k)
    if issymmetric(data) || ishermitian(data) || isskew(data)
        if m != n
            @warn "$(data.name) needs to be quadratic but is ($mx$n)"
        end
        isskew(data) ? (2k, 2k) : (2k - max(m, n), 2k)
    else
        (k, k)
    end
end
function extremnnz(data::RemoteMatrixData)
    extremnnz(data, data.m, data.n, data.dnz)
end

function addmetadata!(db::MatrixDatabase)
    added = 0
    for data in values(db.data)
        if isunloaded(data)
            addmetadata!(data)
            added += isloaded(data)
        end
    end
    added
end

addmetadata!(::MatrixData) = 0
function addmetadata!(data::RemoteMatrixData)
    file = matrixfile(data)
    dir = dirname(file)
    empty!(data.metadata)
    isdir(dir) || return 0
    base = basename(file)
    name, ext = rsplit(base, '.', limit=2)
    filtop(x) = x == base || startswith(x, string(name, '_'))
    append!(data.metadata, filter(filtop, readdir(dir)))

    if !isfile(file)
        file = matrixinfofile(data)
    end
    if (finfo = mmreadheader(file)) !== nothing
        data.properties[] = MMProperties(MATRIX, finfo[:format], finfo[:field], finfo[:symmetry])
        m, n, k = finfo[:m], finfo[:n], finfo[:nz]
        n1, n2 = extremnnz(data, m, n, k)
        hdr = data.header
        hdr.m = hdr.m in (0, m) ? m : data_warn(data, "m", hdr.m, m)
        hdr.n = hdr.n in (0, n) ? n : data_warn(data, "n", hdr.n, n)
        hdr.nnz = hdr.nnz == 0  ? n2 : hdr.nnz <= n2 ? hdr.nnz :
                                        data_warn(data, "nnz", hdr.nnz, k)
        hdr.dnz = k
        date = get(finfo, :date, 0)
        hdr.date = hdr.date in (0, date) ? date : data_warn(data, "date", hdr.date, date)
        kind = get(finfo, :kind, "")
        if kind != ""
            if hdr.kind == ""
                hdr.kind = kind
            else
                if lowercase(replace(kind, '-' => ' ')) != lowercase(hdr.kind)
                    data_warn(data, "kind", hdr.kind, kind)
                end
            end
        end
        for s in (:title, :author, :ed, :fields, :notes)
            val = get(finfo, s, "")
            val != "" && setfield!(hdr, s, val)
        end
    end
    return 1
end


text:
function ARMA(y,u,regx,regu,modelform)
# y = streams of outputs
# u = streams of inputs
# regx = number of regression steps in x
# regu = number of regression steps in u
# modelform = "linear","bilinear",etc
# returns ARMA parameters

params = [];

return params;

max_stars_repo_name: smukoehler/SDB-control

max_issues_repo_name: smukoehler/SDB-control

max_forks_repo_name: smukoehler/SDB-control


text:
const AutoScalingTypes = [
    :Activity => [
        :ActivityId => String,
        :AutoScalingGroupName => String,
        :Cause => String,
        :Description => String,
        :Details => String,
        :EndTime => Base.Dates.DateTime,
        :Progress => Int,
        :StartTime => Base.Dates.DateTime,
        :StatusCode => String,
        :StatusMessage => String
    ],
    :AdjustmentType => [
        :AdjustmentType => String
    ],
    :Alarm => [
        :AlarmARN => String,
        :AlarmName => String
    ],
    :EnabledMetric => [
        :Granularity => String,
        :Metric => String
    ],
    :Instance => [
        :AvailabilityZone => String,
        :HealthStatus => String,
        :InstanceId => String,
        :LaunchConfigurationName => String,
        :LifecycleState => String,
        :ProtectedFromScaleIn => Bool
    ],
    :SuspendedProcess => [
        :ProcessName => String,
        :SuspensionReason => String
    ],
    :TagDescription => [
        :Key => String,
        :PropagateAtLaunch => Bool,
        :ResourceId => String,
        :ResourceType => String,
        :Value => String
    ],
    :AutoScalingGroup => [
        :AutoScalingGroupARN => String,
        :AutoScalingGroupName => String,
        :AvailabilityZones => Vector{String},
        :CreatedTime => Base.Dates.DateTime,
        :DefaultCooldown => Int,
        :DesiredCapacity => Int,
        :EnabledMetrics => Vector{EnabledMetric},
        :HealthCheckGracePeriod => Int,
        :HealthCheckType => String,
        :Instances => Vector{Instance},
        :LaunchConfigurationName => String,
        :LoadBalancerNames => Vector{String},
        :MaxSize => Int,
        :MinSize => Int,
        :NewInstancesProtectedFromScaleIn => Bool,
        :PlacementGroup => String,
        :Status => String,
        :SuspendedProcesses => Vector{SuspendedProcess},
        :Tags => Vector{TagDescription},
        :TargetGroupARNs => Vector{String},
        :TerminationPolicies => Vector{String},
        :VPCZoneIdentifier => String
    ],
    :AutoScalingInstanceDetails => [
        :AutoScalingGroupName => String,
        :AvailabilityZone => String,
        :HealthStatus => String,
        :InstanceId => String,
        :LaunchConfigurationName => String,
        :LifecycleState => String,
        :ProtectedFromScaleIn => Bool
    ],
    :Ebs => [
        :DeleteOnTermination => Bool,
        :Encrypted => Bool,
        :Iops => Int,
        :SnapshotId => String,
        :VolumeSize => Int,
        :VolumeType => String
    ],
    :BlockDeviceMapping => [
        :DeviceName => String,
        :Ebs => Ebs,
        :NoDevice => Bool,
        :VirtualName => String
    ],
    :MetricDimension => [
        :Name => String,
        :Value => String
    ],
    :CustomizedMetricSpecification => [
        :Dimensions => Vector{MetricDimension},
        :MetricName => String,
        :Namespace => String,
        :Statistic => String,
        :Unit => String
    ],
    :Filter => [
        :Name => String,
        :Values => Vector{String}
    ],
    :InstanceMonitoring => [
        :Enabled => Bool
    ],
    :LaunchConfiguration => [
        :AssociatePublicIpAddress => Bool,
        :BlockDeviceMappings => Vector{BlockDeviceMapping},
        :ClassicLinkVPCId => String,
        :ClassicLinkVPCSecurityGroups => Vector{String},
        :CreatedTime => Base.Dates.DateTime,
        :EbsOptimized => Bool,
        :IamInstanceProfile => String,
        :ImageId => String,
        :InstanceMonitoring => InstanceMonitoring,
        :InstanceType => String,
        :KernelId => String,
        :KeyName => String,
        :LaunchConfigurationARN => String,
        :LaunchConfigurationName => String,
        :PlacementTenancy => String,
        :RamdiskId => String,
        :SecurityGroups => Vector{String},
        :SpotPrice => String,
        :UserData => String
    ],
    :LifecycleHook => [
        :AutoScalingGroupName => String,
        :DefaultResult => String,
        :GlobalTimeout => Int,
        :HeartbeatTimeout => Int,
        :LifecycleHookName => String,
        :LifecycleTransition => String,
        :NotificationMetadata => String,
        :NotificationTargetARN => String,
        :RoleARN => String
    ],
    :LoadBalancerState => [
        :LoadBalancerName => String,
        :State => String
    ],
    :LoadBalancerTargetGroupState => [
        :LoadBalancerTargetGroupARN => String,
        :State => String
    ],
    :MetricCollectionType => [
        :Metric => String
    ],
    :MetricGranularityType => [
        :Granularity => String
    ],
    :NotificationConfiguration => [
        :AutoScalingGroupName => String,
        :NotificationType => String,
        :TopicARN => String
    ],
    :PredefinedMetricSpecification => [
        :PredefinedMetricType => String,
        :ResourceLabel => String
    ],
    :ProcessType => [
        :ProcessName => String
    ],
    :StepAdjustment => [
        :MetricIntervalLowerBound => Float64,
        :MetricIntervalUpperBound => Float64,
        :ScalingAdjustment => Int
    ],
    :TargetTrackingConfiguration => [
        :CustomizedMetricSpecification => CustomizedMetricSpecification,
        :DisableScaleIn => Bool,
        :PredefinedMetricSpecification => PredefinedMetricSpecification,
        :TargetValue => Float64
    ],
    :ScalingPolicy => [
        :AdjustmentType => String,
        :Alarms => Vector{Alarm},
        :AutoScalingGroupName => String,
        :Cooldown => Int,
        :EstimatedInstanceWarmup => Int,
        :MetricAggregationType => String,
        :MinAdjustmentMagnitude => Int,
        :MinAdjustmentStep => Int,
        :PolicyARN => String,
        :PolicyType => String,
        :ScalingAdjustment => Int,
        :StepAdjustments => Vector{StepAdjustment},
        :TargetTrackingConfiguration => TargetTrackingConfiguration
    ],
    :ScheduledUpdateGroupAction => [
        :AutoScalingGroupName => String,
        :DesiredCapacity => Int,
        :EndTime => Base.Dates.DateTime,
        :MaxSize => Int,
        :MinSize => Int,
        :Recurrence => String,
        :ScheduledActionARN => String,
        :ScheduledActionName => String,
        :StartTime => Base.Dates.DateTime,
        :Time => Base.Dates.DateTime
    ],
    :Tag => [
        :Key => String,
        :PropagateAtLaunch => Bool,
        :ResourceId => String,
        :ResourceType => String,
        :Value => String
    ]
]

const AutoScalingApi = Dict(
    :AttachInstances => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :InstanceIds => Vector{String}
        ]
    ),
    :AttachLoadBalancers => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :LoadBalancerNames => Vector{String}
        ]
    ),
    :AttachLoadBalancerTargetGroups => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :TargetGroupARNs => Vector{String}
        ]
    ),
    :CompleteLifecycleAction => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :InstanceId => String,
            :LifecycleActionResult => String,
            :LifecycleActionToken => String,
            :LifecycleHookName => String
        ]
    ),
    :CreateAutoScalingGroup => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :AvailabilityZones => Vector{String},
            :DefaultCooldown => Int,
            :DesiredCapacity => Int,
            :HealthCheckGracePeriod => Int,
            :HealthCheckType => String,
            :InstanceId => String,
            :LaunchConfigurationName => String,
            :LoadBalancerNames => Vector{String},
            :MaxSize => Int,
            :MinSize => Int,
            :NewInstancesProtectedFromScaleIn => Bool,
            :PlacementGroup => String,
            :Tags => Vector{Tag},
            :TargetGroupARNs => Vector{String},
            :TerminationPolicies => Vector{String},
            :VPCZoneIdentifier => String
        ]
    ),
    :CreateLaunchConfiguration => Dict(
        :Request => [
            :AssociatePublicIpAddress => Bool,
            :BlockDeviceMappings => Vector{BlockDeviceMapping},
            :ClassicLinkVPCId => String,
            :ClassicLinkVPCSecurityGroups => Vector{String},
            :EbsOptimized => Bool,
            :IamInstanceProfile => String,
            :ImageId => String,
            :InstanceId => String,
            :InstanceMonitoring => InstanceMonitoring,
            :InstanceType => String,
            :KernelId => String,
            :KeyName => String,
            :LaunchConfigurationName => String,
            :PlacementTenancy => String,
            :RamdiskId => String,
            :SecurityGroups => Vector{String},
            :SpotPrice => String,
            :UserData => String
        ]
    ),
    :CreateOrUpdateTags => Dict(
        :Request => [
            :Tags => Vector{Tag}
        ]
    ),
    :DeleteAutoScalingGroup => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :ForceDelete => Bool
        ]
    ),
    :DeleteLaunchConfiguration => Dict(
        :Request => [
            :LaunchConfigurationName => String
        ]
    ),
    :DeleteLifecycleHook => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :LifecycleHookName => String
        ]
    ),
    :DeleteNotificationConfiguration => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :TopicARN => Vector{String}
        ]
    ),
    :DeletePolicy => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :PolicyName => String
        ]
    ),
    :DeleteScheduledAction => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :ScheduledActionName => String
        ]
    ),
    :DeleteTags => Dict(
        :Request => [
            :Tags => Vector{Tag}
        ]
    ),
    :DescribeAccountLimits => Dict(
        :Result => [
            :MaxNumberOfAutoScalingGroups => Int,
            :MaxNumberOfLaunchConfigurations => Int,
            :NumberOfAutoScalingGroups => Int,
            :NumberOfLaunchConfigurations => Int
        ]
    ),
    :DescribeAdjustmentTypes => Dict(
        :Result => [
            :AdjustmentTypes => Vector{AdjustmentType}
        ]
    ),
    :DescribeAutoScalingGroups => Dict(
        :Request => [
            :AutoScalingGroupNames => Vector{String},
            :MaxRecords => Int,
            :NextToken => String
        ],
        :Result => [
            :AutoScalingGroups => Vector{AutoScalingGroup},
            :NextToken => String
        ]
    ),
    :DescribeAutoScalingInstances => Dict(
        :Request => [
            :InstanceIds => Vector{String},
            :MaxRecords => Int,
            :NextToken => String
        ],
        :Result => [
            :AutoScalingInstances => Vector{AutoScalingInstanceDetails},
            :NextToken => String
        ]
    ),
    :DescribeAutoScalingNotificationTypes => Dict(
        :Result => [
            :AutoScalingNotificationTypes => Vector{String}
        ]
    ),
    :DescribeLaunchConfigurations => Dict(
        :Request => [
            :LaunchConfigurationNames => Vector{String},
            :MaxRecords => Int,
            :NextToken => String
        ],
        :Result => [
            :LaunchConfigurations => Vector{LaunchConfiguration},
            :NextToken => String
        ]
    ),
    :DescribeLifecycleHooks => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :LifecycleHookNames => Vector{String}
        ],
        :Result => [
            :LifecycleHooks => Vector{LifecycleHook}
        ]
    ),
    :DescribeLifecycleHookTypes => Dict(
        :Result => [
            :LifecycleHookTypes => Vector{String}
        ]
    ),
    :DescribeLoadBalancers => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :MaxRecords => Int,
            :NextToken => String
        ],
        :Result => [
            :LoadBalancers => Vector{LoadBalancerState},
            :NextToken => String
        ]
    ),
    :DescribeLoadBalancerTargetGroups => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :MaxRecords => Int,
            :NextToken => String
        ],
        :Result => [
            :LoadBalancerTargetGroups => Vector{LoadBalancerTargetGroupState},
            :NextToken => String
        ]
    ),
    :DescribeMetricCollectionTypes => Dict(
        :Result => [
            :Granularities => Vector{MetricGranularityType},
            :Metrics => Vector{MetricCollectionType}
        ]
    ),
    :DescribeNotificationConfigurations => Dict(
        :Request => [
            :AutoScalingGroupNames => Vector{String},
            :MaxRecords => Int,
            :NextToken => String
        ],
        :Result => [
            :NextToken => String,
            :NotificationConfigurations => Vector{NotificationConfiguration}
        ]
    ),
    :DescribePolicies => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :MaxRecords => Int,
            :NextToken => String,
            :PolicyNames => Vector{String},
            :PolicyTypes => Vector{String}
        ],
        :Result => [
            :NextToken => String,
            :ScalingPolicies => Vector{ScalingPolicy},
        ]
    ),
    :DescribeScalingActivities => Dict(
        :Request => [
            :ActivityIds => Vector{String},
            :AutoScalingGroupName => String,
            :MaxRecords => Int,
            :NextToken => String
        ],
        :Result => [
            :Activities => Vector{Activity},
            :NextToken => String
        ]
    ),
    :DescribeScalingProcessTypes => Dict(
        :Result => [
            :Processes => Vector{ProcessType}
        ]
    ),
    :DescribeScheduledActions => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :EndTime => Base.Dates.DateTime,
            :MaxRecords => Int,
            :NextToken => String,
            :ScheduledActionNames => Vector{String},
            :StartTime => Base.Dates.DateTime
        ],
        :Result => [
            :NextToken => String,
            :ScheduledUpdateGroupActions => Vector{ScheduledUpdateGroupAction}
        ]
    ),
    :DescribeTags => Dict(
        :Request => [
            :Filters => Vector{Filter},
            :MaxRecords => Int,
            :NextToken => String,
        ],
        :Result => [
            :NextToken => String,
            :Tags => Vector{TagDescription}
        ]
    ),
    :DescribeTerminationPolicyTypes => Dict(
        :Result => [
            :TerminationPolicyTypes => Vector{String}
        ]
    ),
    :DetachInstances => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :InstanceIds => Vector{String},
            :ShouldDecrementDesiredCapacity => Bool
        ],
        :Result => [
            :Activities => Vector{Activity}
        ]
    ),
    :DetachLoadBalancers => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :LoadBalancerNames => Vector{String}
        ]
    ),
    :DetachLoadBalancerTargetGroups => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :TargetGroupARNs => Vector{String}
        ]
    ),
    :DisableMetricsCollection => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :Metrics => Vector{String}
        ]
    ),
    :EnableMetricsCollection => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :Granularity => String,
            :Metrics => Vector{String}
        ]
    ),
    :EnterStandby => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :InstanceIds => Vector{String},
            :ShouldDecrementDesiredCapacity => Bool
        ],
        :Result => [
            :Activities => Vector{Activity}
        ]
    ),
    :ExecutePolicy => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :BreachThreshold => Float64,
            :HonorCooldown => Bool,
            :MetricValue => Float64,
            :PolicyName => String
        ]
    ),
    :ExitStandby => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :InstanceIds => Vector{String}
        ],
        :Result => [
            :Activities => Vector{Activity}
        ]
    ),
    :PutLifecycleHook => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :DefaultResult => String,
            :HeartbeatTimeout => Int,
            :LifecycleHookName => String,
            :LifecycleTransition => String,
            :NotificationMetadata => String,
            :NotificationTargetARN => String,
            :RoleARN => String
        ]
    ),
    :PutNotificationConfiguration => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :NotificationTypes => Vector{String},
            :TopicARN => String
        ]
    ),
    :PutScalingPolicy => Dict(
        :Request => [
            :AdjustmentType => String,
            :AutoScalingGroupName => String,
            :Cooldown => Int,
            :EstimatedInstanceWarmup => Int,
            :MetricAggregationType => String,
            :MinAdjustmentMagnitude => Int,
            :MinAdjustmentStep => Int,
            :PolicyName => String,
            :PolicyType => String,
            :ScalingAdjustment => Int,
            :StepAdjustments => Vector{StepAdjustment},
            :TargetTrackingConfiguration => TargetTrackingConfiguration
        ],
        :Result => [
            :Alarms => Vector{Alarm},
            :PolicyARN => String
        ]
    ),
    :PutScheduledUpdateGroupAction => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :DesiredCapacity => Int,
            :EndTime => Base.Dates.DateTime,
            :MaxSize => Int,
            :MinSize => Int,
            :Recurrence => String,
            :ScheduledActionName => String,
            :StartTime => Base.Dates.DateTime,
            :Time => Base.Dates.DateTime
        ]
    ),
    :RecordLifecycleActionHeartbeat => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :InstanceId => String,
            :LifecycleActionToken => String,
            :LifecycleHookName => String
        ]
    ),
    :ResumeProcesses => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :ScalingProcesses => Vector{String}
        ]
    ),
    :SetDesiredCapacity => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :DesiredCapacity => Int,
            :HonorCooldown => Bool
        ]
    ),
    :SetInstanceHealth => Dict(
        :Request => [
            :HealthStatus => String,
            :InstanceId => String,
            :ShouldRespectGracePeriod => Bool
        ]
    ),
    :SetInstanceProtection => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :InstanceIds => Vector{String},
            :ProtectedFromScaleIn => Bool
        ]
    ),
    :SuspendProcesses => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :ScalingProcesses => Vector{String}
        ]
    ),
    :TerminateInstanceInAutoScalingGroup => Dict(
        :Request => [
            :InstanceId => String,
            :ShouldDecrementDesiredCapacity => Bool
        ],
        :Result => [
            :Activity => Activity
        ]
    ),
    :UpdateAutoScalingGroup => Dict(
        :Request => [
            :AutoScalingGroupName => String,
            :AvailabilityZones => Vector{String},
            :DefaultCooldown => Int,
            :DesiredCapacity => Int,
            :HealthCheckGracePeriod => Int,
            :HealthCheckType => String,
            :LaunchConfigurationName => String,
            :MaxSize => Int,
            :MinSize => Int,
            :NewInstancesProtectedFromScaleIn => Bool,
            :PlacementGroup => String,
            :TerminationPolicies => Vector{String},
            :VPCZoneIdentifier => String
        ]
    )
)

function _autoscaling_type_map()
    d = Dict(AutoScalingTypes...)
    for (apiname,apispec) in AutoScalingApi
        for (opname,optype) in apispec
            d[Symbol(string(apiname)*string(opname))] = optype
        end
    end
    d
end

const AutoScalingTypeDict = _autoscaling_type_map()
const AutoScalingApiVer = "2011-01-01"


text:
# Qmatrix.jl

function explanatoryMIRT(ndims, I)
    q = ones(Bool, I, ndims)
    # Constraints for identification
    if ndims > 1
        for d in 2:ndims
            q[end-d+2, d] = false
        end
    end
    return q
end


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

name = "cryptopp"
version = v"8.5"

# Collection of sources required to build cryptopp
sources = [
    GitSource("https://github.com/weidai11/cryptopp.git", "f2102243e6fdd48c0b2a393a0993cca228f20573"),
    DirectorySource("./bundled"),
]

# Bash recipe for building across all platforms
script = raw"""
cd $WORKSPACE/srcdir/cryptopp
# We need to fix the required glibc version.  They check that `getauxval` is available by
# checking the version of glibc (at least 2.16), but then they assume `AT_HWCAP2` is
# defined, which was introduced in glibc 2.18, which then sets a higher requirement than
# only having `getauxval`.
atomic_patch -p1 ../patches/powerpc-getauxval.patch
make -j${nproc} dynamic
make -j${nproc} install-lib PREFIX=${prefix}
if [[ "${target}" == *-mingw* ]]; then
    # The build system creates the shared library with the wrong name...
    mkdir -p "${libdir}"
    mv "${prefix}/lib/libcryptopp.so" "${libdir}/libcryptopp.${dlext}"
fi
"""

# These are the platforms we will build for by default, unless further
# platforms are passed in on the command line
platforms = expand_cxxstring_abis(supported_platforms())

# The products that we will ensure are always built
products = [
    LibraryProduct("libcryptopp", :libcryptopp)
]

# Dependencies that must be installed before this package can be built
dependencies = Dependency[
]

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

max_stars_repo_name: sharanry/Yggdrasil

max_issues_repo_name: sharanry/Yggdrasil

max_forks_repo_name: sharanry/Yggdrasil


text:
### A Pluto.jl notebook ###
# v0.16.4

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ e4dca908-1016-11ec-0b7f-e9d01bd4e1b2
md"""
# Solution Explorer
This is an interactive visualization of by-county solutions. Solutions
are taken from the `out` directory in the repository root, and can be
produced by executing `m-core/sweep.m` in MatLab.

*Note:* currently, only county-level solutions are supported. Solutions aggregated
to a different level will not be displayed.

---
Authors: Kara Ignatenko, 2021;
		 Yaroslav Salii, 2021

Changelog:
- 2021-10-09  v.1.0: First complete version
- 2021-10-24  v.1.1: Add control effort choropleth

"""

# ╔═╡ 3389dc98-b90f-4c6a-8c96-31840b0516ea
# a workaround to allow dynamically reloaded includes
# --refresh this cell to reload the included files
module includes
	include("viz-ui.jl")
	include("vega-specs.jl")
	include("exporter.jl")
end

# ╔═╡ e598c897-61bf-4bc2-91db-e225362f5606
# Start off by importing all the stuff
begin
	using VegaLite,VegaDatasets
	using DataFrames, CSV, Statistics, Revise
	using PlutoUI, HypertextLiteral

	UI = includes.VizUI
	Specs = includes.VegaSpecs
	Exp = includes.Exporter

	thisPath = splitpath(@__DIR__)
	projRoot = thisPath[1:findfirst(isequal("epi-net-m"), thisPath)]

	const iDir = joinpath(projRoot...,"out") #here be the simulator's outputs

end

# ╔═╡ 0d909765-a87d-4a3c-bb9f-ff80e463992a
#TODO: file selector based on the contents of /out
begin
	#get the list of csv files in ../out
	outDir = readdir(iDir)
	csvs = filter(endswith(".csv"), outDir)
	#get the list of unique solution names (prefixes)
	solNames = map(fname -> split(fname, "-")[1], csvs) |> unique
	filter!(contains("cty"), solNames)

	#create a <select> to pick from the prefixes
	picker = @bind slnName Select(solNames)

	md"""
	Select solution: $(picker)
	"""
end

# ╔═╡ ced90367-407c-4b67-8276-68edc17933d3
"""
Read the solutions into dataframes and stuff them into a named tuple
:f,:f0 are *fractional* per-node per-day s/z/r
:a,:a0 are *absolute* per-node per-day S/Z/R
:avgc are per-day average infected fraction z, zNull, and average optimal control effort u
"""
function rdSolutions(iName, slnDir)
	slnSuffs = ["-frac.csv","-frac0.csv","-abs.csv","-abs0.csv","-avg.csv"]
    slns= map( p -> CSV.read(p,DataFrame), (joinpath(iDir,slnName * suff) for suff in slnSuffs))
    return NamedTuple([:f,:f0,:a,:a0, :avgc] .=> slns)
end

# ╔═╡ 6a2ca31f-716c-48f4-8e1a-70e3c033358b
try
	ss = rdSolutions(slnName, iDir)

	global a0Median = ss.a0.Z180 |> median
	global a0Max = ss.a0.Z180 |> maximum

	#get rid of irrelevant (for now) data by only selecting the Z values
	a_clean = select(ss.a, "id", r"^Z")
	a0_clean = select(ss.a0, "id", r"^Z")
	f_clean = select(ss.f, "id", r"^u")

	#toss everything into one dataframe to simplify rendering
	#for the null case, add _NULL to the column names
	global sol = innerjoin(a_clean, a0_clean, on="id", renamecols=("" => "_NULL"))
	#add *control effort* data; f_clean should be fine too
	global sol2 = innerjoin(sol, f_clean, on="id")

	#"-avg.csv" has 3 columns: z_avg; zNull_avg; u_avg, and per-day rows.
	#insert day numbers
	insertcols!(ss.avgc,1,:day => 0:(nrow(ss.avgc)-1))
	global long_avgc = stack(ss.avgc,[:z_avg,:zNull_avg,:u_avg],variable_name=:symbol)

	md"Solution files read successfully!"
catch err
	md"**Error while reading solution files:** $(err)"
end

# ╔═╡ dcabd381-1316-449b-9ebf-68410ee6e3fd
@bind day UI.dayPicker(0, 180, [1, 10])

# ╔═╡ cc533197-dbe3-41b6-8478-64f82da52ba8
md"Day selected: $day"

# ╔═╡ 8398bb71-aa8e-42b6-b3e3-c08ca57ee5b0
begin
	plt_u = Specs.pltCtrlByCty(sol2,day)
	UI.vegaEmbed(plt_u)
end

# ╔═╡ a6a64f62-316a-48f5-bd5c-0eebff53022d
begin
	pltZ = Specs.pltZOptVsNullByCty(sol, day, a0Median)
	UI.vegaEmbed(pltZ)
end

# ╔═╡ f4bf1143-c315-4521-b19b-06bf0373828a
begin
	pltAvgZandU = Specs.pltAvgInfdCtrl(long_avgc)
	UI.vegaEmbed(pltAvgZandU)
end

# ╔═╡ 86ddda30-351d-41f6-94b2-4c84dea08448
begin
	sel = @bind format Select(["svg", "pdf"])
	cbx = @bind exportMap UI.booleanButton("Export map")
	cbx2 = @bind exportPlot UI.booleanButton("Export avg. plot")
	figDir = Exp.figDir
	@htl("""Select export format: $sel $cbx $cbx2 – Will be saved to $figDir""")
end

# ╔═╡ bfd9753c-323a-4a82-b5e6-057e7519ed6c
if exportMap
	Exp.savePlot(slnName * "_day$(day)_", pltZ, format)
end

# ╔═╡ ff5a5927-6bc6-4b05-b0e8-e0bfdc623440
if exportPlot
	Exp.savePlot(slnName * "_avgZ_", pltAvgZandU, format)
end

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
HypertextLiteral = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
VegaDatasets = "0ae4a718-28b7-58ec-9efb-cded64d6d5b4"
VegaLite = "112f6efa-9a02-5b7d-90c0-432ed331239a"

[compat]
CSV = "~0.8.5"
DataFrames = "~1.2.2"
HypertextLiteral = "~0.9.0"
PlutoUI = "~0.7.9"
Revise = "~3.1.19"
VegaDatasets = "~2.1.1"
VegaLite = "~2.6.0"
"""

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

[[ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

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

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

[[CSV]]
deps = ["Dates", "Mmap", "Parsers", "PooledArrays", "SentinelArrays", "Tables", "Unicode"]
git-tree-sha1 = "b83aa3f513be680454437a0eee21001607e5d983"
uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
version = "0.8.5"

[[ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "30ee06de5ff870b45c78f529a6b093b3323256a3"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.3.1"

[[CodeTracking]]
deps = ["InteractiveUtils", "UUIDs"]
git-tree-sha1 = "9aa8a5ebb6b5bf469a7e0e2b5202cf6f8c291104"
uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"
version = "1.0.6"

[[CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.0"

[[Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "727e463cfebd0c7b999bbf3e9e7e16f254b94193"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.34.0"

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

[[ConstructionBase]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "f74e9d5388b8620b4cee35d4c5a618dd4dc547f4"
uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
version = "1.3.0"

[[Crayons]]
git-tree-sha1 = "3f71217b538d7aaee0b69ab47d9b7724ca8afa0d"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.0.4"

[[DataAPI]]
git-tree-sha1 = "bec2532f8adb82005476c141ec23e921fc20971b"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.8.0"

[[DataFrames]]
deps = ["Compat", "DataAPI", "Future", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrettyTables", "Printf", "REPL", "Reexport", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"]
git-tree-sha1 = "d785f42445b63fc86caa08bb9a9351008be9b765"
uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
version = "1.2.2"

[[DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "7d9d316f04214f7efdbb6398d545446e246eff02"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.10"

[[DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[DataValues]]
deps = ["DataValueInterfaces", "Dates"]
git-tree-sha1 = "d88a19299eba280a6d062e135a43f00323ae70bf"
uuid = "e7dc6d0d-1eca-5fa6-8ad6-5aecde8b7ea5"
version = "0.4.13"

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

[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "a32185f5428d3986f47c2ab78b1f216d5e6cc96f"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.5"

[[DoubleFloats]]
deps = ["GenericLinearAlgebra", "LinearAlgebra", "Polynomials", "Printf", "Quadmath", "Random", "Requires", "SpecialFunctions"]
git-tree-sha1 = "1c962cf7e75c09a5f1fbf504df7d6a06447a1129"
uuid = "497a8b3b-efae-58df-a0af-a86822472b78"
version = "1.1.23"

[[Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[ExprTools]]
git-tree-sha1 = "b7e3d17636b348f005f11040025ae8c6f645fe92"
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
version = "0.1.6"

[[FileIO]]
deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "937c29268e405b6808d958a9ac41bfe1a31b08e7"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.11.0"

[[FilePaths]]
deps = ["FilePathsBase", "MacroTools", "Reexport", "Requires"]
git-tree-sha1 = "919d9412dbf53a2e6fe74af62a73ceed0bce0629"
uuid = "8fc22ac5-c921-52a6-82fd-178b2807b824"
version = "0.8.3"

[[FilePathsBase]]
deps = ["Dates", "Mmap", "Printf", "Test", "UUIDs"]
git-tree-sha1 = "0f5e8d0cb91a6386ba47bd1527b240bd5725fbae"
uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
version = "0.9.10"

[[FileWatching]]
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"

[[Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

[[Future]]
deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"

[[GenericLinearAlgebra]]
deps = ["LinearAlgebra", "Printf", "Random"]
git-tree-sha1 = "ff291c1827030ffaacaf53e3c83ed92d4d5e6fb6"
uuid = "14197337-ba66-59df-a3e3-ca00e7dcff7a"
version = "0.2.5"

[[HTTP]]
deps = ["Base64", "Dates", "IniFile", "MbedTLS", "Sockets"]
git-tree-sha1 = "c7ec02c4c6a039a98a15f955462cd7aea5df4508"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.8.19"

[[HypertextLiteral]]
git-tree-sha1 = "72053798e1be56026b81d4e2682dbe58922e5ec9"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.0"

[[IniFile]]
deps = ["Test"]
git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.0"

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

[[Intervals]]
deps = ["Dates", "Printf", "RecipesBase", "Serialization", "TimeZones"]
git-tree-sha1 = "323a38ed1952d30586d0fe03412cde9399d3618b"
uuid = "d8418881-c3e1-53bb-8760-2df7ec849ed5"
version = "1.5.0"

[[InvertedIndices]]
git-tree-sha1 = "bee5f1ef5bf65df56bdd2e40447590b272a5471f"
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
version = "1.1.0"

[[IrrationalConstants]]
git-tree-sha1 = "f76424439413893a832026ca355fe273e93bce94"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.0"

[[IterableTables]]
deps = ["DataValues", "IteratorInterfaceExtensions", "Requires", "TableTraits", "TableTraitsUtils"]
git-tree-sha1 = "70300b876b2cebde43ebc0df42bc8c94a144e1b4"
uuid = "1c8ee90f-4401-5389-894e-7a04a3dc0f4d"
version = "1.0.0"

[[IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

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

[[JSONSchema]]
deps = ["HTTP", "JSON", "ZipFile"]
git-tree-sha1 = "b84ab8139afde82c7c65ba2b792fe12e01dd7307"
uuid = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
version = "0.3.3"

[[JuliaInterpreter]]
deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"]
git-tree-sha1 = "e273807f38074f033d94207a201e6e827d8417db"
uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a"
version = "0.8.21"

[[LazyArtifacts]]
deps = ["Artifacts", "Pkg"]
uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3"

[[LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

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

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

[[LogExpFunctions]]
deps = ["ChainRulesCore", "DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "86197a8ecb06e222d66797b0c2d2f0cc7b69e42b"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.2"

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

[[LoweredCodeUtils]]
deps = ["JuliaInterpreter"]
git-tree-sha1 = "491a883c4fef1103077a7f648961adbf9c8dd933"
uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b"
version = "2.1.2"

[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "0fb723cd8c45858c22169b2e42269e53271a6df7"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.7"

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

[[MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"]
git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.3"

[[MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "2ca267b08821e86c5ef4376cffed98a46c2cb205"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.1"

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

[[Mocking]]
deps = ["ExprTools"]
git-tree-sha1 = "748f6e1e4de814b101911e64cc12d83a6af66782"
uuid = "78c3b35d-d492-501b-9361-3d52fe80e533"
version = "0.7.2"

[[MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[MutableArithmetics]]
deps = ["LinearAlgebra", "SparseArrays", "Test"]
git-tree-sha1 = "3927848ccebcc165952dc0d9ac9aa274a87bfe01"
uuid = "d8a4904e-b15c-11e9-3269-09a3773c0cb0"
version = "0.2.20"

[[NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[NodeJS]]
deps = ["Pkg"]
git-tree-sha1 = "905224bbdd4b555c69bb964514cfa387616f0d3a"
uuid = "2bd173c7-0d6d-553b-b6af-13a54713934c"
version = "1.3.0"

[[Nullables]]
git-tree-sha1 = "8f87854cc8f3685a60689d8edecaa29d2251979b"
uuid = "4d1e1d77-625e-5b40-9113-a560ec7a8ecd"
version = "1.0.0"

[[OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

[[OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

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

[[Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

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

[[Polynomials]]
deps = ["Intervals", "LinearAlgebra", "MutableArithmetics", "RecipesBase"]
git-tree-sha1 = "0bbfdcd8cda81b8144de4be8a67f5717e959a005"
uuid = "f27b6e38-b328-58d1-80ce-0feddd5e7a45"
version = "2.0.14"

[[PooledArrays]]
deps = ["DataAPI", "Future"]
git-tree-sha1 = "a193d6ad9c45ada72c14b731a318bedd3c2f00cf"
uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
version = "1.3.0"

[[Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

[[PrettyTables]]
deps = ["Crayons", "Formatting", "Markdown", "Reexport", "Tables"]
git-tree-sha1 = "0d1245a357cc61c8cd61934c07447aa569ff22e6"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "1.1.0"

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

[[Quadmath]]
deps = ["Printf", "Random", "Requires"]
git-tree-sha1 = "5a8f74af8eae654086a1d058b4ec94ff192e3de0"
uuid = "be4d8f0f-7fa4-5f49-b795-2f01399ab2dd"
version = "0.5.5"

[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

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

[[RecipesBase]]
git-tree-sha1 = "44a75aa7a527910ee3d1751d1f0e4148698add9e"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.1.2"

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

[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

[[Revise]]
deps = ["CodeTracking", "Distributed", "FileWatching", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "Pkg", "REPL", "Requires", "UUIDs", "Unicode"]
git-tree-sha1 = "1947d2d75463bd86d87eaba7265b0721598dd803"
uuid = "295af30f-e4ad-537b-8983-00126c2a3abe"
version = "3.1.19"

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

[[SentinelArrays]]
deps = ["Dates", "Random"]
git-tree-sha1 = "54f37736d8934a12a200edea2f9206b03bdf3159"
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
version = "1.3.7"

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

[[Setfield]]
deps = ["ConstructionBase", "Future", "MacroTools", "Requires"]
git-tree-sha1 = "fca29e68c5062722b5b4435594c3d1ba557072a3"
uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46"
version = "0.7.1"

[[SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"

[[SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[SpecialFunctions]]
deps = ["ChainRulesCore", "LogExpFunctions", "OpenSpecFun_jll"]
git-tree-sha1 = "a322a9493e49c5f3a10b50df3aedaf1cdb3244b7"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.6.1"

[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

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

[[TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[TableShowUtils]]
deps = ["DataValues", "Dates", "JSON", "Markdown", "Test"]
git-tree-sha1 = "14c54e1e96431fb87f0d2f5983f090f1b9d06457"
uuid = "5e66a065-1f0a-5976-b372-e0b8c017ca10"
version = "0.2.5"

[[TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[TableTraitsUtils]]
deps = ["DataValues", "IteratorInterfaceExtensions", "Missings", "TableTraits"]
git-tree-sha1 = "78fecfe140d7abb480b53a44f3f85b6aa373c293"
uuid = "382cd787-c1b6-5bf2-a167-d5b971a19bda"
version = "1.0.2"

[[Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "d0c690d37c73aeb5ca063056283fde5585a41710"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.5.0"

[[Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[TextParse]]
deps = ["CodecZlib", "DataStructures", "Dates", "DoubleFloats", "Mmap", "Nullables", "WeakRefStrings"]
git-tree-sha1 = "eb1f4fb185c8644faa2d18d14c72f2c24412415f"
uuid = "e0df1984-e451-5cb5-8b61-797a481e67e3"
version = "1.0.2"

[[TimeZones]]
deps = ["Dates", "Future", "LazyArtifacts", "Mocking", "Pkg", "Printf", "RecipesBase", "Serialization", "Unicode"]
git-tree-sha1 = "6c9040665b2da00d30143261aea22c7427aada1c"
uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53"
version = "1.5.7"

[[TranscodingStreams]]
deps = ["Random", "Test"]
git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.6"

[[URIParser]]
deps = ["Unicode"]
git-tree-sha1 = "53a9f49546b8d2dd2e688d216421d050c9a31d0d"
uuid = "30578b45-9adc-5946-b283-645ec420af67"
version = "0.4.1"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

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

[[Vega]]
deps = ["DataStructures", "DataValues", "Dates", "FileIO", "FilePaths", "IteratorInterfaceExtensions", "JSON", "JSONSchema", "MacroTools", "NodeJS", "Pkg", "REPL", "Random", "Setfield", "TableTraits", "TableTraitsUtils", "URIParser"]
git-tree-sha1 = "43f83d3119a868874d18da6bca0f4b5b6aae53f7"
uuid = "239c3e63-733f-47ad-beb7-a12fde22c578"
version = "2.3.0"

[[VegaDatasets]]
deps = ["DataStructures", "DataValues", "FilePaths", "IterableTables", "IteratorInterfaceExtensions", "JSON", "TableShowUtils", "TableTraits", "TableTraitsUtils", "TextParse"]
git-tree-sha1 = "c997c7217f37205c5795de8c797f8f8531890f1d"
uuid = "0ae4a718-28b7-58ec-9efb-cded64d6d5b4"
version = "2.1.1"

[[VegaLite]]
deps = ["Base64", "DataStructures", "DataValues", "Dates", "FileIO", "FilePaths", "IteratorInterfaceExtensions", "JSON", "MacroTools", "NodeJS", "Pkg", "REPL", "Random", "TableTraits", "TableTraitsUtils", "URIParser", "Vega"]
git-tree-sha1 = "3e23f28af36da21bfb4acef08b144f92ad205660"
uuid = "112f6efa-9a02-5b7d-90c0-432ed331239a"
version = "2.6.0"

[[WeakRefStrings]]
deps = ["DataAPI", "Parsers", "Random", "Test"]
git-tree-sha1 = "9ef95db08bf767499a74586bcbd4b5df30c19b9f"
uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5"
version = "1.1.0"

[[ZipFile]]
deps = ["Libdl", "Printf", "Zlib_jll"]
git-tree-sha1 = "c3a5637e27e914a7a445b8d0ad063d701931e9f7"
uuid = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea"
version = "0.9.3"

[[Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╟─e4dca908-1016-11ec-0b7f-e9d01bd4e1b2
# ╠═3389dc98-b90f-4c6a-8c96-31840b0516ea
# ╟─e598c897-61bf-4bc2-91db-e225362f5606
# ╠═0d909765-a87d-4a3c-bb9f-ff80e463992a
# ╠═ced90367-407c-4b67-8276-68edc17933d3
# ╠═6a2ca31f-716c-48f4-8e1a-70e3c033358b
# ╠═cc533197-dbe3-41b6-8478-64f82da52ba8
# ╠═dcabd381-1316-449b-9ebf-68410ee6e3fd
# ╠═8398bb71-aa8e-42b6-b3e3-c08ca57ee5b0
# ╠═a6a64f62-316a-48f5-bd5c-0eebff53022d
# ╟─f4bf1143-c315-4521-b19b-06bf0373828a
# ╠═86ddda30-351d-41f6-94b2-4c84dea08448
# ╠═bfd9753c-323a-4a82-b5e6-057e7519ed6c
# ╠═ff5a5927-6bc6-4b05-b0e8-e0bfdc623440
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


text:
@system Weight begin
    CO2_weight => 44.0098 ~ preserve(u"g/mol")
    C_weight => 12.0107 ~ preserve(u"g/mol")
    CH2O_weight => 30.031 ~ preserve(u"g/mol")
    H2O_weight => 18.01528 ~ preserve(u"g/mol")

    C_to_CH2O_ratio(C_weight, CH2O_weight) => begin
        CH2O_weight / C_weight
    end ~ preserve

    CH2O_to_C_ratio(C_to_CH2O_ratio) => (1 / C_to_CH2O_ratio) ~ preserve
end


text:
module CompetitiveEquilibriumInvestments

using JuMP

import MathOptInterface
const MOI = MathOptInterface

import DataFrames: DataFrame, DataFrame!, stack, Not
import CSV

const ExpressionRef = GenericAffExpr{Float64,VariableRef}
const QuadExpressionRef = GenericQuadExpr{Float64,VariableRef}

const ConstrRef{T} = ConstraintRef{Model,MOI.ConstraintIndex{
     MOI.ScalarAffineFunction{Float64},T},ScalarShape}

const LessThanConstraintRef    = ConstrRef{MOI.LessThan{Float64}}
const GreaterThanConstraintRef = ConstrRef{MOI.GreaterThan{Float64}}
const EqualToConstraintRef     = ConstrRef{MOI.EqualTo{Float64}}

const SparseAxisArray{T,N} = JuMP.Containers.SparseAxisArray{T,N,NTuple{N,Int64}}
const Optimizer = Union{Type{<:MOI.AbstractOptimizer}, MOI.OptimizerWithAttributes}

abstract type AbstractProblem{R,G1,G2,G3,I,T,P} end
abstract type AbstractScenario end

function varnames!(vars::Array{VariableRef},
                   basename::String, labels::Vector{String}...)

    dims = size(vars)
    dims == length.(labels) || error("Provided labels do not match array size")

    for idxs in Iterators.product(Base.OneTo.(dims)...)
        idx_labels = getindex.(labels, idxs)
        set_name(vars[idxs...], basename * "[" * join(idx_labels, ",") * "]")
    end

end

include("readutils.jl")

include("resources.jl")
include("investments.jl")
include("operations/operations.jl")
include("markets/markets.jl")
include("Scenario.jl")
include("InvestmentProblem.jl")
include("report.jl")

export

    Technologies,
    ThermalGenerators, VariableGenerators, StorageDevices, Interfaces,

    InitialConditions, InitialInvestments, Investments, ResourceInvestments,

    Operations,
    ThermalGeneratorOperations, VariableGeneratorOperations,
    StorageOperations, TransmissionOperations,

    Markets, CapacityMarket, RECMarket,
    EnergyMarket, RaiseReserveMarket, LowerReserveMarket,

    Scenario, InvestmentProblem, solve!, report

end


text:
import Base: ==, ≈, copy, eltype
import Random.rand

export LazySet,
       basetype,
       ρ, support_function,
       σ, support_vector,
       dim,
       norm,
       radius,
       diameter,
       an_element,
       isbounded, isbounded_unit_dimensions,
       neutral,
       absorbing,
       tosimplehrep,
       isuniversal,
       translate,
       affine_map,
       reflect,
       is_interior_point,
       isoperation,
       isoperationtype,
       isequivalent,
       isconvextype,
       area,
       surface

"""
    LazySet{N}

Abstract type for convex sets, i.e., sets characterized by a (possibly infinite)
intersection of halfspaces, or equivalently, sets ``S`` such that for any two
elements ``x, y ∈ S`` and ``0 ≤ λ ≤ 1`` it holds that ``λ·x + (1-λ)·y ∈ S``.

### Notes

`LazySet` types should be parameterized with a type `N`, typically `N<:Real`,
for using different numeric types.

Every concrete `LazySet` must define the following functions:
- `σ(d::AbstractVector{N}, S::LazySet{N}) where {N<:Real}` -- the support vector
    of `S` in a given direction `d`; note that the numeric type `N` of `d` and
    `S` must be identical; for some set types `N` may be more restrictive than
    `Real`
- `dim(S::LazySet)` -- the ambient dimension of `S`

The function
- `ρ(d::AbstractVector{N}, S::LazySet{N}) where {N<:Real}` -- the support
    function of `S` in a given direction `d`; note that the numeric type `N` of
    `d` and `S` must be identical; for some set types `N` may be more
    restrictive than `Real`
is optional because there is a fallback implementation relying on `σ`.
However, for unbounded sets (which includes most lazy set types) this fallback
cannot be used and an explicit method must be implemented.

The subtypes of `LazySet` (including abstract interfaces):

```jldoctest; setup = :(using LazySets: subtypes)
julia> subtypes(LazySet, false)
14-element Array{Any,1}:
 AbstractAffineMap
 AbstractCentrallySymmetric
 AbstractPolyhedron
 Bloating
 CachedMinkowskiSumArray
 CartesianProduct
 CartesianProductArray
 ConvexHull
 ConvexHullArray
 EmptySet
 Intersection
 IntersectionArray
 MinkowskiSum
 MinkowskiSumArray
```

If we only consider *concrete* subtypes, then:

```jldoctest; setup = :(using LazySets: subtypes)
julia> concrete_subtypes = subtypes(LazySet, true);

julia> length(concrete_subtypes)
39

julia> println.(concrete_subtypes);
AffineMap
Ball1
Ball2
BallInf
Ballp
Bloating
CachedMinkowskiSumArray
CartesianProduct
CartesianProductArray
ConvexHull
ConvexHullArray
Ellipsoid
EmptySet
ExponentialMap
ExponentialProjectionMap
HPolygon
HPolygonOpt
HPolyhedron
HPolytope
HalfSpace
Hyperplane
Hyperrectangle
Intersection
IntersectionArray
Interval
Line
LineSegment
LinearMap
MinkowskiSum
MinkowskiSumArray
ResetMap
Singleton
SymmetricIntervalHull
Translation
Universe
VPolygon
VPolytope
ZeroSet
Zonotope
```
"""
abstract type LazySet{N} end

# --- common LazySet functions ---

"""
    eltype(::Type{<:LazySet{N}}) where {N}

Return the numeric type (`N`) of the given set type.

### Input

- `T` -- set type, used for dispatch

### Output

The numeric type of `T`.
"""
eltype(::Type{<:LazySet{N}}) where {N} = N

"""
    eltype(::LazySet{N}) where {N}

Return the numeric type (`N`) of the given set.

### Input

- `X` -- set instance, used for dispatch

### Output

The numeric type of `X`.
"""
eltype(::LazySet{N}) where {N} = N

"""
    basetype(T::Type{<:LazySet})

Return the base type of the given set type (i.e., without type parameters).

### Input

- `T` -- set type, used for dispatch

### Output

The base type of `T`.
"""
basetype(T::Type{<:LazySet}) = Base.typename(T).wrapper

"""
    basetype(S::LazySet)

Return the base type of the given set (i.e., without type parameters).

### Input

- `S` -- set instance, used for dispatch

### Output

The base type of `S`.

### Examples

```jldoctest
julia> z = rand(Zonotope);

julia> basetype(z)
Zonotope

julia> basetype(z + z)
MinkowskiSum

julia> basetype(LinearMap(rand(2, 2), z + z))
LinearMap
```
"""
basetype(S::LazySet) = Base.typename(typeof(S)).wrapper

"""
    ρ(d::AbstractVector{N}, S::LazySet{N}) where {N<:Real}

Evaluate the support function of a set in a given direction.

### Input

- `d` -- direction
- `S` -- convex set

### Output

The support function of the set `S` for the direction `d`.

### Notes

The numeric type of the direction and the set must be identical.
"""
function ρ(d::AbstractVector{N}, S::LazySet{N}) where {N<:Real}
    return dot(d, σ(d, S))
end

"""
    support_function

Alias for the support function ρ.
"""
const support_function = ρ

"""
    σ

Function to compute the support vector σ.
"""
function σ end

"""
    support_vector

Alias for the support vector σ.
"""
const support_vector = σ

"""
    isbounded(S::LazySet)

Determine whether a set is bounded.

### Input

- `S` -- set

### Output

`true` iff the set is bounded.

### Algorithm

We check boundedness via [`isbounded_unit_dimensions`](@ref).
"""
function isbounded(S::LazySet)
    return isbounded_unit_dimensions(S)
end

"""
    isbounded_unit_dimensions(S::LazySet{N}) where {N<:Real}

Determine whether a set is bounded in each unit dimension.

### Input

- `S` -- set

### Output

`true` iff the set is bounded in each unit dimension.

### Algorithm

This function performs ``2n`` support function checks, where ``n`` is the
ambient dimension of `S`.
"""
function isbounded_unit_dimensions(S::LazySet{N}) where {N<:Real}
    n = dim(S)
    @inbounds for i in 1:n
        for o in [one(N), -one(N)]
            d = SingleEntryVector(i, n, o)
            if ρ(d, S) == N(Inf)
                return false
            end
        end
    end
    return true
end

"""
    norm(S::LazySet, [p]::Real=Inf)

Return the norm of a convex set.
It is the norm of the enclosing ball (of the given ``p``-norm) of minimal volume
that is centered in the origin.

### Input

- `S` -- convex set
- `p` -- (optional, default: `Inf`) norm

### Output

A real number representing the norm.
"""
function norm(S::LazySet, p::Real=Inf)
    if p == Inf
        return norm(Approximations.ballinf_approximation(S), p)
    else
        error("the norm for this value of p=$p is not implemented")
    end
end

"""
    radius(S::LazySet, [p]::Real=Inf)

Return the radius of a convex set.
It is the radius of the enclosing ball (of the given ``p``-norm) of minimal
volume with the same center.

### Input

- `S` -- convex set
- `p` -- (optional, default: `Inf`) norm

### Output

A real number representing the radius.
"""
function radius(S::LazySet, p::Real=Inf)
    if p == Inf
        return radius(Approximations.ballinf_approximation(S), p)
    else
        error("the radius for this value of p=$p is not implemented")
    end
end

"""
    diameter(S::LazySet, [p]::Real=Inf)

Return the diameter of a convex set.
It is the maximum distance between any two elements of the set, or,
equivalently, the diameter of the enclosing ball (of the given ``p``-norm) of
minimal volume with the same center.

### Input

- `S` -- convex set
- `p` -- (optional, default: `Inf`) norm

### Output

A real number representing the diameter.
"""
function diameter(S::LazySet, p::Real=Inf)
    return radius(S, p) * 2
end

"""
    affine_map(M::AbstractMatrix, X::LazySet, v::AbstractVector)

Compute a concrete affine map.

### Input

- `M` -- linear map
- `X` -- convex set
- `v` -- translation vector

### Output

A set representing the affine map of `X`.

### Algorithm

The implementation applies the functions `linear_map` and `translate`.
"""
function affine_map(M::AbstractMatrix, X::LazySet, v::AbstractVector)
    return translate(linear_map(M, X), v)
end

"""
    an_element(S::LazySet{N}) where {N<:Real}

Return some element of a convex set.

### Input

- `S` -- convex set

### Output

An element of a convex set.

### Algorithm

An element of the set is obtained by evaluating its support vector along
direction ``[1, 0, …, 0]``.
"""
function an_element(S::LazySet{N}) where {N<:Real}
    e₁ = SingleEntryVector(1, dim(S), one(N))
    return σ(e₁, S)
end

"""
    ==(X::LazySet, Y::LazySet)

Return whether two LazySets of the same type are exactly equal.

### Input

- `X` -- any `LazySet`
- `Y` -- another `LazySet` of the same type as `X`

### Output

- `true` iff `X` is equal to `Y`.

### Notes

The check is purely syntactic and the sets need to have the same base type.
For instance, `X::VPolytope == Y::HPolytope` returns `false` even if `X` and `Y`
represent the same polytope.
However `X::HPolytope{Int64} == Y::HPolytope{Float64}` is a valid comparison.

### Algorithm

We recursively compare the fields of `X` and `Y` until a mismatch is found.

### Examples

```jldoctest
julia> HalfSpace([1], 1) == HalfSpace([1], 1)
true

julia> HalfSpace([1], 1) == HalfSpace([1.0], 1.0)
true

julia> Ball1([0.], 1.) == Ball2([0.], 1.)
false
```
"""
function ==(X::LazySet, Y::LazySet)
    # if the common supertype of X and Y is abstract, they cannot be compared
    if isabstracttype(promote_type(typeof(X), typeof(Y)))
        return false
    end

    for f in fieldnames(typeof(X))
        if getfield(X, f) != getfield(Y, f)
            return false
        end
    end

    return true
end
"""
    ≈(X::LazySet, Y::LazySet)

Return whether two LazySets of the same type are approximately equal.

### Input

- `X` -- any `LazySet`
- `Y` -- another `LazySet` of the same type as `X`

### Output

- `true` iff `X` is equal to `Y`.

### Notes

The check is purely syntactic and the sets need to have the same base type.
For instance, `X::VPolytope ≈ Y::HPolytope` returns `false` even if `X` and `Y`
represent the same polytope.
However `X::HPolytope{Int64} ≈ Y::HPolytope{Float64}` is a valid comparison.

### Algorithm

We recursively compare the fields of `X` and `Y` until a mismatch is found.

### Examples

```jldoctest
julia> HalfSpace([1], 1) ≈ HalfSpace([1], 1)
true

julia> HalfSpace([1], 1) ≈ HalfSpace([1.00000001], 0.99999999)
true

julia> HalfSpace([1], 1) ≈ HalfSpace([1.0], 1.0)
true

julia> Ball1([0.], 1.) ≈ Ball2([0.], 1.)
false
```
"""
function ≈(X::LazySet, Y::LazySet)
    # if the common supertype of X and Y is abstract, they cannot be compared
    if isabstracttype(promote_type(typeof(X), typeof(Y)))
        return false
    end

    for f in fieldnames(typeof(X))
        if !_isapprox(getfield(X, f), getfield(Y, f))
            return false
        end
    end

    return true
end

# hook into random API
function rand(rng::AbstractRNG, ::SamplerType{T}) where T<:LazySet
    rand(T, rng=rng)
end

"""
    copy(S::LazySet)

Return a deep copy of the given set by copying its values recursively.

### Input

- `S` -- any `LazySet`

### Output

A copy of `S`.

### Notes

This function performs a `deepcopy` of each field in `S`, resulting in a
completely independent object. See the documentation of `?deepcopy` for further
details.
"""
copy(S::LazySet) = deepcopy(S)

"""
    tosimplehrep(S::LazySet)

Return the simple H-representation ``Ax ≤ b`` of a set from its list of linear
constraints.

### Input

- `S` -- set

### Output

The tuple `(A, b)` where `A` is the matrix of normal directions and `b` is the
vector of offsets.

### Notes

This function only works for sets that can be represented exactly by a finite
list of linear constraints.
This fallback implementation relies on `constraints_list(S)`.
"""
tosimplehrep(S::LazySet) = tosimplehrep(constraints_list(S))

"""
    reflect(P::LazySet{N}) where {N<:Real}

Concrete reflection of a convex set `P`, resulting in the reflected set `-P`.

### Note

This function requires that the list of constraints of the set `P` is
available, i.e. such that it can be written as
``P = \\{z ∈ ℝⁿ: ⋂ sᵢᵀz ≤ rᵢ, i = 1, ..., N\\}.``

This function can be used to implement the alternative definition of the
Minkowski Difference, which writes as
```math
A ⊖ B = \\{a − b | a ∈ A, b ∈ B\\} = A ⊕ (-B)
```
by calling `minkowski_sum(A, reflect(B))`.
"""
function reflect(P::LazySet)
    @assert applicable(constraints_list, P)  "this function " *
        "requires that the list of constraints is available, but it is not; " *
        "if the set is bounded, try overapproximating with an `HPolytope` first"

    F, g = tosimplehrep(P)
    T = isbounded(P) ? HPolytope : HPolyhedron
    return T(-F, g)
end

"""
    isuniversal(X::LazySet{N}, [witness]::Bool=false) where {N<:Real}

Check whether a given convex set is universal, and otherwise optionally compute
a witness.

### Input

- `X`       -- convex set
- `witness` -- (optional, default: `false`) compute a witness if activated

### Output

* If `witness` option is deactivated: `true` iff ``X`` is universal
* If `witness` option is activated:
  * `(true, [])` iff ``X`` is universal
  * `(false, v)` iff ``X`` is not universal and ``v ∉ X``

### Notes

This is a naive fallback implementation.
"""
function isuniversal(X::LazySet{N}, witness::Bool=false) where {N<:Real}
    if isbounded(X)
        result = false
    else
        error("cannot determine universality of the set")
    end

    if result
        return witness ? (true, N[]) : true
    elseif witness
        error("witness production is currently not supported")
    else
        return false
    end
end

"""
    is_interior_point(d::AbstractVector{N}, P::LazySet{N};
                      p=N(Inf), ε=_rtol(N)) where {N<:Real}

Check if the point `d` is contained in the interior of the convex set `P`.

### Input

- `d`  -- point
- `P`  -- set
- `p`  -- (optional; default: `N(Inf)`) norm of the ball used to apply the error
          tolerance
- `ε`  -- (optional; default: `_rtol(N)`) error tolerance of check

### Output

Boolean which indicates if the point `d` is contained in `P`.

### Algorithm

The implementation checks if a `Ballp` of norm `p` with center `d` and radius
`ε` is contained in the set `P`.
This is a numerical check for `d ∈ interior(P)` with error tolerance `ε`.
"""
function is_interior_point(d::AbstractVector{N}, P::LazySet{N};
                           p=N(Inf), ε=_rtol(N)) where {N<:Real}
    return Ballp(p, d, ε) ⊆ P
end

"""
    plot_recipe(X::LazySet{N}, [ε]::N=N(PLOT_PRECISION)) where {N<:Real}

Convert a convex set to a pair `(x, y)` of points for plotting.

### Input

- `X` -- convex set
- `ε` -- (optional, default: `PLOT_PRECISION`) approximation error bound

### Output

A pair `(x, y)` of points that can be plotted.

### Notes

Plotting of unbounded sets is not implemented yet (see
[#576](https://github.com/JuliaReach/LazySets.jl/issues/576)).

### Algorithm

We first assert that `X` is bounded.

One-dimensional sets are converted to an `Interval`.
We do not support three-dimensional or higher-dimensional sets at the moment.

For two-dimensional sets, we first compute a polygonal overapproximation.
The second argument, `ε`, corresponds to the error in Hausdorff distance between
the overapproximating set and `X`.
The default value `PLOT_PRECISION` is chosen such that the unit ball in the
2-norm is approximated with reasonable accuracy.
On the other hand, if you only want to produce a fast box-overapproximation of
`X`, pass `ε=Inf`.
Finally, we use the plot recipe for polygons.
"""
function plot_recipe(X::LazySet{N}, ε::N=N(PLOT_PRECISION)) where {N<:Real}
    @assert dim(X) <= 2 "cannot plot a $(dim(X))-dimensional $(typeof(X))"
    @assert isbounded(X) "cannot plot an unbounded $(typeof(X))"

    if dim(X) == 1
        Y = convert(Interval, X)
    else
        Y = overapproximate(X, ε)
    end
    return plot_recipe(Y, ε)
end

"""
    isoperation(X::LazySet)

Check whether the given `LazySet` is an instance of a set operation or not.

### Input

- `X` -- a `LazySet`

### Output

`true` if `X` is an instance of a set-based operation and `false` otherwise.

### Notes

The fallback implementation returns whether the set type of the input is an
operation or not using `isoperationtype`.

See also [`isoperationtype(X::Type{<:LazySet})`](@ref).

### Examples

```jldoctest
julia> B = BallInf([0.0, 0.0], 1.0);

julia> isoperation(B)
false

julia> isoperation(B ⊕ B)
true
```
"""
function isoperation(X::LazySet)
    return isoperationtype(typeof(X))
end

isoperation(::Type{<:LazySet}) = error("`isoperation` cannot be applied to a set " *
                                       "type; use `isoperationtype` instead")

"""
    isoperationtype(X::Type{<:LazySet})

Check whether the given `LazySet` type is an operation or not.

### Input

- `X` -- subtype of `LazySet`

### Output

`true` if the given set type is a set-based operation and `false` otherwise.

### Notes

The fallback for this function returns an error that `isoperationtype` is not
implemented. Subtypes of `LazySet` should dispatch on this function as required.

See also [`isoperation(X<:LazySet)`](@ref).

### Examples

```jldoctest
julia> isoperationtype(BallInf)
false

julia> isoperationtype(LinearMap)
true
```
"""
function isoperationtype(X::Type{<:LazySet})
    error("`isoperationtype` is not implemented for type $X")
end

isoperationtype(::LazySet) = error("`isoperationtype` cannot be applied to " *
                                   "a set instance; use `isoperation` instead")

"""
    isequivalent(X::LazySet, Y::LazySet)

Return whether two LazySets are equal in the mathematical sense, i.e. equivalent.

### Input

- `X` -- any `LazySet`
- `Y` -- another `LazySet`

### Output

`true` iff `X` is equivalent to `Y`.

## Algorithm

First, the check `X == Y` is performed which returns `true` if and only if the given sets are of the same type,
and have the same values (modulo floating-point tolerance). Otherwise, the double inclusion check `X ⊆ Y && Y ⊆ X` is
used.

### Examples

```jldoctest
julia> X = BallInf([0.1, 0.2], 0.3);

julia> Y = convert(HPolytope, X);

julia> X == Y
false

julia> isequivalent(X, Y)
true
```
"""
function isequivalent(X::LazySet, Y::LazySet)
    if X ≈ Y
        return true
    end
    return X ⊆ Y && Y ⊆ X
end

"""
    isconvextype(X::Type{<:LazySet})

Check whether the given `LazySet` type is convex.

### Input

- `X` -- subtype of `LazySet`

### Output

`true` if the given set type is guaranteed to be convex by using only type
information, and `false` otherwise.

### Notes

Since this operation only acts on types (not on values), it can return false
negatives, i.e. there may be instances where the set is convex, even though the
answer of this function is `false`. The examples below illustrate this point.

### Examples

A ball in the infinity norm is always convex, hence we get:

```jldoctest convex_types
julia> isconvextype(BallInf)
true
```

For instance, the union (`UnionSet`) of two sets may in general be either convex
or not, since convexity cannot be decided by just using type information.
Hence, `isconvextype` returns `false` if `X` is `Type{<:UnionSet}`.

```jldoctest convex_types
julia> isconvextype(UnionSet)
false
```

However, the type parameters from the set operations allow to decide convexity
in some cases, by falling back to the convexity of the type of its arguments.
Consider for instance the lazy intersection. The intersection of two convex sets
is always convex, hence we can get:

```jldoctest convex_types
julia> isconvextype(Intersection{Float64, BallInf{Float64}, BallInf{Float64}})
true
```
"""
isconvextype(X::Type{<:LazySet}) = false

"""
    surface(X::LazySet{N}) where {N}

Compute the surface area of a set.

### Input

- `X` -- set

### Output

A number representing the surface area of `X`.
"""
function surface(X::LazySet{N}) where {N}
    if dim(X) == 2
        return area(X)
    else
        throw(ArgumentError("the surface function is only implemented for " *
                    "two-dimensional sets, but the given set is $(dim(X))-dimensional"))
    end
end

"""
    area(X::LazySet{N}) where {N}

Compute the area of a two-dimensional polytopic set using the Shoelace formula.

### Input

- `X` -- two-dimensional set

### Output

A number representing the area of `X`.

### Notes

This algorithm is applicable to any lazy set `X` such that its list of vertices,
`vertices_list`, can be computed.

### Algorithm

Let `m` be the number of vertices of `X`. The following instances are considered:

- `m = 0, 1, 2`: the output is zero.
- `m = 3`: the triangle case is computed using the Shoelace formula with 3 points.
- `m = 4`: the quadrilateral case is obtained by the factored version of the Shoelace
           formula with 4 points.

Otherwise, the general Shoelace formula is used; for detals see the wikipedia
article [Shoelace formula](https://en.wikipedia.org/wiki/Shoelace_formula).
"""
function area(X::LazySet{N}) where {N}
    @assert dim(X) == 2 "this function only applies to two-dimensional sets, " *
    "but the given set is $(dim(X))-dimensional"

    vlist = vertices_list(X)
    m = length(vlist)

    if m <= 2
        return zero(N)
    end

    if m == 3 # triangle
        res = _area_triangle(vlist)

    elseif m == 4 # quadrilateral
        res = _area_quadrilateral(vlist)

    else # general case
        res = _area_polygon(vlist)
    end

    return res
end

function _area_triangle(v::Vector{VN}) where {N, VN<:AbstractVector{N}}
    A = v[1]
    B = v[2]
    C = v[3]
    res = A[1] * (B[2] - C[2]) + B[1] * (C[2] - A[2]) + C[1] * (A[2] - B[2])
    return abs(res/2)
end

function _area_quadrilateral(v::Vector{VN}) where {N, VN<:AbstractVector{N}}
    A = v[1]
    B = v[2]
    C = v[3]
    D = v[4]
    res = A[1] * (B[2] - D[2]) + B[1] * (C[2] - A[2]) + C[1] * (D[2] - B[2]) + D[1] * (A[2] - C[2])
    return abs(res/2)
end

function _area_polygon(v::Vector{VN}) where {N, VN<:AbstractVector{N}}
    m = length(v)
    @inbounds res = v[m][1] * v[1][2] - v[1][1] * v[m][2]
    for i in 1:m-1
        @inbounds res += v[i][1] * v[i+1][2] - v[i+1][1] * v[i][2]
    end
    return abs(res/2)
end


text:
@testset "Schema Object" begin
    ST = ScientificTypes
    sch = ST.Schema((:a, :b), (Count, Count), (Int, Int))
    @test sch isa ST.Schema{(:a, :b), Tuple{Count,Count}, Tuple{Int64,Int64}}
    @test sch.names == (:a, :b)
    @test sch.types == (Int, Int)
    @test sch.scitypes == (Count, Count)

    @test_throws ArgumentError sch.something
    @test propertynames(sch) == (:names, :scitypes, :types)

    # Tables.jl interface
    @test Tables.istable(sch) == true
    @test Tables.columnaccess(sch) == true
    @test Tables.columns(sch) == sch
    @test Tables.columnnames(sch) == (:names, :scitypes, :types)
    @test Tables.schema(sch) == Tables.Schema(Tables.columnnames(sch), Tuple{Symbol, Type, Type})
    @test Tables.getcolumn(sch, 1) == (:a, :b)
    @test Tables.getcolumn(sch, 2) == (Count, Count)
    @test Tables.getcolumn(sch, 3) == (Int, Int)
    @test Tables.getcolumn(sch, :types) == (Int, Int)
    @test Tables.getcolumn(sch, :names) == (:a, :b)

    sch2 = ST.Schema((:a, :b), Tuple{Count, Count}, nothing)
    @test sch2 isa ST.Schema{(:a, :b), Tuple{Count,Count}, nothing}
    @test ST.stored(sch2) == false
    @test Tables.getcolumn(sch2, :types) == [nothing, nothing]
    @test Tables.getcolumn(sch2, 3) == [nothing, nothing]

    sch3 = ST.Schema((:a, :b), (Count, Count), (Int, Int); stored=true)
    @test sch3 isa ST.Schema{nothing, nothing, nothing}
    @test ST.stored(sch3) == true

    # Schema show
    df = DataFrame(x=[1.0,2.0,3.0], y=["a","b","c"])
    s = schema(df)
    io = IOBuffer()
    show(io, MIME("text/plain"), ScientificTypes.schema(df))
    @test String(take!(io)) == "┌───────┬────────────┬─────────┐\n│ names │ scitypes   │ types   │\n├───────┼────────────┼─────────┤\n│ x     │ Continuous │ Float64 │\n│ y     │ Textual    │ String  │\n└───────┴────────────┴─────────┘\n"
end

struct MySchemalessTable{U, V}
    x::Vector{U}
    y::Vector{V}
end

Tables.istable(::MySchemalessTable) = true
Tables.columnaccess(::Type{<:MySchemalessTable}) = true
Tables.columns(t::MySchemalessTable) = t
Tables.columnnames(::MySchemalessTable) = (:x, :y)

function Tables.getcolumn(t::MySchemalessTable, i::Int)
    return Tables.getcolumn(t, Tables.columnnames(t)[i])
end

function Tables.getcolumn(t::MySchemalessTable, nm::Symbol)
    return ifelse(nm === :x, t.x, t.y)
end

Tables.schema(t::MySchemalessTable) = nothing

struct ExtremelyWideTable{U, V}
    a::Vector{U}
    b::Vector{V}
end

Tables.istable(::ExtremelyWideTable) = true
Tables.columnaccess(::Type{<:ExtremelyWideTable}) = true
Tables.columns(t::ExtremelyWideTable) = t
Tables.columnnames(::ExtremelyWideTable) = (:a, :b)

function Tables.getcolumn(t::ExtremelyWideTable, i::Int)
    return Tables.getcolumn(t, Tables.columnnames(t)[i])
end

function Tables.getcolumn(t::ExtremelyWideTable, nm::Symbol)
    return ifelse(nm === :a, t.a, t.b)
end
function Tables.schema(t::ExtremelyWideTable{U, V}) where {U, V}
    return Tables.Schema(Tables.columnnames(t), (U, V); stored=true)
end

@testset "schema" begin
    ST = ScientificTypes
    # NamedTuple schema
    X = (
        x = rand(5),
        y = rand(Int, 5),
        z = categorical(collect("asdfa")),
        w = rand(5)
     )
     s = schema(X)
    @test s.scitypes == (Continuous, Count, Multiclass{4}, Continuous)
    @test s.types == (Float64, Int64, CategoricalValue{Char,UInt32}, Float64)

    # Dataframe schema
    df = DataFrame(X = [1,2,3,missing], Y = [1.0,2.0,3.0,missing])
    sch = schema(df)
    @test sch.scitypes[1] == Union{Missing, Count}
    @test sch.scitypes[2] == Union{Missing, Continuous}
    df2 = df[1:3,:]
    sch = schema(df2)
    @test sch.scitypes[1] == Union{Missing, Count}
    @test sch.scitypes[2] == Union{Missing, Continuous}

    # schema of non-tabular objects
    @test_throws ArgumentError schema([:x, :y])

    # PR #61 "schema check for `Tables.DictColumn`"
    X1 = Dict(:a=>rand(5), :b=>rand(Int, 5))
    s1 = schema(X1)
    @test s1.scitypes == (Continuous, Count)
    @test s1.types == (Float64, Int64)

    # issue 47 (schema for objects, `X` with, `Tables.schema(X) == nothing`)
    X2 = MySchemalessTable(rand(3), rand(Int, 3))
    s2 = schema(X2)
    @test s2 === nothing
    @test ST.__cols_schema(
        Tables.columns(X2), Tables.Schema(Tables.columnnames(X2), nothing)
    ) == ST.Schema(Tables.columnnames(X2), Tuple{Continuous, Count}, nothing)

    # For ExtremelyWideTable's
    X3 = ExtremelyWideTable(rand(3), rand(Int, 3))
    s3 = schema(X3)
    @test s3.names == Tables.columnnames(X3)
    @test s3.scitypes == (Continuous, Count)
    @test s3.types == (Float64, Int)
    @test ST._cols_schema(
        Tables.columns(X3), Tables.schema(X3)
    ) == ST.Schema(Tables.columnnames(X3), (Continuous, Count), (Float64, Int))
    @test ST._rows_schema(
        Tables.rows(X3), Tables.schema(X3)
    ) == ST.Schema(Tables.columnnames(X3), (Continuous, Count), (Float64, Int))

    # test schema for column oreinted tables with number of columns
    # exceeding COLS_SPECIALIZATION_THRESHOLD.
    nt = NamedTuple{
            Tuple(Symbol("x$i") for i in Base.OneTo(ST.COLS_SPECIALIZATION_THRESHOLD + 1))
        }(
            Tuple(rand(2) for i in Base.OneTo(ST.COLS_SPECIALIZATION_THRESHOLD + 1))
    )
    @test ST.__cols_schema(
        Tables.columns(nt), Tables.Schema(Tables.columnnames(nt), nothing)
    ) == ST.Schema(
        Tables.columnnames(nt),
        NTuple{ST.COLS_SPECIALIZATION_THRESHOLD + 1, Continuous},
        nothing
    )

    @test ST.__cols_schema(
        Tables.columns(nt), Tables.schema(nt)
    ) == ST.Schema(
        Tables.columnnames(nt),
        NTuple{ST.COLS_SPECIALIZATION_THRESHOLD + 1, Continuous},
        NTuple{ST.COLS_SPECIALIZATION_THRESHOLD + 1, Float64}
    )

 end


text:
module Classic

using DataStructures, Statistics, StatsBase

# Order matters

include("node.jl")
export Node, DijkstraNode
export node_to_path

include("maze.jl")
export Cell, Maze
export mark_path!, new_grid, new_maze, set_original!, solution_grid

include("edge.jl")
export Edge, UnweightedGraph, SimpleEdge, WeightedEdge, WeightedGraph
#export reverse, weight
export weight

include("gene.jl")
export Codon, Gene, Nucleotide
export binary_contains, linear_contains

include("graph.jl")
export Graph
export add!, index_of, is_goal, next_points

include("kmean.jl")
export Album, Cluster, DataPoint, Governor, KMeans, SimpleDataPoint
export distance, run!, slice_derived, slice_name, slice_original, slice_state

include("algorithm.jl")
export DijkstraResult
export distances_to_distance_db, a_star, bfs, dfs, dijkstra, mst, shortest_path

end