"""
`Archive` saves information about promising solutions during an optimization run.
"""
abstract type Archive{F,FS<:FitnessScheme} end

numdims(a::Archive) = a.numdims
fitness_type(a::Archive{F}) where F = F
fitness_scheme(a::Archive) = a.fit_scheme

"""
    archived_fitness(fit, a::Archive)

Converts given fitness `fit` to the format used by the archive `a`.
"""
archived_fitness(fit::Any, a::Archive) = convert(fitness_type(a), fit, fitness_scheme(a))

"""
Base class for individuals stored in `Archive`.
"""
abstract type ArchivedIndividual{F} <: FitIndividual{F} end

tag(indi::ArchivedIndividual) = indi.tag

"""
Individual stored in `TopListArchive`.
"""
struct TopListIndividual{F} <: ArchivedIndividual{F}
    params::Individual
    fitness::F
    tag::Int

    TopListIndividual(params::AbstractIndividual, fitness::F, tag::Int) where F =
        new{F}(params, fitness, tag)
end

Base.:(==)(x::TopListIndividual{F}, y::TopListIndividual{F}) where F =
  (x.fitness == y.fitness) && (x.params == y.params)

"""
Fitness as stored in `TopListArchive`.
"""
struct TopListFitness{F<:Number}
    fitness::F            # current fitness
    fitness_improvement_ratio::Float64
    num_fevals::Int     # number of fitness evaluations so far
    timestamp::Float64    # when archived
end

fitness(f::TopListFitness) = f.fitness

"""
Archive that maintains a top list of the best performing (best fitness)
candidates seen so far.

The two last best fitness values could be used to estimate a confidence interval for how much improvement
potential there is.
"""
mutable struct TopListArchive{F<:Number,FS<:FitnessScheme} <: Archive{F,FS}
    fit_scheme::FS        # Fitness scheme used
    start_time::Float64   # Time when archive created, we use this to approximate the starting time for the opt...
    numdims::Int          # Number of dimensions in the optimization problem. Needed for confidence interval estimation.

    num_fitnesses::Int    # Number of calls to add_candidate

    capacity::Int         # Max size of top lists
    candidates::Vector{TopListIndividual{F}}  # Top candidates and their fitness values

    # Stores a fitness history that we can later print to a csv file.
    # For each magnitude class (as defined by `magnitude_class()` function below) we
    # we save the first entry of that class. The tuple saved for each magnitude
    # class is: `(magnitude_class, time, num_fevals, fitness, width_of_confidence_interval)`
    fitness_history::Vector{TopListFitness{F}}

    function TopListArchive(fit_scheme::FS, numdims::Integer,
                            capacity::Integer = 10) where FS<:FitnessScheme
        F = fitness_type(FS)
        new{F,FS}(fit_scheme, time(), numdims, 0, capacity,
                  TopListIndividual{F}[], TopListFitness{F}[])
    end
end

fitness_scheme(a::TopListArchive) = a.fit_scheme
capacity(a::TopListArchive) = a.capacity
Base.length(a::TopListArchive) = length(a.candidates)
Base.isempty(a::TopListArchive) = isempty(a.candidates)

best_candidate(a::TopListArchive) = a.candidates[1].params
best_fitness(a::TopListArchive) = !isempty(a.candidates) ? fitness(a.candidates[1]) : nafitness(fitness_scheme(a))
last_top_fitness(a::TopListArchive) = !isempty(a.candidates) ? fitness(a.candidates[end]) : nafitness(fitness_scheme(a))

"""
    delta_fitness(a::TopListArchive)

The difference between the current best fitness and the former best fitness.
"""
function delta_fitness(a::TopListArchive)
    if length(a.fitness_history) < 2
        Inf
    else
        # FIXME aggregate fitness?
        abs(a.fitness_history[end].fitness - a.fitness_history[end-1].fitness)
    end
end

function check_stop_condition(a::TopListArchive, p::OptimizationProblem, ctrl)
    if delta_fitness(a) < ctrl.min_delta_fitness_tol
        return "Delta fitness ($(delta_fitness(a))) below tolerance ($(ctrl.min_delta_fitness_tol))"
    end

    if fitness_is_within_ftol(p, best_fitness(a), ctrl.fitness_tol)
        return "Fitness ($(best_fitness(ctrl))) within tolerance ($(ctrl.fitness_tol)) of optimum"
    end

    return "" # no conditions met
end

"""
    add_candidate!(a::TopListArchive, fitness::F, candidate[, tag=0][, num_fevals=-1])

Add a candidate with a fitness to the archive (if it is good enough).
"""
function add_candidate!(a::TopListArchive{F}, fitness::F, candidate::AbstractIndividual,
                        tag::Int=0, num_fevals::Int=-1) where F
    a.num_fitnesses += 1
    if (num_fevals == -1) num_fevals = a.num_fitnesses end

    if isempty(a.fitness_history) || is_better(fitness, best_fitness(a), fitness_scheme(a))
        # Save fitness history so we can reconstruct the most important events later.
        push!(a.fitness_history, TopListFitness{F}(fitness, fitness_improvement_ratio(a, fitness), num_fevals, time()))
    end

    if length(a) < capacity(a) ||
       !isempty(a.candidates) && is_better(fitness, last_top_fitness(a), fitness_scheme(a))
        new_cand = TopListIndividual(copy(candidate), fitness, tag)
        fs = fitness_scheme(a)
        ix = searchsortedfirst(a.candidates, new_cand,
                               # FIXME use lt=fitness_scheme(a) when v0.5 #14919 would be fixed
                               by=BlackBoxOptim.fitness, lt=(x, y) -> is_better(x, y, fs))
        if ix > length(a) || a.candidates[ix] != new_cand
            insert!(a.candidates, ix, new_cand)
        end
        (length(a) > capacity(a)) && pop!(a.candidates) # don't grow over the capacity
    end
end

"""
Get a tuple of the sign and the magnitude of the value rounded to the first digit.
Used for archiving candidates separately for each magnitude class.
"""
function magnitude_class(f)
    f = float(f)
    if f == 0.0
        (-1.0, 1e100)
    else
        (sign(f), floor(10.0*log10(abs(f)))/10.0)
    end
end

function fitness_improvement_ratio(a::Archive, newFitness)
    try
        bestfitness = best_fitness(a)
        return abs( (bestfitness - newFitness) / bestfitness )
    catch
        return 0.0
    end
end

"""
Get the distance from a fitness value to the optimum/best known fitness value.
"""
distance_to_optimum(fitness, bestfitness) = abs(fitness - bestfitness)

function fitness_history_csv_header(a::Archive)
    "Date,Time,ElapsedTime,Magnitude,NumFuncEvals,FitnessImprovementRatio,Fitness"
end

function save_fitness_history_to_csv_file(a::Archive, filename = "fitness_history.csv";
    header_prefix = "", line_prefix = "",
    include_header = true, bestfitness = nothing)

    fh = open(filename, "a+")

    if include_header
        header = [header_prefix, fitness_history_csv_header(a)]

        if bestfitness != nothing
        push!(header, "DistanceToBestFitness")
        end

        println(fh, join(header, ","))
    end

    for af in a.fitness_history
        mc = magnitude_class(af.fitness)

        line = [line_prefix, strftime("%Y-%m-%d,%T", af.timestamp),
            af.timestamp-a.start_time,
            mc[1]*mc[2], af.num_fevals, af.fitness_improvement_ratio, af.fitness]

        if bestfitness != nothing
            push!(line, distance_to_optimum(af.fitness, bestfitness))
        end

        println(fh, join(line, ","))
    end

    close(fh)
end

"""
    merge_fitness_histories(histories)

Merge the collection of multiple fitness histories and calculate the `min`, `max`, `avg` and `median`
values for fitness and FIR (fitness improvement ratio) at each point where the fitness is changing.
"""
function merge_fitness_histories(histories)
    numhist = length(histories)
    counts = ones(numhist)
    fitnesses = zeros(numhist)
    firs = zeros(numhist)
    current_feval = 1

    # Find max history length
    maxlen = mapreduce( length, max, histories )

    while maximum(counts) < maxlen
        # Find min feval for current events, this is the next current feval
        for i in 1:numhist
            t, nf, f, fir = histories[i][counts[i]]
            # FIXME ???
        end
    end
end

# Merge the fitness histories and save the average values of the fitness,
# and distance to best fitness for each change in any of the histories.
#function merge_fitness_histories_to_csv(archives::Archive[], filename = "fitness_history.csv";
#  header_prefix = "", line_prefix = "",
#  include_header = true, bestfitness = nothing)
#
#end

"""
Calculate the width of the confidence interval at a certain `p`-value.
This is based on the paper:
    Carvalho (2011), "Confidence intervals for the minimum of a
    function using extreme value statistics"

This means that the current estimate of the confidence interval for the minimum
of the optimized function lies within the interval

```
] l1 - w, l1 [
```

with probability ``(1-p)`` as the number of sampled function points goes to infinity,
where
```
w = width_of_confidence_interval(a, p)
l1 = best_fitness(a)
```
"""
function width_of_confidence_interval(a::Archive, p = 0.01)
    if length(a) < 2
        return Inf
    else
        l1 = fitness(a.candidates[1])
        l2 = fitness(a.candidates[2])
        # We use abs below so it works also for maximization.
        abs(l2 - l1) / ( (1-p)^(-2/a.numdims) - 1)
    end
end

"""
    fitness_improvement_potential(a::Archive[, p = 0.01])

Calculate the solution improvement potential.

The percentage improvement that can be expected from the current fitness value at a given `p`-value.
In theory, an optimization run should be terminated when this value is very
small, i.e. there is little improvement potential left in the run.
"""
fitness_improvement_potential(a::Archive, p = 0.01) =
    width_of_confidence_interval(a, p) / abs(best_fitness(a))


"""
SOME provides methods to train Kohonen's self-organising maps.
The SOM.jl package is licensed under the MIT "Expat" License.

    Andreas Dominik
    Life Science Informaticvs Laboratory
    THM University of Applied Sciences
    Gießen, Germany
"""
module SOM

using DataFrames
using Distances
using ProgressMeter
using StatsBase
using Distributions
using TensorToolbox
using LinearAlgebra
# if VERSION < v"0.7.0-DEV.5183"
#     # nothing: dotproduct is in Base
# else
#     # import LinearAlgebra Package
#     using LinearAlgebra
# end

include("errors.jl")

include("types.jl")
include("helpers.jl")
include("grids.jl")
include("kernels.jl")
include("soms.jl")
include("api.jl")

global MPL_INSTALLED = false
try
    # Do the pyimports at runtime and not at time
    # of precompilation:
    using PyPlot
    using PyCall
    global const patches = PyNULL()
    global const cm = PyNULL()
    global const ag1 = PyNULL()
    global const mp3 = PyNULL()

    include("plotPyPlot.jl")
    include("plotSpheres.jl")
    global MPL_INSTALLED = true
catch e
    println("Error loading PyPlot $e")
    println(" ")
    println(SOM_ERRORS[:ERR_MPL])
    global MPL_INSTALLED = false
end

function __init__()

    # rescue Python-pointers at runtime:
    if MPL_INSTALLED
        copy!(patches, pyimport_conda("matplotlib.patches", "matplotlib"))
        copy!(cm, pyimport_conda("matplotlib.cm", "matplotlib"))
        copy!(ag1, pyimport_conda("mpl_toolkits.axes_grid1", "mpl_toolkits"))
        copy!(mp3, pyimport_conda("mpl_toolkits.mplot3d", "mpl_toolkits"))

        # @pyimport matplotlib.patches as patches
        # @pyimport matplotlib.cm as cm               # colourmap
        # @pyimport mpl_toolkits.axes_grid1 as ag1
        # @pyimport mpl_toolkits.mplot3d as mp3
    end
end

export Som,
       initSOM, trainSOM, mapToSOM,
       bubbleKernel, gaussianKernel,
       classFrequencies,
       plotDensity, plotClasses,
       rowSample, prettyPrintArray,
       MPL_INSTALLED

end # of module SOM


using Test, simgym
import simgym: leapfrog!
include("lineagetest.jl")

@test 1==1

testpos = [0.,0.]
testvel = [.0001,.0001]

cell = Cell[Cell(copy(testpos),copy(testvel),40,30)]
s = Simulation(cell,len_jones)

dt = .1
nsteps = 1

# this is what leapfrog SHOULD be doing
@. testpos += dt*testvel

leapfrog!(s,dt)

#@test testpos == s.cells[1].p


#
# The given combinator.
#

function GivenQuery(base::Query, param::Query)
    tag = decoration(output(param), :tag, Symbol, Symbol(""))
    @assert tag != Symbol("")
    remparams = filter(p -> p.first != tag, slots(input(base)))
    q = RecordQuery(istub(base), param)
    q >>
    Query(
        GivenSig(),
        [FieldQuery(output(q), 1) >> base],
        Input(domain(output(q)), mode(input(base)) |> setslots(remparams)),
        output(base))
end

