function exportToPly(msh::Mesh, fn::String)
    vts = msh.vertices
    fcs = msh.faces
    nV = size(vts,1)
    nF = size(fcs,1)

    str = open(fn,"w")

    # write the header
    write(str,"ply\n")
    write(str,"format binary_little_endian 1.0\n")
    write(str,"element vertex $nV\n")
    write(str,"property float x\nproperty float y\nproperty float z\n")
    write(str,"element face $nF\n")
    write(str,"property list uchar int vertex_index\n")
    write(str,"end_header\n")

    # write the data
    for i = 1:nV
        v = vts[i]
        write(str,float32(v.e1))
        write(str,float32(v.e2))
        write(str,float32(v.e3))
    end

   for i = 1:nF
        f = fcs[i]
        write(str,uint8(3))
        write(str,int32(f.v1-1))
        write(str,int32(f.v2-1))
        write(str,int32(f.v3-1))
    end
    close(str)
end
export exportToPly

function exportToStl(msh::Mesh, fn::String)
    vts = msh.vertices
    fcs = msh.faces
    nV = size(vts,1)
    nF = size(fcs,1)

    str = open(fn,"w")

    # write the header
    write(str,"solid vcg\n")

    # write the data
    for i = 1:nF
        f = fcs[i]
        n = [0,0,0] # TODO: properly compute normal(f)
        txt = @sprintf "  facet normal %e %e %e\n" n[1] n[2] n[3]
        write(str,txt)
        write(str,"    outer loop\n")
        v = vts[f.v1]
        txt = @sprintf "      vertex  %e %e %e\n" v[1] v[2] v[3]
        write(str,txt)

        v = vts[f.v2]
        txt = @sprintf "      vertex  %e %e %e\n" v[1] v[2] v[3]
        write(str,txt)

        v = vts[f.v3]
        txt = @sprintf "      vertex  %e %e %e\n" v[1] v[2] v[3]
        write(str,txt)

        write(str,"    endloop\n")
        write(str,"  endfacet\n")
    end

    write(str,"endsolid vcg\n")
    close(str)
end
export exportToStl

# | Read a .2dm (SMS Aquaveo) mesh-file and construct a @Mesh@
function import2dm(file::String)
    parseNode(w::Array{String}) = Vertex(float64(w[3]), float64(w[4]), float64(w[5]))
    parseTriangle(w::Array{String}) = Face(int64(w[3]), int64(w[4]), int64(w[5]))
    # Qudrilateral faces are split up into triangles
    function parseQuad(w::Array{String})
        w[7] = w[3]                     # making a circle
        Face[Face(int64(w[i]), int64(w[i+1]), int64(w[i+2])) for i = [3,5]]
    end
    con = open(file, "r")
    nd =  Array(Vertex, 0)
    ele = Array(Face,0)
    for line = readlines(con)
        line = chomp(line)
        w = split(line)
        if w[1] == "ND"
            push!(nd, parseNode(w))
        elseif w[1] == "E3T"
            push!(ele,parseTriangle(w))
        elseif w[1] == "E4Q"
            append!(ele, parseQuad(w))
        else
            continue
        end
    end
    close(con)
    Mesh(nd,ele)
end
export import2dm

# | Write @Mesh@ to an IOStream
function exportTo2dm(f::IO,m::Mesh)
    function renderVertex(i::Int,v::Vertex)
        "ND $i $(v.x) $(v.y) $(v.z)\n"
    end
    function renderFace(i::Int, f::Face)
        "E3T $i $(f.v1) $(f.v2) $(f.v3) 0\n"
    end
    for i = 1:length(m.faces)
        write(f, renderFace(i, m.faces[i]))
    end
    for i = 1:length(m.vertices)
        write(f, renderVertex(i, m.vertices[i]))
    end
    nothing
end

# | Write a @Mesh@ to file in SMS-.2dm-file-format
function exportTo2dm(f::String,m::Mesh)
    con = open(f, "w")
    exportTo2dm(con, m)
    close(con)
    nothing
end
export exportTo2dm


"""
    open-loop policy
"""
mutable struct OpenLoop{T} <: Policy{T}
    u::Vector{Vector{T}} # nominal controls
    idx::Int
    cnt::Int
    N_sample::Int
end

function open_loop_policy(u; N_sample = 1)
    OpenLoop(u, 0, N_sample, N_sample)
end

function policy(p::OpenLoop, traj, t)
    # reset
    if t == 1
        p.idx = 0
        p.cnt = p.N_sample
    end

    if p.cnt == p.N_sample
        p.idx += 1
        p.cnt = 0
    end

    p.cnt += 1

    return p.u[p.idx] ./ p.N_sample
end


module Lora

using Base.LinAlg.BLAS
using Distributions
using StatsBase
using ReverseDiffSource

import Base:
  show,
  run,
  select,
  mean,
  var,
  std
import Distributions:
  Bernoulli,
  Beta,
  Binomial,
  Cauchy,
  Exponential,
  Gamma,
  Laplace,
  LogNormal,
  Normal,
  Poisson,
  TDist,
  Uniform,
  Weibull,
  logpdf,
  logcdf,
  logccdf

export
  ### types
  MCLikelihood,
  MCChain,
  ARS,
  SliceSampler,
  MH,
  RAM,
  HMC,
  MALA,
  SMMALA,
  SerialMC,
  VanillaMCTuner,
  EmpiricalMCTuner,
  MCJob,
  PlainMCJob,
  TaskMCJob,
  ### functions
  model,
  run,
  resume,
  select,
  mean,
  mcvar,
  mcse,
  ess,
  actime,
  acceptance,
  describe,
  linearzv,
  quadraticzv

include("api/api.jl")
include("api/samples.jl")
include("api/states.jl")
include("api/chains.jl")
include("parsers/expr_funcs.jl")
include("parsers/modelparser.jl")
include("parsers/definitions/DistributionsExtensions.jl")
include("parsers/definitions/AccumulatorDerivRules.jl")
include("parsers/definitions/MCMCDerivRules.jl")
include("parsers/expr_funcs.jl")
include("models/MCLikelihood.jl")
include("models/models.jl")
include("samplers/ARS.jl")
include("samplers/SliceSampler.jl")
include("samplers/MH.jl")
include("samplers/RAM.jl")
# include("samplers/IMH.jl")
include("samplers/HMC.jl")
# include("samplers/HMCDA.jl")
# include("samplers/NUTS.jl")
include("samplers/MALA.jl")
include("samplers/SMMALA.jl")
# include("samplers/RMHMC.jl")
# include("samplers/PMALA.jl")
include("runners/SerialMC.jl")
# include("runners/SerialTempMC.jl")
# include("runners/SeqMC.jl")
include("tuners/VanillaMCTuner.jl")
include("tuners/EmpiricalMCTuner.jl")
include("jobs/PlainMCJob.jl")
include("jobs/TaskMCJob.jl")
include("jobs/jobs.jl")
include("stats/mean.jl")
include("stats/var.jl")
include("stats/zv.jl")
include("diagnostics/ess.jl")
include("diagnostics/actime.jl")
include("diagnostics/summary.jl")
# Output management: filter, merge, extract, read and write chains, convert btwn chains, arrays and dataframes
# include("output/filter.jl")
# include("output/merge.jl")
include("ui/minimal.jl")

end


using TensorBoardLogger, ValueHistories, DelimitedFiles

function load_ho_values(path)
    (data, header) = readdlm(path, ','; header=true)
end

function loaddata(path; kwargs...)
    hist = MVHistory()
    TensorBoardLogger.map_summaries(path; kwargs...) do tag, step, val
        push!(hist, Symbol(tag), step, val)
    end
    hist
end

function getvalues(data, name; smoothing_window=100, sample_range)
    values = data[name]
    smoothed = smooth(values.values, smoothing_window)
    return smoothed[sample_range]
end

function gettime(data)
    data["training/reward"].iterations / (24 * 3600)
end

function smooth(x, window)
    y = similar(x)
    total = 0
    for i in eachindex(x)
        total += x[i]
        if i > window
            total -= x[i-window]
            y[i] = total / window
        else
            y[i] = total / i
        end
    end
    y
end

function read_all(exp_path)
    dict = Dict{String,String}()
    for (root, dirs, files) in walkdir(exp_path)
        for d in dirs
            if d == "tb_log"
                dict[splitpath(root)[end-1]] = joinpath(root, d)
            end
        end
    end
    dict
end


#=
nothing fancy - only used internally (and therefore cryptic) - stores some partially contracted things
seperates out this bit of logic from effective_excitation_hamiltonian (now more readable)
can also - potentially - partially reuse this in other algorithms
=#
struct QPEnv{A,B} <: Cache
    lBs::PeriodicArray{A,1}
    rBs::PeriodicArray{A,1}

    lenvs::B
    renvs::B
end

