text:
export VelPose2VelPose2, PackedVelPose2VelPose2

"""
$(TYPEDEF)
"""
mutable struct VelPose2VelPose2{T1 <: IIF.SamplableBelief,T2 <: IIF.SamplableBelief} <: IIF.AbstractManifoldMinimize
  Zpose::Pose2Pose2{T1} #Zpose::T1
  Zvel::T2
  reuseres::Vector{Vector{Float64}}
  VelPose2VelPose2{T1,T2}() where {T1 <: IIF.SamplableBelief, T2 <: IIF.SamplableBelief} = new{T1,T2}()
  VelPose2VelPose2{T1,T2}(z1::T1, z2::T2) where {T1 <: IIF.SamplableBelief, T2 <: IIF.SamplableBelief} = new{T1,T2}(Pose2Pose2(z1),z2,[zeros(3) for i in 1:Threads.nthreads()])
end
VelPose2VelPose2(z1::T1, z2::T2) where {T1 <: IIF.SamplableBelief, T2 <: IIF.SamplableBelief} = VelPose2VelPose2{T1,T2}(z1, z2)

getManifold(::VelPose2VelPose2) = getManifold(DynPose2)

function getSample(cf::CalcFactor{<:VelPose2VelPose2})
    #Pose2 part
    Xc = rand(cf.factor.Zpose.z)
    M = getManifold(Pose2)
    ϵ = getPointIdentity(Pose2)
    # ϵ = Manifolds.Identity(M)
    Xpose = hat(M, ϵ, Xc)
    #velocity part
    Xvel = rand(cf.factor.Zvel)

    return ProductRepr(Xpose, Xvel)
end

function IIF.getMeasurementParametric(s::VelPose2VelPose2{<:MvNormal, <:MvNormal})

  meas = [mean(s.Zpose.z); mean(s.Zvel)]

  iΣp = invcov(s.Zpose.z)
  iΣv = invcov(s.Zvel)

  iΣ = zeros(eltype(iΣp), 5,5)

  iΣ[1:3,1:3] .= iΣp
  iΣ[4:5,4:5] .= iΣv

  return meas, iΣ
end

function (cf::CalcFactor{<:VelPose2VelPose2})(X, p, q)
  #
  pose_res = Vector{Manifolds.number_eltype(X)}(undef, 3)
  #Pose2 part
  M1 = getManifold(Pose2)
  X1 = X.parts[1]
  p1 = p.parts[1]
  q1 = q.parts[1]
  ϵ1 = identity_element(M1, p1)
  q̂1 = Manifolds.compose(M1, p1, exp(M1, ϵ1, X1))
  vee!(M1, pose_res, q1, log(M1, q1, q̂1))

  #velocity part
  dt = Dates.value(cf.metadata.fullvariables[2].nstime - cf.metadata.fullvariables[1].nstime)*1e-9
  X2 = X.parts[2]
  p2 = p.parts[2]
  q2 = q.parts[2]
  # bDXij = TransformUtils.R(-wxi[3])*wDXij
  bDXij = transpose(p1.parts[2])*(q2 .- p2)

  Xpq = log(M1, ϵ1, Manifolds.compose(M1, Manifolds.inv(M1, p1), q1))
  dx = Vector{Manifolds.number_eltype(X)}(undef, 3)
  vee!(M1, dx, ϵ1, Xpq)
  # calculate the residual
  res_vel = (X2 .- bDXij).^2 .+ (view(dx, 1:2)/dt .- 0.5*(p2 .+ q2)).^2
  res_vel = sqrt.(res_vel)

  return [pose_res; res_vel]
end
#=
function (cf::CalcFactor{<:VelPose2VelPose2})(meas,
                                              _Xi,
                                              _Xj  )
  #
  M = getManifold(DynPose2)
  Xi = vee(M, _Xi, log(M, identity_element(M, _Xi), _Xi))
  Xj = vee(M, _Xj, log(M, identity_element(M, _Xj), _Xj))

  #FIXME JT - Createing new res for simplicity, it may not hold up well though
  res = Vector{eltype(Xi)}(undef, 5)

  z = meas
  wxi, wxj = Xi, Xj
  # @show z, wxi, wxj
  dt = Dates.value(cf.metadata.fullvariables[2].nstime - cf.metadata.fullvariables[1].nstime)*1e-9     # roughly the intended use of userdata
  # fill!(vp2vp2.reuseres[Threads.threadid()], 0.0)
  wXjhat = SE2(wxi[1:3])*SE2(meas[1:3])
  jXjhat = SE2(wxj[1:3]) \ wXjhat

  #FIXME this does not work with parametric
  # se2vee!(cf.factor.reuseres[Threads.threadid()], jXjhat)
  #FIXME cf.factor.reuseres has type issues with parametric
  se2vee!(res, jXjhat)

  # vp2vp2.Zpose(vp2vp2.reuseres[Threads.threadid()], userdata, idx, meas, Xi, Xj)

  wDXij = (wxj[4:5]-wxi[4:5])
  bDXij = TransformUtils.R(-wxi[3])*wDXij

  # calculate the residual
  dx = se2vee(SE2(wxi[1:3]) \ SE2(wxj[1:3]))
  #FIXME cf.factor.reuseres has type issues with parametric
  # res[1:3] .= cf.factor.reuseres[Threads.threadid()]
  res[4:5] .= z[4:5] .- bDXij
  res[4:5] .^= 2
  res[4:5] .+= (dx[1:2]/dt .- 0.5*(wxj[4:5] .+ wxi[4:5])).^2
  res[4:5] .= sqrt.(res[4:5])

  # res[1] = sum((cf.factor.reuseres[Threads.threadid()]).^2)
  # res[1] += sum((z[4:5] - bDXij).^2)
  # res[1] += sum((dx[1:2]/dt - 0.5*(wxj[4:5]+wxi[4:5])).^2)  # first order integration
  # res[1]

  return res
end
=#

function compare(a::VelPose2VelPose2, b::VelPose2VelPose2; tol::Float64=1e-10)::Bool
  TP = true
  TP = TP && RoME.compare(a.Zpose, b.Zpose)
  TP = TP && RoME.compareDensity(a.Zvel, b.Zvel)
  TP = TP && norm(a.reuseres - b.reuseres) < tol
  return TP
end

"""
$(TYPEDEF)
"""
mutable struct PackedVelPose2VelPose2 <: IncrementalInference.PackedInferenceType
  strpose::AbstractString
  strvel::AbstractString
  PackedVelPose2VelPose2() = new()
  PackedVelPose2VelPose2(z1::AS, z2::AS) where {AS <: AbstractString} = new(z1, z2)
end

function convert(::Type{PackedVelPose2VelPose2}, d::VelPose2VelPose2)
  return PackedVelPose2VelPose2(convert(PackedSamplableBelief, d.Zpose.z),
                                convert(PackedSamplableBelief, d.Zvel))
end
function convert(::Type{VelPose2VelPose2}, d::PackedVelPose2VelPose2)
  posedistr = convert(SamplableBelief, d.strpose)
  veldistr = convert(SamplableBelief, d.strvel)
  return VelPose2VelPose2(posedistr, veldistr)
end


text:
function convertmsgtojson(msgs, style, dict_data)
    if style == "lint-message"
        return msgs
    end
    output = Any[]
    for msg in msgs
        evar = msg.variable
        txt = msg.message
        f = file(msg)
        linenumber = line(msg)
        # Atom index starts from zero thus minus one
        errorrange = Array[[linenumber-1, 0], [linenumber-1, 80]]
        code = string(msg.code)
        if code[1] == 'I'
            etype = "info"
            etypenumber = 3
        elseif code[1] == 'W'
            etype = "warning"
            etypenumber = 2
        else
            etype = "error"
            etypenumber = 1
        end

        if style == "standard-linter-v1"
            if haskey(dict_data,"show_code")
                if dict_data["show_code"]
                    msgtext = "$code $evar: $txt"
                else
                    msgtext = "$evar: $txt"
                end
            else
                msgtext = "$code $evar: $txt"
            end
            push!(output, Dict("type" => etype,
                               "text" => msgtext,
                               "range" => errorrange,
                               "filePath" => f))
        elseif style == "vscode"
            push!(output, Dict("severity" => etypenumber,
                               "message" => "$evar: $txt",
                               "range" => errorrange,
                               "filePath" => f,
                               "code" => code,
                               "source" => "Lint.jl"))
        elseif style == "standard-linter-v2"
            push!(output, Dict("severity" => etype,
                               "location" => Dict("file" => f,
                                                  "position" => errorrange),
                               "excerpt" => "$evar: $txt",
                               "description" => code))

        end
    end
    return output
end

function filtermsgs(msgs,dict_data)
    if haskey(dict_data,"ignore_warnings")
        if dict_data["ignore_warnings"]
            msgs = filter(i -> !iswarning(i), msgs)
        end
    end
    if haskey(dict_data,"ignore_info")
        if dict_data["ignore_info"]
            msgs = filter(i -> !isinfo(i), msgs)
        end
    end
    if haskey(dict_data,"ignore_codes")
        msgs = filter(i -> !(string(i.code) in dict_data["ignore_codes"]), msgs)
    end
    return msgs
end

function readandwritethestream(conn,style)
    if style == "original_behaviour"
        # println("Connection accepted")
        # Get file, code length and code
        file = readline(conn)
        # println("file: ", file)
        code_len = parse(Int, readline(conn))
        # println("Code bytes: ", code_len)
        code = Compat.String(read(conn, code_len))
        # println("Code received")
        # Do the linting
        msgs = lintfile(file, code)
        # Write response to socket
        for i in msgs
            write(conn, string(i))
            write(conn, "\n")
        end
        # Blank line to indicate end of messages
        write(conn, "\n")
    else
        dict_data = JSON.parse(conn)
        msgs = lintfile(dict_data["file"], dict_data["code_str"])
        msgs = filtermsgs(msgs, dict_data)
        out = convertmsgtojson(msgs, style, dict_data)
        JSON.print(conn, out)
    end
end

function lintserver(port,style="original_behaviour")
    server = listen(port)
    try
        println("Server running on port/pipe $port ...")
        while true
            conn = accept(server)
            @async try
                readandwritethestream(conn,style)
            catch err
                println(STDERR, "connection ended with error $err")
            finally
                close(conn)
                # println("Connection closed")
            end
        end
    finally
        close(server)
        println("Server closed")
    end
end

ext: jl


text:
# Command line args
!isempty(ARGS) || error("No config supplied.")
isfile(ARGS[1]) || error("Cannot read '$(ARGS[1])'")
isabspath(ARGS[1]) || error("Please use an absolute path for the config.")
println("Config supplied: '$(ARGS[1])'")
config_file = ARGS[1]

include(joinpath(@__DIR__, "gmm_utils.jl"))
using SVDD, OneClassActiveLearning
using MLKernels
using Memento
using Serialization, DelimitedFiles, Logging, Random

Random.seed!(0)
include(config_file)