function GivenQuery(base::Query)
    oty = fields(domain(input(base)))[2]
    tag = decoration(oty, :tag, Symbol, Symbol(""))
    @assert tag != Symbol("")
    remparams = filter(p -> p.first != tag, slots(input(base)))
    Query(
        GivenSig(),
        [base],
        Input(domain(input(base)), mode(input(base)) |> setslots(remparams)),
        output(base))
end

GivenQuery(base::Query, param::Pair{Symbol, Query}) =
    GivenQuery(base, param.second |> decorate(:tag => param.first))

GivenQuery(base::Query, params::Vector{Query}) =
    isempty(params) ? base : GivenQuery(GivenQuery(base, params[1:end-1]), params[end])

GivenQuery(base::Query, param, params...) =
    GivenQuery(GivenQuery(base, params...), param)

immutable GivenSig <: AbstractSignature
end

ev(::GivenSig, args::Vector{Query}, ::Input, oty::Output, iflow::InputFlow) =
    OutputFlow(
        oty,
        given_impl(args..., iflow.ctx, iflow.frameoffs, iflow.slotflows, values(iflow), domain(iflow)))

function given_impl(
        arg::Query, ctx::InputContext, frameoffs::InputFrame, slotflows::InputSlotFlows, dv::DataVector,
        dom::Domain)
    fs = fields(dom)
    tag = decoration(fs[2], :tag, Symbol, Symbol(""))
    pmap = Dict{Symbol,OutputFlow}(slotflows)
    pmap[tag] = OutputFlow(fs[2], column(dv, 2))
    pkeys = collect(keys(pmap))
    sort!(pkeys)
    pflows = InputSlotFlow[pkey => pmap[pkey] for pkey in pkeys]
    iflow′ = InputFlow(
        ctx,
        dom,
        dv,
        frameoffs,
        pflows)
    return column(ev(arg, iflow′))
end


# Additional postprocessors for @planar and @plansor

# since temporaries were taken out in preprocessing by _decompose_planar_contractions, they
# are not identified by the parsing step of TensorOperations, so we have to manually fix this
# Step 1: we have to find the new name that TO.tensorify assigned to these temporaries
# since it parses `tmp[] := a[] * b[]` as `newtmp = similar...; tmp = contract!(.... , newtmp, ...)`
function _update_temporaries(ex, temporaries)
    if ex isa Expr && ex.head == :(=)
        lhs = ex.args[1]
        i = findfirst(==(lhs), temporaries)
        if i !== nothing
            rhs = ex.args[2]
            if rhs isa Expr && rhs.head == :call && rhs.args[1] == :add!
                newname = rhs.args[6]
                temporaries[i] = newname
            elseif rhs isa Expr && rhs.head == :call && rhs.args[1] == :contract!
                newname = rhs.args[8]
                temporaries[i] = newname
            elseif rhs isa Expr && rhs.head == :call && rhs.args[1] == :trace!
                newname = rhs.args[6]
                temporaries[i] = newname
            else
                @error "lhs = $lhs , rhs = $rhs"
            end
        end
    elseif ex isa Expr
        for a in ex.args
            _update_temporaries(a, temporaries)
        end
    end
    return ex
end

# Step 2: we find `newtmp = similar_from_...` and replace with `newtmp = cached_similar_from...`
function _annotate_temporaries(ex, temporaries)
    if ex isa Expr && ex.head == :(=)
        lhs = ex.args[1]
        i = findfirst(==(lhs), temporaries)
        if i !== nothing
            rhs = ex.args[2]
            if !(rhs isa Expr && rhs.head == :call && rhs.args[1] == :similar_from_indices)
                @error "lhs = $lhs , rhs = $rhs"
            end
            newrhs = Expr(:call, :cached_similar_from_indices,
                            QuoteNode(lhs), rhs.args[2:end]...)
            return Expr(:(=), lhs, newrhs)
        end
    elseif ex isa Expr
        return Expr(ex.head, [_annotate_temporaries(a, temporaries) for a in ex.args]...)
    end
    return ex
end

# add correct modules (`GlobalRef`) to various functions
const _TOFUNCTIONS = (:similar_from_indices, :cached_similar_from_indices,
                        :scalar, :IndexError)

function _add_modules(ex::Expr)
    if ex.head == :call && ex.args[1] in _TOFUNCTIONS
        return Expr(ex.head, GlobalRef(TensorOperations, ex.args[1]),
                        (_add_modules(ex.args[i]) for i in 2:length(ex.args))...)
    elseif ex.head == :call && ex.args[1] == :add!
        @assert ex.args[4] == :(:N)
        argind = [2,3,5,6,7,8]
        return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planar_add!)),
                        (_add_modules(ex.args[i]) for i in argind)...)
    elseif ex.head == :call && ex.args[1] == :trace!
        @assert ex.args[4] == :(:N)
        argind = [2,3,5,6,7,8,9,10]
        return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planar_trace!)),
                        (_add_modules(ex.args[i]) for i in argind)...)
    elseif ex.head == :call && ex.args[1] == :contract!
        @assert ex.args[4] == :(:N) && ex.args[6] == :(:N)
        argind = vcat([2,3,5], 7:length(ex.args))
        return Expr(ex.head, GlobalRef(TensorKit, Symbol(:planar_contract!)),
                        (_add_modules(ex.args[i]) for i in argind)...)
    else
        return Expr(ex.head, (_add_modules(e) for e in ex.args)...)
    end
end
_add_modules(ex) = ex


export InstrumentPosition, EQLoc, GenLoc, GeoLoc, NodalLoc, UTMLoc, XYLoc

@doc """
**InstrumentPosition**

An abstract type whose subtypes (GenLoc, GeoLoc, UTMLoc, XYLoc)
  describe instrument positions in different ways.

Additional structures can be added for custom types.

Matrix of structure fields and rough equivalencies:

| **GenLoc** | **GeoLoc** | **UTMLoc** | **XYLoc** | **EQLoc** | **NodalLoc |
|:---|:---|:---|:---|:---|:---|
| datum | datum | datum | datum | datum | |
| loc | | | | | |
| | zone | orig | | |
| | lon | E | x | lon | x |
| | lat | N | y | lat | |
| | el | el | z | | |
| | dep | dep | | dep | |
| | az | az | az | | |
| | inc | inc | inc | | |

""" InstrumentPosition
abstract type InstrumentPosition end

function showloc_full(io::IO, Loc::T) where {T<:InstrumentPosition}
  F = fieldnames(T)
  println(io, T, " with fields:")
  for f in F
    fn = lpad(String(f), 5, " ")
    println(io, fn, ": ", getfield(Loc,f))
  end
  return nothing
end

function loctyp2code(Loc::InstrumentPosition)
  T = typeof(Loc)
  c = UInt8(
  if T == GeoLoc
    0x01
  elseif T == UTMLoc
    0x02
  elseif T == XYLoc
    0x03
  elseif T == NodalLoc
    0x04
  else
    0x00
  end
  )
  return c
end

function code2loctyp(c::UInt8)
  T::Type = (
  if c == 0x01
    GeoLoc
  elseif c == 0x02
    UTMLoc
  elseif c == 0x03
    XYLoc
  elseif c == 0x04
    NodalLoc
  else
    GenLoc
  end
  )
  return T
end

"""
    GenLoc

Generic instrument location with two fields:
* datum::String
* loc::Array{Float64,1}
"""
mutable struct GenLoc <: InstrumentPosition
  datum::String
  loc::Array{Float64,1}

  GenLoc(datum::String, loc::Array{Float64,1}) = new(datum, loc)
end
GenLoc(; datum::String = "", loc::Array{Float64,1} = Float64[]) = GenLoc(datum, loc)
GenLoc(X::Array{Float64,1}) = GenLoc("", X)
getindex(x::GenLoc, i::Int64) = getindex(getfield(x, :loc), i)
setindex!(x::GenLoc, y::Float64, i::Int64) = setindex!(getfield(x, :loc), y, i)

function show(io::IO, Loc::GenLoc)
  if get(io, :compact, false) == false
    showloc_full(io, Loc)
  else
    print(io, repr(getfield(Loc, :loc), context=:compact => true))
  end
  return nothing
end

function write(io::IO, Loc::GenLoc)
  write(io, Int64(sizeof(Loc.datum)))
  write(io, Loc.datum)
  write(io, Int64(length(Loc.loc)))
  write(io, Loc.loc)
  return nothing
end

read(io::IO, ::Type{GenLoc}) = GenLoc(String(fastread(io, fastread(io, Int64))),
  read!(io, Array{Float64, 1}(undef, fastread(io, Int64))))

isempty(Loc::GenLoc) = min(isempty(Loc.datum), isempty(Loc.loc))
hash(Loc::GenLoc) = hash(Loc.datum, hash(Loc.loc))
isequal(S::GenLoc, U::GenLoc) = min(isequal(S.datum, U.datum), isequal(S.loc, U.loc))
==(S::GenLoc, U::GenLoc) = isequal(S, U)

sizeof(Loc::GenLoc) = 16 + sizeof(Loc.datum) + sizeof(Loc.loc)

"""
    GeoLoc

Standard instrument location description:
* datum::String
* lat::Float64 (North is positive)
* lon::Float64 (East is positive)
* el::Float64 (above sea level is positive)
* dep::Float64
* az::Float64 (clockwise from north)
* inc::Float64 (downward from +z = 0°)
"""
mutable struct GeoLoc <: InstrumentPosition
  datum::String
  lat::Float64
  lon::Float64
  el::Float64
  dep::Float64
  az::Float64
  inc::Float64

  function GeoLoc(
                  datum ::String ,
                  lat   ::Float64,
                  lon   ::Float64,
                  el    ::Float64,
                  dep   ::Float64,
                  az    ::Float64,
                  inc   ::Float64
                  )
    return new(datum, lat, lon, el, dep, az, inc)
  end
end

GeoLoc(;
        datum ::String                    = "",
        lat   ::Float64                   = zero(Float64),
        lon   ::Float64                   = zero(Float64),
        el    ::Float64                   = zero(Float64),
        dep   ::Float64                   = zero(Float64),
        az    ::Float64                   = zero(Float64),
        inc   ::Float64                   = zero(Float64)
        ) = GeoLoc(datum, lat, lon, el, dep, az, inc)

function show(io::IO, loc::GeoLoc)
  if get(io, :compact, false) == false
    showloc_full(io, loc)
  else
    c = :compact => true
    print(io, repr(getfield(loc, :lat), context=c), " N, ",
              repr(getfield(loc, :lon), context=c), " E, ",
              repr(getfield(loc, :el), context=c), " m")
  end
  return nothing
end

function write(io::IO, Loc::GeoLoc)
  datum = codeunits(getfield(Loc, :datum))
  L = Int64(length(datum))
  write(io, L)
  write(io, datum)
  write(io, Loc.lat)
  write(io, Loc.lon)
  write(io, Loc.el)
  write(io, Loc.dep)
  write(io, Loc.az)
  write(io, Loc.inc)
  return nothing
end

read(io::IO, ::Type{GeoLoc}) = GeoLoc(
  String(fastread(io, fastread(io, Int64))),
  fastread(io, Float64),
  fastread(io, Float64),
  fastread(io, Float64),
  fastread(io, Float64),
  fastread(io, Float64),
  fastread(io, Float64)
)

function isempty(Loc::GeoLoc)
  q::Bool = isempty(getfield(Loc, :datum))
  for f in (:lat, :lon, :el, :dep, :az, :inc)
    q = min(q, getfield(Loc, f) == 0.0)
  end
  return q
end

function hash(Loc::GeoLoc)
  h = hash(getfield(Loc, :datum))
  for f in (:lat, :lon, :el, :dep, :az, :inc)
    h = hash(getfield(Loc, f), h)
  end
  return h
end

function isequal(S::GeoLoc, U::GeoLoc)
  q::Bool = isequal(getfield(S, :datum), getfield(U, :datum))
  if q == false
    return q
  else
    for f in (:lat, :lon, :el, :dep, :az, :inc)
      q = min(q, getfield(S,f) == getfield(U,f))
    end
    return q
  end
end
==(S::GeoLoc, U::GeoLoc) = isequal(S, U)

sizeof(Loc::GeoLoc) = 104 + sizeof(getfield(Loc, :datum))

"""
    UTMLoc

UTM instrument location
* datum::String
* zone::Int8
* hemi:: Char (hemisphere)
* E::UInt64 (Easting, in meters)
* N::UInt64 (Northing, in meters)
* el::Float64
* dep::Float64
* az::Float64 (clockwise from north)
* inc::Float64 (downward from +z = 0°)
"""
mutable struct UTMLoc <: InstrumentPosition
  datum::String
  zone::Int8
  hemi::Char
  E::UInt64
  N::UInt64
  el::Float64
  dep::Float64
  az::Float64
  inc::Float64

  UTMLoc(
          datum::String,
          zone::Int8,
          hemi::Char,
          E::UInt64,
          N::UInt64,
          el::Float64,
          dep::Float64,
          az::Float64,
          inc::Float64
          ) = new(datum, zone, hemi, E, N, el, dep, az, inc)