function environments(exci::InfiniteQP,ham::MPOHamiltonian,lenvs=environments(exci.left_gs,ham),renvs=exci.trivial ? lenvs : environments(exci.right_gs,ham))
    ids = collect(Iterators.filter(x->isid(ham,x),2:ham.odim-1));

    #build lBs(c)
    lB_cur = [ TensorMap(zeros,eltype(exci),
                    virtualspace(exci.left_gs,0)*ham.domspaces[1,k]',
                    space(exci[1],3)'*virtualspace(exci.left_gs,0)) for k in 1:ham.odim]
    lBs = typeof(lB_cur)[]

    for pos = 1:length(exci)
        lB_cur = exci_transfer_left(lB_cur,ham[pos],exci.right_gs.AR[pos],exci.left_gs.AL[pos])*exp(conj(1im*exci.momentum))
        lB_cur += exci_transfer_left(leftenv(lenvs,pos,exci.left_gs),ham[pos],exci[pos],exci.left_gs.AL[pos])*exp(conj(1im*exci.momentum))

        exci.trivial && for i in ids
            @tensor lB_cur[i][-1,-2,-3,-4] -= lB_cur[i][1,-2,-3,2]*r_RL(exci.left_gs,pos)[2,1]*l_RL(exci.left_gs,pos+1)[-1,-4]
        end

        push!(lBs,lB_cur)
    end

    #build rBs(c)
    rB_cur = [ TensorMap(zeros,eltype(exci),
                    virtualspace(exci.right_gs,length(exci))*space(exci[1],3),
                    ham.imspaces[length(exci),k]*virtualspace(exci.right_gs,length(exci))) for k in 1:ham.odim]
    rBs = typeof(rB_cur)[]

    for pos=length(exci):-1:1
        rB_cur = exci_transfer_right(rB_cur,ham[pos],exci.left_gs.AL[pos],exci.right_gs.AR[pos])*exp(1im*exci.momentum)
        rB_cur += exci_transfer_right(rightenv(renvs,pos,exci.right_gs),ham[pos],exci[pos],exci.right_gs.AR[pos])*exp(1im*exci.momentum)

        exci.trivial && for i in ids
            @tensor rB_cur[i][-1,-2,-3,-4] -= rB_cur[i][1,-2,-3,2]*l_LR(exci.left_gs,pos)[2,1]*r_LR(exci.left_gs,pos-1)[-1,-4]
        end

        push!(rBs,rB_cur)
    end
    rBs = reverse(rBs)

    lBE::typeof(rB_cur) = left_excitation_transfer_system(lB_cur,ham,exci)
    rBE::typeof(rB_cur) = right_excitation_transfer_system(rB_cur,ham,exci)

    lBs[end] = lBE;

    for i=1:length(exci)-1
        lBE = exci_transfer_left(lBE,ham[i],exci.right_gs.AR[i],exci.left_gs.AL[i])*exp(conj(1im*exci.momentum))

        exci.trivial && for k in ids
            @tensor lBE[k][-1,-2,-3,-4] -= lBE[k][1,-2,-3,2]*r_RL(exci.left_gs,i)[2,1]*l_RL(exci.left_gs,i+1)[-1,-4]
        end

        lBs[i] += lBE;
    end

    rBs[1] = rBE;

    for i=length(exci):-1:2
        rBE = exci_transfer_right(rBE,ham[i],exci.left_gs.AL[i],exci.right_gs.AR[i])*exp(1im*exci.momentum)

        exci.trivial && for k in ids
            @tensor rBE[k][-1,-2,-3,-4]-=rBE[k][1,-2,-3,2]*l_LR(exci.left_gs,i)[2,1]*r_LR(exci.left_gs,i-1)[-1,-4]
        end

        rBs[i] += rBE
    end

    return QPEnv(PeriodicArray(lBs),PeriodicArray(rBs),lenvs,renvs)
end

function environments(exci::FiniteQP,ham::MPOHamiltonian,lenvs=environments(exci.left_gs,ham),renvs=exci.trivial ? lenvs : environments(exci.right_gs,ham))
    #construct lBE
    lB_cur = [ TensorMap(zeros,eltype(exci),
                    virtualspace(exci.left_gs,0)*ham.domspaces[1,k]',
                    space(exci[1],3)'*virtualspace(exci.left_gs,0)) for k in 1:ham.odim]
    lBs = typeof(lB_cur)[]
    for pos = 1:length(exci)
        lB_cur = exci_transfer_left(lB_cur,ham[pos],exci.right_gs.AR[pos],exci.left_gs.AL[pos])
        lB_cur += exci_transfer_left(leftenv(lenvs,pos,exci.left_gs),ham[pos],exci[pos],exci.left_gs.AL[pos])
        push!(lBs,lB_cur)
    end

    #build rBs(c)
    rB_cur = [ TensorMap(zeros,eltype(exci),
                    virtualspace(exci.right_gs,length(exci))*space(exci[1],3),
                    ham.imspaces[length(exci),k]*virtualspace(exci.right_gs,length(exci))) for k in 1:ham.odim]
    rBs = typeof(rB_cur)[]
    for pos=length(exci):-1:1
        rB_cur = exci_transfer_right(rB_cur,ham[pos],exci.left_gs.AL[pos],exci.right_gs.AR[pos])
        rB_cur += exci_transfer_right(rightenv(renvs,pos,exci.right_gs),ham[pos],exci[pos],exci.right_gs.AR[pos])
        push!(rBs,rB_cur)
    end
    rBs=reverse(rBs)

    return QPEnv(PeriodicArray(lBs),PeriodicArray(rBs),lenvs,renvs)
end


#=
This file is auto-generated. Do not edit.
=#
"""
    mutable struct HydroPumpedStorage <: HydroGen
        name::String
        available::Bool
        bus::Bus
        active_power::Float64
        reactive_power::Float64
        rating::Float64
        base_power::Float64
        prime_mover::PrimeMovers
        active_power_limits::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        reactive_power_limits::Union{Nothing, NamedTuple{(:min, :max), Tuple{Float64, Float64}}}
        ramp_limits::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}
        time_limits::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}
        rating_pump::Float64
        active_power_limits_pump::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        reactive_power_limits_pump::Union{Nothing, NamedTuple{(:min, :max), Tuple{Float64, Float64}}}
        ramp_limits_pump::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}
        time_limits_pump::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}
        storage_capacity::NamedTuple{(:up, :down), Tuple{Float64, Float64}}
        inflow::Float64
        outflow::Float64
        initial_storage::NamedTuple{(:up, :down), Tuple{Float64, Float64}}
        storage_target::NamedTuple{(:up, :down), Tuple{Float64, Float64}}
        operation_cost::OperationalCost
        pump_efficiency::Float64
        conversion_factor::Float64
        time_at_status::Float64
        services::Vector{Service}
        dynamic_injector::Union{Nothing, DynamicInjection}
        ext::Dict{String, Any}
        time_series_container::InfrastructureSystems.TimeSeriesContainer
        internal::InfrastructureSystemsInternal
    end

# Arguments
- `name::String`
- `available::Bool`
- `bus::Bus`
- `active_power::Float64`
- `reactive_power::Float64`
- `rating::Float64`: Thermal limited MVA Power Output of the unit. <= Capacity, validation range: `(0, nothing)`, action if invalid: `error`
- `base_power::Float64`: Base power of the unit in MVA, validation range: `(0, nothing)`, action if invalid: `warn`
- `prime_mover::PrimeMovers`: Prime mover technology according to EIA 923
- `active_power_limits::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`, validation range: `(0, nothing)`, action if invalid: `warn`
- `reactive_power_limits::Union{Nothing, NamedTuple{(:min, :max), Tuple{Float64, Float64}}}`, action if invalid: `warn`
- `ramp_limits::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}`: ramp up and ramp down limits in MW (in component base per unit) per minute, validation range: `(0, nothing)`, action if invalid: `error`
- `time_limits::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}`: Minimum up and Minimum down time limits in hours, validation range: `(0, nothing)`, action if invalid: `error`
- `rating_pump::Float64`: Thermal limited MVA Power Withdrawl of the pump. <= Capacity, validation range: `(0, nothing)`, action if invalid: `error`
- `active_power_limits_pump::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`
- `reactive_power_limits_pump::Union{Nothing, NamedTuple{(:min, :max), Tuple{Float64, Float64}}}`, action if invalid: `warn`
- `ramp_limits_pump::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}`: ramp up and ramp down limits in MW (in component base per unit) per minute of pump, validation range: `(0, nothing)`, action if invalid: `error`
- `time_limits_pump::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}`: Minimum up and Minimum down time limits of pump in hours, validation range: `(0, nothing)`, action if invalid: `error`
- `storage_capacity::NamedTuple{(:up, :down), Tuple{Float64, Float64}}`: Maximum storage capacity in the upper and lower reservoirs (units can be p.u-hr or m^3)., validation range: `(0, nothing)`, action if invalid: `error`
- `inflow::Float64`: Baseline inflow into the upper reservoir (units can be p.u. or m^3/hr), validation range: `(0, nothing)`, action if invalid: `error`
- `outflow::Float64`: Baseline outflow from the lower reservoir (units can be p.u. or m^3/hr), validation range: `(0, nothing)`, action if invalid: `error`
- `initial_storage::NamedTuple{(:up, :down), Tuple{Float64, Float64}}`: Initial storage capacity in the upper and lower reservoir (units can be p.u-hr or m^3)., validation range: `(0, nothing)`, action if invalid: `error`
- `storage_target::NamedTuple{(:up, :down), Tuple{Float64, Float64}}`: Storage target of upper reservoir at the end of simulation as ratio of storage capacity.
- `operation_cost::OperationalCost`: Operation Cost of Generation [`OperationalCost`](@ref)
- `pump_efficiency::Float64`: Efficiency of pump, validation range: `(0, 1)`, action if invalid: `warn`
- `conversion_factor::Float64`: Conversion factor from flow/volume to energy: m^3 -> p.u-hr.
- `time_at_status::Float64`
- `services::Vector{Service}`: Services that this device contributes to
- `dynamic_injector::Union{Nothing, DynamicInjection}`: corresponding dynamic injection device
- `ext::Dict{String, Any}`
- `time_series_container::InfrastructureSystems.TimeSeriesContainer`: internal time_series storage
- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify
"""
mutable struct HydroPumpedStorage <: HydroGen
    name::String
    available::Bool
    bus::Bus
    active_power::Float64
    reactive_power::Float64
    "Thermal limited MVA Power Output of the unit. <= Capacity"
    rating::Float64
    "Base power of the unit in MVA"
    base_power::Float64
    "Prime mover technology according to EIA 923"
    prime_mover::PrimeMovers
    active_power_limits::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    reactive_power_limits::Union{Nothing, NamedTuple{(:min, :max), Tuple{Float64, Float64}}}
    "ramp up and ramp down limits in MW (in component base per unit) per minute"
    ramp_limits::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}
    "Minimum up and Minimum down time limits in hours"
    time_limits::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}
    "Thermal limited MVA Power Withdrawl of the pump. <= Capacity"
    rating_pump::Float64
    active_power_limits_pump::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    reactive_power_limits_pump::Union{Nothing, NamedTuple{(:min, :max), Tuple{Float64, Float64}}}
    "ramp up and ramp down limits in MW (in component base per unit) per minute of pump"
    ramp_limits_pump::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}
    "Minimum up and Minimum down time limits of pump in hours"
    time_limits_pump::Union{Nothing, NamedTuple{(:up, :down), Tuple{Float64, Float64}}}
    "Maximum storage capacity in the upper and lower reservoirs (units can be p.u-hr or m^3)."
    storage_capacity::NamedTuple{(:up, :down), Tuple{Float64, Float64}}
    "Baseline inflow into the upper reservoir (units can be p.u. or m^3/hr)"
    inflow::Float64
    "Baseline outflow from the lower reservoir (units can be p.u. or m^3/hr)"
    outflow::Float64
    "Initial storage capacity in the upper and lower reservoir (units can be p.u-hr or m^3)."
    initial_storage::NamedTuple{(:up, :down), Tuple{Float64, Float64}}
    "Storage target of upper reservoir at the end of simulation as ratio of storage capacity."
    storage_target::NamedTuple{(:up, :down), Tuple{Float64, Float64}}
    "Operation Cost of Generation [`OperationalCost`](@ref)"
    operation_cost::OperationalCost
    "Efficiency of pump"
    pump_efficiency::Float64
    "Conversion factor from flow/volume to energy: m^3 -> p.u-hr."
    conversion_factor::Float64
    time_at_status::Float64
    "Services that this device contributes to"
    services::Vector{Service}
    "corresponding dynamic injection device"
    dynamic_injector::Union{Nothing, DynamicInjection}
    ext::Dict{String, Any}
    "internal time_series storage"
    time_series_container::InfrastructureSystems.TimeSeriesContainer
    "power system internal reference, do not modify"
    internal::InfrastructureSystemsInternal
end

function HydroPumpedStorage(name, available, bus, active_power, reactive_power, rating, base_power, prime_mover, active_power_limits, reactive_power_limits, ramp_limits, time_limits, rating_pump, active_power_limits_pump, reactive_power_limits_pump, ramp_limits_pump, time_limits_pump, storage_capacity, inflow, outflow, initial_storage, storage_target=(up=1.0, down=1.0), operation_cost=TwoPartCost(0.0, 0.0), pump_efficiency=1.0, conversion_factor=1.0, time_at_status=INFINITE_TIME, services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), )
    HydroPumpedStorage(name, available, bus, active_power, reactive_power, rating, base_power, prime_mover, active_power_limits, reactive_power_limits, ramp_limits, time_limits, rating_pump, active_power_limits_pump, reactive_power_limits_pump, ramp_limits_pump, time_limits_pump, storage_capacity, inflow, outflow, initial_storage, storage_target, operation_cost, pump_efficiency, conversion_factor, time_at_status, services, dynamic_injector, ext, time_series_container, InfrastructureSystemsInternal(), )
end

function HydroPumpedStorage(; name, available, bus, active_power, reactive_power, rating, base_power, prime_mover, active_power_limits, reactive_power_limits, ramp_limits, time_limits, rating_pump, active_power_limits_pump, reactive_power_limits_pump, ramp_limits_pump, time_limits_pump, storage_capacity, inflow, outflow, initial_storage, storage_target=(up=1.0, down=1.0), operation_cost=TwoPartCost(0.0, 0.0), pump_efficiency=1.0, conversion_factor=1.0, time_at_status=INFINITE_TIME, services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), internal=InfrastructureSystemsInternal(), )
    HydroPumpedStorage(name, available, bus, active_power, reactive_power, rating, base_power, prime_mover, active_power_limits, reactive_power_limits, ramp_limits, time_limits, rating_pump, active_power_limits_pump, reactive_power_limits_pump, ramp_limits_pump, time_limits_pump, storage_capacity, inflow, outflow, initial_storage, storage_target, operation_cost, pump_efficiency, conversion_factor, time_at_status, services, dynamic_injector, ext, time_series_container, internal, )
end

# Constructor for demo purposes; non-functional.
function HydroPumpedStorage(::Nothing)
    HydroPumpedStorage(;
        name="init",
        available=false,
        bus=Bus(nothing),
        active_power=0.0,
        reactive_power=0.0,
        rating=0.0,
        base_power=0.0,
        prime_mover=PrimeMovers.HY,
        active_power_limits=(min=0.0, max=0.0),
        reactive_power_limits=nothing,
        ramp_limits=nothing,
        time_limits=nothing,
        rating_pump=0.0,
        active_power_limits_pump=(min=0.0, max=0.0),
        reactive_power_limits_pump=nothing,
        ramp_limits_pump=nothing,
        time_limits_pump=nothing,
        storage_capacity=(up=0.0, down=0.0),
        inflow=0.0,
        outflow=0.0,
        initial_storage=(up=0.0, down=0.0),
        storage_target=(up=0.0, down=0.0),
        operation_cost=TwoPartCost(nothing),
        pump_efficiency=0.0,
        conversion_factor=0.0,
        time_at_status=INFINITE_TIME,
        services=Device[],
        dynamic_injector=nothing,
        ext=Dict{String, Any}(),
        time_series_container=InfrastructureSystems.TimeSeriesContainer(),
    )
end