function generate_experiments(data_dir, data_gen_settings::DataGenGMM, data_split_strategy, initial_pool_strategy, solver, num_resamples_initial_pool=5)
    exp_dir = joinpath(data_output_root, exp_name)
    println(exp_dir)
    if isdir(exp_dir)
        print("Type 'yes' or 'y' to delete and overwrite experiment $(exp_dir): ")
        argin = readline()
        if argin == "yes" || argin == "y"
            rm(exp_dir, recursive=true)
        else
            error("Overwriting anyways... Just kidding, nothing happened.")
        end
    else
        !isdir(exp_dir) || error("The experiment directory $(exp_dir) already exists.")
    end

    isdir(exp_dir) || mkpath(exp_dir)
    mkpath(joinpath(exp_dir, "data"))
    mkpath(joinpath(exp_dir, "log", "experiment"))
    mkpath(joinpath(exp_dir, "log", "worker"))
    mkpath(joinpath(exp_dir, "results"))
    @info "Generating experiment directory with name: $exp_dir and config: $(config_file). ($num_resamples_initial_pool resamples of the initial pool)"

    experiments = []

    for x in 1:data_gen_settings.num_repetitions
        for dim in data_gen_settings.dimensions
            for num_g in data_gen_settings.num_gaussians
                @info "[Repetition $x] Starting data generation with $dim dimensions and $num_g gaussians."
                Random.seed!(x)
                gmm = rand_gmm(num_gaussians=num_g, d=dim)
                Random.seed!(x)
                data_test, labels_test = generate_gmm_test_data(gmm, data_gen_settings.outlier_threshold, data_gen_settings.num_data)
                oracle = QuerySynthesisGMMOracle(gmm, data_gen_settings.outlier_threshold)
                @info "[Repetition $x] Data generation done."

                for num_g_train in data_gen_settings.num_gaussians_train
                    gmm_train = deconstruct_gmm(gmm, collect(1:num_g_train))
                    data_train, labels_train = generate_gmm_test_data(gmm_train, gmm, data_gen_settings.outlier_threshold, data_gen_settings.num_data, 0)
                    data, labels = hcat(data_train, data_test), vcat(labels_train, labels_test)
                    train_mask = vcat(trues(length(labels_train)), falses(length(labels_test)))
                    split_strategy = DataSplits(train_mask, .~train_mask, LabeledSplitStrat(), FullSplitStrat())

                    @assert size(data_test, 2) == length(labels_test)
                    @assert size(data_train, 2) == length(labels_train)
                    @assert size(data_train, 1) == size(data_test, 1)
                    @assert size(data, 2) == length(labels)
                    @assert length(labels) == length(train_mask)

                    # write data file
                    data_file = joinpath(exp_dir, "data", "gmm_holdout_$(x)_seed_$(dim)_dim_$(num_g)_gaussians_$(num_g_train)_num_gaussians_train.csv")
                    writedlm(data_file, hcat(data', labels), ',')

                    for ip in initial_pool_strategy
                        ip[1] == "Pnin" || error("Cannot generate experiment with initial pools '$(ip[1])'.")
                        haskey(ip[2], :n) || error("Initial pools '$(ip[1])' has no parameter 'n'.")
                        num_train = ip[2][:n]
                        for n in 1:num_resamples_initial_pool
                            Random.seed!(n)
                            initial_pools = get_initial_pools(data, labels, split_strategy, ip[1], n=num_train)
                            @assert length(labels) == length(initial_pools)

                            param = Dict(:num_al_iterations => num_al_iterations,
                                         :solver => solver,
                                         :initial_pools => initial_pools,
                                         :adjust_K => true,
                                         :initial_pool_resample_version => n,
                                         :classify_precision => classify_precision)

                            for model in models
                                for init_strategy in init_strategies
                                    for query_strategy in query_strategies
                                        out_dir = split(data_file, '/')[end-1]
                                        output_path = joinpath(exp_dir, "results", out_dir)
                                        isdir(output_path) || mkdir(output_path)
                                        experiment = Dict{Symbol, Any}(
                                                :data_file => data_file,
                                                :data_set_name => out_dir,
                                                :split_strategy_name => "Shl",
                                                :initial_pool_strategy_name => ip,
                                                :model => merge(model, Dict(:init_strategy => init_strategy)),
                                                :query_strategy => Dict(:type => query_strategy[:type],
                                                                        :param => query_strategy[:param]),
                                                :split_strategy => split_strategy,
                                                :oracle => oracle,
                                                :param => param)

                                        exp_hash = hash(sprint(print, experiment))
                                        @show data_file, ip, n, exp_hash
                                        @assert exp_hash == hash(sprint(print, deepcopy(experiment)))
                                        experiment[:hash] = "$exp_hash"

                                        out_name = splitext(splitdir(data_file)[2])[1]
                                        out_name = joinpath(output_path, "$(out_name)_$(query_strategy[:type])_$(model[:type])_$(exp_hash).json")
                                        experiment[:output_file] = out_name
                                        experiment[:log_dir] = joinpath(exp_dir, "log")
                                        push!(experiments, deepcopy(experiment))
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end

    # save the experimental setup
    cp(@__FILE__, joinpath(exp_dir, splitdir(@__FILE__)[2]), follow_symlinks=true)
    cp(config_file, joinpath(exp_dir, splitdir(config_file)[2]), follow_symlinks=true)

    @info "Created $exp_dir with $(length(experiments)) instances."

    open(joinpath(exp_dir, "experiment_hashes"), "a") do f
        for e in experiments
            write(f, "$(e[:hash])\n")
        end
    end
    serialize(open(joinpath(exp_dir, "experiments.jser"), "w"), experiments)
    return experiments
end

generate_experiments(data_dir, data_gen_settings, split_strategy, initial_pool_strategy, solver, num_resamples_initial_pool)

max_stars_repo_name: englhardt/des-evaluation

max_issues_repo_name: englhardt/des-evaluation

max_forks_repo_name: englhardt/des-evaluation


text:
module RayCaster

function cast_ray_continous_world(obstacle_tile_map::AbstractArray{Bool, 2}, x_start::T1, y_start::T1, cos_theta::T2, sin_theta::T2) where {T1, T2}
    i_start = floor(Int, x_start) + 1
    j_start = floor(Int, y_start) + 1

    delta_euclidean_per_unit_x = abs(1 / cos_theta)
    delta_euclidean_per_unit_y = abs(1 / sin_theta)

    hit_dimension = 1

    if cos_theta < zero(T2)
        delta_i = -1
        delta_x_world_units_to_exit_start_tile = x_start - i_start + 1
    else
        delta_i = 1
        delta_x_world_units_to_exit_start_tile = i_start - x_start
    end

    delta_euclidean_x = delta_x_world_units_to_exit_start_tile * delta_euclidean_per_unit_x

    if sin_theta < zero(T2)
        delta_j = -1
        delta_y_world_units_to_exit_start_tile = y_start - j_start + 1
    else
        delta_j = 1
        delta_y_world_units_to_exit_start_tile = j_start - y_start
    end

    delta_euclidean_y = delta_y_world_units_to_exit_start_tile * delta_euclidean_per_unit_y

    i_stop = i_start
    j_stop = j_start

    while !obstacle_tile_map[i_stop, j_stop]

        if (delta_euclidean_x <= delta_euclidean_y)
            delta_euclidean_x += delta_euclidean_per_unit_x
            i_stop += delta_i
            hit_dimension = 1
        else
            delta_euclidean_y += delta_euclidean_per_unit_y
            j_stop += delta_j
            hit_dimension = 2
        end

    end

    return i_stop, j_stop, hit_dimension, delta_x_world_units_to_exit_start_tile, delta_y_world_units_to_exit_start_tile
end

function cast_ray_discrete_world(obstacle_tile_map::AbstractArray{Bool, 2}, i_start_world_units, j_start_world_units, delta_i_world_units, delta_j_world_units, world_units_per_tile_unit)
    I = typeof(i_start_world_units)

    i_start_tile_units = i_start_world_units ÷ world_units_per_tile_unit + one(I)
    j_start_tile_units = j_start_world_units ÷ world_units_per_tile_unit + one(I)

    delta_euclidean_per_world_unit_i = abs(delta_j_world_units)
    delta_euclidean_per_world_unit_j = abs(delta_i_world_units)

    delta_euclidean_per_tile_unit_i = world_units_per_tile_unit * delta_euclidean_per_world_unit_i
    delta_euclidean_per_tile_unit_j = world_units_per_tile_unit * delta_euclidean_per_world_unit_j

    if delta_i_world_units < zero(I)
        delta_i_tile_units = -one(I)
        delta_i_world_units_to_exit_start_tile = (i_start_world_units - (i_start_tile_units - one(I)) * world_units_per_tile_unit)
    else
        delta_i_tile_units = one(I)
        delta_i_world_units_to_exit_start_tile = (i_start_tile_units * world_units_per_tile_unit - i_start_world_units + one(I))
    end

    delta_euclidean_i = delta_i_world_units_to_exit_start_tile * delta_euclidean_per_world_unit_i

    if delta_j_world_units < zero(I)
        delta_j_tile_units = -one(I)
        delta_j_world_units_to_exit_start_tile = (j_start_world_units - (j_start_tile_units - one(I)) * world_units_per_tile_unit)
    else
        delta_j_tile_units = one(I)
        delta_j_world_units_to_exit_start_tile = (j_start_tile_units * world_units_per_tile_unit - j_start_world_units + one(I))
    end

    delta_euclidean_j = delta_j_world_units_to_exit_start_tile * delta_euclidean_per_world_unit_j

    i_stop_tile_units = i_start_tile_units
    j_stop_tile_units = j_start_tile_units
    hit_dimension = 1

    while !obstacle_tile_map[i_stop_tile_units, j_stop_tile_units]

        if (delta_euclidean_i <= delta_euclidean_j)
            delta_euclidean_i += delta_euclidean_per_tile_unit_i
            i_stop_tile_units += delta_i_tile_units
            hit_dimension = 1
        else
            delta_euclidean_j += delta_euclidean_per_tile_unit_j
            j_stop_tile_units += delta_j_tile_units
            hit_dimension = 2
        end

    end

    return i_stop_tile_units, j_stop_tile_units, hit_dimension, delta_i_world_units_to_exit_start_tile, delta_j_world_units_to_exit_start_tile
end

end


text:
using DataFrames
import Glob

## Use abstract type to select range conditions
## Method inspired by https://www.juliabloggers.com/julia-dispatching-enum-versus-type/
abstract type ValueBound end
struct OpenBound   <: ValueBound end
struct ClosedBound <: ValueBound end
struct LeftClosed  <: ValueBound end
struct RightClosed <: ValueBound end

function getbolddirs(bdir::AbstractString)
	fdrs = Glob.glob("*", bdir)
	[split(f,"/")[end] for f in fdrs]
end

function findpattern(nxfiles::Vector{String}, pattern::String, spl="_", pos=1)
	REPS = []
	for f in nxfiles
		fsx = split(f, spl)
		fa = findall(x->x==pattern, fsx)
		if length(fa) > 0
			indx = fa[1] + pos
			push!(REPS, fsx[indx])
		end
	end
	unique(REPS)
end

function range_bound(xmin::Real, xmax::Real, ::Type{OpenBound  })
	x -> (x >  xmin) & (x <  xmax)
end

function range_bound(xmin::Real, xmax::Real, ::Type{ClosedBound})
	x -> (x >= xmin) & (x <= xmax)
end

function range_bound(xmin::Real, xmax::Real, ::Type{LeftClosed })
	x -> (x >= xmin) & (x <  xmax)
end

function range_bound(xmin::Real, xmax::Real, ::Type{RightClosed})
	x -> (x >  xmin) .& (x <= xmax)
end

"""
	swap(x1::T, x2::T, cond::Bool) where T
	swaps x and y if cond is false
"""
function swap(x1::T, x2::T, cond::Bool) where T
    if cond
        return x1, x2
    else
        return x2, x1
    end
end

# Vector and data frames

"""
	in_range(x, xmin, xmax)

Given vector x, select values between xmin and xmax
"""
function in_range(x::Vector{<:Real}, xmin::Real, xmax::Real,
				  interval::Type{<:ValueBound}=OpenBound)
	mask = broadcast(range_bound(xmin, xmax, interval), x)
    return x[mask]
end

"""
	select_values

Generic function to get values in a DataFrame given a condition
function.

** Arguments **
dbdf     ::DataFrame
cond_func::Function Should take a value and return Boolean (brodcasting in function)
column   ::String The column to be used with cond_func
TODO: Change access from String to Symbol
TODO: Is it possible to specify that cond must take Vector and return Bool?
"""
function select_values(dbdf::DataFrame, cond_func::Function, column::String)
	mask = broadcast(cond_func, dbdf[!, column])
	return dbdf[mask, :]
end

"""
	select_event(dbdf::DataFrame, index::Int64)

Take the event dataframe and the index of an event and returns a data frame
which selects that particular event

"""
function select_event(dbdf::DataFrame, event_id::Integer)
	return select_by_column_value(dbdf, "event_id", event_id)
end

"""
	select_by_column_value(df::DataFrame, column::String, value)

Select elements in the DF which have "value" in "column"
"""
function select_by_column_value(df::DataFrame, column::String, value)
	return select_values(df, x -> x == value, column)
end

"""
	select_by_column_value_lt(df::DataFrame, column::String, value)

Select elements in the DF which are less than "value" in "column"
"""
function select_by_column_value_lt(df::DataFrame, column::String, value)
	return select_values(df, x -> x < value, column)
end

"""
	select_by_column_value_le(df::DataFrame, column::String, value)

Select elements in the DF which are less or equal than "value" in "column"
"""
function select_by_column_value_le(df::DataFrame, column::String, value)
	return select_values(df, x -> x <= value, column)
end

"""
	select_by_column_value_gt(df::DataFrame, column::String, value)

Select elements in the DF which are larger than "value" in "column"
"""
function select_by_column_value_gt(df::DataFrame, column::String, value)
	return select_values(df, x -> x > value, column)
end

"""
	select_by_column_value_ge(df::DataFrame, column::String, value)

Select elements in the DF which are larger or equal than "value" in "column"
"""
function select_by_column_value_ge(df::DataFrame, column::String, value)
	return select_values(df, x -> x >= value, column)
end

"""
	select_by_column_value_interval(df::DataFrame, column::String, valuef, valuel)

Select elements in the DF which are in interval (valuef, valuel)
"""
function select_by_column_value_interval(df::DataFrame, column::String, valuef, valuel)
	cond_func = range_bound(valuef, valuel, OpenBound)
	return select_values(df, cond_func, column)
end

"""
	select_by_column_value_closed_interval(df::DataFrame, column::String, valuef, valuel)

Select elements in the DF which are in interval [valuef, valuel]
"""
function select_by_column_value_closed_interval(df::DataFrame, column::String, valuef, valuel)
	cond_func = range_bound(valuef, valuel, ClosedBound)
	return select_values(df, cond_func, column)
end

"""
	select_by_column_value_closed_left_interval(df::DataFrame, column::String, valuef, valuel)

Select elements in the DF which are in interval [valuef, valuel)
"""
function select_by_column_value_closed_left_interval(df::DataFrame, column::String, valuef, valuel)
	cond_func = range_bound(valuef, valuel, LeftClosed)
	return select_values(df, cond_func, column)
end

"""
	select_by_column_value_closed_right_interval(df::DataFrame, column::String, valuef, valuel)

Select elements in the DF which are in interval (valuef, valuel]
"""
function select_by_column_value_closed_right_interval(df::DataFrame, column::String, valuef, valuel)
	cond_func = range_bound(valuef, valuel, RightClosed)
	return select_values(df, cond_func, column)
end

"""
	select_by_index(df::DataFrame, column::String, value::Integer)

Select elements in the DF which have "value" (Integer) in "column"
!Name is misleading, does not select by index.
"""
function select_by_index(df::DataFrame, column::String, value::Integer)
	return select_by_column_value(df, column, value)
end

""""
	find_max_xy(df, xc, yc)

Return ymax and x such that ymax = f(x).

**Description:**

In a DataFrame one has often "XY" variables, that is, a pair of columns
"X" and "Y" which represent correlated variables (e.g intensity and wavelength).
In such cases one often wants the XY maximum, that is, finding the maximum
of "Y" (call it ymax) and the corresponding value in "X"
(x for which y is ymax). This corresponds, for example, to the wavelength at
which the intensity is maximal.

**Arguments:**
- `df::DataFrame`: data frame holding the data.
- `xc::String`: the X column.
- `yc::String`: the Y column.
"""
find_max_xy(df::DataFrame, xc::String, yc::String) = find_max_xy(df[!, xc], df[!, yc])

function find_max_xy(x::Vector{<:Real}, y::Vector{<:Real})
	ymax, imax = findmax(y)
	x_ymax     = x[imax]
	return imax, x_ymax, ymax
end

max_stars_repo_name: jjgomezcadenas/laserlab

max_issues_repo_name: jjgomezcadenas/laserlab

max_forks_repo_name: jjgomezcadenas/laserlab


text:
module TextDiff

using Compat

# Utilities.

function lcs(old_tokens::Vector, new_tokens::Vector)
    m = length(old_tokens)
    n = length(new_tokens)
    weights = zeros(Int, m + 1, n + 1)
    for i = 2:(m + 1), j = 2:(n + 1)
        weights[i, j] = old_tokens[i - 1] == new_tokens[j - 1] ?
            weights[i - 1, j - 1] + 1 : max(weights[i, j - 1], weights[i - 1, j])
    end
    return weights
end

function makediff(weights::Matrix, old_tokens::Vector, new_tokens::Vector)
    m = length(old_tokens)
    n = length(new_tokens)
    diff = Vector{Pair{Symbol, SubString{String}}}()
    makediff!(diff, weights, old_tokens, new_tokens, m + 1, n + 1)
    return diff
end

function makediff!(out, weights, X, Y, i, j)
    if i > 1 && j > 1 && X[i - 1] == Y[j - 1]
        makediff!(out, weights, X, Y, i - 1, j - 1)
        push!(out, :normal => X[i - 1])
    else
        if j > 1 && (i == 1 || weights[i, j - 1] >= weights[i - 1, j])
            makediff!(out, weights, X, Y, i, j - 1)
            push!(out, :green => Y[j - 1])
        elseif i > 1 && (j == 1 || weights[i, j - 1] < weights[i - 1, j])
            makediff!(out, weights, X, Y, i - 1, j)
            push!(out, :red => X[i - 1])
        end
    end
    return out
end

function splitby(reg::Regex, text::AbstractString)
    out = SubString{String}[]
    last = 1
    for each in eachmatch(reg, text)
        push!(out, SubString(text, last, each.match.offset + each.match.endof))
        last = each.match.endof + each.offset
    end
    laststr = SubString(text, last)
    isempty(laststr) || push!(out, laststr)
    return out
end

# Diff Type.

struct Lines end
struct Words end

splitter(::Type{Lines}) = r"\n"
splitter(::Type{Words}) = r"\s+"

struct Diff{T}
    old_tokens::Vector{SubString{String}}
    new_tokens::Vector{SubString{String}}
    weights::Matrix{Int}
    diff::Vector{Pair{Symbol, SubString{String}}}

    function Diff{T}(old_text::AbstractString, new_text::AbstractString) where T
        reg = splitter(T)
        old_tokens = splitby(reg, old_text)
        new_tokens = splitby(reg, new_text)
        weights = lcs(old_tokens, new_tokens)
        diff = makediff(weights, old_tokens, new_tokens)
        return new{T}(old_tokens, new_tokens, weights, diff)
    end
end

# Display.

prefix(::Diff{Lines}, s::Symbol) = s === :green ? "+ " : s === :red  ? "- " : "  "
prefix(::Diff{Words}, ::Symbol) = ""

function showdiff(io::IO, diff::Diff)
    for (color, text) in diff.diff
        print_with_color(color, io, prefix(diff, color), text)
    end
end

function Base.show(io::IO, diff::Diff)
    print_with_color(:normal, io) # Reset colors.
    showdiff(io, diff)
end

end


text:
function findstate(state::Int,set::Array{Int})
    iimin=1
    iimax=length(set)

    if set[iimin] == state
        return iimin
    end
    if set[iimax] == state
        return iimax
    end

    found=false
    count=0

    while found==false && count < length(set)
        count+=1
        index=floor(Int,iimin+(iimax-iimin)/2)
        if state < set[index]
            iimax=index-1
        elseif state > set[index]
            iimin=index+1
        else
            found=true
            return index
        end
    end

    if found == false
        println("findstate never found a match")
        println("Are you sure the state is in that Array?")
    end

    return 0
end

function bulk(n::Int,mz::Int)

    nstates=2^n

    psi=collect(0:(nstates-1))

    powers2=collect(0:(n-1))
    powers2=2.^powers2

    mask=[0;powers2]+[powers2;0]
    mask=mask[2:end-1]

    m=zeros(psi)
    for ii in 1:nstates
        m[ii]=sum((psi[ii]&powers2)./(powers2))
    end

    ind=sortperm(m)
    m=(m[ind]-n/2)/2
    psi=psi[ind]

    ma=collect(0:.5:n/2.)-n/4

    psia=Array{Array{Int64}}(n+1)
    first=1
    last=1
    for ii in 1:(n+1)
        psia[ii]=psi[first:last]

        #now we compute them for the next round
        first=last+1
        last=last+binomial(n,ii)
    end

    dim=binomial(n,mz-1)
    M=spzeros(Float64,dim,dim);
    Psi=psia[mz]

    mp=sum(Psi[1]&powers2./powers2)
    for ii in 1:length(Psi)
        p=Psi[ii]
        for jj in 1:(n-1)
            flipped=p$mask[jj]
            if sum((flipped&powers2)./powers2) == mp
                index=findstate(flipped,Psi)
                M[ii,index]=.5
                M[index,ii]=.5
            end

        end
    end

    ei=eigvals(full(M))
    println(size(M))

    writedlm("eigvals.dat",ei)
    return 0
end

@allocated @time bulk(17,8)


text:
using DataFlow, DataFlow.Fuzz
using MacroTools, Lazy, Test

import DataFlow: Call, graphm, syntax, dvertex, constant, prewalk

@testset "DataFlow" begin

@testset "I/O" begin

for nodes = 1:10, tries = 1:100

  dl = grow(DVertex, nodes)

  @test dl == graphm(Dict(), syntax(dl))

  @test copy(dl) == dl

  il = grow(IVertex, nodes)

  @test il == @> il DataFlow.dl() DataFlow.il()

  @test copy(il) == il == prewalk(identity, il)

end

end

@testset "Syntax" begin

var = @flow begin
  mean = sum(xs)/length(xs)
  meansqr = sumabs2(xs)/length(xs)
  meansqr - mean^2
end

@test @capture syntax(DataFlow.striplines(var)) begin
  sumabs2(xs)/length(xs) - (sum(xs) / length(xs)) ^ 2
end

let x = :(2+2)
  @test @flow(foo($x)) == vertex(Call(), constant(:foo), constant(x))
end

end

end


text:
#=
This file implements pretty printing of feature registries as tables,
both using rich terminal text (ANSI codes) and as HTML.
=#

#=
To ensure that the values in table cells are displayed nicely, we
define a wrapper [`RichCell`](#) that ensures the richest mimetype is used
to display values, with some defaults for common values like `missing`,
`Bool`s and images.
=#

Base.@kwdef struct RichCell{T}
    val::T
    iocontext = (:color => true,)
end

RichCell(val; kwargs...) = RichCell(; val, kwargs...)

# Text backend

function Base.show(io::IO, cell::RichCell)
    display(TextDisplay(IOContext(io, cell.iocontext...)), cell.val)
end

function Base.show(io::IO, cell::RichCell{String})
    print(io, cell.val)
end

function Base.show(io::IO, ::RichCell{Missing})
    print(io, "$(crayon"dark_gray")missing")
end

function Base.show(io::IO, cell::RichCell{Bool})
    if cell.val
        print(io, "$(crayon"green")✔$(crayon"reset")")
    else
        print(io, "$(crayon"red")⨯$(crayon"reset")")
    end
end

# Printing of `Markdown.MD` adds some indentation and a newline by default,
# we `strip` this for more compact tables.
function Base.show(io::IO, cell::RichCell{Markdown.MD})
    iobuf = IOBuffer()
    display(TextDisplay(IOContext(iobuf, cell.iocontext...)), cell.val)

    s = String(take!(iobuf)) |> strip
    print(io, s)
end

# HTML

const IMAGE_MIMES = [
    MIME("image/jpeg"),
    MIME("image/png"),
]

# fallback
function Base.show(io::IO, mime::MIME"text/html", cell::RichCell)
    if showable(mime, cell.val)
        show(io, mime, cell.val)
    elseif showable(MIME("image/svg+xml"), cell.val)
        show(io, MIME("image/svg+xml"), cell.val)
    elseif any(showable(m, cell.val) for m in IMAGE_MIMES)
        @show showable.(IMAGE_MIMES, cell.val)
        ImageShow.show_element(io, cell.val)
    else
        print(io, cell.val)
    end
end

function Base.show(io::IO, ::MIME"text/html", cell::RichCell{Bool})
    if cell.val
        print(io, """✔""")
    else
        print(io, """⨯""")
    end
end

function Base.show(io::IO, ::MIME"text/html", ::RichCell{Missing})
    print(io, """missing""")
end

function Base.show(io::IO, ::MIME"text/html", cell::RichCell{String})
    print(io, cell.val)
end

# ## `show` methods

function registrytable(registry::Registry)
    data, fields = getdata(registry), getfields(registry)
    names = [f.name for f in fields]
    cols = [":$k" for k in keys(fields)]

    tabledata = if length(data) > 0
        reduce(vcat, map(data) do row
            reshape([formatfieldvalue(field, row[key]) for (key, field) in pairs(fields)], 1, :)
        end)
    else
        fill(missing, (1, length(fields)))
    end

    tabledata, (;
        header=(names, cols),
        alignment=:l,
        title=_title(registry),
        title_alignment=:l,
    )

end

_title(registry) = getfield(registry, :name)

function Base.show(io::IO, registry::Registry)
    tabledata, kwargs = registrytable(registry)
    pretty_table(
        io,
        map(c -> AnsiTextCell(string(RichCell(c))), tabledata);
        backend = Val(:text),
        tf=PrettyTables.tf_borderless,
        hlines=:all,
        vlines=[:begin, :end],
        maximum_columns_width=40,
        vcrop_mode=:middle,
        title_same_width_as_table=true,
        kwargs...)
end

function Base.show(io::IO, ::MIME"text/html", registry::Registry)
    tabledata, kwargs = registrytable(registry)
    PrettyTables.pretty_table(
        io,
        map(_stringhtml, tabledata);
        backend = Val(:html),
        standalone=false,
        tf=tf_html_minimalist,
        allow_html_in_cells=true,
        linebreaks=true,
        kwargs...)
end

function _stringhtml(c)
    io = IOBuffer()
    if showable(MIME("text/html"), c)
        show(io, MIME("text/html"), c)
    else
        print(io, c)
    end
    return String(take!(io))
end

formatfieldvalue(field, value) = field.formatfn(value)
formatfieldvalue(_, ::Missing) = missing

function _showentry(io::IO, entry::RegistryEntry)
    row = getfield(entry, :row)
    registry = getfield(entry, :registry)
    print(io, "(")

    rows = [reshape([
            "   $col",
            "=",
            AnsiTextCell(
                string(
                    RichCell(
                        formatfieldvalue(field, row[col]),
                        (:color => true, :displaysize => (10,50))))),
            AnsiTextCell("$(crayon"dark_gray")($(_fieldtype(field)))")
            ], 1, :)
        for (col, field) in pairs(registry.fields)]
    pretty_table(
        io, reduce(vcat, rows);
        alignment=[:r, :c, :l, :l], vlines=:none, hlines=:none,
        vcrop_mode=:middle,
        tf=PrettyTables.tf_compact,
        header = ["", "", "", ""],
        autowrap=true
    )
    print(io, ")")
end

function Base.show(io::IO, entry::RegistryEntry)
    print(io, "RegistryEntry")
    _showentry(io, entry)
end

@testset "Printing" begin

    @testset "text" begin
        richstring(x) = sprint(io -> show(io, MIME("text/plain"), RichCell(x)))

        @test richstring("hi") == "hi"
        @test richstring(Markdown.parse("hi")) |> strip == "hi"
        @test richstring(Markdown.parse("**hi**")) |> strip == "\e[1mhi\e[22m"
        @test richstring(true) == "\e[32m✔\e[0m"
        @test richstring(false) == "\e[31m⨯\e[0m"
        @test richstring(missing) == "\e[90mmissing"
    end

    @testset "html" begin
        richstring(x) = sprint(io -> show(io, MIME("text/html"), RichCell(x)))

        @test richstring("hi") == "hi"
        @test richstring(Markdown.parse("hi")) == """

hi
\n
"""
        @test richstring(Markdown.parse("**hi**")) == """

hi
\n
"""
        @test richstring(true) == "✔"
        @test richstring(false) == "⨯"

    end
end


text:
export supported_platforms, expand_gfortran_versions, expand_cxxstring_abis, expand_microarchitectures

using Pkg.Artifacts: load_artifacts_toml, ensure_all_artifacts_installed
using Base.BinaryPlatforms: set_compare_strategy!, compare_version_cap

# This is a type that encompasses a shard; it makes it easy to pass it around,
# get its download url, extraction url, mounting url, etc...
struct CompilerShard
    # Something like "RootFS", or "GCCBootstrap"
    name::String

    # Something like v"7.1.0"
    version::VersionNumber

    # Things like Platform("x86_64", "windows"; libgfortran_version=v"3")
    target::Union{Nothing,Platform}

    # Usually `default_host_platform`
    host::AbstractPlatform

    # :unpacked or :squashfs.  Possibly more in the future.
    archive_type::Symbol

    function CompilerShard(name, version, host, archive_type; target = nothing)
        # Ensure we have the right archive type
        if !(archive_type in (:squashfs, :unpacked))
            error("Invalid archive type '$(archive_type)'")
        end

        # If host or target are unparsed, parse them:
        if isa(host, AbstractString)
            host = parse(Platform, host)
        end
        if isa(target, AbstractString)
            target = parse(Platform, target)
        end

        # Ensure the platforms have no ABI portion (that is only used
        # by higher-level things to choose e.g. which version of GCC
        # to use, but once we're at this level we only care about the
        # larger-scale things, not the ABI).
        host = abi_agnostic(host::AbstractPlatform)
        if target !== nothing
            target = abi_agnostic(target)
        end

        # Construct our shiny new CompilerShard object
        return new(name, version, target, host, archive_type)
    end
end

function Base.:(==)(a::CompilerShard, b::CompilerShard)
    return a.name == b.name &&
           a.version == b.version &&
           a.target == b.target &&
           a.host == b.host &&
           a.archive_type == b.archive_type
end

"""
    artifact_name(cs::CompilerShard)

Return the bound artifact name for a particular shard.
"""
function artifact_name(cs::CompilerShard)
    target_str = ""
    if cs.target !== nothing
        target_str = "-$(triplet(cs.target::Platform))"

        if cs.name in ("GCCBootstrap", "PlatformSupport")
            # armv6l uses the same GCC shards as armv7l, so we just rename here.
            target_str = replace(target_str, "-armv6l-linux" => "-armv7l-linux")
        end
    end
    ext = Dict(:squashfs => "squashfs", :unpacked => "unpacked")[cs.archive_type]
    return "$(cs.name)$(target_str).v$(cs.version).$(triplet(cs.host)).$(ext)"
end

# The inverse of `artifact_name(cs)`
function CompilerShard(art_name::String)
    m = match(r"^([^-]+)(?:-(.+))?\.(v[\d\.]+(?:-[^\.]+)?)\.([^0-9].+-.+)\.(\w+)", art_name)
    if m === nothing
        error("Unable to parse '$(art_name)'")
    end
    return CompilerShard(
        m.captures[1],
        VersionNumber(m.captures[3]::AbstractString),
        m.captures[4],
        Symbol(m.captures[5]);
        target=m.captures[2]
    )
end

const ALL_SHARDS = Ref{Union{Vector{CompilerShard},Nothing}}(nothing)
function all_compiler_shards()::Vector{CompilerShard}
    if ALL_SHARDS[] === nothing
        artifacts_toml = joinpath(dirname(@__DIR__), "Artifacts.toml")
        artifact_dict = load_artifacts_toml(artifacts_toml)

        ALL_SHARDS[] = CompilerShard[]
        # Copy all armv7l shards as armv6l shards as well
        names = String[]
        for name in keys(artifact_dict)
            push!(names, name)
            if occursin("armv7l", name)
                push!(names, replace(name, "armv7l" => "armv6l"))
            end
        end
        for name in names
            cs = try
                CompilerShard(name)
            catch
                continue
            end

            # If this compiler shard has an os_version, that should be interpreted as the bound it is.
            if cs.target !== nothing && os_version(cs.target::Platform) !== nothing
                set_compare_strategy!(cs.target::Platform, "os_version", compare_version_cap)
            end
            push!(ALL_SHARDS[]::Vector{CompilerShard}, cs)
        end
    end
    return ALL_SHARDS[]::Vector{CompilerShard}
end

function shard_source_artifact_hash(cs::CompilerShard)
    artifacts_toml = joinpath(dirname(@__DIR__), "Artifacts.toml")
    name = artifact_name(cs)
    hash = artifact_hash(
        name,
        artifacts_toml;
        platform=cs.host,
    )
    if hash === nothing
        error("Compiler shard $(name) for platform $(triplet(something(cs.target, cs.host))) not found in $(artifacts_toml)")
    end
    return hash
end

"""
    shard_path(cs::CompilerShard)

Return the path to this shard on-disk; for unpacked shards, this is a directory.
For squashfs shards, this is a file.  This will not cause a shard to be downloaded.
"""
function shard_path(cs::CompilerShard; uid = getuid())
    hash = shard_source_artifact_hash(cs)

    # .squashfs files get modified per-UID
    if cs.archive_type == :squashfs
        # Store the UID-altered .squashfs in a scratchspace specific to this artifact,
        # and keyed by the name/UUID.  This allows old scratchspaces to be cleaned up
        # as artifacts are retired, and also for multiple users to share a scratchspace.
        return joinpath(@get_scratch!(bytes2hex(hash.bytes)), "$(artifact_name(cs))_$(getuid()).squashfs")
    end

    return artifact_path(hash)
end

"""
    map_target(cs::CompilerShard)

Return the location this compiler shard should be mounted at.  We basically
analyze the name and platform of this shard and return a path based on that.
"""
function map_target(cs::CompilerShard)
    if lowercase(cs.name) == "rootfs"
        return "/"
    elseif lowercase(cs.name) == "rusttoolchain"
        # We override RustToolchain because they all have to sit in the same location
        return "/opt/$(aatriplet(cs.host))/$(cs.name)-$(cs.version)-$(aatriplet(cs.target::Platform)))"
    else
        return joinpath("/opt", aatriplet(something(cs.target, cs.host)), "$(cs.name)-$(cs.version)")
    end
end

function mount_path(cs::CompilerShard, build_prefix::AbstractString)
    if cs.archive_type == :squashfs
        return joinpath(build_prefix, ".mounts", artifact_name(cs))
    else
        name = artifact_name(cs)
        artifacts_toml = joinpath(@__DIR__, "..", "Artifacts.toml")
        hash = artifact_hash(name, artifacts_toml; platform=cs.host)
        if hash === nothing
            error("Unable to find artifact $(name) within $(artifacts_toml)")
        end
        return artifact_path(hash)
    end
end

"""
    accept_apple_sdk(ins::IO, outs::IO) -> Bool

Ask the user whether they accept the terms of the macOS SDK, and return a
boolean with their choice.  Write messages to `outs`, read input from `ins`.
"""
function accept_apple_sdk(ins::IO, outs::IO)
    msg = strip("""
                Apple restricts distribution and usage of the macOS SDK, a necessary
                component to build software for macOS targets.  Please read the Apple
                and Xcode SDK agreement for more information on the restrictions and
                legal terms you agree to when using the SDK to build software for Apple
                operating systems: https://images.apple.com/legal/sla/docs/xcode.pdf.
                """)
    printstyled(outs, msg, bold=true)
    println(outs)
    while true
        print(outs, "Would you like to download and use the macOS SDK? [y/N]: ")
        answer = lowercase(strip(readline(ins)))
        if answer == "y" || answer == "yes"
            return true
        elseif answer == "n" || answer == "no"
            return false
        else
            println(outs, "Unrecognized answer. Answer `y` or `n`.")
        end
    end
end

"""
    mount(cs::CompilerShard, build_prefix::String)

Mount a compiler shard, if possible.  Uses `run()` so will error out if
something goes awry.  Note that this function only does something when
using a `.squashfs` shard, with a UserNS or Docker runner, on Linux.
All other combinations of shard archive type, runner and platform result
in a no-op from this function.
"""
function mount(cs::CompilerShard, build_prefix::AbstractString; verbose::Bool = false)
    # Before doing anything with a MacOS shard, make sure the user knows that
    # they must accept the Xcode EULA.  This will be skipped if either the
    # environment variable BINARYBUILDER_AUTOMATIC_APPLE has been set to `true`
    # or if the SDK has been downloaded in the past.
    if cs.target !== nothing && Sys.isapple(cs.target::Platform) && !isfile(enable_apple_file()) && !macos_sdk_already_installed()
        if !isinteractive()
            msg = strip("""
            This is not an interactive Julia session, so we will not prompt you
            to download and install the macOS SDK.  Because you have not agreed
            to the Xcode license terms, we will not be able to build for MacOS.
            Please read the Apple and Xcode SDK agreement for more information
            on the restrictions and legal terms you agree to when using the SDK
            to build software: https://images.apple.com/legal/sla/docs/xcode.pdf.
            Should you then wish to agree to them, you can do so by setting the
            environment variable BINARYBUILDER_AUTOMATIC_APPLE to `true` and then
            restarting this computation.
            """)
            @error(msg)
            error("macOS SDK not installable")
        else
            if !accept_apple_sdk(stdin, stdout)
                error("macOS SDK not installable")
            end
        end
    end

    # Ensure this artifact is on-disk; hard to mount it if it's not installed
    artifacts_toml = joinpath(dirname(@__DIR__), "Artifacts.toml")
    ensure_artifact_installed(artifact_name(cs), artifacts_toml; platform=cs.host, verbose=true)

    # Easy out if we're not Linux with a UserNSRunner trying to use a .squashfs
    if !Sys.islinux() || (preferred_runner() != UserNSRunner &&
                          preferred_runner() != DockerRunner) ||
                         cs.archive_type != :squashfs
        # We'll just give back the artifact path in this case
        return mount_path(cs, build_prefix)
    end

    # If it's already mounted, also just return the mount path
    if is_mounted(cs, build_prefix)
        return mount_path(cs, build_prefix)
    end

    # Ensure that we've got a UID-appropriate .squashfs
    squashfs_path = generate_per_uid_squashfs(cs; verbose=verbose)

    # Signal to the user what's going on, since this might require sudo.
    mpath = mount_path(cs, build_prefix)
    if verbose
        @debug("Mounting $(squashfs_path) to $(mpath)")
    end

    # If the destination directory does not already exist, create it
    mkpath(mpath)

    # Run the mountaining
    run(`$(sudo_cmd()) mount $(squashfs_path) $(mpath) -o ro,loop`)

    # Give back the mount path
    return mpath
end

"""
    is_mounted(cs::CompilerShard, build_prefix::String)

Return true if the given shard is mounted.  Uses `run()` so will error out if
something goes awry.
"""
function is_mounted(cs::CompilerShard, build_prefix::AbstractString)
    return success(`mountpoint $(mount_path(cs, build_prefix))`)
end

"""
    unmount(cs::CompilerShard, build_prefix::String)

Unmount a compiler shard from a given build prefix, if possible.  Uses `run()`
so will error out if something goes awry.  Note that this function only does
something when using a squashfs shard on Linux.  All other combinations of
shard archive type and platform result in a no-op.
"""
function unmount(cs::CompilerShard, build_prefix::String; verbose::Bool = false, fail_on_error::Bool = false, tries::Int = 3)
    # Only try to unmount if it's mounted
    if Sys.islinux() && is_mounted(cs, build_prefix)
        mpath = mount_path(cs, build_prefix)
        if verbose
            @debug("Unmounting $(mpath)`")
        end
        # Try to unmount a few times
        for ntry in 1:tries
            try
                cmd = `$(sudo_cmd()) umount $(mpath)`
                run(cmd, verbose ? (devnull, stdout, stderr) : (devnull, devnull, devnull))

                # Remove mountpoint directory
                rm(mpath; force=true, recursive=false)
                break
            catch e
                # By default we don't error out if this unmounting fails
                if e isa InterruptException || fail_on_error
                    rethrow(e)
                end
                @info("Couldn't unmount $(mpath), trying again...")
                sleep(0.1)
            end
        end
    end
end

"""
    macos_sdk_already_installed()

Returns `true` if any piece of the MacOS SDK is already installed.
"""
function macos_sdk_already_installed()
    # Get all compiler shards we know about
    css = all_compiler_shards()
    macos_artifact_names = artifact_name.(filter(cs -> cs.target !== nothing && Sys.isapple(cs.target::Platform), css))

    artifacts_toml = joinpath(dirname(@__DIR__), "Artifacts.toml")
    macos_artifact_hashes = artifact_hash.(macos_artifact_names, artifacts_toml; platform=default_host_platform)

    # Return `true` if _any_ of these artifacts exist on-disk:
    return any(artifact_exists.(macos_artifact_hashes))
end

function select_closest_version(preferred::VersionNumber, versions::Vector{VersionNumber})
    ver_to_tuple(v) = (Int(v.major), Int(v.minor), Int(v.patch))
    preferred = ver_to_tuple(preferred)
    closest_idx = findmin([abs.(preferred .- ver_to_tuple(v)) for v in versions])[2]
    return versions[closest_idx]
end

abstract type CompilerBuild end

struct GCCBuild <: CompilerBuild
    version::VersionNumber
    abi::NamedTuple
end
GCCBuild(v::VersionNumber) = GCCBuild(v, ())

struct LLVMBuild <: CompilerBuild
    version::VersionNumber
    abi::NamedTuple
end
LLVMBuild(v::VersionNumber) = LLVMBuild(v, (;))

getversion(c::CompilerBuild) = c.version
getabi(c::CompilerBuild) = c.abi

const available_gcc_builds = [
    GCCBuild(v"4.8.5", (libgfortran_version = v"3", libstdcxx_version = v"3.4.19", cxxstring_abi = "cxx03")),
    GCCBuild(v"5.2.0", (libgfortran_version = v"3", libstdcxx_version = v"3.4.21", cxxstring_abi = "cxx11")),
    GCCBuild(v"6.1.0", (libgfortran_version = v"3", libstdcxx_version = v"3.4.22", cxxstring_abi = "cxx11")),
    GCCBuild(v"7.1.0", (libgfortran_version = v"4", libstdcxx_version = v"3.4.23", cxxstring_abi = "cxx11")),
    GCCBuild(v"8.1.0", (libgfortran_version = v"5", libstdcxx_version = v"3.4.25", cxxstring_abi = "cxx11")),
    GCCBuild(v"9.1.0", (libgfortran_version = v"5", libstdcxx_version = v"3.4.26", cxxstring_abi = "cxx11")),
    GCCBuild(v"10.2.0", (libgfortran_version = v"5", libstdcxx_version = v"3.4.28", cxxstring_abi = "cxx11")),
    GCCBuild(v"11.1.0", (libgfortran_version = v"5", libstdcxx_version = v"3.4.29", cxxstring_abi = "cxx11")),
    GCCBuild(v"11.0.0-iains", (libgfortran_version = v"5", libstdcxx_version = v"3.4.28", cxxstring_abi = "cxx11")),
]
const available_llvm_builds = [
    LLVMBuild(v"6.0.1"),
    LLVMBuild(v"7.1.0"),
    LLVMBuild(v"8.0.1"),
    LLVMBuild(v"9.0.1"),
    LLVMBuild(v"11.0.1"),
    LLVMBuild(v"12.0.0"),
]

"""
    gcc_version(p::AbstractPlatform, GCC_builds::Vector{GCCBuild},
                compilers::Vector{Symbol}=[:c];
                llvm_version::Union{Nothing,VersionNumber}=nothing)

Returns the closest matching GCC version number for the given particular
platform, from the given set of options.  The compiler ABI and the
microarchitecture of the platform will be taken into account.  If no match is
found, returns an empty list.  `compilers` is the list of compilers used in the
build, to choose the right version of GCC to couple with them if necessary.  If
the keyword argument `llvm_version` is passed, it is used to filter the version
of GCC for FreeBSD platforms.

This method assumes that the compiler ABI of the platform represents a platform
that binaries will be run on, and thus versions are always rounded down; e.g. if
the platform supports a `libstdc++` version that corresponds to `GCC 5.1.0`, but
the only GCC versions available to be picked from are `4.8.5` and `5.2.0`, it
will return `4.8.5`, as binaries compiled with that version will run on this
platform, whereas binaries compiled with `5.2.0` may not.
"""
function gcc_version(p::AbstractPlatform,GCC_builds::Vector{GCCBuild},
                     compilers::Vector{Symbol}=[:c];
                     llvm_version::Union{Nothing,VersionNumber}=nothing)
    # First, filter by libgfortran version.
    if libgfortran_version(p) !== nothing
        GCC_builds = filter(b -> getabi(b).libgfortran_version == libgfortran_version(p), GCC_builds)
    end

    # Next, filter by libstdc++ GLIBCXX symbol version.  Note that this
    # mapping is conservative; it is often the case that we return a
    # version that is slightly lower than what is actually installed on
    # a system.  See https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html
    # for the whole list, as well as many other interesting factoids.
    if libstdcxx_version(p) !== nothing
        GCC_builds = filter(b -> getabi(b).libstdcxx_version <= libstdcxx_version(p), GCC_builds)
    end

    # Finally, enforce cxxstring_abi guidelines.  It is possible to build
    # :cxx03 binaries on GCC 5+, (although increasingly rare) so the only
    # filtering we do is that if the platform is explicitly :cxx11, we
    # disallow running on < GCC 5.
    if cxxstring_abi(p) === "cxx11"
        GCC_builds = filter(b -> getversion(b) >= v"5", GCC_builds)
    end

    # LLVMBootstrap 12 needs to be built with GCCBootstrap ≥ 7, see
    # <https://github.com/JuliaPackaging/BinaryBuilderBase.jl/pull/112#issuecomment-776940748>.
    # However, when building for FreeBSD with LLVMBootstrap 12 we can't use `ld` from
    # binutils < 2.26 (which corresponds to GCCBootstrap < 6) to link some object files, see
    # <https://github.com/JuliaPackaging/BinaryBuilderBase.jl/issues/158>.  The solution is
    # to not allow old GCCBootstrap with new versions of LLVMBootstrap for FreeBSD.
    if llvm_version !== nothing && Sys.isfreebsd(p) && llvm_version ≥ v"12"
        GCC_builds = filter(b -> getversion(b) ≥ v"6", GCC_builds)
    end

    # Rust on Windows requires binutils 2.25 (it invokes `ld` with `--high-entropy-va`),
    # which we bundle with GCC 5.
    if :rust in compilers && Sys.iswindows(p)
        GCC_builds = filter(b -> getversion(b) ≥ v"5", GCC_builds)
    end

    # Filter the possible GCC versions depending on the microarchitecture
    if march(p) in ("avx", "avx2", "neonvfpv4")
        # "sandybridge", "haswell", "cortex-a53" introduced in GCC v4.9.0:
        # https://www.gnu.org/software/gcc/gcc-4.9/changes.html
        GCC_builds = filter(b -> getversion(b) >= v"4.9", GCC_builds)
    elseif march(p) in ("avx512", "power9")
        # "skylake-avx512" and "power9" introduced in GCC v6.1:
        # https://www.gnu.org/software/gcc/gcc-6/changes.html
        GCC_builds = filter(b -> getversion(b) >= v"6.1", GCC_builds)
    elseif march(p) in ("armv8_1",)
        # "thunderx2t99" introduced in GCC v7.1:
        # https://www.gnu.org/software/gcc/gcc-7/changes.html
        GCC_builds = filter(b -> getversion(b) >= v"7.1", GCC_builds)
    elseif march(p) in ("armv8_2_crypto", "armv8_4_crypto_sve")
        # "+aes" and "+sha2" extensions for aarch64 introduced in GCC v8:
        # https://www.gnu.org/software/gcc/gcc-8/changes.html
        GCC_builds = filter(b -> getversion(b) >= v"8.1", GCC_builds)
    end

    return getversion.(GCC_builds)
end

function llvm_version(p::AbstractPlatform, LLVM_builds::Vector{LLVMBuild})
    if march(p) in ("armv8_2_crypto", "armv8_4_crypto_sve")
        LLVM_builds = filter(b -> getversion(b) >= v"9.0", LLVM_builds)
    end
    return getversion.(LLVM_builds)
end

function select_compiler_versions(p::AbstractPlatform,
            compilers::Vector{Symbol},
            GCC_builds::Vector{GCCBuild} = available_gcc_builds,
            LLVM_builds::Vector{LLVMBuild} = available_llvm_builds,
            preferred_gcc_version::VersionNumber = getversion(GCC_builds[1]),
            preferred_llvm_version::VersionNumber = getversion(LLVM_builds[end]),
        )
    # Determine which GCC/LLVM build we're going to match with this Platform.  We need to
    # pass the chosen version of LLVM to `gcc_version`, so we first select LLVM, then GCC.
    filtered_llvm_builds = llvm_version(p, LLVM_builds)
    if isempty(filtered_llvm_builds)
        error("Impossible compiler constraints $(p) upon $(LLVM_builds)!")
    end
    llvmv = select_closest_version(preferred_llvm_version, filtered_llvm_builds)

    filtered_gcc_builds = gcc_version(p, GCC_builds, compilers; llvm_version=llvmv)
    if isempty(filtered_gcc_builds)
        error("Impossible compiler constraints $(p) upon $(GCC_builds)!")
    end
    gccv = select_closest_version(preferred_gcc_version, filtered_gcc_builds)

    return gccv, llvmv
end

"""
    choose_shards(p::AbstractPlatform; rootfs_build, ps_build, GCC_builds,
                               LLVM_builds, archive_type)

This method chooses, given a `Platform`, which shards to download, extract and
mount, returning a list of `CompilerShard` objects.  At the moment, this always
consists of four shards, but that may not always be the case.
"""
function choose_shards(p::AbstractPlatform;
            compilers::Vector{Symbol} = [:c],
            rootfs_build::VersionNumber=v"2021.7.13",
            ps_build::VersionNumber=v"2021.08.10",
            GCC_builds::Vector{GCCBuild}=available_gcc_builds,
            LLVM_builds::Vector{LLVMBuild}=available_llvm_builds,
            Rust_build::VersionNumber=v"1.57.0",
            Go_build::VersionNumber=v"1.16.3",
            archive_type::Symbol = (use_squashfs[] ? :squashfs : :unpacked),
            bootstrap_list::Vector{Symbol} = bootstrap_list,
            # Because GCC has lots of compatibility issues, we always default to
            # the earliest version possible.
            preferred_gcc_version::VersionNumber = getversion(GCC_builds[1]),
            # Because LLVM doesn't have compatibility issues, we always default
            # to the newest version possible.
            preferred_llvm_version::VersionNumber = getversion(LLVM_builds[end]),
        )

    function find_shard(name, version, archive_type; target = nothing)
        # aarch64-apple-darwin is a special platform because it has a single GCCBootstrap
        # with a version number different from any other platforms: match this shard with a
        # regular GCCBootstrap v11 for the host.
        if name == "GCCBootstrap" && version == v"11.0.0-iains" && target !== nothing && Sys.islinux(target)
            version = v"11.1.0"
        end

        for cs in all_compiler_shards()
            if cs.name == name && cs.version == version &&
               (target === nothing || platforms_match(cs.target, target)) &&
               cs.archive_type == archive_type
                return cs
            end
        end
        return nothing
    end

    # select GCC builds that can build for this platform.  Normally this is homogenous,
    # however we added support for sparse platform support in order to compile for aarch64-darwin
    # back when we only had a GCC 11 prerelease branch for it.
    function shard_exists(name, version, archive_type; target=nothing)
        return find_shard(name, version, archive_type; target) !== nothing
    end
    this_platform_GCC_builds = filter(GCC_builds) do GCC_build
        if !isa(p, AnyPlatform)
            if !shard_exists("GCCBootstrap", getversion(GCC_build), archive_type; target=p)
                return false
            end
        end

        # Ugly hack alert!  We disable this check because we don't even ship a GCC 11 build
        # for the host platform because it's prerelease and broken.
        #if !shard_exists("GCCBootstrap", getversion(GCC_build), archive_type; target=default_host_platform)
        #    return false
        #end
        return true
    end

    shards = CompilerShard[]
    if isempty(bootstrap_list)
        # Select GCC and LLVM versions given the compiler ABI and target requirements given in `p`
        GCC_build, LLVM_build = select_compiler_versions(p,
            compilers,
            this_platform_GCC_builds,
            LLVM_builds,
            preferred_gcc_version,
            preferred_llvm_version,
        )

        # We _always_ need Rootfs and PlatformSupport for our target, at least
        append!(shards, [
            find_shard("Rootfs", rootfs_build, archive_type),
            find_shard("PlatformSupport", ps_build, archive_type; target=p)
        ])

        if :c in compilers
            append!(shards, [
                find_shard("GCCBootstrap", GCC_build, archive_type; target=p),
                find_shard("LLVMBootstrap", LLVM_build, archive_type),
            ])
        end

        if :rust in compilers
            append!(shards, [
                find_shard("RustBase", Rust_build, archive_type),
                find_shard("RustToolchain", Rust_build, archive_type; target=p),
            ])

            if !platforms_match(p, default_host_platform)
                # In case we need to bootstrap stuff and we bootstrap it for the actual host platform
                push!(shards, find_shard("RustToolchain", Rust_build, archive_type; target=default_host_platform))
            end
        end

        if :rust in compilers || :c in compilers
            # If we're not building for the host platform, then add host shard for host tools
            # This is necessary for both rust and c compilers
            if !platforms_match(p, default_host_platform)
                append!(shards, [
                    find_shard("PlatformSupport", ps_build, archive_type; target=default_host_platform),
                    find_shard("GCCBootstrap", GCC_build, archive_type; target=default_host_platform),
                ])
            end
        end

        if :go in compilers
            push!(shards, find_shard("Go", Go_build, archive_type))
        end
    else
        function find_latest_version(name)
            versions = [cs.version for cs in all_compiler_shards()
                if cs.name == name && cs.archive_type == archive_type && platforms_match(something(cs.target, p), p)
            ]
            isempty(versions) && error("No latest shard found for $name")
            return maximum(versions)
        end

        if :rootfs in bootstrap_list
            push!(shards, find_shard("Rootfs", find_latest_version("Rootfs"), archive_type))
        end
        if :platform_support in bootstrap_list
            push!(shards, find_shard("PlatformSupport", find_latest_version("PlatformSupport"), archive_type; target=p))
        end
    end
    return shards
end

# We want AnyPlatform to look like `default_host_platform` in the build environment.
choose_shards(::AnyPlatform; kwargs...) = choose_shards(default_host_platform; kwargs...)

"""
    supported_platforms(;exclude::Union{Vector{<:Platform},Function}=x->false,
                        experimental::Bool=false)

Return the list of supported platforms as an array of `Platform`s.  These are the platforms we
officially support building for, if you see a mapping in `get_shard_hash()` that isn't
represented here, it's probably because that platform is still considered "in beta".  If
`experimental=true`, include platforms considered experimental.

Platforms can be excluded from the list by specifying an array of platforms to `exclude` i.e.
`supported_platforms(exclude=[Platform("i686", "windows"), Platform("x86_64", "windows")])`
or a function that returns true for exclusions i.e.
```
supported_platforms(exclude=Sys.islinux)
```
"""
function supported_platforms(;exclude::Union{Vector{<:Platform},Function}=x->false,
                             experimental::Bool=false)
    exclude_platforms!(platforms, exclude::Function) = filter(!exclude, platforms)
    exclude_platforms!(platforms, exclude::Vector{<:Platform}) = filter!(!in(exclude), platforms)
    standard_platforms = [
        # glibc Linuces
        Platform("i686", "linux"),
        Platform("x86_64", "linux"),
        Platform("aarch64", "linux"),
        Platform("armv6l", "linux"),
        Platform("armv7l", "linux"),
        Platform("powerpc64le", "linux"),

        # musl Linuces
        Platform("i686", "linux"; libc="musl"),
        Platform("x86_64", "linux"; libc="musl"),
        Platform("aarch64", "linux"; libc="musl"),
        Platform("armv6l", "linux"; libc="musl"),
        Platform("armv7l", "linux"; libc="musl"),

        # BSDs
        Platform("x86_64", "macos"),
        Platform("aarch64", "macos"),
        Platform("x86_64", "freebsd"),

        # Windows
        Platform("i686", "windows"),
        Platform("x86_64", "windows"),
    ]

    # We have experimental support for some platforms, allow easily including them
    if experimental
        append!(standard_platforms, [
        ])
    end
    return exclude_platforms!(standard_platforms,exclude)
end

"""
    expand_gfortran_versions(p::AbstractPlatform)

Given a `Platform`, returns an array of `Platforms` with a spread of identical
entries with the exception of the `libgfortran_version` tag within the
`Platform`.  This is used to take, for example, a list of supported platforms
and expand them to include multiple GCC versions for the purposes of ABI
matching.  If the given `Platform` already specifies a `libgfortran_version`
(as opposed to `nothing`) only that `Platform` is returned.
"""
function expand_gfortran_versions(platform::AbstractPlatform)
    # If this platform is already explicitly libgfortran-versioned, exit out fast here.
    if libgfortran_version(platform) !== nothing
        return [platform]
    end

    # If this is an platform that has limited GCC support (such as aarch64-apple-darwin),
    # the libgfortran versions we can expand to are similarly limited.
    local libgfortran_versions
    if Sys.isapple(platform) && arch(platform) == "aarch64"
        libgfortran_versions = [v"5"]
    else
        libgfortran_versions = [v"3", v"4", v"5"]
    end

    # Create a new platform for each libgfortran version
    return map(libgfortran_versions) do v
        p = deepcopy(platform)
        p["libgfortran_version"] = string(v)
        return p
    end
end
function expand_gfortran_versions(ps::Vector{<:Platform})
    return collect(Iterators.flatten(expand_gfortran_versions.(ps)))
end

"""
    expand_cxxstring_abis(p::AbstractPlatform; skip=Sys.isbsd)

Given a `Platform`, returns an array of `Platforms` with a spread of identical
entries with the exception of the `cxxstring_abi` tag within the `Platform`
object.  This is used to take, for example, a list of supported platforms and
expand them to include multiple GCC versions for the purposes of ABI matching.

If the given `Platform` already specifies a `cxxstring_abi` (as opposed to
`nothing`) only that `Platform` is returned.  If `skip` is a function for which
`skip(platform)` evaluates to `true`, the given platform is not expanded.  By
default FreeBSD and macOS platforms are skipped, due to their lack of a
dependence on `libstdc++` and not needing this compatibility shim.
"""
function expand_cxxstring_abis(platform::AbstractPlatform; skip=Sys.isbsd)
    # If this platform cannot/should not be expanded, then exit out fast here.
    if cxxstring_abi(platform) !== nothing || skip(platform)
        return [platform]
    end

    # Otherwise, generate new versions!
    map(["cxx03", "cxx11"]) do abi
        p = deepcopy(platform)
        p["cxxstring_abi"] = abi
        return p
    end
end
function expand_cxxstring_abis(ps::Vector{<:AbstractPlatform}; kwargs...)
    return collect(Iterators.flatten(expand_cxxstring_abis.(ps; kwargs...)))
end

"""
    expand_microarchitectures(p::AbstractPlatform)

Given a `Platform`, returns a vector of `Platforms` with differing `march` attributes
as specified by the `ARCHITECTURE_FLAGS` mapping.  If the given `Platform` alread has a
`march` tag specified, only that platform is returned.

```jldoctest
julia> using BinaryBuilderBase

julia> expand_microarchitectures(Platform("x86_64", "freebsd"))
4-element Vector{Platform}:
 FreeBSD x86_64 {march=x86_64}
 FreeBSD x86_64 {march=avx}
 FreeBSD x86_64 {march=avx2}
 FreeBSD x86_64 {march=avx512}

julia> expand_microarchitectures(Platform("armv7l", "linux"))
2-element Vector{Platform}:
 Linux armv7l {call_abi=eabihf, libc=glibc, march=armv7l}
 Linux armv7l {call_abi=eabihf, libc=glibc, march=neonvfpv4}

julia> expand_microarchitectures(Platform("aarch64", "linux"))
4-element Vector{Platform}:
 Linux aarch64 {libc=glibc, march=armv8_0}
 Linux aarch64 {libc=glibc, march=armv8_4_crypto_sve}
 Linux aarch64 {libc=glibc, march=armv8_2_crypto}
 Linux aarch64 {libc=glibc, march=armv8_1}

julia> expand_microarchitectures(Platform("i686", "windows"))
2-element Vector{Platform}:
 Windows i686 {march=pentium4}
 Windows i686 {march=prescott}
```
"""
function expand_microarchitectures(platform::AbstractPlatform)
    # If this already has a `march`, or it's an `AnyPlatform`, just return it.
    if isa(platform, AnyPlatform) || march(platform) !== nothing
        return [platform]
    end

    # Otherwise, return a bunch of Platform objects with appropriately-set `march` tags
    return map(get_all_march_names(arch(platform))) do march
        p = deepcopy(platform)
        p["march"] = march
        return p
    end
end

"""
    expand_microarchitectures(ps::Vector{<:Platform})

Expand all platforms in the given vector with the supported microarchitectures.

```jldoctest
julia> using BinaryBuilderBase

julia> expand_microarchitectures(filter!(p -> Sys.islinux(p) && libc(p) == "glibc", supported_platforms()))
13-element Vector{Platform}:
 Linux i686 {libc=glibc, march=pentium4}
 Linux i686 {libc=glibc, march=prescott}
 Linux x86_64 {libc=glibc, march=x86_64}
 Linux x86_64 {libc=glibc, march=avx}
 Linux x86_64 {libc=glibc, march=avx2}
 Linux x86_64 {libc=glibc, march=avx512}
 Linux aarch64 {libc=glibc, march=armv8_0}
 Linux aarch64 {libc=glibc, march=armv8_4_crypto_sve}
 Linux aarch64 {libc=glibc, march=armv8_2_crypto}
 Linux aarch64 {libc=glibc, march=armv8_1}
 Linux armv7l {call_abi=eabihf, libc=glibc, march=armv7l}
 Linux armv7l {call_abi=eabihf, libc=glibc, march=neonvfpv4}
 Linux powerpc64le {libc=glibc, march=power8}
```
"""
expand_microarchitectures(ps::Vector{<:AbstractPlatform}) = collect(Iterators.flatten(expand_microarchitectures.(ps)))

"""
    preferred_libgfortran_version(platform::AbstractPlatform, shard::CompilerShard;
                                  gcc_builds::Vector{GCCBuild} = available_gcc_builds)

Return the libgfortran version preferred by the given platform or GCCBootstrap shard.
"""
function preferred_libgfortran_version(platform::AbstractPlatform, shard::CompilerShard;
                                       gcc_builds::Vector{GCCBuild} = available_gcc_builds)
    # Some input validation
    if shard.name != "GCCBootstrap"
        error("Shard must be `GCCBootstrap`")
    end
    if arch(shard.target::Platform) != arch(platform) || libc(shard.target::Platform) != libc(platform)
        error("Incompatible platform and shard target")
    end

    if libgfortran_version(platform) !== nothing
        # Here we can't use `shard.target` because the shard always has the
        # target as ABI-agnostic, thus we have also to ask for the platform.
        return libgfortran_version(platform)
    else
        idx = findfirst(b -> getversion(b) == shard.version, available_gcc_builds)
        if isnothing(idx)
            error("The shard doesn't match any version of the available GCC builds")
        else
            return getabi(gcc_builds[idx]).libgfortran_version
        end
    end
end

"""
    preferred_cxxstring_abi(platform::AbstractPlatform, shard::CompilerShard;
                            gcc_builds::Vector{GCCBuild} = available_gcc_builds)

Return the C++ string ABI preferred by the given platform or GCCBootstrap shard.
"""
function preferred_cxxstring_abi(platform::AbstractPlatform, shard::CompilerShard;
                                 gcc_builds::Vector{GCCBuild} = available_gcc_builds)
    # Some input validation
    if shard.name != "GCCBootstrap"
        error("Shard must be `GCCBootstrap`")
    end
    if arch(shard.target::Platform) != arch(platform) || libc(shard.target::Platform) != libc(platform)
        error("Incompatible platform and shard target")
    end

    if cxxstring_abi(platform) !== nothing
        # Here we can't use `shard.target` because the shard always has the
        # target as ABI-agnostic, thus we have also to ask for the platform.
        return cxxstring_abi(platform)
    else
        idx = findfirst(b -> getversion(b) == shard.version, available_gcc_builds)
        if isnothing(idx)
            error("The shard doesn't match any version of the available GCC builds")
        else
            return getabi(gcc_builds[idx]).cxxstring_abi
        end
    end
end

"""
    download_all_artifacts(; verbose::Bool=false)

Helper function to download all shards/helper binaries so that no matter what
happens, you don't need an internet connection to build your precious, precious
binaries.
"""
function download_all_artifacts(; verbose::Bool = false)
    artifacts_toml = joinpath(dirname(@__DIR__), "Artifacts.toml")
    ensure_all_artifacts_installed(
        artifacts_toml;
        include_lazy=true,
        verbose=verbose,
        platform=default_host_platform,
    )
end

const _sudo_cmd = Ref{Union{Cmd,Nothing}}(nothing)
function sudo_cmd()::Cmd
    global _sudo_cmd

    # Use cached value if we've already run this
    if _sudo_cmd[] !== nothing
        return _sudo_cmd[]::Cmd
    end

    _sudo_cmd[] = if getuid() == 0
        # If we're already root, don't use any kind of sudo program
        ``
    elseif success(`sudo -V`)
        # If `sudo` is available, use that
        `sudo`
    else
        # Fall back to `su` if all else fails
        `su root -c`
    end
    return _sudo_cmd[]::Cmd
end

"""
    shard_mappings(shards::Vector{CompilerShard})

Return the default mappings for a set of compiler shards
"""
function shard_mappings(shards::Vector{CompilerShard})
    mappings = Pair{String,String}[]
    for shard in shards
        # No mapping for the main rootfs shard
        if lowercase(shard.name) == "rootfs"
            continue
        end

        # For everything else, map it into its proper place
        push!(mappings, mount_path(shard) => map_target(shard))
    end

    # Reverse mapping order, because `sandbox` reads them backwards
    reverse!(mappings)
    return mappings
end

function mount_shards(ur::Runner; verbose::Bool = false)
    mount.(ur.shards, ur.workspace_root; verbose=verbose)
end
function unmount_shards(ur::Runner; verbose::Bool = false)
    unmount.(ur.shards, ur.workspace_root; verbose=verbose)

    # Remove `mounts` if it's empty
    try
        rm(joinpath(ur.workspace_root, ".mounts"))
    catch
    end
end

"""
    installed_shards()
Return a vector of compiler shards currently downloaded on the local system
"""
function installed_shards()
    idx = artifact_exists.(shard_source_artifact_hash.(all_compiler_shards()))

    return all_compiler_shards()[idx]
end


text:
"""
    span(f)

Return the end-points for the [`envelope`](@ref) of the field `f`.
"""
span(f::AbstractField) = span(envelope(f))

const DEFAULT_SAMPLING_FACTOR = 100
default_sampling_frequency(f::AbstractField) = DEFAULT_SAMPLING_FACTOR*austrip(max_frequency(f))

@doc raw"""
    steps(f[, fs])

Return number of time steps for the field `f`, optionally supplying a
sampling frequency.

We construct the time axis such that the highest frequency component
of the field is resolved. By the Nyquist sampling theorem, we need
minimum ``f_s>2f_{\textrm{max}}``, but to be on the safe side, we use,
as default, ``f_s=100f_{\textrm{max}}``. This also makes plots nicer.

If `fs<:Integer`, the sampling frequency is computed as `fs/T`,
i.e. `fs` steps per cycle.
"""
steps(f::AbstractField, fs::Real=default_sampling_frequency(f)) =
    ceil(Int, fs*abs(-(endpoints(span(f))...)))
steps(f::AbstractField, ndt::Int) = steps(f, ndt/austrip(period(f)))

"""
    timeaxis(f[, fs])

Construct a time axis for the field `f`, covering the [`span`](@ref)
of the envelope in the number of [`steps`](@ref) given by the sample
frequency `fs`.
"""
function timeaxis(f::AbstractField, fs::Number=default_sampling_frequency(f))
    num_steps = steps(f, fs)
    s = span(f)
    if num_steps > 1 || s.left == s.right
        range(s, length=num_steps)
    else
        # This hack is mainly useful for calculations where exactly
        # one time step should be taken, (b-a) becomes the step
        # length.
        a,b = endpoints(s)
        a:(b-a):a
    end
end

"""
    timeaxis(f, (N,dt))

Alternate interface that constructs the time axis with `N` steps
starting from `first(span(f))`, with intervals of `dt`.
"""
function timeaxis(f::AbstractField, (N,dt)::Tuple{<:Integer,<:Real})
    a,b = endpoints(span(f))
    t = range(a, length=N, step=dt)
    t[end] < b && @warn "$(N) steps of $(dt) jiffies does not cover the span of the field, $(b) ∉ $(t)"
    t
end

export span, steps, timeaxis


text:
function Polygon(
        points::AbstractVector;
        boundingbox = Rect2D(points),
        isconvex = isconvex(points),
        ishole = false
    )
    ps = convert(AbstractVector{Point}, points)
    NumType = eltype(eltype(ps))
    Polygon{NumType, typeof(ps)}(ps, boundingbox, isconvex, ishole)
end

iterate(polygons::MultiPolygon) = iterate(polygons.polygons)
iterate(polygons::MultiPolygon, state) = iterate(polygons.polygons, state)

vertices(poly::Polygon) = poly.points
boundingbox(poly::Polygon) = poly.boundingbox
ishole(poly::Polygon) = poly.ishole
isconvex(poly::Polygon) = poly.isconvex

"""
    in(point::AbstractPoint, poly::Polygon)

# An implementation of Hormann-Agathos (2001) Point in Polygon algorithm
# See: http://www.sciencedirect.com/science/article/pii/S0925772101000128
# Code segment adapted from PolygonClipping.jl
"""
function in(point::AbstractPoint, poly::Polygon)
    (point in boundingbox(poly)) || return false
    c = false
    detq(q1, q2, point) = (q1[1]-point[1])*(q2[2]-point[2])-(q2[1]-point[1])*(q1[2]-point[2])
    for (q1, q2) in Partition{2}(poly)
        if q1 == point
            @warn("point on polygon vertex - returning false")
            return false
        end
        if q2[2] == point[2]
            if q2[1] == point[1]
                @warn("point on polygon vertex - returning false")
                return false
            elseif (q1[2] == point[2]) && ((q2[1] > x) == (q1[1] < point[1]))
                @warn("point on edge - returning false")
                return false
            end
        end
        if (q1[2] < point[2]) != (q2[2] < point[2]) # crossing
            if q1[1] >= point[1]
                if q2[1] > point[1]
                    c = !c
                elseif ((detq(q1,q2,point) > 0) == (q2[2] > q1[2])) # right crossing
                    c = !c
                end
            elseif q2[1] > point[1]
                if ((detq(q1,q2,point) > 0) == (q2[2] > q1[2])) # right crossing
                    c = !c
                end
            end
        end
    end
    return c
end

function in(point::AbstractPoint, polygons::MultiPolygon)
    point in boundingbox(polygons) || return false
    for polygon in polygons
        if ishole(polygon)
            point in polygon && return false
        else
            point in polygon && return true
        end
    end
    return false
end

function isconvex(points::AbstractVector{<: AbstractPoint})
    length(points) < 4 && return true
    sign = false
    for (i, (a, b, c)) in enumerate(Partition{3}(points, true))
        Δ1 = c .- b
        Δ2 = a .- b
        det = cross(Δ1, Δ2)
        if i == 1
            sign = det > 0
        elseif (sign != (det > 0))
            return false
        end
    end
    return true
end


text:
"""
    load_newick(filename::String)

This function loads all tree representations from a file
"""

function load_newick(filename::String)

    open(filename, "r") do file
        global content = readlines(file)
    end
    content
end

"""
    is_valid_newick_string(newick::String)

This function checks if the given string is valid: is the brackets number matches and if the string ends with ";"
"""

function is_valid_newick_string(newick::String) #TODO: possibly not necessary; could be done as part of recursion, MAYBE
    # Step one: does the stripped string ends with ';'
    if endswith(strip(newick),";")
        # Step two: check for the equal amount of brackets
        bracket_level = 0
        for letter in newick # => char is a type in Julia
            if letter == '('
                bracket_level += 1
            elseif letter == ')'
                    bracket_level -= 1
            end # elseif
        end # for
        if bracket_level != 0
            return false
        end # if
    else # same level as the endswith statement
        return false
    end # else
    return true
end

"""
    function parsing_newick_string(newick::String)::GeneralNode

Parse a single newick string.
"""
function parsing_newick_string(newick::String)::GeneralNode
    if !is_valid_newick_string(newick)
        throw("$newick is not correctly formatted!")
    end # if
    tree = nwk_parser(string(newick))
    set_binary!(tree)
    number_nodes!(tree)
    tree
end

"""
    ParseNewick(filename::String)::Array{GeneralNode, 1}

This function takes a filename as a String, and returns an array of trees(represented as Node objects).
The file should solely consist of newick tree representations, separated by line.
The function checks for proper newick formatting, and will return an error if the file is
incorrectly formatted.

Returns an Array of Nodes; each Node is the root of the tree represented by a newick string in the file.

* `filename` : name of file containing newick strings to parse.
"""
function ParseNewick(filename::String)::Array{GeneralNode, 1}
    list_of_trees = load_newick(filename)
    list_of_newicks = GeneralNode[]
    for content in list_of_trees
        if content == ""
            continue
        end # if
        tree = parsing_newick_string(content)
        push!(list_of_newicks, tree)
    end # for
    list_of_newicks
end

function nwk_parser(nwk_string::S)::GeneralNode where S <: AbstractString
    if  nwk_string[end] == ';' #no need for semicolon
        nwk_string = chop(nwk_string)
    end #if
    parts = split(nwk_string, ")")
    if length(parts) == 1
        desc, node_name = [], parts[1]
    else
        if !(startswith(parts[1], '('))
            throw(ArgumentError("unmatched braces $(parts[1])"))
        else
            desc = parse_sibblings(join(parts[1:end-1],")")[2:end])
            node_name = parts[end]
        end
    end
    node = parse_name_length(node_name)
    if length(desc) > 0
        for child in desc
            add_child!(node, child)
        end
    end
    node
end

function parse_sibblings(nwk_string::S)::Array{GeneralNode} where S <: AbstractString
    bracket_level = 0
    current = Char[]
    children = GeneralNode[]
    # trick to remove special-case of trailing chars
    for c in (nwk_string * ",")
        if c == ',' && bracket_level == 0
            n = nwk_parser(join(current, ""))
            push!(children, n)
            current = Char[]
        else
            if c == '('
                bracket_level += 1
            elseif c == ')'
                bracket_level -= 1
            end
            push!(current, c)
        end
    end
    children
end

function parse_name_length(newick::S)::GeneralNode where S<:AbstractString
    nnam = "no_name"
    ninc = 1.0
    if length(newick) > 0
        if occursin(':',newick)
            name, len = split(strip(newick),':')
            if name == ""
                name = "no_name"
            end # if
            if len == ""
                len = 1.0
            end # if
            if occursin(';',len)
                len = chop(len)
            end # if
            nnam = string(name)
            ninc = parse(Float64, len)
        else
            nnam = string(newick)
        end
    end #if-else
    node = Node(nnam)
    node.inc_length = ninc
    return node
end # function

max_stars_repo_name: erathorn/MCPhylo

max_issues_repo_name: erathorn/MCPhylo

max_forks_repo_name: erathorn/MCPhylo


text:
function stdlib_regdata(stdlib_dir)
    # TODO: use Sys.STDLIBDIR instead once implemented
    isdir(stdlib_dir) || error("stdlib directory does not exist: $stdlib_dir")

    stdlib_uuids = Dict{String,String}()
    stdlib_trees = Dict{String,String}()
    stdlib_deps = Dict{String,Vector{String}}()

    for pkg in readdir(stdlib_dir)
        project_file = joinpath(stdlib_dir, pkg, "Project.toml")
        isfile(project_file) || continue
        project = TOML.parsefile(project_file)
        stdlib_uuids[pkg] = project["uuid"]
        try
        stdlib_trees[pkg] = split(
            readchomp(`git -C $stdlib_dir ls-tree HEAD -- $pkg`)
        )[3]
        catch y
            if y isa BoundsError
                @error "Error while processing $pkg"
            end
            rethrow(y)
        end
        stdlib_deps[pkg] = String[]
        haskey(project, "deps") || continue
        append!(stdlib_deps[pkg], sort!(collect(keys(project["deps"]))))
    end
    stdlib_uuids, stdlib_trees, stdlib_deps
end


text:
using Base.Threads
using FileIO

function test1(x::Array{Float64,1},y::Array{Float64,1})::Array{Float64,1}
    println("number of threads:",Threads.nthreads())
    z = zeros(1000000)
    @threads for i in 1:5
        load("667626_18933d713e.jpg")
    end
    z
end

function test2(x::Array{Float64,1},y::Array{Float64,1})::Array{Float64,1}
    z = zeros(1000000)
    for i in 1:1000000
        z[i] = x[i]+y[i]
    end
    z
end
x = rand(111711111)
y = rand(111711111)
@time test1(x,y)

@time test2(x,y)


text:
const A = 2
println("A is $A")


text:
#' ---
#' title: Solvers and Solutions
#' ---

#' **Originally Contributed by**: Arpit Bhatia

#' The purpose of this part of the tutorial is to introduce you to solvers and how to use them with JuMP. We'll also learn
#' what to do with a problem after the solver has finished optimizing it.

#' ## What is a Solver?
#' A solver is a software package that incorporates algorithms for finding solutions to one or more classes of problem.
#' For example, GLPK, which we used in the previous tutorials is a solver for linear programming (LP) and mixed integer
#' programming (MIP) problems. It incorporates algorithms such as the simplex method, interior-point method etc. JuMP
#' currently supports a number of open-source and commercial solvers which can be viewed
#' [here](http://www.juliaopt.org/JuMP.jl/v0.19.1/installation/#Getting-Solvers-1).

#' ## What is MathOptInterface?
#' Each mathematical optimization solver API has its own concepts and data structures for representing optimization models
#' and obtaining results. However, it is often desirable to represent an instance of an optimization problem at a higher
#' level so that it is easy to try using different solvers. MathOptInterface (MOI) is an abstraction layer designed to provide
#' an interface to mathematical optimization solvers so that users do not need to understand multiple solver-specific
#' APIs. MOI can be used directly, or through a higher-level modeling interface like JuMP.

#' Note that JuMP reexports MathOptInterface and
#' you can use the shortcut MOI to refer to MathOptInterface in your code.

#' ## Interacting with solvers
#' JuMP models can be created in three different modes: `AUTOMATIC`, `MANUAL` and `DIRECT`.
#' We'll use the following LP to illustrate them.

#' $$
#' \begin{align*}
#' & \max_{x,y} & x + 2y \\
#' & \;\;\text{s.t.} & x + y &\leq 1 \\
#' & & 0\leq x, y &\leq 1 \\
#' \end{align*}
#' $$

using JuMP
using GLPK

#' ### `AUTOMATIC` Mode
#' #### With Optimizer
#' This is the easiest method to use a solver in JuMP. In order to do so, we simply set the solver inside the Model constructor.

model_auto = Model(with_optimizer(GLPK.Optimizer))
@variable(model_auto, 0 <= x <= 1)
@variable(model_auto, 0 <= y <= 1)
@constraint(model_auto, x + y <= 1)
@objective(model_auto, Max, x + 2y)
optimize!(model_auto)
objective_value(model_auto)

#' #### No Optimizer (at first)
#' It is also possible to create a JuMP model with no optimizer attached. After the model object is initialized empty
#' and all its variables, constraints and objective are set, then we can attach the solver at `optimize!` time.

model_auto_no = Model()
@variable(model_auto_no, 0 <= x <= 1)
@variable(model_auto_no, 0 <= y <= 1)
@constraint(model_auto_no, x + y <= 1)
@objective(model_auto_no, Max, x + 2y)
optimize!(model_auto_no, with_optimizer(GLPK.Optimizer))
objective_value(model_auto_no)

#' Note that we can also enforce the automatic mode by passing `caching_mode = MOIU.AUTOMATIC` in the Model function call.
#' ### `MANUAL` Mode
#' This mode is similar to the `AUTOMATIC` mode, but there are less protections from the user getting errors from the solver
#' API. On the other side, nothing happens silently, which might give the user more control. It requires attaching the solver
#' before the solve step using the `MOIU.attach_optimizer()` function.

model_manual = Model(with_optimizer(GLPK.Optimizer),caching_mode = MOIU.MANUAL)
@variable(model_manual, 0 <= x <= 1)
@variable(model_manual, 0 <= y <= 1)
@constraint(model_manual, x + y <= 1)
@objective(model_manual, Max, x + 2y)
MOIU.attach_optimizer(model_manual)
optimize!(model_manual)
objective_value(model_manual)

#' ### `DIRECT` Mode
#' Some solvers are able to handle the problem data directly. This is common for LP/MIP solver but not very common for
#' open-source conic solvers. In this case we do not set a optimizer, we set a backend which is more generic and is able
#' to hold data and not only solving a model.

model_direct = direct_model(GLPK.Optimizer())
@variable(model_direct, 0 <= x <= 1)
@variable(model_direct, 0 <= y <= 1)
@constraint(model_direct, x + y <= 1)
@objective(model_direct, Max, x + 2y)
optimize!(model_direct)
objective_value(model_direct)

#' ### Solver Options
#' Many of the solvers also allow options to be passed in. However, these options are solver-specific. To find out the various
#' options available, please check out the individual solver packages. Some examples for the CBC solver are given below.

using Cbc

#' To turn off printing (i.e. silence the solver),

model = Model(with_optimizer(Cbc.Optimizer, logLevel = 0));

#' To increase the maximum number of iterations

model = Model(with_optimizer(Cbc.Optimizer, max_iters = 10000));

#' To set the solution timeout limit

model = Model(with_optimizer(Cbc.Optimizer, seconds = 5));

#' ## Querying Solutions
#' So far we have seen all the elements and constructs related to writing a JuMP optimization model. In this section we
#' reach the point of what to do with a solved problem. JuMP follows closely the concepts defined in MathOptInterface to
#' answer user questions about a finished call to `optimize!(model)`. The three main steps in querying a solution are
#' given below. We'll use the model we created in `AUTOMATIC` mode with an optimizer attached in this section.
#' ### Termination Statuses
#' Termination statuses are meant to explain the reason why the optimizer stopped executing in the most recent call
#' to `optimize!`.

termination_status(model_auto)

#' You can view the different termination status codes by referring to the docs or though checking the possible types using
#' the below command.

display(typeof(MOI.OPTIMAL))

#' ### Solution Statuses
#' These statuses indicate what kind of result is available to be queried from the model. It's possible that no result is
#' available to be queried. We shall discuss more on the dual status and solutions in the Duality tutorial.

primal_status(model_auto)

#+

dual_status(model_auto)

#' As we saw before, the result (solution) status codes can be viewed directly from Julia.

display(typeof(MOI.FEASIBLE_POINT))

#' ### Obtaining Solutions
#' Provided the primal status is not `MOI.NO_SOLUTION`, we can inspect the solution values and optimal cost.

@show value(x)
@show value(y)
@show objective_value(model_auto)

#' Since it is possible that no solution is available to be queried from the model, calls to `value` may throw errors.
#' Hence, it is recommended to check for the presence of solutions.
#+ tangle = false

model_no_solution = Model(with_optimizer(GLPK.Optimizer))
@variable(model_no_solution, 0 <= x <= 1)
@variable(model_no_solution, 0 <= y <= 1)
@constraint(model_no_solution, x + y >= 3)
@objective(model_no_solution, Max, x + 2y)

optimize!(model_no_solution)

if termination_status(model_no_solution) == MOI.OPTIMAL
    optimal_solution = value(x)
    optimal_objective = objective_value(model_no_solution)
elseif termination_status(model_no_solution) == MOI.TIME_LIMIT && has_values(model_no_solution)
    suboptimal_solution = value(x)
    suboptimal_objective = objective_value(model_no_solution)
else
    error("The model was not solved correctly.")
end


text:
using ArrayLayouts, Test

@testset "RangeCumsum" begin
    for r in (RangeCumsum(Base.OneTo(5)), RangeCumsum(2:5), RangeCumsum(2:2:6), RangeCumsum(6:-2:1))
        @test r == cumsum(r.range)
        @test r == r
        @test r .+ 1 == cumsum(r.range) .+ 1
        @test r[Base.OneTo(3)] == r[1:3]
        @test last(r) == r[end]
        @test diff(r) == diff(Vector(r))
    end

    a,b = RangeCumsum(Base.OneTo(5)), RangeCumsum(Base.OneTo(6))
    @test union(a,b) ≡ union(b,a) ≡ b
    @test sort!(a) ≡ a
end


text:
# get optimised hyperparameter
"""
        nlml_stheno(parm,x_train,y_train,K::Kernel)

calculates the negative log marginal likelihood for given parameters and kernel
"""
function nlml_stheno(parm,x_train,y_train,K::Kernel)
        @assert length(parm) == 2
        α,σ²= exp.(parm).+1e-6
        f = α*GP(K, GPC())
        return -logpdf(f(x_train, σ²), y_train)
end

"""
        GP_optimised(parm,constructs_train,activity_train,Mykernel::Kernel)

Optimise the hyperparameters of the GP with the given kernel.
Then calculated the posterior distribution given this optimised hyperparameters and data.
Returns Stheno object
"""

function gp_optimised(constructs_train,activity_train,Mykernel::Kernel; θ₀= zeros(2))
        results = Optim.optimize(parm -> nlml_stheno(parm,constructs_train,activity_train,Mykernel), θ₀, NelderMead())
        #get optimal hyperparameters
        α_opt,σ²_opt = exp.(Optim.minimizer(results)).+10^-6
        #set Gaussian procces in Stheno framework
        fGP = α_opt*GP(Mykernel, GPC())
        #calculate f_postirior based on training data and the prior f
        return  GP_optimised(fGP|(fGP(constructs_train,σ²_opt) ← activity_train),α_opt,σ²_opt)
end

struct GP_optimised{T}
        GP_model::T
        α_opt::Float64
        σ²_opt::Float64
end


text:
@testset "CLEARSWI compile tests" begin

path = tempname()
compile_clearswi(path; audit=true)

@test !isempty(readdir(path))

phasefile = joinpath(pwd(), "data", "small", "Phase.nii")
magfile = joinpath(pwd(), "data", "small", "Mag.nii")

function test_clearswi(args)
    file = tempname()
    args = [args..., "-o", file]
    name = "clearswi" * (Sys.iswindows() ? ".exe" : "")
    clearswifile = joinpath(path, "bin", name)
    @test isfile(clearswifile)
    cmd = `$clearswifile $args`
    @test success(run(cmd))
end

args = ["-p", phasefile, "-m", magfile]
test_clearswi(args)
end


text:
module MittagLefflerFunctions

import SpecialFunctions

Γ = SpecialFunctions.gamma

"""
    rΓ(a)

Returns the reciprocal Gamma function `1/Γ(a)` for any real `a`.
"""
function rΓ(a::T) where T <: AbstractFloat
    if a > 0
        return one(T) / Γ(a)
    else
        return sinpi(a) * Γ(1-a) / π
    end
end

include("submodules/Pade.jl")
include("submodules/Quadrature.jl")
include("submodules/MLF.jl")

using .Pade
using .Quadrature
using .MLF

export MLPade
export MLQuad
export mlf

end # module