end
UTMLoc(;  datum ::String  = "",
          zone  ::Int8    = zero(Int8),
          hemi  ::Char    = ' ',
          E     ::UInt64  = zero(UInt64),
          N     ::UInt64  = zero(UInt64),
          el    ::Float64 = zero(Float64),
          dep   ::Float64 = zero(Float64),
          az    ::Float64 = zero(Float64),
          inc   ::Float64 = zero(Float64)
          ) = UTMLoc(datum, zone, hemi, E, N, el, dep, az, inc)

function show(io::IO, loc::UTMLoc)
  if get(io, :compact, false) == false
    showloc_full(io, loc)
  else
    print(io, getfield(loc, :zone), " ",
              getfield(loc, :hemi), " ",
              getfield(loc, :E), " ",
              getfield(loc, :N))
  end
  return nothing
end

function write(io::IO, Loc::UTMLoc)
  write(io, sizeof(Loc.datum))
  write(io, Loc.datum)
  write(io, Loc.zone)
  write(io, Loc.hemi)
  write(io, Loc.E)
  write(io, Loc.N)
  write(io, Loc.el)
  write(io, Loc.dep)
  write(io, Loc.az)
  write(io, Loc.inc)
  return nothing
end

read(io::IO, ::Type{UTMLoc}) = UTMLoc(
  String(fastread(io, fastread(io, Int64))),
  fastread(io, Int8),
  fastread(io, Char),
  fastread(io, UInt64),
  fastread(io, UInt64),
  fastread(io, Float64),
  fastread(io, Float64),
  fastread(io, Float64),
  fastread(io, Float64)
  )

function isempty(Loc::UTMLoc)
  q::Bool = isempty(getfield(Loc, :datum))
  q = min(q, getfield(Loc, :zone) == zero(Int8))
  q = min(q, getfield(Loc, :hemi) == ' ')
  q = min(q, getfield(Loc, :E) == zero(UInt64))
  q = min(q, getfield(Loc, :N) == zero(UInt64))
  for f in (:el, :dep, :az, :inc)
    q = min(q, getfield(Loc, f) == zero(Float64))
  end
  return q
end

function hash(Loc::UTMLoc)
  h = hash(getfield(Loc, :datum))
  for f in (:zone, :hemi, :E, :N, :el, :dep, :az, :inc)
    h = hash(getfield(Loc, f), h)
  end
  return h
end

function isequal(S::UTMLoc, U::UTMLoc)
  q::Bool = isequal(getfield(S, :datum), getfield(U, :datum))
  if q == false
    return q
  else
    for f in (:zone, :hemi, :E, :N, :el, :dep, :az, :inc)
      q = min(q, getfield(S,f) == getfield(U,f))
    end
    return q
  end
end
==(S::UTMLoc, U::UTMLoc) = isequal(S, U)

sizeof(Loc::UTMLoc) = 114 + sizeof(Loc.datum)

"""
    XYLoc

Locally defined instrument position:
* datum::String
* x::Float64 (meters)
* y::Float64 (meters)
* z::Float64 (meters)
* az::Float64 (clockwise from north)
* inc::Float64 (downward from +z = 0°)
* ox::Float64 (origin, typically geographic)
* oy::Float64
* oz::Float64
"""
mutable struct XYLoc <: InstrumentPosition
  datum::String
  x::Float64
  y::Float64
  z::Float64
  az::Float64
  inc::Float64
  ox::Float64
  oy::Float64
  oz::Float64
  XYLoc(
          datum ::String,
          x     ::Float64,
          y     ::Float64,
          z     ::Float64,
          az    ::Float64,
          inc   ::Float64,
          ox    ::Float64,
          oy    ::Float64,
          oz    ::Float64
          ) = new(datum, x, y, z, az, inc, ox, oy, oz)
end
XYLoc(; datum ::String  = "",
        x     ::Float64 = zero(Float64),
        y     ::Float64 = zero(Float64),
        z     ::Float64 = zero(Float64),
        az    ::Float64 = zero(Float64),
        inc   ::Float64 = zero(Float64),
        ox    ::Float64 = zero(Float64),
        oy    ::Float64 = zero(Float64),
        oz    ::Float64 = zero(Float64)
      ) = XYLoc(datum, x, y, z, az, inc, ox, oy, oz)

function show(io::IO, loc::XYLoc)
  if get(io, :compact, false) == false
    showloc_full(io, loc)
  else
    c = :compact => true
    print(io, "x ", repr(getfield(loc, :x), context=c),
              ", y ", repr(getfield(loc, :x), context=c),
              ", z ", repr(getfield(loc, :x), context=c))
  end
  return nothing
end

function write(io::IO, Loc::XYLoc)
  write(io, sizeof(Loc.datum))
  write(io, Loc.datum)
  write(io, Loc.x)
  write(io, Loc.y)
  write(io, Loc.z)
  write(io, Loc.az)
  write(io, Loc.inc)
  write(io, Loc.ox)
  write(io, Loc.oy)
  write(io, Loc.oz)
  return nothing
end

read(io::IO, ::Type{XYLoc}) = XYLoc(
  String(fastread(io, fastread(io, Int64))),
  fastread(io, Float64),
  fastread(io, Float64),
  fastread(io, Float64),
  fastread(io, Float64),
  fastread(io, Float64),
  fastread(io, Float64),
  fastread(io, Float64),
  fastread(io, Float64)
  )

function isempty(Loc::XYLoc)
  q::Bool = isempty(getfield(Loc, :datum))
  for f in (:x, :y, :z, :az, :inc, :ox, :oy, :oz)
    q = min(q, getfield(Loc, f) == 0.0)
  end
  return q
end

function hash(Loc::XYLoc)
  h = hash(getfield(Loc, :datum))
  for f in (:x, :y, :z, :az, :inc, :ox, :oy, :oz)
    h = hash(getfield(Loc, f), h)
  end
  return h
end

function isequal(S::XYLoc, U::XYLoc)
  q::Bool = isequal(getfield(S, :datum), getfield(U, :datum))
  if q == false
    return q
  else
    for f in (:x, :y, :z, :az, :inc, :ox, :oy, :oz)
      q = min(q, getfield(S,f) == getfield(U,f))
    end
    return q
  end
end
==(S::XYLoc, U::XYLoc) = isequal(S, U)

sizeof(Loc::XYLoc) = 136 + sizeof(getfield(Loc, :datum))

"""
    NodalLoc

Instrument position along a nodal array
* x::Float64 (meters)
* y::Float64 (meters)
* z::Float64 (meters)

"""
mutable struct NodalLoc <: InstrumentPosition
  x::Float64
  y::Float64
  z::Float64
  NodalLoc(
          x     ::Float64,
          y     ::Float64,
          z     ::Float64,
          ) = new(x,y,z)
end
NodalLoc(;
        x     ::Float64 = zero(Float64),
        y     ::Float64 = zero(Float64),
        z     ::Float64 = zero(Float64),
      ) = NodalLoc(x,y,z)

function show(io::IO, loc::NodalLoc)
  if get(io, :compact, false) == false
    showloc_full(io, loc)
  else
    c = :compact => true
    print(io, "x ", repr(getfield(loc, :x), context=c),
              ", y ", repr(getfield(loc, :x), context=c),
              ", z ", repr(getfield(loc, :x), context=c))
  end
  return nothing
end

function write(io::IO, Loc::NodalLoc)
  write(io, Loc.x)
  write(io, Loc.y)
  write(io, Loc.z)
  return nothing
end

read(io::IO, ::Type{NodalLoc}) = NodalLoc(
  fastread(io, Float64),
  fastread(io, Float64),
  fastread(io, Float64),
  )

function isempty(Loc::NodalLoc)
  q::Bool = true
  for f in (:x, :y, :z)
    q = min(q, getfield(Loc, f) == 0.0)
  end
  return q
end

function hash(Loc::NodalLoc)
  h = hash(zero(UInt64))
  for f in (:x, :y, :z)
    h = hash(getfield(Loc, f), h)
  end
  return h
end

function isequal(S::NodalLoc, U::NodalLoc)
  q = true
  for f in (:x, :y, :z)
    q = min(q, getfield(S,f) == getfield(U,f))
  end
  return q
end
==(S::NodalLoc, U::NodalLoc) = isequal(S, U)
sizeof(Loc::NodalLoc) = 48


#=struct VmapFunc
    fun
    in_axes
    out_axes
    obj
end

_ndims(x::AbstractArray) = ndims(x)
_ndims(x::AbstractArray{<:AbstractArray}) = map(ndims, x)

_max_ndims(x::AbstractArray{<:Number}) = ndims(x)
_max_ndims(x::AbstractArray) = maximum(map(ndims, x))
_max_ndims(x::Union{Tuple,NamedTuple,Dict})

mapover(f, iselement, x) = iselement(x) ? f(x) : map(e -> mapover(f, iselement, e), x)
mapover(f, iselement, x::Union{Dict, NamedTuple}) = iselement(x) ? f(x) : map(e -> mapover(f, iselement, e), values(x))

mapreduceover(f, op, iselement, x) = iselement(x) ? f(x) : reduce(op, map(e -> mapreduceover(f, op, iselement, e), x))
mapreduceover(f, op, iselement, x::Union{Dict, NamedTuple}) = iselement(x) ? f(x) : reduce(op, map(e -> mapreduceover(f, op, iselement, e), values(x)))

_isleaf(x) = x isa Number || x isa AbstractArray{<:Number}

(f::Vmapfunc)(args...; kwargs...)=begin
    isnothing(f.obj) || f.obj(args...; kwargs...)
    in_axes = f.in_axes
    out_axes = f.out_axes

    if in_axes isa Colon
        py_in_axes = 0
    elseif in_axes isa Integer
        maxdim = mapreduceover(ndims, max, _isleaf, args)
        py_in_axes = maxdim - in_axes
    elseif in_axes isa Tuple

    end
    if f._in
end=#

"""
    vmap(f, in_axes, out_axes)

Vectorizing map. Creates a function which maps fun over argument axes.

Parameters:
    fun (Callable) – Function to be mapped over additional axes.
    in_axes – A nonnegative integer, None, or (nested) standard Python container
        (tuple/list/dict) thereof specifying which input array axes to map over.
        If each positional argument to fun is an array, then in_axes can be a nonnegative
        integer, a None, or a tuple of integers and Nones with length equal to the number
        of positional arguments to fun. An integer or None indicates which array
        axis to map over for all arguments (with None indicating not to map any axis),
        and a tuple indicates which axis to map for each corresponding positional argument.
        If the positional arguments to fun are container types, the corresponding element
        of in_axes can itself be a matching container, so that distinct array axes can be
        mapped for different container elements. in_axes must be a container tree prefix of
        the positional argument tuple passed to fun.
        At least one positional argument must have in_axes not None. The sizes of the
        mapped input axes for all mapped positional arguments must all be equal.

    out_axes – A nonnegative integer, None, or (nested) standard Python container
        (tuple/list/dict) thereof indicating where the mapped axis should appear in
        the output. All outputs with a mapped axis must have a non-None out_axes specification.
Return type:
        Callable

Returns:
    Batched/vectorized version of fun with arguments that correspond to those of fun,
    but with extra array axes at positions indicated by in_axes, and a return value
    that corresponds to that of fun, but with extra array axes at positions indicated by out_axes.
"""
#vmap(f; in_axes, out_axes=:) = vmap(f, in_axes, out_axes=out_axes)
vmap(f; in_axes_fromend, out_axes_fromend=0) = vmap_endorder(f, in_axes_fromend, out_axes=out_axes)