InfrastructureSystems.get_name(value::HydroPumpedStorage) = value.name
"""Get [`HydroPumpedStorage`](@ref) `available`."""
get_available(value::HydroPumpedStorage) = value.available
"""Get [`HydroPumpedStorage`](@ref) `bus`."""
get_bus(value::HydroPumpedStorage) = value.bus
"""Get [`HydroPumpedStorage`](@ref) `active_power`."""
get_active_power(value::HydroPumpedStorage) = get_value(value, value.active_power)
"""Get [`HydroPumpedStorage`](@ref) `reactive_power`."""
get_reactive_power(value::HydroPumpedStorage) = get_value(value, value.reactive_power)
"""Get [`HydroPumpedStorage`](@ref) `rating`."""
get_rating(value::HydroPumpedStorage) = get_value(value, value.rating)
"""Get [`HydroPumpedStorage`](@ref) `base_power`."""
get_base_power(value::HydroPumpedStorage) = value.base_power
"""Get [`HydroPumpedStorage`](@ref) `prime_mover`."""
get_prime_mover(value::HydroPumpedStorage) = value.prime_mover
"""Get [`HydroPumpedStorage`](@ref) `active_power_limits`."""
get_active_power_limits(value::HydroPumpedStorage) = get_value(value, value.active_power_limits)
"""Get [`HydroPumpedStorage`](@ref) `reactive_power_limits`."""
get_reactive_power_limits(value::HydroPumpedStorage) = get_value(value, value.reactive_power_limits)
"""Get [`HydroPumpedStorage`](@ref) `ramp_limits`."""
get_ramp_limits(value::HydroPumpedStorage) = get_value(value, value.ramp_limits)
"""Get [`HydroPumpedStorage`](@ref) `time_limits`."""
get_time_limits(value::HydroPumpedStorage) = value.time_limits
"""Get [`HydroPumpedStorage`](@ref) `rating_pump`."""
get_rating_pump(value::HydroPumpedStorage) = get_value(value, value.rating_pump)
"""Get [`HydroPumpedStorage`](@ref) `active_power_limits_pump`."""
get_active_power_limits_pump(value::HydroPumpedStorage) = get_value(value, value.active_power_limits_pump)
"""Get [`HydroPumpedStorage`](@ref) `reactive_power_limits_pump`."""
get_reactive_power_limits_pump(value::HydroPumpedStorage) = get_value(value, value.reactive_power_limits_pump)
"""Get [`HydroPumpedStorage`](@ref) `ramp_limits_pump`."""
get_ramp_limits_pump(value::HydroPumpedStorage) = get_value(value, value.ramp_limits_pump)
"""Get [`HydroPumpedStorage`](@ref) `time_limits_pump`."""
get_time_limits_pump(value::HydroPumpedStorage) = value.time_limits_pump
"""Get [`HydroPumpedStorage`](@ref) `storage_capacity`."""
get_storage_capacity(value::HydroPumpedStorage) = get_value(value, value.storage_capacity)
"""Get [`HydroPumpedStorage`](@ref) `inflow`."""
get_inflow(value::HydroPumpedStorage) = get_value(value, value.inflow)
"""Get [`HydroPumpedStorage`](@ref) `outflow`."""
get_outflow(value::HydroPumpedStorage) = value.outflow
"""Get [`HydroPumpedStorage`](@ref) `initial_storage`."""
get_initial_storage(value::HydroPumpedStorage) = get_value(value, value.initial_storage)
"""Get [`HydroPumpedStorage`](@ref) `storage_target`."""
get_storage_target(value::HydroPumpedStorage) = value.storage_target
"""Get [`HydroPumpedStorage`](@ref) `operation_cost`."""
get_operation_cost(value::HydroPumpedStorage) = value.operation_cost
"""Get [`HydroPumpedStorage`](@ref) `pump_efficiency`."""
get_pump_efficiency(value::HydroPumpedStorage) = value.pump_efficiency
"""Get [`HydroPumpedStorage`](@ref) `conversion_factor`."""
get_conversion_factor(value::HydroPumpedStorage) = value.conversion_factor
"""Get [`HydroPumpedStorage`](@ref) `time_at_status`."""
get_time_at_status(value::HydroPumpedStorage) = value.time_at_status
"""Get [`HydroPumpedStorage`](@ref) `services`."""
get_services(value::HydroPumpedStorage) = value.services
"""Get [`HydroPumpedStorage`](@ref) `dynamic_injector`."""
get_dynamic_injector(value::HydroPumpedStorage) = value.dynamic_injector
"""Get [`HydroPumpedStorage`](@ref) `ext`."""
get_ext(value::HydroPumpedStorage) = value.ext

InfrastructureSystems.get_time_series_container(value::HydroPumpedStorage) = value.time_series_container
"""Get [`HydroPumpedStorage`](@ref) `internal`."""
get_internal(value::HydroPumpedStorage) = value.internal

InfrastructureSystems.set_name!(value::HydroPumpedStorage, val) = value.name = val
"""Set [`HydroPumpedStorage`](@ref) `available`."""
set_available!(value::HydroPumpedStorage, val) = value.available = val
"""Set [`HydroPumpedStorage`](@ref) `bus`."""
set_bus!(value::HydroPumpedStorage, val) = value.bus = val
"""Set [`HydroPumpedStorage`](@ref) `active_power`."""
set_active_power!(value::HydroPumpedStorage, val) = value.active_power = val
"""Set [`HydroPumpedStorage`](@ref) `reactive_power`."""
set_reactive_power!(value::HydroPumpedStorage, val) = value.reactive_power = val
"""Set [`HydroPumpedStorage`](@ref) `rating`."""
set_rating!(value::HydroPumpedStorage, val) = value.rating = val
"""Set [`HydroPumpedStorage`](@ref) `base_power`."""
set_base_power!(value::HydroPumpedStorage, val) = value.base_power = val
"""Set [`HydroPumpedStorage`](@ref) `prime_mover`."""
set_prime_mover!(value::HydroPumpedStorage, val) = value.prime_mover = val
"""Set [`HydroPumpedStorage`](@ref) `active_power_limits`."""
set_active_power_limits!(value::HydroPumpedStorage, val) = value.active_power_limits = val
"""Set [`HydroPumpedStorage`](@ref) `reactive_power_limits`."""
set_reactive_power_limits!(value::HydroPumpedStorage, val) = value.reactive_power_limits = val
"""Set [`HydroPumpedStorage`](@ref) `ramp_limits`."""
set_ramp_limits!(value::HydroPumpedStorage, val) = value.ramp_limits = val
"""Set [`HydroPumpedStorage`](@ref) `time_limits`."""
set_time_limits!(value::HydroPumpedStorage, val) = value.time_limits = val
"""Set [`HydroPumpedStorage`](@ref) `rating_pump`."""
set_rating_pump!(value::HydroPumpedStorage, val) = value.rating_pump = val
"""Set [`HydroPumpedStorage`](@ref) `active_power_limits_pump`."""
set_active_power_limits_pump!(value::HydroPumpedStorage, val) = value.active_power_limits_pump = val
"""Set [`HydroPumpedStorage`](@ref) `reactive_power_limits_pump`."""
set_reactive_power_limits_pump!(value::HydroPumpedStorage, val) = value.reactive_power_limits_pump = val
"""Set [`HydroPumpedStorage`](@ref) `ramp_limits_pump`."""
set_ramp_limits_pump!(value::HydroPumpedStorage, val) = value.ramp_limits_pump = val
"""Set [`HydroPumpedStorage`](@ref) `time_limits_pump`."""
set_time_limits_pump!(value::HydroPumpedStorage, val) = value.time_limits_pump = val
"""Set [`HydroPumpedStorage`](@ref) `storage_capacity`."""
set_storage_capacity!(value::HydroPumpedStorage, val) = value.storage_capacity = val
"""Set [`HydroPumpedStorage`](@ref) `inflow`."""
set_inflow!(value::HydroPumpedStorage, val) = value.inflow = val
"""Set [`HydroPumpedStorage`](@ref) `outflow`."""
set_outflow!(value::HydroPumpedStorage, val) = value.outflow = val
"""Set [`HydroPumpedStorage`](@ref) `initial_storage`."""
set_initial_storage!(value::HydroPumpedStorage, val) = value.initial_storage = val
"""Set [`HydroPumpedStorage`](@ref) `storage_target`."""
set_storage_target!(value::HydroPumpedStorage, val) = value.storage_target = val
"""Set [`HydroPumpedStorage`](@ref) `operation_cost`."""
set_operation_cost!(value::HydroPumpedStorage, val) = value.operation_cost = val
"""Set [`HydroPumpedStorage`](@ref) `pump_efficiency`."""
set_pump_efficiency!(value::HydroPumpedStorage, val) = value.pump_efficiency = val
"""Set [`HydroPumpedStorage`](@ref) `conversion_factor`."""
set_conversion_factor!(value::HydroPumpedStorage, val) = value.conversion_factor = val
"""Set [`HydroPumpedStorage`](@ref) `time_at_status`."""
set_time_at_status!(value::HydroPumpedStorage, val) = value.time_at_status = val
"""Set [`HydroPumpedStorage`](@ref) `services`."""
set_services!(value::HydroPumpedStorage, val) = value.services = val
"""Set [`HydroPumpedStorage`](@ref) `ext`."""
set_ext!(value::HydroPumpedStorage, val) = value.ext = val

InfrastructureSystems.set_time_series_container!(value::HydroPumpedStorage, val) = value.time_series_container = val


using ReinforcementLearningZoo
using Test
using ReinforcementLearningBase
using ReinforcementLearningCore
using ReinforcementLearningEnvironments
using Flux
using Statistics
using Random
using OpenSpiel
using StableRNGs
import GridWorlds

function get_optimal_kuhn_policy(α = 0.2)
    TabularRandomPolicy(
        table = Dict(
            "0" => [1 - α, α],
            "0pb" => [1.0, 0.0],
            "1" => [1.0, 0.0],
            "1pb" => [2.0 / 3.0 - α, 1.0 / 3.0 + α],
            "2" => [1 - 3 * α, 3 * α],
            "2pb" => [0.0, 1.0],
            "0p" => [2.0 / 3.0, 1.0 / 3.0],
            "0b" => [1.0, 0.0],
            "1p" => [1.0, 0.0],
            "1b" => [2.0 / 3.0, 1.0 / 3.0],
            "2p" => [0.0, 1.0],
            "2b" => [0.0, 1.0],
        ),
    )
end

@testset "ReinforcementLearningZoo.jl" begin

    @testset "training" begin
        mktempdir() do dir
            for method in (:BasicDQN, :BC, :DQN, :PrioritizedDQN, :Rainbow, :IQN, :VPG)
                res = run(
                    Experiment(
                        Val(:JuliaRL),
                        Val(method),
                        Val(:CartPole),
                        nothing;
                        save_dir = joinpath(dir, "CartPole", string(method)),
                    ),
                )
                @info "stats for $method" avg_reward = mean(res.hook[1].rewards) avg_fps =
                    1 / mean(res.hook[2].times)
            end

            for method in (:BasicDQN, :DQN)
                res = run(
                    Experiment(
                        Val(:JuliaRL),
                        Val(method),
                        Val(:MountainCar),
                        nothing;
                        save_dir = joinpath(dir, "MountainCar", string(method)),
                    ),
                )
                @info "stats for $method" avg_reward = mean(res.hook[1].rewards) avg_fps =
                    1 / mean(res.hook[2].times)
            end

            for method in (:A2C, :A2CGAE, :PPO, :MAC)
                res = run(
                    Experiment(
                        Val(:JuliaRL),
                        Val(method),
                        Val(:CartPole),
                        nothing;
                        save_dir = joinpath(dir, "CartPole", string(method)),
                    ),
                )
                @info "stats for $method" avg_reward =
                    mean(Iterators.flatten(res.hook[1].rewards))
            end

            for method in (:DDPG, :SAC, :TD3)
                res = run(
                    Experiment(
                        Val(:JuliaRL),
                        Val(method),
                        Val(:Pendulum),
                        nothing;
                        save_dir = joinpath(dir, "Pendulum", string(method)),
                    ),
                )
                @info "stats for $method" avg_reward =
                    mean(Iterators.flatten(res.hook[1].rewards))
            end
        end
    end

    @testset "minimax" begin
        e = E`JuliaRL_Minimax_OpenSpiel(tic_tac_toe)`
        run(e)
        @test e.hook[1][] == e.hook[0][] == [0.0]
    end

    @testset "GridWorlds" begin
        mktempdir() do dir
            for method in (:BasicDQN,)
                res = run(
                    Experiment(
                        Val(:JuliaRL),
                        Val(method),
                        Val(:EmptyRoom),
                        nothing;
                        save_dir = joinpath(dir, "EmptyRoom", string(method)),
                    ),
                )
                @info "stats for $method" avg_reward = mean(res.hook[1].rewards) avg_fps =
                    1 / mean(res.hook[2].times)
            end
        end
    end

    @testset "TabularCFR" begin
        e = E`JuliaRL_TabularCFR_OpenSpiel(kuhn_poker)`
        run(e)

        reset!(e.env)
        expected_values = expected_policy_values(e.policy, e.env)
        @test isapprox(expected_values[1], -1 / 18; atol = 0.001)
        @test isapprox(expected_values[2], 1 / 18; atol = 0.001)
    end

    include("cfr/cfr.jl")
end


using .BaseAgent
using Params
using Traffic.Simple
using Channel.Gilbert
using Util

export OptHighestSNR

type OptHighestSNR <: Agent
    s :: AgentState
    status :: Status
    OptHighestSNR(i, P, pos) = new(AgentState(i, P, pos), Initialized)
end

#=
I written my own minumum that returns Inf instead of
throwing an error when there is no suitable channel to
prevent unnecessary exception checking and performance
drop.
=#
function minimum_noise(env :: Environment)
    min_noise = Inf
    for i=1:endof(env.channels)
        if env.traffics[i].traffic
            continue
        end
        min_noise = min(min_noise, env.channels[i].noise)
    end
    min_noise
end

function BaseAgent.act(a :: OptHighestSNR, env, t)
    if a.status == Initialized
        if a.s.B_max - a.s.B_empty == 0
            return idle(a)
        end

        # we've done packet checking
        min_noise = minimum_noise(env)
        if min_noise == Inf
            return idle(a)
        end

        min_noise_channels = filter(i -> ! env.traffics[i].traffic && env.channels[i].noise <= min_noise, 1:Params.n_channel)
        chan = int8(min_noise_channels[rand(1:endof(min_noise_channels))])

        return switch(a, chan)
    elseif a.status == Switched
        return sense(a, env, detect_traffic)
    elseif a.status == Sensed
        if detect_traffic(a.s, env.traffics[a.s.chan], a.s.t_remaining)
            return idle(a)
        else
            P_tx = Params.P_levels[end-1]
            pkgs_to_send = min(a.s.B_max - a.s.B_empty, floor((a.s.t_remaining * capacity(env.channels[a.s.chan], P_tx, a.s.pos)) / Params.pkt_size))

            if pkgs_to_send == 0
                return idle(a)
            end
            return transmit!(a, P_tx, env, pkgs_to_send)
        end
    elseif a.status == Transmitted
        return idle(a)
    end
end


################################################################################
#
#   SQUARE LATTICE
#
################################################################################

# Implementation
# - implementation 1
# - labels <: Any
function getUnitcellSquare(
            unitcell_type  :: Type{U},
            implementation :: Val{1}
        ) :: U where {LS,LB,S<:AbstractSite{LS,2},B<:AbstractBond{LB,2},U<:AbstractUnitcell{S,B}}

    # return a new Unitcell
    return newUnitcell(
        # Type of the unitcell
        U,
        # lattice vectors
        Vector{Float64}[
            Float64[1, 0],
            Float64[0, 1]
        ],
        # sites
        S[
            newSite(S, Float64[0,0], getDefaultLabelN(LS,1))
        ],
        # bonds
        B[
            newBond(B, 1,1, getDefaultLabel(LB), (+1,0)),
            newBond(B, 1,1, getDefaultLabel(LB), (-1,0)),
            newBond(B, 1,1, getDefaultLabel(LB), (0,+1)),
            newBond(B, 1,1, getDefaultLabel(LB), (0,-1))
        ]
    )
end

# Implementation
# - implementation 3
# - labels <: Any
function getUnitcellSquare(
            unitcell_type  :: Type{U},
            implementation :: Val{3}
        ) :: U where {LS,LB,S<:AbstractSite{LS,2},B<:AbstractBond{LB,2},U<:AbstractUnitcell{S,B}}

    # return a new Unitcell
    return newUnitcell(
        # Type of the unitcell
        U,
        # lattice vectors
        Vector{Float64}[
            Float64[1, 0],
            Float64[0, 1]
        ],
        # sites
        S[
            newSite(S, Float64[0.0,0.0], getDefaultLabelA(LS)),
            newSite(S, Float64[0.5,0.0], getDefaultLabelB(LS)),
            newSite(S, Float64[0.5,0.5], getDefaultLabelA(LS)),
            newSite(S, Float64[0.0,0.5], getDefaultLabelB(LS))
        ],
        # bonds
        B[
            newBond(B, 1,2, getDefaultLabelY(LB), (-1,0)),
            newBond(B, 1,4, getDefaultLabelY(LB), (0,-1)),
            newBond(B, 1,2, getDefaultLabelX(LB), (0,0)),
            newBond(B, 1,4, getDefaultLabelX(LB), (0,0)),

            newBond(B, 2,1, getDefaultLabelY(LB), (1,0)),
            newBond(B, 2,3, getDefaultLabelX(LB), (0,0)),
            newBond(B, 2,1, getDefaultLabelX(LB), (0,0)),
            newBond(B, 2,3, getDefaultLabelY(LB), (0,-1)),

            newBond(B, 3,4, getDefaultLabelX(LB), (0,0)),
            newBond(B, 3,2, getDefaultLabelX(LB), (0,0)),
            newBond(B, 3,4, getDefaultLabelY(LB), (1,0)),
            newBond(B, 3,2, getDefaultLabelY(LB), (0,1)),

            newBond(B, 4,3, getDefaultLabelX(LB), (0,0)),
            newBond(B, 4,1, getDefaultLabelY(LB), (0,1)),
            newBond(B, 4,3, getDefaultLabelY(LB), (-1,0)),
            newBond(B, 4,1, getDefaultLabelX(LB), (0,0)),
        ]
    )
end

# Implementation
# - implementation -3 (same as 3 but with pi flux)
# - labels <: Any
function getUnitcellSquare(
            unitcell_type  :: Type{U},
            implementation :: Val{-3}
        ) :: U where {LS,LB,S<:AbstractSite{LS,2},B<:AbstractBond{LB,2},U<:AbstractUnitcell{S,B}}

    # return a new Unitcell
    return newUnitcell(
        # Type of the unitcell
        U,
        # lattice vectors
        Vector{Float64}[
            Float64[1, 0],
            Float64[0, 1]
        ],
        # sites
        S[
            newSite(S, Float64[0.0,0.0], getDefaultLabelA(LS)),
            newSite(S, Float64[0.5,0.0], getDefaultLabelB(LS)),
            newSite(S, Float64[0.5,0.5], getDefaultLabelA(LS)),
            newSite(S, Float64[0.0,0.5], getDefaultLabelB(LS))
        ],
        # bonds
        B[
            newBond(B, 1,2, getDefaultLabelY(LB), (-1,0)),
            newBond(B, 1,4, getDefaultLabelY(LB), (0,-1)),
            newBond(B, 1,2, getDefaultLabelX(LB), (0,0)),
            newBond(B, 1,4, getDefaultLabelX(LB), (0,0)),

            newBond(B, 2,1, getDefaultLabelY(LB), (1,0)),
            newBond(B, 2,3, flipLabel(getDefaultLabelX(LB)), (0,0)),
            newBond(B, 2,1, getDefaultLabelX(LB), (0,0)),
            newBond(B, 2,3, flipLabel(getDefaultLabelY(LB)), (0,-1)),

            newBond(B, 3,4, getDefaultLabelX(LB), (0,0)),
            newBond(B, 3,2, flipLabel(getDefaultLabelX(LB)), (0,0)),
            newBond(B, 3,4, getDefaultLabelY(LB), (1,0)),
            newBond(B, 3,2, flipLabel(getDefaultLabelY(LB)), (0,1)),

            newBond(B, 4,3, getDefaultLabelX(LB), (0,0)),
            newBond(B, 4,1, getDefaultLabelY(LB), (0,1)),
            newBond(B, 4,3, getDefaultLabelY(LB), (-1,0)),
            newBond(B, 4,1, getDefaultLabelX(LB), (0,0)),
        ]
    )
end


using ArcadeLearningEnvironment
using CartesianGeneticProgramming
using IICGP
using UnicodePlots
using ImageCore
using ImageTransformations
using Interpolations
using BenchmarkTools
using TiledIteration
using Images
using ImageMorphology
using LinearAlgebra
using Random

out(plt) = println(IOContext(stdout, :color=>true), plt)

function print_usage()
	out = read(`top -bn1 -p $(getpid())`, String)
	res = split(split(out,  "\n")[end-1])
	println("RES: ", res[6], "   %MEM: ", res[10], "   %CPU: ", res[9])
	parse(Float64, replace(res[10], "," => "."))
end

function fakeplay(n_steps::Int64)
	for t in 1:n_steps
		s = [rand(UInt8, 80, 105)]
		output = IICGP.process(enco, redu, cont, ccfg, s)

		#CartesianGeneticProgramming.process(enco, s)
		#features_flatten = rand(35)
	    #push!(features_flatten, 0:1.0/(10-1):1.0...)
		#CartesianGeneticProgramming.process(cont, features_flatten)
	end
end

function play(game::Game, n_steps::Int64, grayscale::Bool, downscale::Bool)
	IICGP.reset!(game)
	s = get_state_buffer(game, grayscale)
	o = get_observation_buffer(game, grayscale, downscale)
	for t in 1:n_steps
		get_state!(s, game, grayscale)
		get_observation!(o, s, game, grayscale, downscale)
		#s = getScreenRGB(game.ale)
		output = IICGP.process(enco, redu, cont, ccfg, o)
		act(game.ale, game.actions[rand(1:length(game.actions))])
		if game_over(game.ale)
			break
		end
	end
end

seed = 4
Random.seed!(seed)
n_iter = 25000
n_para = 4
n_steps = 10000
rom_name = "space_invaders"
#games = [Game(rom_name, 0) for _ in 1:n_para]
mem_usage = Vector{Float64}()
cfgpath = "/home/opaweynch/.julia/environments/v1.6/dev/IICGP/cfg/eccgp_atari_nsga2.yaml"
mcfg, ecfg, ccfg, redu, bootstrap = IICGP.dualcgp_config(cfgpath, rom_name)
enco = IPCGPInd(ecfg)
cont = CGPInd(ccfg)
grayscale, downscale = true, true

##

for e in 1:n_iter
	#=for i in 1:n_para
		fakeplay(n_steps)
	end=#
	@sync for i in 1:n_para
		Threads.@spawn begin
			fakeplay(n_steps)
			#play(games[i], n_steps, grayscale, downscale)
		end
	end
	mem = print_usage()
	push!(mem_usage, mem)
	out(lineplot(mem_usage, title = "%MEM"))
end

#=for game in games
	close!(game)
end=#


type Module
	speed::Float64
	energy::Float64
	production::Float64
end

function stats(pse)
	if pse[1] > 0
		return [[0.04 -0.15 0.05]; [0.06 -0.15 0.6]; [0.1 -0.15 0.8]][pse[1],:]
	end
	if pse[2] > 0
		return [[0 0.2 0.5]; [0 0.3 0.6]; [0 0.5 0.7]][pse[2],:]
	end
	if pse[3] > 0
		return [[0 0 -0.3]; [0 0 -0.4]; [0 0 -0.5]][pse[3],:]
	end
end

type Assembler
	name::AbstractString
	kw::Int64
	speed::Float64
	slots::Int64
	#slots::Vector{Module}
end