function vmap(f, in_axes; out_axes=:)
    throw(ErrorException("Please use the argument in_axes_fromend, and write the batched dimensions
    starting from the last (0), second last (1)... etc.
    This is required because arrays are stored transposed."))
end

function vmap_endorder(f, in_axes=0, out_axes=0)
    # Change from Julia's 1 based to Python's 0 based indexing
    return jax.vmap(f, in_axes=in_axes, out_axes=out_axes)
end


pwd() ∉ LOAD_PATH && push!(LOAD_PATH, pwd())
"C:\\Users\\Jones\\Desktop\\home\\Code\\julia\\RCS-Invert\\src" ∉ LOAD_PATH && push!(LOAD_PATH, "C:\\Users\\Jones\\Desktop\\home\\Code\\julia\\RCS-Invert\\src")
"C:\\Users\\Jones\\Desktop\\home\\Code\\julia\\RCS-Invert\\src\\3DFDTD" ∉ LOAD_PATH && push!(LOAD_PATH, "C:\\Users\\Jones\\Desktop\\home\\Code\\julia\\RCS-Invert\\src\\3DFDTD")

"C:\\Users\\Jones\\Desktop\\home\\Code\\julia\\RCS-Invert\\tests" ∉ LOAD_PATH && push!(LOAD_PATH, "C:\\Users\\Jones\\Desktop\\home\\Code\\julia\\RCS-Invert\\tests")
#"/home/zander/Code/julia/RCS-Invert/src" ∉ LOAD_PATH && push!(LOAD_PATH, "/home/zander/Code/julia/RCS-Invert/src")
#"/home/zander/Code/julia/RCS-Invert/src" ∉ LOAD_PATH && push!(LOAD_PATH, "/home/zander/Code/julia/RCS-Invert/src")
using Test
using Plots
using Yee

dB(x::Number) = 20*log10(abs(x))
include("C:\\Users\\Jones\\Desktop\\home\\Code\\julia\\RCS-Invert\\tests\\fdtd_test.jl")

detectorindex = (1, 2, [:, :, :]) # detectorindex follows the form (Field, which component to read, [location of detector])
typeof(detectorindex)
field, field_extended = PML_Test(detectorindex)

plotly()
heatmap(dB.(field[100][1][2][:,:,div(54,2)] .-
        field_extended[100][1][2][div(107,2)-div(54,2)+1:div(107,2)+div(54,2),div(107,2)-div(54,2)+1:div(107,2)+div(54,2),div(107,2)]))

heatmap(dB.(field_extended[100][1][2][:,div(107,2),:]))

axislen = size(FDTD_out[1][1][1], 1)
timelen = size(FDTD_out, 1)

lightcone = zeros(axislen, timelen-50)
for i in 1:timelen-50
    lightcone[:,i] .= FDTD_out[i][1][2][div(54,2),div(54,2),:]
end
heatmap(dB.(lightcone))
#plot((wierd))
#@test !any(isnan.(FDTD_out))
#@test !any(isinf.(FDTD_out))
#=
pmllightcone = zeros(size(FDTD_out[1].E.min[1][2],1), timelen)
for i in 1:timelen
    pmllightcone[:,i] = FDTD_out[i].E.max[1][3][:,div(54,2),div(54,2)]
end
heatmap(dB.(pmllightcone))
=#


@verilog function multioutput(inputwire::Wire{3:0v})
  val1 = inputwire[1:0v]
  val2 = inputwire[msb:2v]
  return (val1, val2)
end

@test multioutput(0b1100) == 0b0011

#next, write two functions.
@verilog function vf(x::Wire{3:0v})
  o1 = x[1:0v]
  o2 = x[msb:2v]
  (o1, o2)
end

@test vf() == """
module vf(
  input [3:0] x,
  output [1:0] o1,
  output [1:0] o2);

  assign o1 = x[1:0];
  assign o2 = x[3:2];
endmodule
"""

@verilog function calls_vf(z::Wire{3:0v})
  c1, c2 = vf(z)
  res = c1 & c2
end

@test calls_vf() == """
module calls_vf(
  input [3:0] z,
  output [1:0] res);

  wire [1:0] c1;
  wire [1:0] c2;

  vf vf_c1_c2(
    .x (z),
    .o1 (c1),
    .o2 (c2));

  assign res = (c1 & c2);
endmodule
"""

@verilog function calls_vf_2(z::Wire{3:0v})
  c2 = Wire(3:0v)
  c1, c2[1:0v] = vf(z)
  res = c1 & c2
end

@test calls_vf_2() == """
module calls_vf_2(
  input [3:0] z,
  output [1:0] res);

  wire [1:0] c1;
  wire [3:0] c2;

  vf vf_c1_c2_1_0(
    .x (z),
    .o1 (c1),
    .o2 (c2[1:0]));

  assign res = (c1 & c2);
endmodule
"""


isinteractive() && include("common.jl")

####
#### utilities
####

"""
    $SIGNATURES

Recursive comparison by fields; types and values should match by `==`.
"""
function ≂(x::T, y::T) where T
    fn = fieldnames(T)
    isempty(fn) ? x == y : all(getfield(x, f) ≂ getfield(y, f) for f in fn)
end

≂(x, y) = x == y

"""
Type for testing `≂`.
"""
struct Foo{T}
    a::T
    b::T
end

@testset "≂ comparisons" begin
    @test 1 ≂ 1
    @test 1 ≂ 1.0
    @test [1,2] ≂ [1,2]
    @test [1.0,2.0] ≂ [1,2]
    @test !(1 ≂ 2)
    @test Foo(1,2) ≂ Foo(1,2)
    @test !(Foo(1,2) ≂ Foo(1,3))
    @test !(Foo{Any}(1,2) ≂ Foo(1,2))
end

###
### random booleans
###

@testset "random booleans" begin
    for prob in (1:9) ./ 10
        logprob = log(prob)
        @test abs(mean(rand_bool_logprob(RNG, logprob) for _ in 1:10000) - prob) ≤ 0.01
    end

    # these operations don't call the RNG, this is checked
    RNG′ = copy(RNG)
    @test all(rand_bool_logprob(RNG, 0) for _ in 1:10000)
    @test all(rand_bool_logprob(RNG, 10) for _ in 1:10000)
    @test rand(RNG′) == rand(RNG)
end

###
### test turn statistics
###

@testset "low-level turn statistics" begin
    trajectory = TrajectoryNUTS(nothing, 0, 1, -1000, Val{:generalized})
    n = 3
    ρ₁ = ones(n)
    ρ₂ = 2*ρ₁
    p₁ = ρ₁
    p₂ = -ρ₁
    τ₁ = GeneralizedTurnStatistic(p₁, p₁, ρ₁)
    τ₂ = GeneralizedTurnStatistic(p₂, p₂, ρ₂)
    @test combine_turn_statistics(trajectory, τ₁, τ₂) ≂ GeneralizedTurnStatistic(p₁, p₂, ρ₁+ρ₂)
    @test !is_turning(trajectory, GeneralizedTurnStatistic(p₁, copy(p₁), ρ₁))
    @test is_turning(trajectory, GeneralizedTurnStatistic(p₁, p₂, ρ₁))
    @test is_turning(trajectory, GeneralizedTurnStatistic(p₂, copy(p₂), ρ₁))
    @test is_turning(trajectory, GeneralizedTurnStatistic(p₂, copy(p₂), ρ₁))
end

@testset "low-level visited statistics" begin
    trajectory = TrajectoryNUTS(nothing, 0, 1, -1000, Val{:generalized})
    vs(p, is_initial = false) = leaf_acceptance_statistic(log(p), is_initial)
    x = vs(0.3)
    @test acceptance_rate(x) ≈ 0.3
    y = vs(0.6)
    @test acceptance_rate(y) ≈ 0.6
    x0 = vs(10, true)    # initial node, does not count
    z = reduce((x, y) -> combine_visited_statistics(trajectory, x, y),
               [x, x, y, x0])
    @test acceptance_rate(z) ≈ 0.4
end

# define a distribution which is divergent everywhere except at 0
struct AlwaysDivergentTest
    K::Int
end

function LogDensityProblems.capabilities(::Type{AlwaysDivergentTest})
    LogDensityProblems.LogDensityOrder{1}()
end
LogDensityProblems.dimension(d::AlwaysDivergentTest) = d.K
function LogDensityProblems.logdensity_and_gradient(d::AlwaysDivergentTest, x)
    if all(iszero.(x))
        0.0, zeros(length(x))
    else
        -Inf, nothing
    end
end

@testset "unconditional divergence" begin

    # test NUTS sampler where all movements are divergent
    K = 3
    ℓ = AlwaysDivergentTest(K)
    Q, tree_statistics = sample_tree(RNG, NUTS(), Hamiltonian(GaussianKineticEnergy(K), ℓ),
                                     evaluate_ℓ(ℓ, zeros(K)), 1.0)
    @test is_divergent(tree_statistics.termination)
    @test iszero(tree_statistics.acceptance_rate)
    @test iszero(tree_statistics.depth)
    @test tree_statistics.steps == 1
end

###
### test proposals
###

# NOTE superseded by separating ω and ζ
# @testset "proposal" begin
#     trajectory = TrajectoryNUTS(nothing, 0, 1, -1000)
#     function test_sample(rng, prop1, prop2, bias, prob_prob2; atol = 0.02, N = 10000)
#         count = 0
#         for _ in 1:N
#             prop = combine_proposals(rng, trajectory, prop1, prop2,
#                                      # direction irrelevant for thie method
#                                      rand(Bool),
#                                      # test with given bias
#                                      bias)
#             if prop.z ≂ prop2.z
#                 count += 1
#             else
#                 @test prop.z ≂ prop1.z
#             end
#             @test prop.ω ≈ logaddexp(prop1.ω, prop2.ω)
#         end
#         @test count / N ≈ prob_prob2 atol = atol
#     end
#     prop1 = Proposal(1, log(1.0))
#     prop2 = Proposal(2, log(3.0))
#     prop3 = Proposal(3, log(1/3))
#     test_sample(RNG, prop1, prop2, true, 1; atol = 0, N = 100)
#     test_sample(RNG, prop1, prop2, false, 0.75)
#     test_sample(RNG, prop1, prop3, true, 1/3)
#     test_sample(RNG, prop1, prop3, false, 0.25)
# end


mutable struct MultiCarRacingEnv{A,T,R<:AbstractRNG} <: AbstractEnv
    N::Int
    envs::Vector{CarRacingEnv}
    action_space::A
    observation_space::Space{Vector{A}}
    state::Vector{T}
    done::Bool
    t::Int
    dt::T
    δt::T
    track::Track
    rng::R
end

"""
    MultiCarRacingEnv(;kwargs...)

# Keyword arguments
- `T = Float64`
- `N = 2                                                     # Number of cars`
- `dt = 0.1`,                                                # Time step between actions
- `δt = 0.01,                                                # Time step used for integration`
- `track = string(@__DIR__, "/car_racing_tracks/curve.csv"), # Track to load`
- 'car_params = []                                           # Vector of car parameters to use
- `rng = Random.GLOBAL_RNG`
"""
function MultiCarRacingEnv(N=2;
    T = Float64,
    dt = 0.1,
    δt = 0.01,
    track = string(@__DIR__, "/car_racing_tracks/curve.csv"),
    car_params = [],
    rng = Random.GLOBAL_RNG,
)

    length(car_params) <= N || error("# Car parameters must be ≤ # cars")

    envs = Vector{CarRacingEnv}(undef, N)
    for ii in 1:N
        if length(car_params) >= ii
            cre = CarRacingEnv(car_params, T=T, dt=dt, δt=δt, track=track, rng=rng)
        else
            cre = CarRacingEnv(T=T, dt=dt, δt=δt, track=track, rng=rng)
        end
        envs[ii] = cre
    end

    endpts_l = []
    endpts_r = []
    single_state_size = length(RLBase.state_space(envs[1]))
    obs_space_vec = Vector{ClosedInterval}(undef, N*single_state_size)
    state = zeros(T, N*single_state_size)
    for (idx, en) in enumerate(envs)
        endpts_l = [endpts_l; leftendpoint(RLBase.action_space(en))]
        endpts_r = [endpts_r; rightendpoint(RLBase.action_space(en))]
        start_idx = single_state_size*(idx-1) + 1
        end_idx = single_state_size*idx
        obs_space_vec[start_idx:end_idx] =  RLBase.state_space(en)[:]
    end
    action_space = ClosedInterval{Vector{T}}(endpts_l, endpts_r)
    observation_space = Space(obs_space_vec)

    env = MultiCarRacingEnv(
        N,
        envs,
        action_space,
        observation_space,
        state,
        false,
        0,
        dt,
        δt,
        Track(track),
        rng,
    )

    reset!(env)
    env
end

MultiCarRacingEnv{T}(; kwargs...) where {T} = MultiCarRacingEnv(; T = T, kwargs...)

# Might not want to have the same seed for every envrionment here
function Random.seed!(env::MultiCarRacingEnv, seed)
    for en in env.envs
        Random.seed!(en.rng, seed)
    end
end

RLBase.action_space(env::MultiCarRacingEnv) = env.action_space
RLBase.state_space(env::MultiCarRacingEnv{T}) where {T} = env.observation_space
RLBase.is_terminated(env::MultiCarRacingEnv) = env.done
RLBase.state(env::MultiCarRacingEnv) = env.state

function _update_states_env2envs(env::MultiCarRacingEnv)
    for (idx, en) in enumerate(env.envs)
        ss_size = length(RLBase.state_space(en))
        start_idx = ss_size*(idx-1) + 1
        end_idx = ss_size*idx
        en.state = env.state[start_idx:end_idx]
    end
end

function _update_states_envs2env(env::MultiCarRacingEnv)
    for (idx, en) in enumerate(env.envs)
        ss_size = length(RLBase.state_space(en))
        start_idx = ss_size*(idx-1) + 1
        end_idx = ss_size*idx
        env.state[start_idx:end_idx] = en.state
    end
end

function within_track(env::MultiCarRacingEnv)
    within = true
    for en in env.envs
        within = within && within_track(en).within
    end
    return within
end

function exceed_β(env::MultiCarRacingEnv)
    exceed = false
    for en in env.envs
        exceed = exceed || exceed_β(en)
    end
    return exceed
end

"""
    reward(env::MultiCarRacingEnv)
    - Sum over rewards of the ohter envrionments
    - Collision (≤ 4m): -7000
    - Penalize distance away by the distance amount
"""
function RLBase.reward(env::MultiCarRacingEnv{T}) where {T}
    rew = 0.0
    for (ii, en) in enumerate(env.envs)
        rew += reward(en)
        for jj ∈ (ii+1):env.N
            Δd = norm(env.envs[jj].state[1:2] - en.state[1:2])
            rew += -Δd
            if Δd ≤ 4.0
                rew += -11000.0
            end
        end
    end
    return rew
end

function RLBase.reset!(env::MultiCarRacingEnv{A,T}) where {A,T}
    ss_size = length(env.state)
    ind_ss_size = round(Int, length(env.state)/env.N)
    env.envs[1].state = zeros(T, ind_ss_size)
    env.envs[1].state[3] = deg2rad(90)
    env.envs[1].state[4] = 10.0
    for ii ∈ 2:env.N
        if mod(ii,2) == 0
            env.envs[ii].state[1] = ii/2*5.0
        else
            env.envs[ii].state[1] = (1-ii)/2*5.0
        end
        env.envs[ii].state[3] = deg2rad(90)
        env.envs[ii].state[4] = 10.0
    end

    _update_states_envs2env(env)
    env.t = 0
    env.done = false
    nothing
end

function RLBase.reset!(env::MultiCarRacingEnv{A,T}, state::Vector{T}) where {A,T}
    env.state = state
    _update_states_env2envs(env)
    env.t = 0
    env.done = false
    nothing
end

"""
    a::Vector{Float64}
    size = (2N,)
    a[1] = Turn angle [-max turn angle, max turn angle] (-1 right turn, +1 left turn)
    a[2] = Pedal amount (-1 = full brake, 1 = full throttle)
    a[3] = Turn angle Car 2
    a[4] = Pedal amount Car 2
    a[2N-1] = turn angle Car N
    a[2N] = Pedal amount Car N
"""
function (env::MultiCarRacingEnv)(a::Vector{Float64})
    length(a) == env.N * 2 || error("Action space of each car is of size 2")
    for (ii, en) in enumerate(env.envs)
        aᵢ = a[(2*ii-1):(2*ii)]
        _step!(en, aᵢ)
    end
    _update_states_envs2env(env)
end

function (env::MultiCarRacingEnv)(a::Vector{Int})
    env(Float64.(a))
end

function (env::MultiCarRacingEnv)(a::Matrix{Float64})
    size(a)[2] == 1 || error("Only implented for one step")
    env(vec(a))
end


# This file was generated, do not modify it. # hide
X = MLJ.transform(fit!(machine(Standardizer(), X)), X);


#!/usr/bin/env julia

using Plots
pyplot()

julia1 = readdlm("julia_times-1.dat")
julia4 = readdlm("julia_times-4.dat")
python = readdlm("python_times.dat")

plot(xaxis = (:log,), yaxis = (:log,),
     xlabel = "Datapoints", ylabel = "Time (seconds)",
     size=(900, 600))
plot!(julia1[:,1], julia1[:,2], linewidth = 2, marker = (:auto,), color = :blue,   lab = "LombScargle.jl - single thread")
plot!(julia4[:,1], julia4[:,2], linewidth = 2, marker = (:auto,), color = :orange, lab = "LombScargle.jl - 4 threads")
plot!(python[:,1], python[:,2], linewidth = 2, marker = (:auto,), color = :green,  lab = "Astropy")
savefig("benchmarks.svg")
savefig("benchmarks.png")


abstract type AbstractModel{T} end

function Base.show(io::IO, m::AbstractModel)
    @printf io "Dynamic Stochastic General Equilibrium Model\n"
    @printf io "no. states:             %i\n" n_states(m)
    @printf io "no. anticipated shocks: %i\n" n_anticipated_shocks(m)
    @printf io "data vintage:           %s\n" data_vintage(m)
    @printf io "description:\n %s\n"          description(m)
end

@inline function Base.getindex(m::AbstractModel, i::Integer)
    if i <= (j = length(m.parameters))
        return m.parameters[i]
    else
        return m.steady_state[i-j]
    end
end

# need to define like this so we can disable bounds checking
@inline function Base.getindex(m::AbstractModel, k::Symbol)
    i = m.keys[k]
    @inbounds if i <= (j = length(m.parameters))
        return m.parameters[i]
    else
        return m.steady_state[i-j]
    end
end

@inline function Base.setindex!(m::AbstractModel, value::Number, i::Integer)
    if i <= (j = length(m.parameters))
        param = m.parameters[i]
        param.value = value
        if isa(param, ScaledParameter)
            param.scaledvalue = param.scaling(value)
        end
        return param
    else
        steady_state_param = m.steady_state[i-j]
        steady_state_param.value = value
        return steady_state_param
    end
end

"""
```
setindex!(m::AbstractModel, param::AbstractParameter, i::Integer)
```

If `i`<length(m.parameters), overwrites m.parameters[i] with
param. Otherwise, overwrites m.steady_state[i-length(m.parameters).
"""
@inline function Base.setindex!(m::AbstractModel, param::AbstractParameter, i::Integer)
    if i <= (j = length(m.parameters))
        m.parameters[i] = param
    else
        m.steady_state[i-j] = param
    end
    return param
end

Base.setindex!(m::AbstractModel, value, k::Symbol) = Base.setindex!(m, value, m.keys[k])

"""
```
(<=){T}(m::AbstractModel{T}, p::AbstractParameter{T})
```

Syntax for adding a parameter to a model: m <= parameter.
NOTE: If `p` is added to `m` and length(m.steady_state) > 0, `keys(m)` will not generate the
index of `p` in `m.parameters`.
"""
function (<=){T}(m::AbstractModel{T}, p::AbstractParameter{T})

    if !in(p.key, keys(m.keys))

        new_param_index = length(m.keys) + 1

        # grow parameters and add the parameter
        push!(m.parameters, p)

        # add parameter location to dict
        setindex!(m.keys, new_param_index, p.key)
    else
        # overwrite the previous parameter with the new one
        setindex!(m, p, p.key)
    end
end

"""
```
(<=){T}(m::AbstractModel{T}, ssp::SteadyStateParameter)
```

Add a new steady-state value to the model by appending `ssp` to the `m.steady_state` and
adding `ssp.key` to `m.keys`.
"""
function (<=){T}(m::AbstractModel{T}, ssp::SteadyStateParameter)

    if !in(ssp.key, keys(m.keys))
        new_param_index = length(m.keys) + 1

        # append ssp to steady_state vector
        push!(m.steady_state, ssp)

        # add parameter location to dict
        setindex!(m.keys, new_param_index, ssp.key)
    else
        # overwrite the previous parameter with the new one
        setindex!(m, ssp, ssp.key)
    end
end

Distributions.logpdf(m::AbstractModel) = logpdf(m.parameters)
Distributions.pdf(m::AbstractModel) = exp(logpdf(m))

# Number of anticipated policy shocks
n_anticipated_shocks(m::AbstractModel) = get_setting(m, :n_anticipated_shocks)
n_anticipated_shocks_padding(m::AbstractModel) = get_setting(m, :n_anticipated_shocks_padding)

# Dates, indices, number of periods for each regime
date_presample_start(m::AbstractModel) = get_setting(m, :date_presample_start)
date_mainsample_start(m::AbstractModel) = get_setting(m, :date_mainsample_start)
date_zlb_start(m::AbstractModel) = get_setting(m, :date_zlb_start)

date_presample_end(m::AbstractModel) = Dates.lastdayofquarter(get_setting(m, :date_mainsample_start) - Dates.Month(3))
date_prezlb_end(m::AbstractModel) = Dates.lastdayofquarter(get_setting(m, :date_zlb_start) - Dates.Month(3))
date_mainsample_end(m::AbstractModel) = Dates.lastdayofquarter(get_setting(m, :date_forecast_start) - Dates.Month(3))
date_conditional_end(m::AbstractModel) = get_setting(m, :date_conditional_end)

index_presample_start(m::AbstractModel) = 1
index_mainsample_start(m::AbstractModel) = subtract_quarters(date_mainsample_start(m), date_presample_start(m)) + 1
index_zlb_start(m::AbstractModel) = subtract_quarters(date_zlb_start(m), date_presample_start(m)) + 1
index_forecast_start(m::AbstractModel) = subtract_quarters(date_forecast_start(m), date_presample_start(m)) + 1

"""
```
index_shockdec_start(m::AbstractModel)
```

Returns the index starting from which the shock decomposition is saved, where 1 is the index corresponding to date_mainsample_start(m).
"""
index_shockdec_start(m::AbstractModel) = subtract_quarters(date_shockdec_start(m), date_mainsample_start(m)) + 1

"""
```
index_shockdec_end(m::AbstractModel)
```

Returns the last index for which the shock decomposition is saved, where 1 is the index corresponding to date_mainsample_start(m).
"""
index_shockdec_end(m::AbstractModel) = subtract_quarters(date_shockdec_end(m), date_mainsample_start(m)) + 1

n_presample_periods(m::AbstractModel)   = subtract_quarters(date_mainsample_start(m), date_presample_start(m))
n_prezlb_periods(m::AbstractModel)      = subtract_quarters(date_zlb_start(m), date_mainsample_start(m))
n_zlb_periods(m::AbstractModel)         = subtract_quarters(date_forecast_start(m), date_zlb_start(m))
n_mainsample_periods(m::AbstractModel)  = subtract_quarters(date_forecast_start(m), date_mainsample_start(m))
n_conditional_periods(m::AbstractModel) = subtract_quarters(date_conditional_end(m), date_mainsample_end(m))

inds_presample_periods(m::AbstractModel)  = collect(index_presample_start(m):(index_mainsample_start(m)-1))
inds_prezlb_periods(m::AbstractModel)     = collect(index_mainsample_start(m):(index_zlb_start(m)-1))
inds_zlb_periods(m::AbstractModel)        = collect(index_zlb_start(m):(index_forecast_start(m)-1))
inds_mainsample_periods(m::AbstractModel) = collect(index_mainsample_start(m):(index_forecast_start(m)-1))

# Number of a few things that are useful
n_states(m::AbstractModel)                  = length(m.endogenous_states)
n_states_augmented(m::AbstractModel)        = n_states(m) + length(m.endogenous_states_augmented)
n_shocks_exogenous(m::AbstractModel)        = length(m.exogenous_shocks)
n_shocks_expectational(m::AbstractModel)    = length(m.expected_shocks)
n_observables(m::AbstractModel)             = length(m.observables)
n_pseudo_observables(m::AbstractModel)      = length(m.pseudo_observables)
n_equilibrium_conditions(m::AbstractModel)  = length(m.equilibrium_conditions)
n_parameters(m::AbstractModel)              = length(m.parameters)
n_parameters_steady_state(m::AbstractModel) = length(m.steady_state)
n_parameters_free(m::AbstractModel)         = sum([!α.fixed for α in m.parameters])

"""
```
get_key(m, class, index)
```

Returns the name of the state (`class = :states`), observable (`:obs`),
pseudo-observable (`:pseudo`), or shock (`:shocks` or `:stdshocks`)
corresponding to the given `index`.
"""
function get_key(m::AbstractModel, class::Symbol, index::Int)
    dict = if class == :states
        m.endogenous_states
    elseif class == :obs
        m.observables
    elseif class == :pseudo
        m.pseudo_observables
    elseif class in [:shocks, :stdshocks]
        m.exogenous_shocks
    else
        throw(ArgumentError("Invalid class: $class. Must be :states, :obs, :pseudo, :shocks, or :stdshocks"))
    end

    out = Base.filter(key -> dict[key] == index, collect(keys(dict)))
    if length(out) == 0
        error("Key corresponding to index $index not found for class: $class")
    elseif length(out) > 1
        error("Multiple keys corresponding to index $index found for class: $class")
    else
        return out[1]
    end
end

# Parse population mnemonic into 2 Nullable{Symbol}s from one
function parse_population_mnemonic(m::AbstractModel)
    mnemonic = get_setting(m, :population_mnemonic)
    if isnull(mnemonic)
        return [Nullable{Symbol}(), Nullable{Symbol}()]
    else
        return map(s -> Nullable(Symbol(s)), split(string(get(mnemonic)), DSGE_DATASERIES_DELIM))
    end
end

# From an augmented state space with anticipated policy shocks, get indices
# corresponding to pre-ZLB states, shocks, and observables
function inds_states_no_ant(m::AbstractModel)
    if n_anticipated_shocks(m) > 0
        ind_ant1 = m.endogenous_states[:rm_tl1]
        ind_antn = m.endogenous_states[Symbol("rm_tl$(n_anticipated_shocks(m))")]
        return [1:(ind_ant1-1); (ind_antn+1):n_states_augmented(m)]
    else
        return collect(1:n_states_augmented(m))
    end
end

function inds_shocks_no_ant(m::AbstractModel)
    if n_anticipated_shocks(m) > 0
        ind_ant1 = m.exogenous_shocks[:rm_shl1]
        ind_antn = m.exogenous_shocks[Symbol("rm_shl$(n_anticipated_shocks(m))")]
        return [1:(ind_ant1-1); (ind_antn+1):n_shocks_exogenous(m)]
    else
        return collect(1:n_shocks_exogenous(m))
    end
end

function inds_obs_no_ant(m::AbstractModel)
    if n_anticipated_shocks(m) > 0
        ind_ant1 = m.observables[:obs_nominalrate1]
        ind_antn = m.observables[Symbol("obs_nominalrate$(n_anticipated_shocks(m))")]
        return [1:(ind_ant1-1); (ind_antn+1):n_observables(m)]
    else
        return collect(1:n_observables(m))
    end
end

# Interface for I/O settings
spec(m::AbstractModel)         = m.spec
subspec(m::AbstractModel)      = m.subspec
saveroot(m::AbstractModel)     = get_setting(m, :saveroot)
dataroot(m::AbstractModel)     = get_setting(m, :dataroot)

# Interface for data
data_vintage(m::AbstractModel)    = get_setting(m, :data_vintage)
data_id(m::AbstractModel)         = get_setting(m, :data_id)
cond_vintage(m::AbstractModel)    = get_setting(m, :cond_vintage)
cond_id(m::AbstractModel)         = get_setting(m, :cond_id)
cond_full_names(m::AbstractModel) = get_setting(m, :cond_full_names)
cond_semi_names(m::AbstractModel) = get_setting(m, :cond_semi_names)
use_population_forecast(m::AbstractModel) = get_setting(m, :use_population_forecast)
hpfilter_population(m::AbstractModel)     = get_setting(m, :hpfilter_population)

# Interface for general computation settings
use_parallel_workers(m::AbstractModel)    = get_setting(m, :use_parallel_workers)

# Interface for estimation settings
reoptimize(m::AbstractModel)          = get_setting(m, :reoptimize)
calculate_hessian(m::AbstractModel) = get_setting(m, :calculate_hessian)
hessian_path(m::AbstractModel)      = get_setting(m, :hessian_path)
n_hessian_test_params(m::AbstractModel) = get_setting(m, :n_hessian_test_params)

# Interface for Metropolis-Hastings settings
n_mh_blocks(m::AbstractModel)      =  get_setting(m, :n_mh_blocks)
n_mh_simulations(m::AbstractModel) =  get_setting(m, :n_mh_simulations)
n_mh_burn(m::AbstractModel)        =  get_setting(m, :n_mh_burn)
mh_thin(m::AbstractModel)          =  get_setting(m, :mh_thin)

# Interface for forecast settings
date_forecast_start(m::AbstractModel)   = get_setting(m, :date_forecast_start)
forecast_block_size(m::AbstractModel)   = get_setting(m, :forecast_block_size)
forecast_start_block(m::AbstractModel)  = get_setting(m, :forecast_start_block)
forecast_input_file_overrides(m::AbstractModel) = get_setting(m, :forecast_input_file_overrides)
forecast_uncertainty_override(m::AbstractModel) = get_setting(m, :forecast_uncertainty_override)
forecast_smoother(m::AbstractModel)     = get_setting(m, :forecast_smoother)
forecast_tdist_df_val(m::AbstractModel) = get_setting(m, :forecast_tdist_df_val)
forecast_tdist_shocks(m::AbstractModel) = get_setting(m, :forecast_tdist_shocks)
forecast_zlb_value(m::AbstractModel)    = get_setting(m, :forecast_zlb_value)
impulse_response_horizons(m::AbstractModel) = get_setting(m, :impulse_response_horizons)
n_shockdec_periods(m::AbstractModel)    = index_shockdec_end(m) - index_shockdec_start(m) + 1

# Interface for alternative policy settings
alternative_policy(m::AbstractModel) = get_setting(m, :alternative_policy)

function date_forecast_end(m::AbstractModel)
    date = date_forecast_start(m) + Dates.Month(3 * (forecast_horizons(m)-1))
    return Dates.lastdayofquarter(date)
end

function forecast_horizons(m::AbstractModel; cond_type::Symbol = :none)
    horizons = get_setting(m, :forecast_horizons)
    if cond_type == :none
        return horizons
    else
        return horizons - n_conditional_periods(m)
    end
end

function date_shockdec_start(m::AbstractModel)
    startdate = get_setting(m, :shockdec_startdate)
    if !isnull(startdate)
        return get(startdate)
    else
        return date_mainsample_start(m)
    end
end

function date_shockdec_end(m::AbstractModel)
    enddate =  get_setting(m, :shockdec_enddate)
    if !isnull(enddate)
        return get(enddate)
    else
        return date_forecast_end(m)
    end
end

"""
```
load_parameters_from_file(m::AbstractModel,path::String)
```
Returns a vector of parameters, read from a file, suitable for updating `m`.
"""
function load_parameters_from_file(m::AbstractModel, path::String)

    if isfile(path) && splitext(path)[2] == ".h5"
        x  = h5open(path, "r") do file
            try
                read(file, "params")
            catch
                error("$path does not contain variable params")
            end
        end
    else
        error("$path is not a valid HDF5 file.")
    end

    @assert length(x) == length(m.parameters)
    @assert eltype(x) == typeof(m.parameters[1].value)
    return x
end

"""
```
specify_mode!(m::AbstractModel, mode_file::String=""; verbose=:low)
```

Updates the values of `m.parameters` with the values from
`mode_file`. Sets `reoptimize` setting to `false`.

Usage: should be run before calling `estimate(m)`, e.g.:

    m = Model990()
    specify_mode!(m, modefile)
    estimate(m)
"""
function specify_mode!(m::AbstractModel, mode_file::String = ""; verbose=:low)

    m <= Setting(:reoptimize, false)

    if mode_file == ""
        mode_file = inpath(m, "user", "paramsmode.h5")
    end
    mode_file = normpath(mode_file)

    update!(m,load_parameters_from_file(m,mode_file))

    if VERBOSITY[verbose] >= VERBOSITY[:low]
        println("Loaded previous mode from $mode_file.")
    end

end

"""
```
specify_hessian(m::AbstractModel, path::String=""; verbose=:low)
```

Specify a Hessian matrix calculated at the posterior mode to use in the model estimation. If
no path is provided, will attempt to detect location.
"""
function specify_hessian(m::AbstractModel, path::String=""; verbose=:low)
    if isempty(path)
        path = inpath(m, "user", "hessian.h5")
    end

    if isfile(path) && splitext(path)[2] == ".h5"
        m <= Setting(:hessian_path, normpath(abspath(path)))
    else
        error("Invalid input Hessian file: $path",)
    end

    m <= Setting(:calculate_hessian, false)

    if VERBOSITY[verbose] >= VERBOSITY[:low]
        println("Specified hessian from $path.")
    end
end

#=
Build paths to where input/output/results data are stored.

Description:
Creates the proper directory structure for input and output files, treating the DSGE/save
    directory as the root of a savepath directory subtree. Specifically, the following
    structure is implemented:

    dataroot/

    savepathroot/
                 output_data/<spec>/<subspec>/log/
                 output_data/<spec>/<subspec>/<out_type>/raw/
                 output_data/<spec>/<subspec>/<out_type>/work/
                 output_data/<spec>/<subspec>/<out_type>/tables/
                 output_data/<spec>/<subspec>/<out_type>/figures/

Note: we refer to the savepathroot/output_data/<spec>/<subspec>/ directory as saveroot.
=#
# """
# ```
# logpath(model)
# ```
# Returns path to log file. Path built as
# ```
# <output root>/output_data/<spec>/<subspec>/log/log_<filestring>.log
# ```
# """
# function logpath(m::AbstractModel)
#     return savepath(m, "log", "log.log")
# end

strs = [:work, :raw, :tables, :figures, :log]
fns = [Symbol(x, "path") for x in strs]
for (str, fn) in zip(strs, fns)
    @eval begin
        # First eval function
        function $fn(m::AbstractModel,
                     out_type::String,
                     file_name::String = "",
                     filestring_addl::Vector{String}=Vector{String}())
            return savepath(m, out_type, $(string(str)), file_name, filestring_addl)
        end

        # Then, add docstring to it
        @doc $(
        """
        ```
        $fn(m::AbstractModel, out_type::String, file_name::String="", filestring_addl::Vector{String}=Vector{String}())
        ```

        Returns path to specific $str output file, creating containing directory as needed. If
        `file_name` not specified, creates and returns path to containing directory only. Path built
        as
        ```
        <output root>/output_data/<spec>/<subspec>/<out_type>/$str/<file_name>_<filestring>.<ext>
        ```
        """
        ) $fn
    end
end

# Not exposed to user. Actually create path and insert model string to file name.
function savepath(m::AbstractModel,
                  out_type::String,
                  sub_type::String,
                  file_name::String = "",
                  filestring_addl::Vector{String} = Vector{String}())
    # Containing directory
    dir = String(joinpath(saveroot(m), "output_data", spec(m), subspec(m), out_type, sub_type))

    if !isempty(file_name)
        base = filestring_base(m)
        return savepath(dir, file_name, base, filestring_addl)
    else
        return dir
    end
end

function savepath(dir::String,
                  file_name::String = "",
                  filestring_base::Vector{String} = Vector{String}(),
                  filestring_addl::Vector{String} = Vector{String}())
    if !isdir(dir)
        mkpath(dir)
    end

    if !isempty(file_name)
        (base, ext) = splitext(file_name)
        myfilestring = filestring(filestring_base, filestring_addl)
        file_name_detail = base * myfilestring * ext

        return joinpath(dir, file_name_detail)
    else
        return dir
    end
end

# Input data handled slightly differently, because it is not model-specific.
"""
```
inpath{T<:String}(m::AbstractModel, in_type::T, file_name::T="")
```

Returns path to specific input data file, creating containing directory as needed. If
`file_name` not specified, creates and returns path to containing directory only. Valid
`in_type` includes:

* `\"raw\"`: raw input series
* `\"data\"`: transformed data in model units
* `\"cond\"`: conditional data - nowcasts for the current forecast quarter, or related
* `\"user\"`: user-supplied data for starting parameter vector, hessian, or related
* `\"scenarios\"`: alternative scenarios

Path built as
```
<data root>/<in_type>/<file_name>
```
"""
function inpath(m::AbstractModel, in_type::String, file_name::String="")
    path = dataroot(m)
    # Normal cases.
    if in_type in ["raw", "data", "cond", "scenarios"]
        path = joinpath(path, in_type)
    # User-provided inputs. May treat this differently in the future.
    elseif in_type == "user"
        path = joinpath(path, "user")
    else
        error("Invalid in_type: ", in_type)
    end

    # Containing dir
    if !isdir(path)
        mkpath(path)
    end

    # If file_name provided, return full path
    if !isempty(file_name)
        path = joinpath(path, file_name)
    end

    return path
end

function filestring_base(m::AbstractModel)
    if !m.testing
        base = Vector{String}()
        for (skey, sval) in m.settings
            if sval.print
                push!(base, to_filestring(sval))
            end
        end
        return base
    else
        return ["test"]
    end
end

filestring(m::AbstractModel) = filestring(m, Vector{String}())
filestring(m::AbstractModel, d::String) = filestring(m, [String(d)])
function filestring(m::AbstractModel, d::Vector{String})
    base = filestring_base(m)
    return filestring(base, d)
end

function filestring(base::Vector{String}, d::Vector{String})
    filestrings = vcat(base, d)
    sort!(filestrings)
    return "_" * join(filestrings, "_")
end

function filestring(d::Vector{String})
    sort!(d)
    return "_" * join(d, "_")
end

"""
```
transform_to_model_space!{T<:AbstractFloat}(m::AbstractModel, values::Vector{T})
```

Transforms `values` from the real line to the model space, and assigns `values[i]` to
`m.parameters[i].value` for non-steady-state parameters. Recomputes the steady-state
paramter values.

### Arguments
- `m`: the model object
- `values`: the new values to assign to non-steady-state parameters.
"""
function transform_to_model_space!{T<:AbstractFloat}(m::AbstractModel, values::Vector{T})
    new_values = transform_to_model_space(m.parameters, values)
    update!(m, new_values)
    steadystate!(m)
end

"""
```
update!{T<:AbstractFloat}(m::AbstractModel, values::Vector{T})
```

Update `m.parameters` with `values`, recomputing the steady-state parameter values.

### Arguments:
- `m`: the model object
- `values`: the new values to assign to non-steady-state parameters.
"""
function update!{T<:AbstractFloat}(m::AbstractModel, values::Vector{T})
    update!(m.parameters, values)
    steadystate!(m)
end

"""
```
rand(d::DegenerateMvNormal, m::AbstractModel; cc::AbstractFloat = 1.0)
```

Generate a draw from `d` with variance optionally scaled by `cc^2`.
"""
function rand(d::DegenerateMvNormal, m::AbstractModel; cc::AbstractFloat = 1.0)
    return d.μ + cc*d.σ*randn(m.rng, length(d))
end

"""
`rand_prior(m::AbstractModel; ndraws::Int = 100_000)`

Draw a random sample from the model's prior distribution.
"""
function rand_prior(m::AbstractModel; ndraws::Int = 100_000)
    T = typeof(m.parameters[1].value)
    npara = length(m.parameters)
    priorsim = Array{T}(ndraws, npara)

    for i in 1:ndraws
        priodraw = Array{T}(npara)

        # Parameter draws per particle
        for j in 1:length(m.parameters)

            priodraw[j] = if !m.parameters[j].fixed
                prio = rand(m.parameters[j].prior.value)

                # Resample until all prior draws are within the value bounds
                while !(m.parameters[j].valuebounds[1] < prio < m.parameters[j].valuebounds[2])
                    prio = rand(m.parameters[j].prior.value)
                end

                prio
            else
                m.parameters[j].value
            end
        end
        priorsim[i,:] = priodraw'
    end

    priorsim
end

"""
```
type ShockGroup
```

The `ShockGroup` type is used in `prepare_means_table_shockdec` and
`plot_shock_decompositions`. When plotting shock decompositions, we usually want
to group the shocks into categories (financial, monetary policy, etc.) so that
the resulting grouped bar plot is legible.

### Fields

- `name::String`
- `shocks::Vector{Symbol}`
- `color::Colorant`
"""
type ShockGroup
    name::String
    shocks::Vector{Symbol}
    color::Colorant
end

function ShockGroup(name::String, shocks::Vector{Symbol}, color_name::Symbol)
    color = parse(Colorant, color_name)
    return ShockGroup(name, shocks, color)
end


export TR_Nwt

function TR_Nwt(h :: AbstractNLPModel,
				nlpstop :: AbstractStopping;
				kwargs...)

	optimal, stop = TR_generic(h, nlpstop; direction = :Nwt, kwargs...)
	return optimal, stop
end


module RandUtils

using Compat

if VERSION >= v"0.7.0-DEV.3406"
    using Random
end
if VERSION >= v"0.7.0-DEV.3389"
    using SparseArrays
end

const SEED = MersenneTwister(1)
const DEFAULT_ELTYPE = typeof(rand())

samerand(args...) = rand(deepcopy(SEED), args...)
samesprand(args...) = sprand(deepcopy(SEED), args...)

samesprandbool(args...) = sprand(deepcopy(SEED), Bool, args...)

randvec(T, n) = samerand(T, n)
randvec(n) = samerand(n)

randmat(T, n) = samerand(T, n, n)
randmat(n) = samerand(n, n)

export samerand, samesprand, samesprandbool, randvec, randmat

end


module ExampleBase

import DifferentialEquations: solve, solve!
using DiffEqBase: init
using Parameters: @with_kw

using ...LyapunovExponents: LEProblem, ContinuousLEProblem, DiscreteLEProblem,
    LESolver, lyapunov_exponents
import ...LyapunovExponents: dimension, report

"""
A type to hold an example dynamical system and its known Lyapunov exponents.
"""
@with_kw struct LEExample{ProblemType}
    name
    phase_dynamics
    u0
    t_renorm
    param
    tangent_dynamics
    t_attr
    known_exponents
    atol
    rtol
    terminator_options
    integrator_options = []
end

LEExample{ProblemType}(
            name,
            phase_dynamics,
            u0,
            t_renorm,
            param,
            tangent_dynamics,
            t_attr,
            known_exponents,
            atol,
            rtol,
            terminator_options;
            kwargs...
        ) where {ProblemType} =
    LEExample{ProblemType}(;
            name = name,
            phase_dynamics = phase_dynamics,
            u0 = u0,
            t_renorm = t_renorm,
            param = param,
            tangent_dynamics = tangent_dynamics,
            t_attr = t_attr,
            known_exponents = known_exponents,
            atol = atol,
            rtol = rtol,
            terminator_options = terminator_options,
            kwargs...)

const ContinuousExample = LEExample{ContinuousLEProblem}
const DiscreteExample = LEExample{DiscreteLEProblem}

function LEProblem(example::LEExample{P}; kwargs...) where {P <: LEProblem}
    t_tran = example.t_attr * 0.1
    if P <: DiscreteLEProblem
        t_tran = ceil(typeof(example.t_attr), digits=t_tran)
    end
    P(example.phase_dynamics,
      example.u0,
      example.param;
      t_renorm = example.t_renorm,
      t_attr = example.t_attr,
      t_tran = t_tran,
      tangent_dynamics = example.tangent_dynamics,
      kwargs...)
end

dimension(example::LEExample) = length(example.u0)

function solve(example::LEExample;
               dim_lyap=dimension(example), kwargs...)
    solve(LEProblem(example; dim_lyap=dim_lyap);
          record = true,
          terminator = true,
          terminator_options = example.terminator_options,
          integrator_options = example.integrator_options,
          kwargs...)
end

mutable struct LEDemo
    example::LEExample
    prob::LEProblem
    solver::LESolver

    LEDemo(example, prob) = new(example, prob)
end

"""
    LEDemo(example::LEExample; <keyword arguments>)

Here is an example code for constructing an example dynamical system,
calculate its LEs and plot them:
```julia
using LyapunovExponents
using Plots
demo = solve!(LyapunovExponents.lorenz_63())
plot(demo)
```

Create a `LEDemo` holding an `example` and an appropriate `LEProblem`
created from the `example`.
"""
function LEDemo(example::LEExample; kwargs...)
    LEDemo(example, LEProblem(example; kwargs...))
end

"""
    solve!(demo::LEDemo; progress=-1, <keyword arguments>)

Initialize `demo.solver` from `demo.prob` and run
`solve!(demo.solver)` to calculate the Lyapunov exponents.
"""
function solve!(demo::LEDemo; progress = -1, kwargs...)
    demo.solver = init(demo.prob;
                       record = true,
                       terminator = true,
                       terminator_options = demo.example.terminator_options,
                       integrator_options = demo.example.integrator_options,
                       kwargs...)
    solve!(demo.solver,
           progress = progress)
    return demo
end

function Base.show(io::IO, demo::LEDemo)
    print(io, "Demo: ", demo.example.name)
    if isdefined(demo, :solver)
        print(io, ", ", demo.solver)
    else
        print(io, " [solver not initialized]")
    end
end

function report(io::IO, demo::LEDemo;
                convergence::Bool = true)
    printstyled(io, "Lyapunov Exponents Demo", color=:blue)
    println(io, ": ", demo.example.name)

    if isdefined(demo, :solver)
        report(io, demo.solver; convergence = false)
    else
        printstyled(io, "[solver not initialized]", color=:red)
        println(io)
    end

    if ! isempty(demo.example.known_exponents)
        known_exponents = demo.example.known_exponents

        printstyled(io, "Known LEs", color=:yellow)
        print(io, ": ")
        show(IOContext(io, :limit => true), known_exponents)
        println(io)

        LEs = lyapunov_exponents(demo.solver)
        dim = min(length(known_exponents), length(LEs))
        actual = LEs[1:dim]
        desired = known_exponents[1:dim]
        abserr = abs.(actual .- desired)
        relerr = abserr ./ max.(abs.(actual), abs.(desired))

        printstyled(io, "Abs. Error", color=:yellow)
        print(io, ": ")
        show(IOContext(io, :limit => true), abserr)
        println(io)

        printstyled(io, "Rel. Error", color=:yellow)
        print(io, ": ")
        show(IOContext(io, :limit => true), relerr)
        println(io)
   end

    if convergence
        report(io, demo.solver.sol.convergence)
    end
end

end


#####################
# Circuit line types to interface between file parsers and circuit compilers
#####################

"""Circuit and vtree node ids used for IO"""
const ID = UInt32

"""A parsed circuit file format line"""
abstract type CircuitFormatLine <: FormatLine end

"""A file consisting for circuit formal lines"""
const CircuitFormatLines = AbstractVector{<:CircuitFormatLine}

"""A string comment line for circuit files"""
struct CircuitCommentLine <: CircuitFormatLine
    comment::AbstractString
end

"""A header line for circuit files"""
abstract type CircuitHeaderLine <: CircuitFormatLine end

"""A circuit format line without child IDs"""
abstract type LeafCircuitLine <: CircuitFormatLine end

"""A circuit format line with child IDs"""
abstract type InnerCircuitLine <: CircuitFormatLine end

"""A line that represents a logical literal"""
abstract type LiteralLine <: LeafCircuitLine end

struct SddHeaderLine <: CircuitHeaderLine
    num_nodes::Int
end

struct PsddHeaderLine <: CircuitHeaderLine
    num_nodes::Int
end

struct LcHeaderLine <: CircuitHeaderLine end

"""
A line representing a weighted single literal (for example a logistic circuit literal).
"""
struct WeightedLiteralLine <: LiteralLine
    node_id::ID
    vtree_id::ID
    literal::Lit
    normalized::Bool
    weights::Vector{Float64}
end

"""
A line representing a single literal without parameters.
"""
struct UnweightedLiteralLine <: LiteralLine
    node_id::ID
    vtree_id::ID
    literal::Lit
    normalized::Bool
end

is_normalized(l::LiteralLine) = l.normalized

import ...LogicCircuits: literal, variable # extend
literal(l::LiteralLine) = l.literal
variable(l::LiteralLine) = lit2var(literal(l))

"""
A line representing either the true or false logical constants
"""
abstract type ConstantLine <: LeafCircuitLine end

"""
A weighted constant line for a known variable
"""
struct WeightedNamedConstantLine <: ConstantLine
    node_id::ID
    vtree_id::ID
    variable::Var
    # constant::Bool always true because one cannot associate a weight with the models of false
    # normalized::Bool always true because we have a single variable field given above, which only exists if the node is normalized
    weight::Float64
end

struct AnonymousConstantLine <: ConstantLine
    node_id::ID
    constant::Bool
    normalized::Bool
end

is_normalized(l::AnonymousConstantLine) = l.normalized
is_normalized(::WeightedNamedConstantLine) = true
variable(l::WeightedNamedConstantLine) = l.variable
lnconstant(::WeightedNamedConstantLine) = true
lnconstant(l::AnonymousConstantLine) = l.constant

"""
Paired boxes, or elements, are conjunctions
in a larger decision node line
"""
abstract type Element end
abstract type NormalizedElement <: Element end

struct LCElement <: NormalizedElement
    prime_id::ID
    sub_id::ID
    weights::Vector{Float64}
end

struct PSDDElement <: NormalizedElement
    prime_id::ID
    sub_id::ID
    weight::Float64
end

abstract type TrimmedElement <: Element end

struct SDDElement <: TrimmedElement
    prime_id::ID
    sub_id::ID
end

is_normalized(::NormalizedElement) = true
is_normalized(::TrimmedElement) = false

"""
A line representing a decision node in the circuit (an OR of AND elements)
"""
struct DecisionLine{ET<:Element} <: InnerCircuitLine
    node_id::ID
    vtree_id::ID
    num_elements::UInt32
    elements:: Vector{ET}
end

"""
A line representing a bias node in the circuit (an OR with one child)
"""
struct BiasLine <: InnerCircuitLine
    node_id::ID
    weights::Vector{Float64}
    BiasLine(weights) = new(typemax(ID), weights)
end

"""
String representations of each line type
"""
# TODO rename this IO module so as to not conflict with Core.IO below
Base.show(io::Core.IO, ln::SddHeaderLine) =  print(io, "sdd $(ln.num_nodes)")
Base.show(io::Core.IO, ln::PsddHeaderLine) =  print(io, "psdd $(ln.num_nodes)")
Base.show(io::Core.IO, ln::LcHeaderLine) =  print(io, "Logistic Circuit")
Base.show(io::Core.IO, ln::CircuitCommentLine) =  print(io, "c $(ln.comment)")
Base.show(io::Core.IO, ln::DecisionLine) =  print(io, "D $(ln.node_id) $(ln.vtree_id) $(ln.num_elements) $(ln.elements)")
Base.show(io::Core.IO, ln::BiasLine) =  print(io, "B $(ln.weights)")
Base.show(io::Core.IO, ln::WeightedNamedConstantLine) =  print(io, "T $(ln.node_id) $(ln.vtree_id) $(ln.variable) $(ln.weight)")
Base.show(io::Core.IO, ln::UnweightedLiteralLine) =  print(io, "L $(ln.node_id) $(ln.vtree_id) $(ln.literal)")

Base.show(io::Core.IO, ln::WeightedLiteralLine) = begin
    @assert ln.normalized
    if ln.literal > 0
        print(io, "T $(ln.node_id) $(ln.vtree_id) $(ln.literal) $(ln.weights)")
    else
        print(io, "F $(ln.node_id) $(ln.vtree_id) $(-ln.literal) $(ln.weights)")
    end
end

Base.show(io::Core.IO, ln::AnonymousConstantLine) = begin
    @assert !ln.normalized
    if ln.constant
        print(io, "T $(ln.node_id)")
    else
        print(io, "F $(ln.node_id)")
    end
end

Base.show(io::Core.IO, e::SDDElement) =  print(io, "$(e.prime_id) $(e.sub_id)")
Base.show(io::Core.IO, e::PSDDElement) =  print(io, "$(e.prime_id) $(e.sub_id) $(e.weight)")
Base.show(io::Core.IO, e::LCElement) =  print(io, "($(e.prime_id) $(e.sub_id) $(e.weights))")

Base.show(io::Core.IO, v::Vector{<:Union{Element, AbstractFloat}}) =  join(io, v, " ")


using Colors

# ??? code_warntype not defined?
# @code_warntype colordiff(colorant"red", colorant"darkred")

#=
function pointDifference(targetColor, corpusColor )::Float64
    return colordiff(
        corpusColor,
        targetColor,
        metric )
    # WAS metric=DE_94() i.e. computing the metric deep in the loops
end
=#

function maxPointDifference()
    #=
    According to Wolfram (but not the Julia site)
    the "typical" max for CIE2000 colordiff
    =#
    # return 1.16839  # CIE2000
    return 1.48916  # CIE84 DE_94
end

pointDifference = colordiff

#=
if subtract in loop, only 27 allocations.

=#
function testDiff()
    a = CartesianIndex(1,2)
    b = CartesianIndex(3,4)
    for i in 1:10000
        # This mutates the local a
        # a -= b

        # This does not mutate the local a
        # TODO why not?
        diffCI(a,b)
    end
    println(a)
end

function diffCI(a, b)
    # println itself requires allocations
    # println(typeof(a))
    a -= b
end

#=
Test whether a passed struct containing a CI
allocates ?
=#

struct Foo
    ci::CartesianIndex{N} where N
    b::Int64
end

mutable struct MutableFoo
    ci::CartesianIndex{N} where N
    b::Int64
end

function testDiff2()
    a = Foo(CartesianIndex(1,2), 1)
    b = Foo(CartesianIndex(3,4), 2)
    c = MutableFoo(CartesianIndex(1,2), 1)
    for i in 1:10000
        c.ci = diffCI2(a,b)
    end
    println(a)
end

function diffCI2(a, b)
    # Why isn't this returned without an allocation?
    # !!! Because the struct must be fully annotated with types?
    # return Foo(a.ci - b.ci, 3)
    return a.ci - b.ci
end


struct ResourceCounter
    path::String
    id::Cint
    nresources::Cint
    undo::Bool
    tok::Cftok
    handle::Cint
    counts::Vector{Cushort}

    function ResourceCounter(path::Union{String,Tuple{String,Integer}}, nresources::Integer=1; create::Bool=true, create_exclusive::Bool=false, permission::Integer=0o660, undo::Bool=false)
        id = 0
        if isa(path, Tuple)
            path,id = path
        end
        tok = ftok(path, id)
        handle = semcreate(tok, nresources; create=create, create_exclusive=create_exclusive, permission=permission)
        new(path, id, nresources, undo, tok, handle, Vector{Cushort}(undef, nresources))
    end
end

close(sem::ResourceCounter) = nothing
delete!(sem::ResourceCounter) = semrm(sem.handle)

function count(sem::ResourceCounter)
    semget(sem.handle, sem.counts)
    sem.counts
end
count(sem::ResourceCounter, which) = semget(sem.handle, which)

reset(sem::ResourceCounter, val::T, which=0) where {T<:Integer} = semset(sem.handle, Cint(val), which)
reset(sem::ResourceCounter, vals::Vector{T}) where {T<:Integer} = semset(sem.handle, convert(Vector{Cushort},vals))

change(sem::ResourceCounter, by::T, which=0; wait::Bool=true, undo::Bool=sem.undo) where {T<:Integer} = change(sem, [SemBuf(which,by;wait=wait, undo=undo)])
change(sem::ResourceCounter, operations::Vector{SemBuf}) = semop(sem.handle, operations)


# Test methods for reduced infinite variables
@testset "Reduced Variable" begin
    # initialize model and references
    m = InfiniteModel()
    @infinite_parameter(m, 1 <= par1 <= 2)
    @infinite_parameter(m, 1 <= par2 <= 2)
    @infinite_variable(m, 0 <= inf1(par1, par2) <= 1, Int)
    @infinite_variable(m, inf2(par1, par2) == 1, Bin, start = 0)
    index = m.next_var_index + 1
    rvref1 = ReducedInfiniteVariableRef(m, index)
    m.reduced_info[index] = ReducedInfiniteInfo(inf1, Dict(1 => 1))
    rvref2 = ReducedInfiniteVariableRef(m, index + 1)
    m.reduced_info[index + 1] = ReducedInfiniteInfo(inf2, Dict(2 => 1))
    # test _reduced_info
    @testset "_reduced_info" begin
        @test InfiniteOpt._reduced_info(rvref1).infinite_variable_ref == inf1
        @test InfiniteOpt._reduced_info(rvref2).infinite_variable_ref == inf2
        @test InfiniteOpt._reduced_info(rvref1).eval_supports == Dict(1 => 1)
        @test InfiniteOpt._reduced_info(rvref2).eval_supports == Dict(2 => 1)
    end
    # test infinite_variable_ref
    @testset "infinite_variable_ref" begin
        @test infinite_variable_ref(rvref1) == inf1
        @test infinite_variable_ref(rvref2) == inf2
    end
    # test eval_supports
    @testset "eval_supports" begin
        @test eval_supports(rvref1) == Dict(1 => 1)
        @test eval_supports(rvref2) == Dict(2 => 1)
    end
    # test name
    @testset "JuMP.name" begin
        @test name(rvref1) == "inf1(1, par2)"
        @test name(rvref2) == "inf2(par1, 1)"
    end
    # parameter_refs (reduced infinite variable)
    @testset "parameter_refs (reduced infinite)" begin
        # test the references
        @test parameter_refs(rvref1) == (par2, )
        @test parameter_refs(rvref2) == (par1, )
    end
    # has_lower_bound
    @testset "JuMP.has_lower_bound" begin
        @test has_lower_bound(rvref1)
        @test !has_lower_bound(rvref2)
    end
    # lower_bound
    @testset "JuMP.lower_bound" begin
        @test lower_bound(rvref1) == 0
        @test_throws ErrorException lower_bound(rvref2)
    end
    # _lower_bound_index
    @testset "JuMP._lower_bound_index" begin
        @test JuMP._lower_bound_index(rvref1) == JuMP._lower_bound_index(inf1)
        @test_throws ErrorException JuMP._lower_bound_index(rvref2)
    end
    # LowerBoundRef
    @testset "JuMP.LowerBoundRef" begin
        @test LowerBoundRef(rvref1) == LowerBoundRef(inf1)
        @test_throws ErrorException LowerBoundRef(rvref2)
    end
    # has_upper_bound
    @testset "JuMP.has_upper_bound" begin
        @test has_upper_bound(rvref1)
        @test !has_upper_bound(rvref2)
    end
    # upper_bound
    @testset "JuMP.upper_bound" begin
        @test upper_bound(rvref1) == 1
        @test_throws ErrorException upper_bound(rvref2)
    end
    # _upper_bound_index
    @testset "JuMP._upper_bound_index" begin
        @test JuMP._upper_bound_index(rvref1) == JuMP._upper_bound_index(inf1)
        @test_throws ErrorException JuMP._upper_bound_index(rvref2)
    end
    # UpperBoundRef
    @testset "JuMP.UpperBoundRef" begin
        @test UpperBoundRef(rvref1) == UpperBoundRef(inf1)
        @test_throws ErrorException UpperBoundRef(rvref2)
    end
    # is_fixed
    @testset "JuMP.is_fixed" begin
        @test is_fixed(rvref2)
        @test !is_fixed(rvref1)
    end
    # fix_value
    @testset "JuMP.fix_value" begin
        @test fix_value(rvref2) == 1
        @test_throws ErrorException fix_value(rvref1)
    end
    # _fix_index
    @testset "JuMP._fix_index" begin
        @test JuMP._fix_index(rvref2) == JuMP._fix_index(inf2)
        @test_throws ErrorException JuMP._fix_index(rvref1)
    end
    # FixRef
    @testset "JuMP.FixRef" begin
        @test FixRef(rvref2) == FixRef(inf2)
        @test_throws ErrorException FixRef(rvref1)
    end
    # start_value
    @testset "JuMP.start_value" begin
        @test start_value(rvref2) == 0
    end
    # is_binary
    @testset "JuMP.is_binary" begin
        @test is_binary(rvref2)
        @test !is_binary(rvref1)
    end
    # _binary_index
    @testset "JuMP._binary_index" begin
        @test JuMP._binary_index(rvref2) == JuMP._binary_index(inf2)
        @test_throws ErrorException JuMP._binary_index(rvref1)
    end
    # BinaryRef
    @testset "JuMP.BinaryRef" begin
        @test BinaryRef(rvref2) == BinaryRef(inf2)
        @test_throws ErrorException BinaryRef(rvref1)
    end
    # is_integer
    @testset "JuMP.is_integer" begin
        @test is_integer(rvref1)
        @test !is_integer(rvref2)
    end
    # _integer_index
    @testset "JuMP._integer_index" begin
        @test JuMP._integer_index(rvref1) == JuMP._integer_index(inf1)
        @test_throws ErrorException JuMP._integer_index(rvref2)
    end
    # IntegerRef
    @testset "JuMP.IntegerRef" begin
        @test IntegerRef(rvref1) == IntegerRef(inf1)
        @test_throws ErrorException IntegerRef(rvref2)
    end
    # used_by_constraint
    @testset "used_by_constraint" begin
        # test not used
        @test !used_by_constraint(rvref1)
        # prepare use case
        m.reduced_to_constrs[JuMP.index(rvref1)] = [1]
        # test used
        @test used_by_constraint(rvref1)
    end
    # used_by_measure
    @testset "used_by_measure" begin
        # test not used
        @test !used_by_measure(rvref1)
        # prepare use case
        m.reduced_to_meas[JuMP.index(rvref1)] = [1]
        # test used
        @test used_by_measure(rvref1)
    end
    # test is_valid
    @testset "JuMP.is_valid" begin
        @test is_valid(m, rvref1)
        @test is_valid(m, rvref2)
        @test !is_valid(m, ReducedInfiniteVariableRef(m, 100))
    end
end


module LearnBase

import StatsBase
using StatsBase: nobs

# AGGREGATION MODES
include("aggmode.jl")

# VIEW AND ITERATORS
include("iteration.jl")

# OBSERVATION DIMENSIONS
include("observation.jl")

# LEARNING COSTS (e.g. loss & penalty)
include("costs.jl")

# LABELS
include("labels.jl")

# OTHER CONCEPTS
include("other.jl")

end # module


using Random

const rng = MersenneTwister()
const letters = 'A':'Z'
const digits = '0':'9'

const possiblenames = [c1 * c2 * d1 * d2 * d3 for c1 in letters for c2 in letters for d1 in digits for d2 in digits for d3 in digits]
shuffle!(rng, possiblenames)

mutable struct Robot
    name::String
    Robot() = new(generatename!())
end

function reset!(instance::Robot)
    instance.name = generatename!()
    return instance
end

function generatename!()
    isempty(possiblenames) ? error("there are no more available robot names") : pop!(possiblenames)
end

function name(instance::Robot)
    instance.name
end


struct ExtractString{T} <: AbstractExtractor
	datatype::Type{T}
	n::Int
	b::Int
	m::Int
end

ExtractString(::Type{T}) where {T<:String} = ExtractString(T, 3, 256, 2053)
(s::ExtractString)(v)   = ArrayNode(Mill.NGramMatrix([v], s.n, s.b, s.m))
(s::ExtractString)(v::S) where {S<:Nothing}= ArrayNode(Mill.NGramMatrix([""], s.n, s.b, s.m))
extractsmatrix(s::ExtractString) = false

function Base.show(io::IO, m::ExtractString;pad = [], key::String="")
	c = COLORS[(length(pad)%length(COLORS))+1]
	key *= isempty(key) ? "" : ": ";
	paddedprint(io,"$(key)$(m.datatype)\n", color = c)
end

"""
	extractscalar(Type{String}, n = 3, b = 256, m = 2053)

	represents strings as ngrams with
		--- `n` (the degree of ngram),
		--- `b` base of string,
		--- `m` modulo on index of the token to reduce dimension

	extractscalar(Type{Number}, m = 0, s = 1)
	extracts number subtracting `m` and multiplying by `s`
"""
extractscalar(::Type{T}, n = 3, b = 256, m = 2053) where {T<:AbstractString} = ExtractString(T, n, b, m)
extractscalar(::Type{T}, m = zero(T), s = one(T)) where {T<:Number} = ExtractScalar(T, m, s)

Base.hash(e::ExtractString, h::UInt) = hash((e.datatype, e.n, e.b, e.m), h)
Base.:(==)(e1::ExtractString, e2::ExtractString) = e1.datatype == e2.datatype && e1.n === e2.n && e1.b === e2.b && e1.m === e2.m