function stats(a::Assembler, pse)
	([1 a.speed a.kw]' * [1+pse[1] 1+pse[2] 1+pse[3]])[1,:]
end

function string(a::Assembler, prods, speeds, effs)

	#@sprintf "%s : %0.2fkW %0.2f speed %0.2f J per Work" a.name
end

macro taken()
	:(sum([sum(prod) sum(speed) sum(eff)]))
end

macro rem()
	:(0:max(0, a.slots-@taken))
end

prod = [0 0 0]
speed = [0 0 0]
eff = [0 0 0]
for a in [Assembler("A1", 90, 0.5, 0) Assembler("A2", 150, 0.75, 2) Assembler("A3", 210, 1.25, 4)]
	for prod[1] in @rem
		for prod[2] in @rem
			for prod[3] in @rem
				for speed[1] in @rem
					for speed[2] in @rem
						for speed[3] in @rem
							for eff[1] in @rem
								for eff[2] in @rem
									for eff[3] in @rem
										@printf "%s - " a.name
										stat = [0 0 0]
										for p in 1:3
											if prod[p] > 0
												stat = prod[p] * stats([p 0 0])
												@printf "\t%d Prod %d" prod[p] p
											end
										end
										for s in 1:3
											if speed[s] > 0
												stat = speed[s] * stats([0 s 0])
												@printf "\t%d Speed %d" speed[s] s
											end
										end
										for e in 1:3
											if eff[e] > 0
												stat = eff[e] * stats([0 0 e])
												@printf "\t%d Eff %d" eff[e] e
											end
										end
										@printf "\t%s\n" stats(a, stat)
									end
									eff[3] = 0
								end
								eff[2] = 0
							end
							eff[1] = 0
						end
						speed[3] = 0
					end
					speed[2] = 0
				end
				speed[1] = 0
			end
			prod[3] = 0
		end
		prod[2] = 0
	end
	prod[1] = 0
end


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
# ==============================================================================
#
#   Constants to compute the Equation of Origins according to the IAU-2006
#   theory.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Obtained from the files:
#
#   ftp://tai.bipm.org/iers/conv2010/chapter5/tab5.2e.txt
#
# accessed on 18 Mar 2021.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

const COEFS_IAU2006_EO_0 = Float64[
    1        2640.96         -0.39    0    0    0    0    1    0    0    0    0    0    0    0    0    0
    2          63.52         -0.02    0    0    0    0    2    0    0    0    0    0    0    0    0    0
    3          11.75          0.01    0    0    2   -2    3    0    0    0    0    0    0    0    0    0
    4          11.21          0.01    0    0    2   -2    1    0    0    0    0    0    0    0    0    0
    5          -4.55          0.00    0    0    2   -2    2    0    0    0    0    0    0    0    0    0
    6           2.02          0.00    0    0    2    0    3    0    0    0    0    0    0    0    0    0
    7           1.98          0.00    0    0    2    0    1    0    0    0    0    0    0    0    0    0
    8          -1.72          0.00    0    0    0    0    3    0    0    0    0    0    0    0    0    0
    9          -1.41         -0.01    0    1    0    0    1    0    0    0    0    0    0    0    0    0
   10          -1.26         -0.01    0    1    0    0   -1    0    0    0    0    0    0    0    0    0
   11          -0.63          0.00    1    0    0    0   -1    0    0    0    0    0    0    0    0    0
   12          -0.63          0.00    1    0    0    0    1    0    0    0    0    0    0    0    0    0
   13           0.46          0.00    0    1    2   -2    3    0    0    0    0    0    0    0    0    0
   14           0.45          0.00    0    1    2   -2    1    0    0    0    0    0    0    0    0    0
   15           0.36          0.00    0    0    4   -4    4    0    0    0    0    0    0    0	  0    0
   16          -0.24         -0.12    0    0    1   -1    1    0   -8   12    0    0    0    0    0    0
   17           0.32          0.00    0    0    2    0    0    0    0    0    0    0    0    0    0    0
   18           0.28          0.00    0    0    2    0    2    0    0    0    0    0    0    0    0    0
   19           0.27          0.00    1    0    2    0    3    0    0    0    0    0    0    0    0    0
   20           0.26          0.00    1    0    2    0    1    0    0    0    0    0    0    0    0    0
   21          -0.21          0.00    0    0    2   -2    0    0    0    0    0    0    0    0    0    0
   22           0.19          0.00    0    1   -2    2   -3    0    0    0    0    0    0    0    0    0
   23           0.18          0.00    0    1   -2    2   -1    0    0    0    0    0    0    0    0    0
   24          -0.10          0.05    0    0    0    0    0    0    8  -13    0    0    0    0    0   -1
   25           0.15          0.00    0    0    0    2    0    0    0    0    0    0    0    0    0    0
   26          -0.14          0.00    2    0   -2    0   -1    0    0    0    0    0    0    0    0    0
   27           0.14          0.00    1    0    0   -2    1    0    0    0    0    0    0    0    0    0
   28          -0.14          0.00    0    1    2   -2    2    0    0    0    0    0    0    0    0    0
   29           0.14          0.00    1    0    0   -2   -1    0    0    0    0    0    0    0    0    0
   30           0.13          0.00    0    0    4   -2    4    0    0    0    0    0    0    0    0    0
   31          -0.11          0.00    0    0    2   -2    4    0    0    0    0    0    0    0    0    0
   32           0.11          0.00    1    0   -2    0   -3    0    0    0    0    0    0    0    0    0
   33           0.11          0.00    1    0   -2    0   -1    0    0    0    0    0    0    0    0    0
]'

const COEFS_IAU2006_EO_1 = Float64[
  34          -0.87          0.00    0    0    0    0    1    0    0    0    0    0    0    0    0    0
]'


#Plot a 2D-array as an image to the terminal
#Needs xterm-256color to run properly
#try to add export TERM=xterm-256color to your .bashrc
module ImageTerm
using Colors
using Images
export imageterm,sethd
const use_hd=Bool[false]

sethd(hd::Bool)=use_hd[1]=hd

function colstr(r::Integer,g::Integer,b::Integer)
  #r,g,b should be between 1 and 6
  r=min(r,6);r=max(r,1)
  g=min(g,6);g=max(g,1)
  b=min(b,6);b=max(b,1)
  return(36*(r-1)+6*(g-1)+b+15)
end
function colstr(c::AbstractRGB)
  return(colstr(ceil(Int,red(c)*6),ceil(Int,green(c)*6),ceil(Int,blue(c)*6)))
end

function stringfull(c)
  return(string("\e[48;5;",c,"m  "))
end

function stringtd(ct,cb)
  return(string("\e[48;5;",ct,"m\e[38;5;",cb,"m\u2584"))
end

abstract Normalization{T}
immutable LinearNormalization{T} <: Normalization{T}
  missval::T
  minval::T
  maxval::T
end

type ColorMapMiss{T,K}
  colors::Vector{RGB{T}}
  misscolor::RGB{T}
  missval::K
end
ColorMapMiss{T,V}(colors::Vector{RGB{T}},misscolor::RGB{V},missval)=ColorMapMiss(colors,convert(RGB{T},misscolor),missval)

getColor(c::ColorMapMiss,x::Number)=(x==c.missval) ? c.misscolor : c.colors[round(Int,x*(length(c.colors)-1))+1]
normalize(n::LinearNormalization,x::Number)= x==n.missval ? x : (x-n.minval)/(n.maxval-n.minval)
normalize!(n::LinearNormalization,x::Array)=for i in eachindex(x) x[i]=normalize(n,x[i]) end

function getPlotArray(a,npc,npl,transpose)
  mout=zeros(elemtype(a),npl,npc)
  if transpose
    f1=(size(a,1)-1)/(npc-1)
    f2=(size(a,2)-1)/(npl-1)
    for i=1:npc,j=1:npl
      mout[j,i]=readval(a,min(round(Int,(i-1)*f1)+1,size(a,1)),min(round(Int,(j-1)*f2)+1,size(a,2)))
    end
  else
    f1=(size(a,1)-1)/(npl-1)
    f2=(size(a,2)-1)/(npc-1)
    for i=1:npc,j=1:npl
      mout[j,i]=readval(a,min(round(Int,(j-1)*f1)+1,size(a,1)),min(round(Int,(i-1)*f2)+1,size(a,2)))
    end
  end

  mout
end

function getMinMax(x,missval)
  mi=typemax(eltype(x))
  ma=typemin(eltype(x))
  for ix in x
    if ix!=missval
      if ix<mi mi=ix end
      if ix>ma ma=ix end
    end
  end
  mi,ma
end

Base.one(x::UTF8String)=""

function imageterm(io,a::AbstractMatrix;col::Vector=colormap("Blues"),missval::Number=1.0e32,misscol=colorant"black",hd::Bool=use_hd[1],title::AbstractString="",subtitle::AbstractString="",transpose=false)
  ldim=size(a)
  l,c=Base.tty_size()
  legendwidth = 15
  titleheight = title=="" ? 0 : 1
  # Determine size available for the plot
  npc = div(c-legendwidth,hd ? 1 : 2)
  npl = (l-titleheight)*(hd ? 2 : 1)
  #Determine final plotsize
  npl,npc=getPlotsize(ldim[1],ldim[2],npl,npc,transpose)
  #Get the data necessary for plotting from input
  pa=getPlotArray(a,npc,npl,transpose)
  #Normalize plotting data to 0..1
  no=LinearNormalization(pa,convert(eltype(pa),missval))
  normalize!(no,pa)
  #
  cm=ColorMapMiss(col,misscol,missval)
  #
  rgbar = pa2rgb(pa,cm)
  #
  sa=rgbar2sa(rgbar,hd=hd)
  #Make the legend
  sl=get_legend(cm,size(sa,1),no)
  #Concatenate with plot
  s=reducedim(string,hcat(sa,sl),2,UTF8String(""))
  #Join title and subtitle
  vcat(get_title(title,npc),s,get_subtitle(subtitle,cm))
  print(io,join(s),"\n")
end
imageterm(a::AbstractMatrix;col::Vector=colormap("Blues"),missval::Number=1.0e32,misscol=colorant"black",hd::Bool=use_hd[1],title::AbstractString="",subtitle::AbstractString="",transpose=false)=
imageterm(STDOUT,a,col=col,missval=missval,misscol=misscol,hd=hd,title=title,subtitle=subtitle,transpose=transpose)

function imageterm(io,img::AbstractImageDirect;hd::Bool=use_hd[1])
  title=string(colorspace(img), " ", typeof(img).name)
  subtitle=string("data: ", summary(img.data))
  transpose=img.properties["spatialorder"][1]=="x" ? true : false
  imageterm(io,img.data,hd=hd,title=title,transpose=transpose)
end
imageterm(img::AbstractImageDirect;hd::Bool=use_hd[1])=imageterm(STDOUT,img,hd=hd)

function imageterm{T<:AbstractRGB}(io,a::AbstractMatrix{T};hd::Bool=use_hd[1],title::AbstractString="",subtitle::AbstractString="",transpose=false)
  ldim=size(a)
  l,c=Base.tty_size()
  titleheight = title=="" ? 0 : 1
  # Determine size available for the plot
  npc = div(c,hd ? 1 : 2)
  npl = (l-titleheight)*(hd ? 2 : 1)
  #Determine final plotsize
  npl,npc=getPlotsize(ldim[1],ldim[2],npl,npc,transpose)
  #Get the data necessary for plotting from input
  pa=getPlotArray(a,npc,npl,transpose)
  # Convert to STring representation
  sa=rgbar2sa(pa,hd=hd)
  s=reducedim(string,hcat(sa,UTF8String["\e[0m\n" for i=1:size(sa,1), j=1]),2,UTF8String(""))
  #Join title and subtitle
  vcat(get_title(title,npc),s,subtitle)
  print(io,join(s),"\n")
end
imageterm{T<:AbstractRGB}(a::AbstractMatrix{T};hd::Bool=false,title::AbstractString="",subtitle::AbstractString="",transpose=false)=
imageterm{T<:AbstractRGB}(STDOUT,a;hd=hd,title=title,subtitle=subtitle,transpose=transpose)

pa2rgb(pa,cm)=[getColor(cm,pa[irowpa,icol]) for irowpa=1:size(pa,1), icol=1:size(pa,2)]

#Convert values to String Array
function rgbar2sa(rgbar;hd=false)
  if hd==false
    sa=Array(UTF8String,size(rgbar))
    for i in eachindex(sa)
      sa[i]=stringfull(colstr(rgbar[i]))
    end
  else
    sa=Array(UTF8String,ceil(Int,size(rgbar,1)/2),size(rgbar,2))
    irowpa=1
    for irow=1:size(sa,1)
      for icol=1:size(sa,2)
        if irowpa<size(rgbar,2)
          sa[irow,icol]=stringtd(colstr(rgbar[irowpa,icol]),colstr(rgbar[irowpa+1,icol]))
        else
          sa[irow,icol]=stringtd(colstr(rgbar[irowpa,icol]),colstr(rgbar[irowpa,icol]))
        end
      end
      irowpa+=2
    end
  end
  sa
end

LinearNormalization(pa,missval)=LinearNormalization(missval,getMinMax(pa,missval)...)
get_legend(cm,npl,no::Normalization)=UTF8String[string("\e[0m ",repeat(stringfull(colstr(getColor(cm,(npl-iline)/(npl-1)))),2),"\e[0m",@sprintf("%10.2e",no.minval+(npl-iline)/(npl-1)*(no.maxval-no.minval)),"\n") for iline=1:npl]

get_title(title,npc)= title!="" ? string("\n\e[1m",^(" ",round(Int64,(npc-length(title))/2)),title,"\e[0m\n\n") : title
get_subtitle(subtitle,cm)= subtitle!="" ? "\e[0m; min=$(cm.norm.minval); max=$(cm.norm.maxval)" : subtitle

function getPlotsize(slines,scol,l,c,transpose)
  if transpose slines,scol = scol,slines end
  asprat=slines/scol
  if slines>l
    slines=l
    scol=round(Int,slines/asprat)
  end
  if scol>c
    scol=c
    slines=round(Int,scol*asprat)
  end
  return slines,scol
end

readval(a::AbstractArray,i,j)=getindex(a,i,j)
elemtype(a::AbstractArray)=eltype(a)
# Overwrite writemime for Images
Base.writemime(io::IO, ::MIME"text/plain", img::AbstractImageDirect) = imageterm(io, img)

#haskey(Pkg.installed(),"NetCDF") ? include("ncplot.jl") : nothing
end


#EasyPlot: docstring definitions
#-------------------------------------------------------------------------------

#==
===============================================================================#

@doc """`fill(fn::Function d::DataRS, sweeplist)`

Construct a DataRS structure storing results from parametric sweeps using recursive data structures.

# Examples
```julia-repl
signal = fill(DataRS, PSweep("A", [1, 2, 4] .* 1e-3)) do A
    fill(DataRS, PSweep("phi", [0, 0.5, 1] .* (π/4))) do 𝜙
    fill(DataRS{DataF1}, PSweep("freq", [1, 4, 16] .* 1e3)) do 𝑓
       𝜔 = 2π*𝑓; T = 1/𝑓
       Δt = T/100 #Define resolution from # of samples per period
       Tsim = 4T #Simulated time
       t = DataF1(0:Δt:Tsim) #DataF1 creates a t:{y, x} container with y == x
       sig = A * sin(𝜔*t + 𝜙) #Still a DataF1 sig:{y, x=t} container
       return sig
end; end; end
```

Note that inner-most sweep needs to specify element type (DataF1).
Other (scalar) element types include: DataInt/DataFloat/DataComplex.
""" Base.fill(::Function, ::Type{DataRS}, args...)

@doc """`fill(value, [DataRS/DataF1], sweeplist)`

Construct a filled DataRS or DataHR structure with the provided list of parametric sweeps.

# Examples
```julia-repl
sweeplist = PSweep[
	PSweep("A", [1, 2, 4])
	PSweep("freq", [1, 2, 4, 8, 16] .* 1e3)
	PSweep("phi", π .* [1/4, 1/3, 1/2])
]

all32s = fill(32.0, DataRS, sweeplist)
```
""" Base.fill(::DF1_Num, ::Type{DataMD}, args...)

@doc """`zeros([DataRS/DataF1], sweeplist)`

Construct a 0.0-filled DataRS or DataHR structure with the provided list of parametric sweeps.

# Examples
```julia-repl
sweeplist = PSweep[
	PSweep("A", [1, 2, 4])
	PSweep("freq", [1, 2, 4, 8, 16] .* 1e3)
	PSweep("phi", π .* [1/4, 1/3, 1/2])
]

all0s = zeros(DataRS, sweeplist)
```
""" Base.zeros(::Type{DataMD}, args...)

@doc """`ones([DataRS/DataF1], sweeplist)`

Construct a 1.0-filled DataRS or DataHR structure with the provided list of parametric sweeps.

# Examples
```julia-repl
sweeplist = PSweep[
	PSweep("A", [1, 2, 4])
	PSweep("freq", [1, 2, 4, 8, 16] .* 1e3)
	PSweep("phi", π .* [1/4, 1/3, 1/2])
]

all1s = ones(DataRS, sweeplist)
```
""" Base.ones(::Type{DataMD}, args...)

#Last line


using ImageSegmentation, Images, Test, SimpleWeightedGraphs, LightGraphs, StaticArrays, RegionTrees

include("core.jl")
include("region_growing.jl")
include("felzenszwalb.jl")
include("fast_scanning.jl")
include("watershed.jl")
include("region_merging.jl")
include("meanshift.jl")


@testset "Segregating sites" begin

    S = [dna"aatcga", dna"aaa", dna"tatcg", dna"catcgac", dna"aatc"]
    @test count(Segregating, S)[1] == 2 && count(Segregating, S)[2] == 3

    S = [dna"ATAATAAAAAAATAATAAAAAAATAAAAAAAATAAAAAAAA",
         dna"AAAAAAAATAAATAATAAAAAAATAAAAAAAAAAAAAAAAA",
         dna"AAAATAAAAATATAATAAAAAAATATAAAAAAAAAAAAAAA",
         dna"AAAAAAAAAAAATAATAAAAAAATAAATAAATAAAAAAAAA",
         dna"AAAATAAAAAAAATATAAAAAAATAAAAAAAAAAAAAAAAA",
         dna"AAAATAAAAAAAAAATAAAAAAAAAAAAAAAAAAATAAAAA",
         dna"AAAAAATAAAAATAATAAAAAAATAAAAAAAAAAAAAAAAA",
         dna"AAAAAAAAAAAAAAATAAAAAAATAAAAAAAAAAAAAAATA",
         dna"AAAAAAAAAAAAAAAAAAAAAAATAAAAAAAAAAAAAAAAA",
         dna"AAAAAAAAAAAAAAATAAAAAAATAATAAAAAAAAAAAAAA"]
    @test count(Segregating, S)[1] == 16 && count(Segregating, S)[2] == 41

end


module TestMetadataUtils

using MLJBase, Test

@mlj_model mutable struct FooRegressor <: Deterministic
    a::Int = 0::(_ ≥ 0)
    b
end
metadata_pkg(FooRegressor,
    name="FooRegressor",
    uuid="10745b16-79ce-11e8-11f9-7d13ad32a3b2",
    url="http://existentialcomics.com/",
    julia=true,
    license="MIT",
    is_wrapper=false
    )
metadata_model(FooRegressor,
    input=MLJBase.Table(Continuous),
    target=AbstractVector{Continuous},
    descr="La di da")

infos = info_dict(FooRegressor)

@test infos[:input_scitype] == MLJBase.Table(Continuous)
@test infos[:target_scitype] == AbstractVector{Continuous}
@test infos[:is_pure_julia]
@test !infos[:is_wrapper]
@test infos[:docstring] == raw"""La di da
    → based on [FooRegressor](http://existentialcomics.com/).
    → do `@load FooRegressor pkg="FooRegressor"` to use the model.
    → do `?FooRegressor` for documentation."""
@test infos[:name] == "FooRegressor"

@test infos[:is_supervised]
@test infos[:prediction_type] == :deterministic

end
true


using ReducedBasisMethods
using Test

@testset "ReducedBasisMethods.jl" begin
    include("parameter_tests.jl")
    include("parameterspace_tests.jl")
    include("trainingset_tests.jl")
end


"""
	resample(x, resolution::Number; crs, method)
	resample(x; to, method)
    resample(xs...; to=first(xs), method)

`resample` uses `ArchGDAL.gdalwarp` to resample a [`Raster`](@ref) or
[`RasterStack`](@ref) to a new `resolution` and optionally new `crs`,
or to snap to the bounds, resolution and crs of the object `to`.

# Arguments

- `x`: the object to resample.
- `resolution`: a `Number` specifying the resolution for the output.
    If the keyword argument `crs` (described below) is specified, `resolution` must be in units of the `crs`.

# Keywords

- `to`: an `AbstractRaster` whos resolution, crs and bounds will be snapped to.
    For best results it should roughly cover the same extent, or a subset of `A`.
- `crs`: A `GeoFormatTypes.GeoFormat` such as `EPSG(x)` or `WellKnownText(string)` specifying an
    output crs (`A` will be reprojected to `crs` in addition to being resampled). Defaults to `crs(A)`
- `method`: A `Symbol` or `String` specifying the method to use for resampling.
    From the docs for [`gdalwarp`](https://gdal.org/programs/gdalwarp.html#cmdoption-gdalwarp-r):
    * `:near`: nearest neighbour resampling (default, fastest algorithm, worst interpolation quality).
    * `:bilinear`: bilinear resampling.
    * `:cubic`: cubic resampling.
    * `:cubicspline`: cubic spline resampling.
    * `:lanczos`: Lanczos windowed sinc resampling.
    * `:average`: average resampling, computes the weighted average of all non-NODATA contributing pixels.
        rms root mean square / quadratic mean of all non-NODATA contributing pixels (GDAL >= 3.3)
    * `:mode`: mode resampling, selects the value which appears most often of all the sampled points.
    * `:max`: maximum resampling, selects the maximum value from all non-NODATA contributing pixels.
    * `:min`: minimum resampling, selects the minimum value from all non-NODATA contributing pixels.
    * `:med`: median resampling, selects the median value of all non-NODATA contributing pixels.
    * `:q1`: first quartile resampling, selects the first quartile value of all non-NODATA contributing pixels.
    * `:q3`: third quartile resampling, selects the third quartile value of all non-NODATA contributing pixels.
    * `:sum`: compute the weighted sum of all non-NODATA contributing pixels (since GDAL 3.1)

    Where NODATA values are set to `missingval`.

Note: `missingval` of `missing` does not work with GDAL. Use `replace_missing(A, newmissingval)` to
assign a missing value before using `resample` if the current value is `missing`. This will be
automated in future versions.

# Example

Resample a WorldClim layer to match an EarthEnv layer:

```jldoctest
using Rasters, Plots
A = Raster(WorldClim{Climate}, :prec; month=1)
B = Raster(EarthEnv{HabitatHeterogeneity}, :evenness)

a = plot(A)
b = plot(resample(A; to=B))

savefig(a, "build/resample_example_before.png")
savefig(b, "build/resample_example_after.png")
# output
```

### Before `resample`:

![before resample](resample_example_before.png)

### After `resample`:

![after resample](resample_example_after.png)

$EXPERIMENTAL
"""
function resample end
resample(xs::RasterStackOrArray...; kw...) = resample(xs; kw...)
function resample(ser::AbstractRasterSeries, args...; kw...)
    map(x -> resample(x, args...; kw...), ser)
end
function resample(xs::Union{Tuple,NamedTuple}; to=first(xs), kw...)
    map(x -> resample(x; to, kw...), xs)
end
function resample(A::RasterStackOrArray, resolution::Number;
    crs::GeoFormat=crs(A), method=:near, kw...
)
    wkt = convert(String, convert(WellKnownText, crs))
    flags = Dict(
        :t_srs => wkt,
        :tr => [resolution, resolution],
        :r => method,
    )
    return warp(A, flags; kw...)
end
function resample(A::RasterStackOrArray; to, method=:near, kw...)
    all(hasdim(to, (XDim, YDim))) || throw(ArgumentError("`to` mush have both XDim and YDim dimensions to resize with GDAL"))
    if sampling(to, XDim) isa Points
        to = set(to, dims(to, XDim) => Intervals(Start()))
    end
    if sampling(to, YDim) isa Points
        to = set(to, dims(to, YDim) => Intervals(Start()))
    end

    wkt = convert(String, convert(WellKnownText, crs(to)))
    xres, yres = map(abs ∘ step, span(to, (XDim, YDim)))
    (xmin, xmax), (ymin, ymax) = bounds(to, (XDim, YDim))
    flags = Dict(
        :t_srs => wkt,
        :tr => [yres, xres],
        :te => [xmin, ymin, xmax, ymax],
        :r => method,
    )
    return warp(A, flags; kw...)
end


;; This file declares the various types used by intrinsic/prelude definitions
;; It is sort of special in that it doesn't care whether there are any associated definitions
;; just that there are type definitions for that particular binding's name

;; Type definitions
deftype String (Vector Char)

deftype (Int) Intrinsic

deftype (Float) Intrinsic

deftype (Char) Intrinsic

deftype (Byte) Intrinsic

deftype (Void) Intrinsic

deftype (IO a) Intrinsic

deftype (Vector a) Intrinsic

deftype (List a)
  (Empty |
   (Cons a (List a)))

deftype (Bottom)
  Undefined

deftype (Maybe a)
  (Nothing |
   (Just a))

deftype (Either a b)
  ((Left a) |
   (Right b))

;; List functions

(: :: (a -> (List a) -> (List a)))

(map :: ((a -> b) -> (List a) -> (List b)))

(head :: ((List a) -> a))

(tail :: ((List a) -> (List a)))

(!! :: (Int -> (List a) -> a))

(take :: (Int -> (List a) -> (Maybe (List a))))

(drop :: (Int -> (List a) -> (Maybe (List a))))

;; Optional functions

(maybe :: (b -> (a -> b) -> (Maybe a) -> b))

(either :: ((b -> c) -> (b -> c) -> (Either a b) -> c))

;; I/O functions

(print :: (String -> (IO Void)))

;; Operator definitions

defop 1 Left (a + b)
  (add a b)

defop 1 Left (a - b)
  (minus a b)

defop 2 Left (a * b)
  (mul a b)

defop 2 Left (a / b)
  (div a b)

defop 2 Right (a ^ b)
  (pow a b)

defop 3 Left (a ++ b)
  (listConcat a b)

defop 3 Left (a == b)
  (eq a b)

defop 3 Left (a > b)
  (gt a b)

defop 3 Left (a >= b)
  (gte a b)

defop 3 Left (a < b)
  (lt a b)

defop 3 Left (a <= b)
  (lte a b)

defop 3 Left (a && b)
  (and a b)

defop 3 Left (a || b)
  (or a b)

defop 4 Left (x : xs)
  (cons x xs)

defop 5 Left (f $ x)
  (fapply f x)

defop 5 Left (f . g)
  (compose f g)

defop 3 Left (a | b)
  (bitwiseOr a b)

defop 3 Left (a & b)
  (bitwiseAnd a b)


# Adapted from ParameterHandling.jl with the following license.
#=
Copyright (c) 2020 Invenia Technical Computing Corporation

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=#

"""
    flatten(x)

Returns a "flattened" representation of `x` as a vector of real numbers, and a function
`unflatten` that takes a vector of reals of the same length and returns an object of the
same type as `x`.

`unflatten` is the inverse of `flatten`, so
```julia
julia> x = (randn(5), 5.0, (a=5.0, b=randn(2, 3)));

julia> v, unflatten = flatten(x);

julia> x == unflatten(v)
true
```
"""
function flatten end

maybeflatten(x::Real) = x
maybeflatten(x) = flatten(x)

function flatten(x::Real)
    v = [x]
    unflatten_to_Real(v) = only(v)
    return v, unflatten_to_Real
end

flatten(x::Vector{<:Real}) = (identity.(x), identity)

function flatten(x::AbstractVector)
    x_vecs_and_backs = map(val -> flatten(val), identity.(x))
    x_vecs, backs = first.(x_vecs_and_backs), last.(x_vecs_and_backs)
    function Vector_from_vec(x_vec)
        sz = _cumsum(map(_length, x_vecs))
        x_Vec = [backs[n](x_vec[sz[n] - _length(x_vecs[n]) + 1:sz[n]]) for n in eachindex(x)]
        return x_Vec
    end
    return reduce(vcat, x_vecs), Vector_from_vec
end

function flatten(x::AbstractArray)
    x_vec, from_vec = flatten(vec(identity.(x)))
    Array_from_vec(x_vec) = reshape(from_vec(x_vec), size(x))
    return identity.(x_vec), Array_from_vec
end

# Zygote can return a sparse vector co-tangent
# even if the input is a vector. This is causing
# issues in the rrule definition of Unflatten
flatten(x::SparseVector) = flatten(Array(x))

function flatten(x::JuMP.Containers.DenseAxisArray)
    x_vec, from_vec = flatten(vec(identity.(x.data)))
    Array_from_vec(x_vec) = JuMP.Containers.DenseAxisArray(reshape(from_vec(x_vec), size(x)), axes(x)...)
    return identity.(x_vec), Array_from_vec
end

function flatten(x::SparseMatrixCSC)
    x_vec, from_vec = flatten(x.nzval)
    Array_from_vec(x_vec) = SparseMatrixCSC(x.m, x.n, x.colptr, x.rowval, from_vec(x_vec))
    return identity.(x_vec), Array_from_vec
end

function flatten(x::Tuple)
    x_vecs_and_backs = map(val -> flatten(val), x)
    x_vecs, x_backs = first.(x_vecs_and_backs), last.(x_vecs_and_backs)
    lengths = map(_length, x_vecs)
    sz = _cumsum(lengths)
    function unflatten_to_Tuple(v)
        map(x_backs, lengths, sz) do x_back, l, s
            return x_back(v[s - l + 1:s])
        end
    end
    return reduce(vcat, x_vecs), unflatten_to_Tuple
end

function flatten(x::NamedTuple)
    x_vec, unflatten = flatten(values(x))
    function unflatten_to_NamedTuple(v)
        v_vec_vec = unflatten(v)
        return typeof(x)(v_vec_vec)
    end
    return identity.(x_vec), unflatten_to_NamedTuple
end

function flatten(d::AbstractDict, ks = collect(keys(d)))
    _d = OrderedDict(k => d[k] for k in ks)
    d_vec, unflatten = flatten(identity.(collect(values(_d))))
    function unflatten_to_Dict(v)
        v_vec_vec = unflatten(v)
        return _build_ordered_dict(v_vec_vec, keys(_d))
    end
    return identity.(d_vec), unflatten_to_Dict
end
function _build_ordered_dict(vals, keys)
    OrderedDict(key => vals[n] for (n, key) in enumerate(keys))
end
function ChainRulesCore.rrule(::typeof(_build_ordered_dict), vals, keys)
    _build_ordered_dict(vals, keys), Δ -> begin
        NoTangent(), values(Δ), NoTangent()
    end
end

function flatten(x)
    v, un = flatten(ntfromstruct(x))
    return identity.(v), Unflatten(x, y -> structfromnt(typeof(x), un(y)))
end

function zygote_flatten(::Real, x::Real)
    v = [x]
    unflatten_to_Real(v) = only(v)
    return v, unflatten_to_Real
end

zygote_flatten(::Vector{<:Real}, x::Vector{<:Real}) = (identity.(x), identity)

function zygote_flatten(x1::AbstractVector, x2::AbstractVector)
    x_vecs_and_backs = map((val) -> zygote_flatten(val[1], val[2]), zip(identity.(x1), identity.(x2)))
    x_vecs, backs = first.(x_vecs_and_backs), last.(x_vecs_and_backs)
    function Vector_from_vec(x_vec)
        sz = _cumsum(map(_length, x_vecs))
        x_Vec = [backs[n](x_vec[sz[n] - _length(x_vecs[n]) + 1:sz[n]]) for n in eachindex(x2)]
        return x_Vec
    end
    return reduce(vcat, x_vecs), Vector_from_vec
end

function zygote_flatten(x1::AbstractArray, x2::AbstractArray)
    x_vec, from_vec = zygote_flatten(vec(identity.(x1)), vec(identity.(x2)))
    Array_from_vec(x_vec) = reshape(from_vec(x_vec), size(x2))
    return identity.(x_vec), Array_from_vec
end

# Zygote can return a sparse vector co-tangent
# even if the input is a vector. This is causing
# issues in the rrule definition of Unflatten
zygote_flatten(x1::SparseVector, x2::SparseVector) = zygote_flatten(Array(x1), Array(x2))

function zygote_flatten(x1::JuMP.Containers.DenseAxisArray, x2::NamedTuple)
    x_vec, from_vec = zygote_flatten(vec(identity.(x1.data)), vec(identity.(x2.data)))
    Array_from_vec(x_vec) = JuMP.Containers.DenseAxisArray(reshape(from_vec(x_vec), size(x2)), axes(x2)...)
    return identity.(x_vec), Array_from_vec
end

function zygote_flatten(x1::JuMP.Containers.DenseAxisArray, x2::JuMP.Containers.DenseAxisArray)
    x_vec, from_vec = zygote_flatten(vec(identity.(x1.data)), vec(identity.(x2.data)))
    Array_from_vec(x_vec) = JuMP.Containers.DenseAxisArray(reshape(from_vec(x_vec), size(x2)), axes(x2)...)
    return identity.(x_vec), Array_from_vec
end

function zygote_flatten(x1::SparseMatrixCSC, x2::SparseMatrixCSC)
    x_vec, from_vec = zygote_flatten(x1.nzval, x2.nzval)
    Array_from_vec(x_vec) = SparseMatrixCSC(x.m, x.n, x.colptr, x.rowval, from_vec(x_vec))
    return identity.(x_vec), Array_from_vec
end

function zygote_flatten(x1::Tuple, x2::Tuple)
    x_vecs_and_backs = map(val -> zygote_flatten(val[1], val[2]), zip(x1, x2))
    x_vecs, x_backs = first.(x_vecs_and_backs), last.(x_vecs_and_backs)
    lengths = map(_length, x_vecs)
    sz = _cumsum(lengths)
    function unflatten_to_Tuple(v)
        map(x_backs, lengths, sz) do x_back, l, s
            return x_back(v[s - l + 1:s])
        end
    end
    return reduce(vcat, x_vecs), unflatten_to_Tuple
end

function zygote_flatten(x1, x2::Tangent)
    zygote_flatten(x1, ntfromstruct(x2).backing)
end
function zygote_flatten(x1, x2::NamedTuple)
    zygote_flatten(ntfromstruct(x1), x2)
end

function zygote_flatten(x1::NamedTuple, x2::NamedTuple)
    x_vec, unflatten = zygote_flatten(values(x1), values(x2))
    function unflatten_to_NamedTuple(v)
        v_vec_vec = unflatten(v)
        return typeof(x)(v_vec_vec)
    end
    return identity.(x_vec), unflatten_to_NamedTuple
end

function zygote_flatten(d1::AbstractDict, d2::AbstractDict, ks = collect(keys(d2)))
    _d1 = OrderedDict(k => d1[k] for k in ks)
    _d2 = OrderedDict(k => d2[k] for k in ks)
    d_vec, unflatten = zygote_flatten(identity.(collect(values(_d1))), identity.(collect(values(_d2))))
    function unflatten_to_Dict(v)
        v_vec_vec = unflatten(v)
        return OrderedDict(key => v_vec_vec[n] for (n, key) in enumerate(ks))
    end
    return identity.(d_vec), unflatten_to_Dict
end

function zygote_flatten(x1, x2)
    v, un = zygote_flatten(ntfromstruct(x1), ntfromstruct(x2))
    return identity.(v), Unflatten(x1, y -> structfromnt(typeof(x2), un(y)))
end

_length(x) = length(x)
_length(::Nothing) = 0

function ChainRulesCore.rrule(::typeof(flatten), x)
    d_vec, un = flatten(x)
    return (d_vec, un), Δ -> begin
        (NoTangent(), un(Δ[1]), NoTangent())
    end
end
function ChainRulesCore.rrule(::typeof(flatten), d::AbstractDict, ks)
    _d = OrderedDict(k => d[k] for k in ks)
    d_vec, un = flatten(_d, ks)
    return (d_vec, un), Δ -> begin
        (NoTangent(), un(Δ[1]), NoTangent())
    end
end

struct Unflatten{X, F} <: Function
    x::X
    unflatten::F
end
(f::Unflatten)(x) = f.unflatten(x)

_zero(x::Real) = zero(x)
_zero(x::AbstractArray) = _zero.(x)
_zero(x::AbstractDict) = Dict(keys(x) .=> map(_zero, values(x)))
_zero(x::NamedTuple) = map(_zero, x)
_zero(x::Tuple) = map(_zero, x)
_zero(x) = structfromnt(typeof(x), _zero(ntfromstruct(x)))

function _merge(d1, d2::AbstractDict)
    _d = OrderedDict(k => _zero(v) for (k, v) in d1)
    return sort!(merge(_d, OrderedDict(d2)))
end
_merge(::Any, d2) = d2

function ChainRulesCore.rrule(un::Unflatten, v)
    x = un(v)
    return x, Δ -> begin
        _Δ = _merge(x, Δ)
        return (NoTangent(), zygote_flatten(un.x, _Δ)[1])
    end
end

function flatten(::Nothing)
    return Float64[], _ -> nothing
end
function flatten(::NoTangent)
    return Float64[], _ -> NoTangent()
end
function flatten(::ZeroTangent)
    return Float64[], _ -> ZeroTangent()
end
function flatten(::Tuple{})
    return Float64[], _ -> ()
end

function zygote_flatten(x, ::Nothing)
    t = flatten(x)
    return zero(t[1]), Base.tail(t)
end
function zygote_flatten(x, ::NoTangent)
    t = flatten(x)
    return zero(t[1]), Base.tail(t)
end
function zygote_flatten(x, ::ZeroTangent)
    t = flatten(x)
    return zero(t[1]), Base.tail(t)
end
function zygote_flatten(::Any, ::Tuple{})
    return Float64[], _ -> ()
end

macro constructor(T)
    return flatten_expr(T, T)
end
macro constructor(T, C)
    return flatten_expr(T, C)
end
flatten_expr(T, C) = quote
    function flatten(x::$(esc(T)))
        v, un = flatten(ntfromstruct(x))
        return identity.(v), Unflatten(x, y -> structfromnt($(esc(C)), un(y)))
    end
    function zygote_flatten(x1::$(esc(T)), x2::$(esc(T)))
        v, un = zygote_flatten(ntfromstruct(x1), ntfromstruct(x2))
        return identity.(v), Unflatten(x2, y -> structfromnt($(esc(C)), un(y)))
    end
    _zero(x::$(esc(T))) = structfromnt($(esc(C)), _zero(ntfromstruct(x)))
end

_cumsum(x) = cumsum(x)
if VERSION < v"1.5"
    _cumsum(x::Tuple) = (_cumsum(collect(x))..., )
end


module collision_2_boxes

using Modia3D
using Modia3D.StaticArrays
import Modia3D.ModiaMath

vmat1 = Modia3D.Material(color="LightBlue" , transparency=0.5)    # material of SolidFileMesh
vmat2 = deepcopy(vmat1)                                           # material of convex decomposition of SolidFileMesh
vmat2.transparency = 0.7

cmat = "Steel"

@assembly ThreeDFiles begin
  world = Modia3D.Object3D(visualizeFrame=true)

  boxMoving     = Modia3D.Object3D(world, Modia3D.Solid(Modia3D.SolidBox(0.5,0.5,0.5) , "Aluminium", vmat1; contactMaterial = cmat); r=[0.0, 0.0, 5.0], fixed=false) # ,R=ModiaMath.rot2(-pi/3),

  box     = Modia3D.Object3D(world, Modia3D.Solid(Modia3D.SolidBox(3.0,2.0,5.0) , "Aluminium", vmat1; contactMaterial = cmat); r=[-3.0, 0.0, 2.5], fixed=true) # R=ModiaMath.rot2(-pi/3),
end

gravField = Modia3D.UniformGravityField(g=9.81, n=[-1,0,0])
threeD = ThreeDFiles(sceneOptions=Modia3D.SceneOptions(gravityField=gravField,visualizeFrames=true, defaultFrameLength=0.7,nz_max = 100, enableContactDetection=true, visualizeContactPoints=true))

# Modia3D.visualizeAssembly!( threeD )

model = Modia3D.SimulationModel( threeD )
result = ModiaMath.simulate!(model; stopTime=0.71, tolerance=1e-6,interval=0.001, log=false)

println("... success of collision_2_boxes.jl!")

end


include("ukbb.jl")
include("estimators.jl")
include("utils.jl")
include("models.jl")


using DataStructures: SortedDict, OrderedDict

abstract type AbstractBags end

Base.getindex(b::AbstractBags, i) = b.bags[i]
Base.setindex!(b::AbstractBags, v, i) = setindex!(b.bags, v, i)
Base.iterate(b::AbstractBags) = iterate(b.bags)
Base.iterate(b::AbstractBags, s) = iterate(b.bags, s)
Base.length(b::AbstractBags) = length(b.bags)

# one instance belongs to only one bag
# sorted from left to right
struct AlignedBags <: AbstractBags
    bags::Vector{UnitRange{Int}}
end

AlignedBags() = AlignedBags(Vector{UnitRange{Int}}())
AlignedBags(ks::UnitRange{Int}...) = AlignedBags(collect(ks))
function AlignedBags(k::Vector{T}) where {T<:Integer}
    b = AlignedBags()
    !isempty(k) || return b
    a, v = 1, k[1]
    s = Set{T}(v)
    for (i, x) in enumerate(k[2:end])
        if x != v
            !(x in s) || error("Scattered bags")
            push!(b.bags, a:i)
            v = x
            a = i+1
            push!(s, x)
        end
    end
    push!(b.bags, a:length(k))
    b
end

# one instance may belong to more bags
struct ScatteredBags <: AbstractBags
    bags::Vector{Vector{Int}}
end

ScatteredBags() = ScatteredBags(Vector{Vector{Int}}())
function ScatteredBags(k::Vector{T}) where {T<:Integer}
    !isempty(k) || return ScatteredBags()
    d = SortedDict{T, Vector{Int}}()
    for (i, x) in enumerate(k)
        if !(x in keys(d))
            d[x] = Int[]
        end
        push!(d[x], i)
    end
    ScatteredBags(collect(values(d)))
end

Zygote.@nograd function length2bags(ls::Vector{Int})
    ls = vcat([0], cumsum(ls))
    bags = map(i -> i[1]+1:i[2],zip(ls[1:end-1],ls[2:end]))
    bags = map(b -> isempty(b) ? (0:-1) : b,bags)
    AlignedBags(bags)
end

# """
# 		function bags(k::Vector)

# 		creates AlignedBags if it is possible to do so, otherwise creates ScatteredBags instance
# """
bags(b::AbstractBags) = b
bags(b::Vector{UnitRange{Int}}) = AlignedBags(b)
function bags(k::Vector{T}) where {T<:Integer}
    try
        return AlignedBags(k)
    catch ErrorException
        return ScatteredBags(k)
    end
end

"""
    function remapbag(b::Bags,idcs::Vector{Int})

    bags corresponding to indices with collected indices
"""
function remapbag(b::AlignedBags, idcs::VecOrRange)
    rb = AlignedBags(Vector{UnitRange{Int}}(undef, length(idcs)))
    offset = 1
    for (i,j) in enumerate(idcs)
        rb[i] = (b[j] == 0:-1) ? b[j] : b[j] .- b[j].start .+ offset
        offset += length(b[j])
    end
    rb, Array{Int}(vcat([collect(b[i]) for i in idcs]...))
end

function remapbag(b::ScatteredBags, idcs::VecOrRange)
    rb = ScatteredBags(Vector{Vector{Int}}(undef, length(idcs)))
    m = OrderedDict{Int, Int}((v => i for (i, v) in enumerate(unique(vcat(b.bags[idcs]...)))))
    for (i,j) in enumerate(idcs)
        rb[i] = [m[v] for v in b[j]]
    end
    rb, collect(keys(m))
end

adjustbags(bags::AlignedBags, mask::T) where {T<:Union{Vector{Bool}, BitArray{1}}} = length2bags(map(b -> sum(@view mask[b]), bags))

Base.vcat(b1::AbstractBags, b2::AbstractBags) = _catbags([b1, b2])
Base.vcat(bs::AbstractBags...) = _catbags(collect(bs))

function _catbags(bs::Vector{AlignedBags})
    nbs = AlignedBags()
    offset = 0
    for b in bs
        !isempty(b) || continue
        append!(nbs.bags, [bb .+ (isempty(bb) ? 0 : offset) for bb in b])
        offset += max(0, mapreduce(i -> isempty(i) ? 0 : maximum(i), max, b))
    end
    mask = length.(nbs.bags) .== 0
    if sum(mask) > 0
        nbs[mask] = fill(0:-1, sum(mask))
    end
    nbs
end

function _catbags(bs::Vector{ScatteredBags})
    nbs = ScatteredBags()
    offset = 0
    for b in bs
        !isempty(b) || continue
        append!(nbs.bags, [bb .+ offset for bb in b])
        offset += max(0, mapreduce(i -> isempty(i) ? 0 : maximum(i), max, b))
    end
    nbs
end


function mountaincar_run_online_model_search(rock_c::Float64,
                                             num_eval_samples::Int64;
                                             max_likelihood = false,
                                             optimistic = false,
                                             model_evaluation = false,
                                             debug = false,
                                             seed = 0)
    rng = MersenneTwister(seed)
    mountaincar = MountainCar(rock_c)
    model = MountainCar(0.0)
    agent = MountainCarOnlineModelSearchAgent(mountaincar, model,
                                              num_eval_samples)
    n_steps = run(agent, rng; debug = debug, max_likelihood = max_likelihood,
                  optimistic = optimistic, model_evaluation = model_evaluation,)
    n_steps
end

function mountaincar_run_cmax(rock_c::Float64;
                              debug = false,
                              seed = 0)
    rng = MersenneTwister(seed)
    mountaincar = MountainCar(rock_c)
    model = MountainCar(0.0)
    agent = MountainCarOnlineModelSearchAgent(mountaincar, model,
                                              10)
    n_steps = run_cmax(agent, rng; debug = debug)
    n_steps
end

function mountaincar_run_true(rock_c::Float64;
                              debug = false,
                              seed = 0)
    rng = MersenneTwister(seed)
    mountaincar = MountainCar(rock_c)
    model = MountainCar(0.0)
    agent = MountainCarOnlineModelSearchAgent(mountaincar, model, 10)
    n_steps = run_true(agent, rng; debug = debug)
    n_steps
end

function mountaincar_run_online_experiments()
    num_eval_samples = 50
    seeds = collect(1:10)
    # ml_steps = []
    # oms_steps = []
    # cmax_steps = []
    # true_steps = []
    # rbms_steps = []
    me_steps = []
    rock_c_values = collect(0.02:0.002:0.03)
    for rock_c in rock_c_values
        # ml_sub_steps = []
        # oms_sub_steps = []
        # cmax_sub_steps = []
        # true_sub_steps = []
        # rbms_sub_steps = []
        me_sub_steps = []
        for seed in seeds
            # println("MLE with rock_c ", rock_c, " seed ", seed)
            # push!(ml_sub_steps, mountaincar_run_online_model_search(
            #     rock_c, num_eval_samples; max_likelihood = true, seed = seed,
            #     debug = false,
            # ))
            # println("OMS with rock_c ", rock_c, " seed ", seed)
            # push!(oms_sub_steps, mountaincar_run_online_model_search(
            #     rock_c, num_eval_samples; optimistic = true, seed = seed,
            #     debug = false,
            # ))
            # println("CMAX with rock_c ", rock_c, " seed ", seed)
            # push!(cmax_sub_steps, mountaincar_run_cmax(
            #     rock_c; seed = seed, debug = false,
            # ))
            # println("True with rock_c ", rock_c, " seed ", seed)
            # push!(true_sub_steps, mountaincar_run_true(
            #     rock_c; seed = seed, debug = false,
            # ))
            # println("RBMS with rock_c ", rock_c, " seed ", seed)
            # push!(rbms_sub_steps, mountaincar_run_online_model_search(
            #     rock_c, num_eval_samples; optimistic = false, seed = seed,
            #     debug = false,
            # ))
            println("ME with rock_c ", rock_c, " seed ", seed)
            push!(me_sub_steps, mountaincar_run_online_model_search(
                rock_c, num_eval_samples; model_evaluation = true, seed = seed,
                debug = false,
            ))
        end
        # push!(ml_steps, ml_sub_steps)
        # push!(oms_steps, oms_sub_steps)
        # push!(cmax_steps, cmax_sub_steps)
        # push!(true_steps, true_sub_steps)
        # push!(rbms_steps, rbms_sub_steps)
        push!(me_steps, me_sub_steps)
    end
    # ml_mean_steps = [mean(x) for x in ml_steps]
    # ml_std_steps = [std(x) for x in ml_steps] ./ sqrt(length(seeds))
    # oms_mean_steps = [mean(x) for x in oms_steps]
    # oms_std_steps = [std(x) for x in oms_steps] ./ sqrt(length(seeds))
    # cmax_mean_steps = [mean(x) for x in cmax_steps]
    # cmax_std_steps = [std(x) for x in cmax_steps] ./ sqrt(length(seeds))
    # true_mean_steps = [mean(x) for x in true_steps]
    # true_std_steps = [std(x) for x in true_steps] ./ sqrt(length(seeds))
    # rbms_mean_steps = [mean(x) for x in rbms_steps]
    # rbms_std_steps = [std(x) for x in rbms_steps] ./ sqrt(length(seeds))
    me_mean_steps = [mean(x) for x in me_steps]
    me_std_steps = [std(x) for x in me_steps] ./ sqrt(length(seeds))

    # println("MLE")
    # println(ml_steps)
    # println("OMS")
    # println(oms_steps)
    # println("CMAX")
    # println(cmax_steps)
    # println("TRUE")
    # println(true_steps)
    # println("RBMS")
    # println(rbms_steps)
    println("ME")
    println(me_steps)

    # println("MLE")
    # println(ml_mean_steps)
    # println("OMS")
    # println(oms_mean_steps)
    # println("CMAX")
    # println(cmax_mean_steps)
    # println("TRUE")
    # println(true_mean_steps)
    # println("RBMS")
    # println(rbms_mean_steps)
    println("ME")
    println(me_mean_steps)

    # println("MLE")
    # println(ml_std_steps)
    # println("OMS")
    # println(oms_std_steps)
    # println("CMAX")
    # println(cmax_std_steps)
    # println("TRUE")
    # println(true_std_steps)
    # println("RBMS")
    # println(rbms_std_steps)
    println("ME")
    println(me_std_steps)

    # plot(rock_c_values, ml_mean_steps, grid=true, yerror=ml_std_steps, lw=3,
    #      label="MLE")
    # plot!(rock_c_values, oms_mean_steps, grid=true, yerror=oms_std_steps, lw=3,
    #      label="TOMS")
    # plot!(rock_c_values, cmax_mean_steps, grid=true, yerror=cmax_std_steps, lw=3,
    #       label="CMAX")
    # plot!(rock_c_values, true_mean_steps, grid=true, yerror=true_std_steps, lw=3,
    #      label="TRUE")
    # plot!(rock_c_values, rbms_mean_steps, grid=true, yerror=rbms_std_steps, lw=3,
    #      label="RBMS")
    # xlabel!("Misspecification")
    # ylabel!("Number of steps to reach goal")
end