function codegen(::Type{Val{:iterate}}, ::Type{HMC}, job::BasicMCJob)
  result::Expr
  update = []
  noupdate = []
  burninbody = []
  ifburninbody = []
  body = []
  dindex::Integer

  vform = variate_form(job.pstate)
  if vform != Univariate && vform != Multivariate
    error("Only univariate or multivariate parameter states allowed in HMC code generation")
  end

  if isa(job.tuner, DualAveragingMCTuner)
    push!(body, :(_job.sstate.count += 1))
  end

  if (isa(job.tuner, VanillaMCTuner) && job.tuner.verbose) ||
    isa(job.tuner, AcceptanceRateMCTuner) ||
    (isa(job.tuner, DualAveragingMCTuner) && job.tuner.verbose)
    push!(body, :(_job.sstate.tune.proposed += 1))
  end

  if vform == Univariate
    push!(body, :(_job.sstate.momentum = randn()))
  elseif vform == Multivariate
    push!(body, :(_job.sstate.momentum[:] = randn(_job.pstate.size)))
  end

  push!(body, :(_job.sstate.oldhamiltonian = hamiltonian(_job.pstate.logtarget, _job.sstate.momentum)))

  if vform == Univariate
    push!(body, :(_job.sstate.pstate.value = _job.pstate.value))
    push!(body, :(_job.sstate.pstate.gradlogtarget = _job.pstate.gradlogtarget))
  elseif vform == Multivariate
    push!(body, :(_job.sstate.pstate.value = copy(_job.pstate.value)))
    push!(body, :(_job.sstate.pstate.gradlogtarget = copy(_job.pstate.gradlogtarget)))
  end

  if isa(job.tuner, DualAveragingMCTuner)
    push!(body, :(_job.sstate.nleaps = max(1, Int(round(_job.sstate.tune.λ/_job.sstate.tune.step)))))
  end

  if vform == Univariate
    push!(body, :(
      for i in 1:_job.sstate.nleaps
        _job.sstate.momentum =
          leapfrog!(
            _job.sstate.pstate,
            _job.sstate.pstate,
            _job.sstate.momentum,
            _job.sstate.tune.step,
            _job.parameter.gradlogtarget!
          )
      end
    ))
  elseif vform == Multivariate
    push!(body, :(
      for i in 1:_job.sstate.nleaps
        leapfrog!(
          _job.sstate.pstate,
          _job.sstate.momentum,
          _job.sstate.pstate,
          _job.sstate.momentum,
          _job.sstate.tune.step,
          _job.parameter.gradlogtarget!
        )
      end
    ))
  end

  push!(body, :(_job.parameter.logtarget!(_job.sstate.pstate)))

  push!(body, :(_job.sstate.newhamiltonian = hamiltonian(_job.sstate.pstate.logtarget, _job.sstate.momentum)))

  push!(body, :(_job.sstate.ratio = _job.sstate.newhamiltonian-_job.sstate.oldhamiltonian))

  push!(body, :(_job.sstate.a = min(1., exp(_job.sstate.ratio))))

  if vform == Univariate
    push!(update, :(_job.pstate.value = _job.sstate.pstate.value))
    push!(update, :(_job.pstate.gradlogtarget = _job.sstate.pstate.gradlogtarget))
    if in(:gradloglikelihood, job.outopts[:monitor]) && job.parameter.gradloglikelihood! != nothing
      push!(update, :(_job.pstate.gradloglikelihood = _job.sstate.pstate.gradloglikelihood))
    end
    if in(:gradlogprior, job.outopts[:monitor]) && job.parameter.gradlogprior! != nothing
      push!(update, :(_job.pstate.gradlogprior = _job.sstate.pstate.gradlogprior))
    end
  elseif vform == Multivariate
    push!(update, :(_job.pstate.value = copy(_job.sstate.pstate.value)))
    push!(update, :(_job.pstate.gradlogtarget = copy(_job.sstate.pstate.gradlogtarget)))
    if in(:gradloglikelihood, job.outopts[:monitor]) && job.parameter.gradloglikelihood! != nothing
      push!(update, :(_job.pstate.gradloglikelihood = copy(_job.sstate.pstate.gradloglikelihood)))
    end
    if in(:gradlogprior, job.outopts[:monitor]) && job.parameter.gradlogprior! != nothing
      push!(update, :(_job.pstate.gradlogprior = copy(_job.sstate.pstate.gradlogprior)))
    end
  end
  push!(update, :(_job.pstate.logtarget = _job.sstate.pstate.logtarget))
  if in(:loglikelihood, job.outopts[:monitor]) && job.parameter.loglikelihood! != nothing
    push!(update, :(_job.pstate.loglikelihood = _job.sstate.pstate.loglikelihood))
  end
  if in(:logprior, job.outopts[:monitor]) && job.parameter.logprior! != nothing
    push!(update, :(_job.pstate.logprior = _job.sstate.pstate.logprior))
  end
  dindex = findfirst(job.outopts[:diagnostics], :accept)
  if dindex != 0
    push!(update, :(_job.pstate.diagnosticvalues[$dindex] = true))
    push!(noupdate, :(_job.pstate.diagnosticvalues[$dindex] = false))
  end
  if (isa(job.tuner, VanillaMCTuner) && job.tuner.verbose) ||
    isa(job.tuner, AcceptanceRateMCTuner) ||
    (isa(job.tuner, DualAveragingMCTuner) && job.tuner.verbose)
    push!(update, :(_job.sstate.tune.accepted += 1))
  end

  push!(body, Expr(:if, :(rand() < _job.sstate.a), Expr(:block, update...), noupdate...))

  if (isa(job.tuner, VanillaMCTuner) && job.tuner.verbose) || isa(job.tuner, AcceptanceRateMCTuner)
    push!(burninbody, :(rate!(_job.sstate.tune)))

    if isa(job.tuner, AcceptanceRateMCTuner)
      push!(burninbody, :(tune!(_job.sstate.tune, _job.tuner)))
    end

    if job.tuner.verbose
      fmt_iter = format_iteration(ndigits(job.range.burnin))
      fmt_perc = format_percentage()

      push!(burninbody, :(println(
        "Burnin iteration ",
        $(fmt_iter)(_job.sstate.tune.totproposed),
        " of ",
        _job.range.burnin,
        ": ",
        $(fmt_perc)(100*_job.sstate.tune.rate),
        " % acceptance rate"
      )))
    end

    push!(burninbody, :(reset_burnin!(_job.sstate.tune)))

    push!(
      body,
      Expr(
        :if,
        :(_job.sstate.tune.totproposed <= _job.range.burnin && mod(_job.sstate.tune.proposed, _job.tuner.period) == 0),
        Expr(:block, burninbody...)
      )
    )
  elseif isa(job.tuner, DualAveragingMCTuner)
    push!(burninbody, :(tune!(_job.sstate.tune, _job.tuner, _job.sstate.count, _job.sstate.a)))

    if job.tuner.verbose
      fmt_iter = format_iteration(ndigits(job.tuner.nadapt))
      fmt_perc = format_percentage()

      push!(ifburninbody, :(rate!(_job.sstate.tune)))

      push!(ifburninbody, :(println(
        "Burnin iteration ",
        $(fmt_iter)(_job.sstate.tune.totproposed),
        " of ",
        _job.tuner.nadapt,
        ": ",
        $(fmt_perc)(100*_job.sstate.tune.rate),
        " % acceptance rate"
      )))

      push!(ifburninbody, :(reset_burnin!(_job.sstate.tune)))

      push!(burninbody, Expr(:if, :(mod(_job.sstate.tune.proposed, _job.tuner.period) == 0), Expr(:block, ifburninbody...)))
    end

    push!(
      body,
      Expr(
        :if,
        :(_job.sstate.count <= _job.tuner.nadapt),
        Expr(:block, burninbody...),
        :(_job.sstate.tune.step = _job.sstate.tune.εbar)
      )
    )
  end

  if !job.plain
    push!(body, :(produce()))
  end

  @gensym _iterate

  result = quote
    function $_iterate(_job::BasicMCJob)
      $(body...)
    end
  end

  result
end


using SimpleDirectMediaLayer
const SDL2 = SimpleDirectMediaLayer

using SimpleDirectMediaLayer:
    Renderer

export
    RenderContext,
    save!, restore!, post, pre, apply!,
    render_line, render_lines, render_aa_rectangle, render_outlined_polygon

using .Rendering
using ..Math
using ..Geometry

import ..Math.transform!

@enum RenderStyle FILLED OUTLINE BOTH
@enum PolygonState OPEN CLOSED

mutable struct RenderContext
    renderer::Ptr{Renderer}

    raster_font::RasterFont
    vector_font::VectorFont

    # State management
    state::Array{State}
    stack_top::Integer

    clear_color::Palette
    draw_color::Palette

    # Device/Window dimensions
    width::Int32
    height::Int32

    # The current transform based on top
    current::Math.AffineTransform

    post::Math.AffineTransform   # A preallocated cache

    # Scratch working variables
    v1::Geometry.Point{Float64}
    v2::Geometry.Point{Float64}

    function RenderContext(world::Ranger.World, raster_font::RasterFont, vector_font::Rendering.VectorFont)
        o = new()

        o.renderer = world.renderer
        o.raster_font = raster_font
        o.vector_font = vector_font
        o.state = []
        o.stack_top = 1
        o.clear_color = Orange()
        o.draw_color = White()
        o.width = world.window_width
        o.height = world.window_height

        o.current = AffineTransform{Float64}()
        o.post = AffineTransform{Float64}()

        o.v1 = Geometry.Point{Float64}()
        o.v2 = Geometry.Point{Float64}()

        o
    end
end

# const SAMPLESIZE = 16

function initialize(context::RenderContext, world::Ranger.World)
    # SDL2.GL_SetAttribute(SDL2.GL_MULTISAMPLEBUFFERS, SAMPLESIZE)
    # SDL2.GL_SetAttribute(SDL2.GL_MULTISAMPLESAMPLES, SAMPLESIZE)
    # SDL2.SetHint(SDL2.HINT_RENDER_SCALE_QUALITY, "1")

    SDL2.SetRenderDrawBlendMode(context.renderer, SDL2.BLENDMODE_BLEND);

    state = State()

    for _ in 0:STATE_STACK_DEPTH
        push!(context.state, copy(state))
    end

    # Apply centered view-space matrix
    apply!(context, world.view_space);
end

function apply!(context::RenderContext, aft::AffineTransform)
    # Concat this transform onto the current transform but don't push it.
    # Use post multiply
    Math.multiply!(aft, context.current, context.post)
    Math.set!(context.current, context.post)
end

function pre(context::RenderContext)
    c = context.clear_color
    SDL2.SetRenderDrawColor(context.renderer, c.r, c.g, c.b, c.a)
    SDL2.RenderClear(context.renderer);

    # Draw checkered board as an clear indicator for debugging
    # NOTE: disable this code for release builds
    # draw_checkerboard(context);
end

# Push the current state onto the stack
function save!(context::RenderContext)
    top = context.state[context.stack_top]

    top.clear_color = context.clear_color
    top.draw_color = context.draw_color
    Math.set!(top.current, context.current)

    context.stack_top += 1;

    # debugging
    # rect = SDL2.Rect(0, 0, 1, 1)
    # rect.x = 100
    # rect.y = 100
    # rect.w = 400
    # rect.h = 400

    # SDL2.SetRenderDrawColor(context.renderer, 255, 127, 0, 255)
    # draw_text(context, 100, 100, "Ranger", 3, 2, false)
    # draw_filled_rectangle(context, rect)
    # SDL2.SetRenderDrawColor(context.renderer, 255, 255, 255, 255)
    # draw_outlined_rectangle(context, rect)
end

# Pop the current state
function restore!(context::RenderContext)
    context.stack_top -= 1

    top = context.state[context.stack_top]

    context.clear_color = top.clear_color
    context.draw_color = top.draw_color
    Math.set!(context.current, top.current)

    c = context.clear_color
    SDL2.SetRenderDrawColor(context.renderer, c.r, c.g, c.b, c.a)
end

function post(context::RenderContext)
    SDL2.RenderPresent(context.renderer);
end

# Transform a Point
function transform!(context::RenderContext, p::Geometry.Point{Float64}, out::Geometry.Point{Float64})
    Math.transform!(context.current, p, out)
end

# Transform a line or rectangle corners
function transform!(context::RenderContext, p1::Geometry.Point{Float64}, p2::Geometry.Point{Float64}, out1::Geometry.Point{Float64}, out2::Geometry.Point{Float64})
    Math.transform!(context.current, p1, out1)
    Math.transform!(context.current, p2, out2)
end

function transform!(context::RenderContext, vertices::Array{Geometry.Point{Float64},1}, bucket::Array{Point{Float64},1})
    for (idx, vertex) in enumerate(vertices)
        Math.transform!(context.current, vertex, bucket[idx])
    end
end

function transform!(context::RenderContext, mesh::Geometry.Mesh)
    for (idx, vertex) in enumerate(mesh.vertices)
        Math.transform!(context.current, vertex, mesh.bucket[idx])
    end
end

function transform!(context::RenderContext, polygon::Math.Polygon)
    for (idx, vertex) in enumerate(polygon.mesh.vertices)
        Math.transform!(context.current, vertex, polygon.mesh.bucket[idx])
    end
end

function transform!(context::RenderContext, line::Math.Line)
    Math.transform!(context.current, line.p1, line.bucket[1])
    Math.transform!(context.current, line.p2, line.bucket[2])
end

# ,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.
# Draw functions render directly to the device.
# ,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.
function set_draw_color(context::RenderContext, color::Palette)
    context.draw_color = color
    SDL2.SetRenderDrawColor(context.renderer, color.r, color.g, color.b, color.a)
end

function draw_point(context::RenderContext, x::Int32, y::Int32)
    SDL2.RenderDrawPoint(context.renderer, x, y);
end

function draw_line(context::RenderContext, x1::Float64, y1::Float64, x2::Float64, y2::Float64)
    SDL2.RenderDrawLine(context.renderer, Int32(round(x1)), Int32(round(y1)), Int32(round(x2)), Int32(round(y2)));
end

function draw_outlined_rectangle(context::RenderContext, rect::SDL2.Rect)
    SDL2.RenderDrawRect(context.renderer, pointer_from_objref(rect));
end

function draw_filled_rectangle(context::RenderContext, rect::SDL2.Rect)
    SDL2.RenderFillRect(context.renderer, pointer_from_objref(rect));
end

rect_draw = SDL2.Rect(0, 0, 1, 1)

function draw_outlined_rectangle(context::RenderContext, minx::Int32, miny::Int32, maxx::Int32, maxy::Int32)
    rect_draw.x = minx
    rect_draw.y = miny
    rect_draw.w = maxx - minx
    rect_draw.h = maxy - miny
    SDL2.RenderDrawRect(context.renderer, pointer_from_objref(rect_draw));
end

function draw_filled_rectangle(context::RenderContext, minx::Int32, miny::Int32, maxx::Int32, maxy::Int32)
    rect_draw.x = minx
    rect_draw.y = miny
    rect_draw.w = maxx - minx
    rect_draw.h = maxy - miny
    SDL2.RenderFillRect(context.renderer, pointer_from_objref(rect_draw));
end

function draw_horz_line(context::RenderContext, x1::Int32, x2::Int32, y::Int32)
    SDL2.RenderDrawLine(context.renderer, x1, y, x2, y);
end

function draw_horz_line(context::RenderContext, x1::AbstractFloat, x2::AbstractFloat, y::AbstractFloat)
    SDL2.RenderDrawLine(context.renderer, Int32(round(x1)), Int32(round(y)), Int32(round(x2)), Int32(round(y)));
end

function draw_vert_line(context::RenderContext, x::Int32, y1::Int32, y2::Int32)
    SDL2.RenderDrawLine(context.renderer, x, y1, x, y2);
end

function draw_vert_line(context::RenderContext, x::AbstractFloat, y1::AbstractFloat, y2::AbstractFloat)
    SDL2.RenderDrawLine(context.renderer, Int32(round(x)), Int32(round(y1)), Int32(round(x)), Int32(round(y2)));
end

const shifts = [0,1,2,3,4,5,6,7]

# This type of font doesn't support transforms. It will always be axis aligned.
function draw_text(context::RenderContext, x::Integer, y::Integer, text::String, scale::Integer, fill::Integer, invert::Bool)
    cx = Int32(x)
    s = Int32(scale)
    row_width = Int32(get_glyph_width())

    # Is the text colored or the space around it (aka inverted)
    bit_invert = invert ? 0 : 1

    for char in text
        if char == ' '
            cx += row_width * s # move to next column/char/glyph
            continue
        end

        gy = Int32(y) # move y back to the "top" for each char
        glyph = get_glyph(context.raster_font, char)
        for g in glyph
            gx = cx # set to current column
            for shift in shifts # scan each pixel in the glyph
                bit = (g >> shift) & 1

                if bit == bit_invert
                    if scale == 1
                        SDL2.RenderDrawPoint(context.renderer, gx, gy)
                    else
                        fillet = fill
                        if fill > scale
                            fillet = 0
                        end
                        for xl in 0:(scale - fillet)
                            for yl in 0:(scale - fillet)
                                SDL2.RenderDrawPoint(context.renderer, Int32(gx + xl), Int32(gy + yl));
                            end
                        end
                    end
                end
                gx += s
            end
            gy += s # move to next pixel-row in char
        end
        cx += row_width * s # move to next column/char/glyph
    end
end

function draw_checkerboard(context::RenderContext)
    flip = false
    size = 200
    col = 0
    row = 0

    while row < context.height
        while col < context.width
            if flip
                SDL2.SetRenderDrawColor(context.renderer, 100, 100, 100, 255)
            else
                SDL2.SetRenderDrawColor(context.renderer, 80, 80, 80, 255)
            end

            rect_draw.x = col
            rect_draw.y = row
            rect_draw.w = col + size
            rect_draw.h = row + size

            SDL2.RenderFillRect(context.renderer, pointer_from_objref(rect_draw))

            flip = !flip;
            col += size;
        end

        flip = !flip;
        col = 0;
        row += size;
    end
end

# ,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.,--.
# Render functions render based on transformed vertices
# The Render functions use the Draw functions.
# ,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.,__.
function render_line(context::RenderContext, x1::Float64, y1::Float64, x2::Float64, y2::Float64)
    draw_line(context, Int32(round(x1)), Int32(round(y1)), Int32(round(x2)), Int32(round(y2)));
end

function render_lines(context::RenderContext, mesh::Geometry.Mesh)
    first = true

    for v in mesh.bucket
        if first
            context.v1 = v
            first = false
            continue
        else
            context.v2 = v
            first = true
        end

        draw_line(context,
            context.v1.x, context.v1.y,
            context.v2.x, context.v2.y);
    end
end

function render_checkerboard(context::RenderContext, mesh::Geometry.Mesh, oddColor::Palette, evenColor::Palette)
    # render a grid of rectangles defined by min/max points
    flip = false
    vertices = mesh.bucket
    build = true

    # TODO detect negative change in X so that the flip value
    # alternates correctly for even grid sizes. To lazy to fix at the moment.
    for (idx, vertex) in enumerate(mesh.bucket)
        if build
            Geometry.set!(context.v1, vertex)
            build = false
            continue
        else
            Geometry.set!(context.v2, vertex)
            build = true
        end

        if flip
            SDL2.SetRenderDrawColor(context.renderer, oddColor.r, oddColor.g, oddColor.b, oddColor.a)
        else
            SDL2.SetRenderDrawColor(context.renderer, evenColor.r, evenColor.g, evenColor.b, evenColor.a)
        end

        # upper-left
        minx = Int32(round(context.v1.x))
        miny = Int32(round(context.v1.y))

        # bottom-right
        maxx = Int32(round(context.v2.x))
        maxy = Int32(round(context.v2.y))

        rect_draw.x = minx
        rect_draw.y = miny
        rect_draw.w = maxx - minx
        rect_draw.h = maxy - miny

        SDL2.RenderFillRect(context.renderer, pointer_from_objref(rect_draw))

        flip = !flip;
    end
end

# Render an axis aligned rectangle. Rotating any of the vertices
# will cause strange rendering behaviours
function render_aa_rectangle(context::RenderContext, min::Geometry.Point{Float64}, max::Geometry.Point{Float64}, fillStyle::RenderStyle)
    # upper-left
    minx = Int32(round(min.x))
    miny = Int32(round(min.y))

    # bottom-right
    maxx = Int32(round(max.x))
    maxy = Int32(round(max.y))

    if fillStyle == FILLED
        draw_filled_rectangle(context, minx, miny, maxx, maxy)
    elseif fillStyle == OUTLINE
        draw_outlined_rectangle(context, minx, miny, maxx, maxy)
    else
        draw_filled_rectangle(context, minx, miny, maxx, maxy)
        draw_outlined_rectangle(context, minx, miny, maxx, maxy)
    end
end

function render_aabb_rectangle(context::RenderContext, aabb::Geometry.AABB)
    # upper-left
    minx = Int32(round(aabb.min.x))
    miny = Int32(round(aabb.min.y))

    # bottom-right
    maxx = Int32(round(aabb.max.x))
    maxy = Int32(round(aabb.max.y))

    draw_outlined_rectangle(context, minx, miny, maxx, maxy)
end

function render_outlined_polygon(context::RenderContext, polygon::Geometry.Polygon, state::PolygonState)
    bucs = polygon.mesh.bucket

    for idx in 1:(length(bucs) - 1)
        SDL2.RenderDrawLine(context.renderer,
            Int32(round(bucs[idx].x)), Int32(round(bucs[idx].y)),
            Int32(round(bucs[idx + 1].x)), Int32(round(bucs[idx + 1].y)))
    end

    if state == CLOSED
        SDL2.RenderDrawLine(context.renderer,
            Int32(round(bucs[length(bucs)].x)), Int32(round(bucs[length(bucs)].y)),
            Int32(round(bucs[1].x)), Int32(round(bucs[1].y)))
    end
end


function jump!(
    ata_model::Union{
        MaximinModel,
        DeJongMaximinModel,
        SoysterMaximinModel,
        NoObjModel,
        MinimaxModel,
    };
    starting_design = Matrix{Float64}(undef, 0, 0),
    results_folder = "results",
    optimizer_constructor = "GLPK",
    optimizer_attributes = [("tm_lim", 500000), ("msg_lev", 3)],
    kwargs...,
)

    try
        if !isdir(results_folder)
            mkdir(results_folder)
        else
            success!(
                ata_model,
                string(
                    "There is already a folder with this name, files in ",
                    results_folder,
                    " will be overwritten.",
                ),
            )
        end
        n_items = ata_model.settings.n_items
        if ata_model.obj.name in
           ["maximin", "minimax", "soyster_maximin", "de_jong_maximin"]
            if any([size(ata_model.obj.cores[t].IIF, 1) > 0 for t = 1:ata_model.settings.T])
                IIF = [ata_model.obj.cores[t].IIF for t = 1:ata_model.settings.T]
                success!(
                    ata_model,
                    string("Assembling tests with ", ata_model.obj.name, " objective..."),
                )
            else
                error!(ata_model, "IIFs have not been computed. Run add_obj_fun!() first.")

                return nothing
            end
        elseif ata_model.obj.name == "cc_maximin"
            error!(
                ata_model,
                "You must use the siman solver to assemble tests with chance-constrained MAXIMIN objective function.",
            )

            return nothing
        elseif ata_model.obj.name == ""
            IIF = []
            success!(ata_model, "Assembling tests with NO objective function...")
        end

        if ata_model.settings.n_fs == 0
            ata_model.settings.n_fs = ata_model.settings.n_items
        end

        ################################################################################
        #                                overlap
        ################################################################################
        overlap_matrix = ata_model.settings.ol_max
        nPairs = 0
        if ata_model.settings.to_apply[3]
            if size(overlap_matrix, 1) > 0
                Pairs_t = _combinations(ata_model.settings.T)
                nPairs_t = size(Pairs_t, 1)
                Pairs = _combinations(ata_model.settings.T)
                nPairs = size(Pairs, 1)
                ol_max = Array{Int64,1}(undef, nPairs)
                fInd = [Pairs[pair][1] for pair = 1:nPairs]
                fIndFirst = [Pairs[pair][2] for pair = 1:nPairs]
                for pair = 1:nPairs
                    ol_max[pair] = overlap_matrix[fInd[pair], fIndFirst[pair]]
                end
            end
        end
        #Group IIFs by friend set
        if ata_model.obj.name in
           ["maximin", "soyster_maximin", "de_jong_maximin", "minimax"]
            IIF_new = [zeros(Float64, 0, 0) for t = 1:ata_model.settings.T]
            if ata_model.settings.n_fs != ata_model.settings.n_items
                #group IIFs
                for t = 1:ata_model.settings.T
                    if size(IIF, 1) > 0
                        IIF_new[t] =
                            Matrix{Float64}(undef, size(IIF[t], 1), ata_model.settings.n_fs)
                    end
                end
                for fs = 1:ata_model.settings.n_fs
                    for t = 1:ata_model.settings.T
                        if size(IIF[t], 1) > 0
                            IIF_new[t][:, fs] =
                                sum(IIF[t][:, ata_model.settings.fs.items[fs]], dims = 2)
                        end
                    end
                end
            else
                IIF_new = IIF
                ICF_new = [
                    ata_model.constraints[t].expected_score.val for
                    t = 1:ata_model.settings.T
                ]
            end
        end
        # group expected score by friend set
        ICF_new = [zeros(Float64, 0, 0) for t = 1:ata_model.settings.T]
        if ata_model.settings.n_fs != ata_model.settings.n_items
            #group IIFs
            for t = 1:ata_model.settings.T
                if size(ata_model.constraints[t].expected_score.val, 1) > 0
                    ICF_new[t] = zeros(
                        Float64,
                        size(ata_model.constraints[t].expected_score.val, 1),
                        ata_model.settings.n_fs,
                    )
                end
            end
            for fs = 1:ata_model.settings.n_fs
                for t = 1:ata_model.settings.T
                    if size(ata_model.constraints[t].expected_score.val, 1) > 0
                        ICF_new[t][:, fs] = sum(
                            ata_model.constraints[t].expected_score.val[
                                :,
                                ata_model.settings.fs.items[fs],
                            ],
                            dims = 2,
                        )
                    end
                end
            end
        else
            ICF_new =
                [ata_model.constraints[t].expected_score.val for t = 1:ata_model.settings.T]
        end
        ################################################################################
        #                                count constraints
        ################################################################################
        c = 1
        #length
        for t = 1:ata_model.settings.T
            c += ata_model.constraints[t].length_max .> 0 ? 1 : 0
            c += ata_model.constraints[t].length_min .> 0 ? 1 : 0
        end

        # item use
        if ata_model.settings.to_apply[1]
            c += size(ata_model.settings.iu.max, 1)
        end
        if ata_model.settings.to_apply[2]
            c += sum(ata_model.settings.iu.min .> 0)
        end
        #overlap old
        if ata_model.settings.to_apply[3]
            c += nPairs
        end
        #expected score
        for t = 1:ata_model.settings.T
            if size(ICF_new[t], 1) > 0
                for k = 1:size(ICF_new[t], 1)
                    if ata_model.constraints[t].expected_score.min[k] > 0
                        c += 1
                    end
                    if ata_model.constraints[t].expected_score.max[k] < 1
                        c += 1
                    end
                end
            end
        end

        #constraints
        for t = 1:ata_model.settings.T
            if size(ata_model.constraints[t].constr_A, 1) > 0
                c += size(ata_model.constraints[t].constr_A, 1)
            end
        end
        ncons = copy(c) - 1
        ################################################################################
        #                                JuMP model
        ################################################################################

        m = JuMP.Model()
        if optimizer_constructor == "GLPK"
            add_GLPK!(m)
        elseif optimizer_constructor == "CPLEX"
            add_CPLEX!(m)
        elseif optimizer_constructor == "Gurobi"
            add_Gurobi!(m)
        elseif optimizer_constructor == "KNITRO"
            add_KNITRO!(m)
        elseif optimizer_constructor == "Cbc"
            add_Cbc!(m)
        elseif optimizer_constructor == "Xpress"
            add_Xpress!(m)
        elseif optimizer_constructor == "Juniper"
            add_Juniper!(m)
        elseif optimizer_constructor == "MosekTools"
            add_MosekTools!(m)
        elseif optimizer_constructor == "SCIP"
            add_SCIP!(m)
        end

        for (name, value) in optimizer_attributes
            JuMP.set_optimizer_attribute(m, name, value)
        end

        #starting design check
        if size(starting_design, 1) > 0
            if ata_model.settings.n_fs != ata_model.settings.n_items
                if (
                    size(starting_design, 1) != ata_model.settings.n_fs ||
                    size(starting_design, 2) != ata_model.settings.T
                )
                    error!(ata_model, "Starting design must be of size: (n_fs x T).")

                    return nothing
                end
            else
                if (
                    size(starting_design, 1) != ata_model.settings.n_items ||
                    size(starting_design, 2) != ata_model.settings.T
                )
                    error!(ata_model, "Starting design must be of size: (I x T).")

                    return nothing
                end
            end
            if any((ata_model.output.design .!= 0.0) .& (ata_model.output.design .!= 1.0))
                error!(ata_model, "Starting design must contain only 1s or 0s.")

                return nothing
            end
        else
            starting_design = zeros(ata_model.settings.n_fs, ata_model.settings.T)
        end

        #decision variables
        JuMP.@variable(
            m,
            x[i = 1:ata_model.settings.n_fs, t = 1:ata_model.settings.T] <=
            Int(ata_model.settings.forced0[t][i]),
            Bin,
            start = starting_design[i, t]
        )

        if ata_model.settings.to_apply[3]
            if size(overlap_matrix, 1) > 0
                if maximum(overlap_matrix) > 0
                    #Overlap Vars new
                    JuMP.@variable(m, y[i = 1:ata_model.settings.n_fs, p = 1:nPairs], Bin)
                end
            end
        end

        # Item Use
        for i = 1:ata_model.settings.n_fs
            if ata_model.settings.to_apply[2]
                if ata_model.settings.iu.min[i] .> 0
                    JuMP.@constraint(
                        m,
                        ata_model.settings.iu.min[i] -
                        sum(x[i, t] for t = 1:ata_model.settings.T) <= 0
                    ) # z[c])
                    c += 1
                end
            end
            if ata_model.settings.to_apply[1]
                JuMP.@constraint(
                    m,
                    sum(x[i, t] for t = 1:ata_model.settings.T) -
                    ata_model.settings.iu.max[i] <= 0
                ) # z[c])
                c += 1
            end
        end
        if ata_model.settings.to_apply[3]
            if size(overlap_matrix, 1) > 0
                if maximum(overlap_matrix) > 0
                    #overlap classic
                    for p = 1:nPairs
                        JuMP.@constraint(
                            m,
                            sum(
                                y[i, p] * ata_model.settings.fs.counts[i] for
                                i = 1:ata_model.settings.n_fs
                            ) <= ol_max[p]
                        )
                        JuMP.@constraint(
                            m,
                            [i = 1:ata_model.settings.n_fs],
                            2 * y[i, p] <= x[i, fInd[p]] + x[i, fIndFirst[p]]
                        )
                        JuMP.@constraint(
                            m,
                            [i = 1:ata_model.settings.n_fs],
                            y[i, p] >= x[i, fInd[p]] + x[i, fIndFirst[p]] - 1
                        )
                    end
                else
                    #no overlap
                    for p = 1:nPairs
                        for i = 1:ata_model.settings.n_fs
                            JuMP.@constraint(m, x[i, fInd[p]] + x[i, fIndFirst[p]] <= 1)
                        end
                    end
                end
            end
        end
        #expected score
        for t = 1:ata_model.settings.T
            if size(ICF_new[t], 1) > 0
                for k = 1:size(ICF_new[t], 1)
                    if ata_model.constraints[t].expected_score.min[k] > 0
                        JuMP.@constraint(
                            m,
                            sum(
                                x[i, t] * round(ICF_new[t][k, i]; digits = 3) for
                                i = 1:ata_model.settings.n_fs
                            ) >= round(
                                ata_model.constraints[t].expected_score.min[k] *
                                ata_model.constraints[t].length_min;
                                digits = 3,
                            )
                        )
                        c += 1
                    end
                    if ata_model.constraints[t].expected_score.max[k] < 1
                        JuMP.@constraint(
                            m,
                            sum(
                                x[i, t] * round(ICF_new[t][k, i]; digits = 3) for
                                i = 1:ata_model.settings.n_fs
                            ) <= round(
                                ata_model.constraints[t].expected_score.max[k] *
                                ata_model.constraints[t].length_max;
                                digits = 3,
                            )
                        )
                        c += 1
                    end
                end
            end
        end

        #constraints
        for t = 1:ata_model.settings.T
            if size(ata_model.constraints[t].constr_A, 1) > 0
                for constr = 1:size(ata_model.constraints[t].constr_A, 1)
                    JuMP.@constraint(
                        m,
                        sum(
                            x[i, t] * ata_model.constraints[t].constr_A[constr, i] for
                            i = 1:ata_model.settings.n_fs
                        ) <= ata_model.constraints[t].constr_b[constr] + 0
                    ) # z[c])
                    c += 1
                end
            end
        end

        ncons = copy(c) - 1

        if ata_model.obj.name in ["maximin", "soyster_maximin", "de_jong_maximin"]
            #Objective bound
            JuMP.@variable(m, w >= 0)
            for t = 1:ata_model.settings.T
                for k = 1:size(ata_model.obj.cores[t].points, 1)
                    JuMP.@constraint(
                        m,
                        sum(
                            round(IIF_new[t][k, i]; digits = 4) * x[i, t] for
                            i = 1:ata_model.settings.n_fs
                        ) >= w
                    )
                end
            end
            JuMP.@objective(m, Min, (-w))
        elseif ata_model.obj.name == "minimax"
            #Objective bound
            JuMP.@variable(m, epsilon >= 0)
            for t = 1:ata_model.settings.T
                for k = 1:size(ata_model.obj.cores[t].points, 1)
                    JuMP.@constraint(
                        m,
                        sum(
                            round(IIF_new[t][k, i]; digits = 4) * x[i, t] for
                            i = 1:ata_model.settings.n_fs
                        ) - ata_model.obj.cores[t].targets[k] <= epsilon
                    )
                    JuMP.@constraint(
                        m,
                        sum(
                            round(IIF_new[t][k, i]; digits = 4) * x[i, t] for
                            i = 1:ata_model.settings.n_fs
                        ) - ata_model.obj.cores[t].targets[k] >= -epsilon
                    )
                end
            end
            JuMP.@objective(m, Min, epsilon)
        end
        JuMP.optimize!(m)
        success!(
            ata_model,
            string("The model has termination status:", JuMP.termination_status(m), "."),
        )
        #println(string("The model has termination status:", JuMP.termination_status(m)))
        design = abs.(round.(JuMP.value.(x)))
        DelimitedFiles.writedlm(string(results_folder, "/design.csv"), design)
        ata_model.output.design = design
        JLD2.@save string(results_folder, "/ata_model.jld2") ata_model

    catch e
        error!(ata_model, string(sprint(showerror, e)))

    end
    return nothing
end


mutable struct DQMCStack{
        GreensElType <: Number,
        HoppingElType <: Number,
        GreensMatType <: AbstractArray{GreensElType},
        HoppingMatType <: AbstractArray{HoppingElType},
        InteractionMatType <: AbstractArray
    } <: AbstractDQMCStack

    u_stack::Vector{GreensMatType}
    d_stack::Vector{Vector{Float64}}
    t_stack::Vector{GreensMatType}

    Ul::GreensMatType
    Ur::GreensMatType
    Dl::Vector{Float64}
    Dr::Vector{Float64}
    Tl::GreensMatType
    Tr::GreensMatType
    pivot::Vector{Int64}
    tempv::Vector{GreensElType}
    tempvf::Vector{Float64}

    greens::GreensMatType
    greens_temp::GreensMatType

    tmp1::GreensMatType
    tmp2::GreensMatType

    ranges::Array{UnitRange, 1}
    n_elements::Int
    current_slice::Int # running internally over 0:mc.parameters.slices+1, where 0 and mc.parameters.slices+1 are artifcial to prepare next sweep direction.
    direction::Int

    # # -------- Global update backup
    # gb_u_stack::Array{GreensElType, 3}
    # gb_d_stack::Matrix{Float64}
    # gb_t_stack::Array{GreensElType, 3}

    # gb_greens::GreensMatType
    # gb_log_det::Float64

    # gb_conf::Array{Float64, 3}
    # # --------

    # preallocated, reused arrays
    curr_U::GreensMatType
    eV::InteractionMatType

    # hopping matrices (mu included)
    hopping_matrix::HoppingMatType
    hopping_matrix_exp::HoppingMatType
    hopping_matrix_exp_inv::HoppingMatType
    hopping_matrix_exp_squared::HoppingMatType
    hopping_matrix_exp_inv_squared::HoppingMatType

    # checkerboard hopping matrices
    checkerboard::Matrix{Int} # src, trg, bondid
    groups::Vector{UnitRange}
    n_groups::Int
    chkr_hop_half::Vector{SparseMatrixCSC{HoppingElType, Int64}}
    chkr_hop_half_inv::Vector{SparseMatrixCSC{HoppingElType, Int64}}
    chkr_hop_half_dagger::Vector{SparseMatrixCSC{HoppingElType, Int64}}
    chkr_hop::Vector{SparseMatrixCSC{HoppingElType, Int64}} # without prefactor 0.5 in matrix exponentials
    chkr_hop_inv::Vector{SparseMatrixCSC{HoppingElType, Int64}}
    chkr_hop_dagger::Vector{SparseMatrixCSC{HoppingElType, Int64}}
    chkr_mu_half::SparseMatrixCSC{HoppingElType, Int64}
    chkr_mu_half_inv::SparseMatrixCSC{HoppingElType, Int64}
    chkr_mu::SparseMatrixCSC{HoppingElType, Int64}
    chkr_mu_inv::SparseMatrixCSC{HoppingElType, Int64}

    function DQMCStack{GET, HET, GMT, HMT, IMT}() where {
            GET<:Number, HET<:Number,
            GMT<:AbstractArray{GET}, HMT<:AbstractArray{HET}, IMT<:AbstractArray
        }
        @assert isconcretetype(GET);
        @assert isconcretetype(HET);
        @assert isconcretetype(GMT);
        @assert isconcretetype(HMT);
        @assert isconcretetype(IMT);
        @assert eltype(GMT) == GET;
        @assert eltype(HMT) == HET;
        new{GET, HET, GMT, HMT, IMT}()
    end
end

# type helpers
geltype(::DQMCStack{GET, HET, GMT, HMT, IMT}) where {GET, HET, GMT, HMT, IMT} = GET
heltype(::DQMCStack{GET, HET, GMT, HMT, IMT}) where {GET, HET, GMT, HMT, IMT} = HET
gmattype(::DQMCStack{GET, HET, GMT, HMT, IMT}) where {GET, HET, GMT, HMT, IMT} = GMT
hmattype(::DQMCStack{GET, HET, GMT, HMT, IMT}) where {GET, HET, GMT, HMT, IMT} = HMT
imattype(::DQMCStack{GET, HET, GMT, HMT, IMT}) where {GET, HET, GMT, HMT, IMT} = IMT

geltype(mc::DQMC) = geltype(mc.stack)
heltype(mc::DQMC) = heltype(mc.stack)
gmattype(mc::DQMC) = gmattype(mc.stack)
hmattype(mc::DQMC) = hmattype(mc.stack)
imattype(mc::DQMC) = imattype(mc.stack)

################################################################################
### Stack Initialization
################################################################################

function initialize_stack(mc::DQMC, ::DQMCStack)
    GreensElType = geltype(mc)
    GreensMatType = gmattype(mc)
    HoppingElType = heltype(mc)
    N = length(lattice(mc))
    flv = nflavors(mc.model)

    mc.stack.n_elements = convert(Int, mc.parameters.slices / mc.parameters.safe_mult) + 1

    mc.stack.u_stack = [GreensMatType(undef, flv*N, flv*N) for _ in 1:mc.stack.n_elements]
    mc.stack.d_stack = [zeros(Float64, flv*N) for _ in 1:mc.stack.n_elements]
    mc.stack.t_stack = [GreensMatType(undef, flv*N, flv*N) for _ in 1:mc.stack.n_elements]

    mc.stack.greens = GreensMatType(undef, flv*N, flv*N)
    mc.stack.greens_temp = GreensMatType(undef, flv*N, flv*N)

    # used in calculate_greens
    # do not change in slice_matrices.jl or interaction_matrix_exp!
    mc.stack.Ul = GreensMatType(I, flv*N, flv*N)
    mc.stack.Ur = GreensMatType(I, flv*N, flv*N)
    mc.stack.Tl = GreensMatType(I, flv*N, flv*N)
    mc.stack.Tr = GreensMatType(I, flv*N, flv*N)
    mc.stack.Dl = ones(Float64, flv*N)
    mc.stack.Dr = ones(Float64, flv*N)
    # can be changed anywhere
    mc.stack.pivot = Vector{Int64}(undef, flv*N)
    mc.stack.tempv = Vector{GreensElType}(undef, flv*N)
    mc.stack.tempvf = Vector{Float64}(undef, flv*N)

    # can be changed anywhere
    mc.stack.tmp1 = GreensMatType(undef, flv*N, flv*N)
    mc.stack.tmp2 = GreensMatType(undef, flv*N, flv*N)

    # # Global update backup
    # mc.stack.gb_u_stack = zero(mc.stack.u_stack)
    # mc.stack.gb_d_stack = zero(mc.stack.d_stack)
    # mc.stack.gb_t_stack = zero(mc.stack.t_stack)
    # mc.stack.gb_greens = zero(mc.stack.greens)
    # mc.stack.gb_log_det = 0.
    # mc.stack.gb_conf = zero(mc.conf)

    mc.stack.ranges = UnitRange[]

    for i in 1:mc.stack.n_elements - 1
        push!(mc.stack.ranges, 1 + (i - 1) * mc.parameters.safe_mult:i * mc.parameters.safe_mult)
    end

    mc.stack.curr_U = GreensMatType(undef, flv*N, flv*N)
    mc.stack.eV = init_interaction_matrix(mc.model)

    nothing
end

# hopping
function init_hopping_matrices(mc::DQMC{M,CB}, m::Model) where {M, CB<:Checkerboard}
    init_hopping_matrix_exp(mc, m)
    CB <: CheckerboardTrue && init_checkerboard_matrices(mc, m)
    nothing
end
function init_hopping_matrix_exp(mc::DQMC, m::Model)
    N = length(lattice(m))
    flv = nflavors(m)
    dtau = mc.parameters.delta_tau

    T = hopping_matrix(mc, m)
    size(T) == (flv*N, flv*N) || error("Hopping matrix should have size "*
                                "$((flv*N, flv*N)) but has size $(size(T)) .")
    mc.stack.hopping_matrix = T
    mc.stack.hopping_matrix_exp = exp(-0.5 * dtau * T)
    mc.stack.hopping_matrix_exp_inv = exp(0.5 * dtau * T)
    mc.stack.hopping_matrix_exp_squared = mc.stack.hopping_matrix_exp * mc.stack.hopping_matrix_exp
    mc.stack.hopping_matrix_exp_inv_squared = mc.stack.hopping_matrix_exp_inv * mc.stack.hopping_matrix_exp_inv
    nothing
end

# checkerboard
rem_eff_zeros!(X::AbstractArray) = map!(e -> abs.(e)<1e-15 ? zero(e) : e,X,X)
function init_checkerboard_matrices(mc::DQMC, m::Model)
    s = mc.stack
    l = lattice(m)
    flv = nflavors(m)
    H = heltype(mc)
    N = length(l)
    dtau = mc.parameters.delta_tau
    mu = m.mu

    s.checkerboard, s.groups, s.n_groups = build_checkerboard(l)
    n_grps = s.n_groups
    cb = s.checkerboard

    T = reshape(hopping_matrix(mc, m), (N, flv, N, flv))

    s.chkr_hop_half = Vector{SparseMatrixCSC{H, Int}}(undef, n_grps)
    s.chkr_hop_half_inv = Vector{SparseMatrixCSC{H, Int}}(undef, n_grps)
    s.chkr_hop = Vector{SparseMatrixCSC{H, Int}}(undef, n_grps)
    s.chkr_hop_inv = Vector{SparseMatrixCSC{H, Int}}(undef, n_grps)

    for (g, gr) in enumerate(s.groups)
        Tg = zeros(H, N, flv, N, flv)
        for i in gr
            src, trg = cb[1:2,i]
            for f1 in 1:flv, f2 in 1:flv
                Tg[trg, f1, src, f2] = T[trg, f1, src, f2]
            end
        end

        Tgg = reshape(Tg, (N*flv, N*flv))
        s.chkr_hop_half[g] = sparse(rem_eff_zeros!(exp(-0.5 * dtau * Tgg)))
        s.chkr_hop_half_inv[g] = sparse(rem_eff_zeros!(exp(0.5 * dtau * Tgg)))
        s.chkr_hop[g] = sparse(rem_eff_zeros!(exp(- dtau * Tgg)))
        s.chkr_hop_inv[g] = sparse(rem_eff_zeros!(exp(dtau * Tgg)))
    end

    s.chkr_hop_half_dagger = adjoint.(s.chkr_hop_half)
    s.chkr_hop_dagger = adjoint.(s.chkr_hop)

    mus = diag(reshape(T, (N*flv, N*flv)))
    s.chkr_mu_half = spdiagm(0 => exp.(-0.5 * dtau * mus))
    s.chkr_mu_half_inv = spdiagm(0 => exp.(0.5 * dtau * mus))
    s.chkr_mu = spdiagm(0 => exp.(-dtau * mus))
    s.chkr_mu_inv = spdiagm(0 => exp.(dtau * mus))

    # hop_mat_exp_chkr = foldl(*,s.chkr_hop_half) * sqrt.(s.chkr_mu)
    # r = effreldiff(s.hopping_matrix_exp,hop_mat_exp_chkr)
    # r[find(x->x==zero(x),hop_mat_exp_chkr)] = 0.
    # println("Checkerboard - Exact ≈ ", round(maximum(absdiff(s.hopping_matrix_exp,hop_mat_exp_chkr)), 4))
    nothing
end

"""
    build_stack(mc::DQMC)

Build slice matrix stack from scratch.
"""
@bm function build_stack(mc::DQMC, ::DQMCStack)
    copyto!(mc.stack.u_stack[1], I)
    mc.stack.d_stack[1] .= one(eltype(mc.stack.d_stack[1]))
    copyto!(mc.stack.t_stack[1], I)

    @inbounds for i in 1:length(mc.stack.ranges)
        add_slice_sequence_left(mc, i)
    end

    mc.stack.current_slice = mc.parameters.slices + 1
    mc.stack.direction = -1

    nothing
end

@bm function reverse_build_stack(mc::DQMC, ::DQMCStack)
    copyto!(mc.stack.u_stack[end], I)
    mc.stack.d_stack[end] .= one(eltype(mc.stack.d_stack[end]))
    copyto!(mc.stack.t_stack[end], I)

    @inbounds for i in length(mc.stack.ranges):-1:1
        add_slice_sequence_right(mc, i)
    end

    mc.stack.current_slice = 0
    mc.stack.direction = 1

    nothing
end

################################################################################
### Slice matrix stack manipulations/updates
################################################################################

"""
    add_slice_sequence_left(mc::DQMC, idx)

Computes the next `mc.parameters.safe_mult` slice matrix products from the current `idx`
and writes them to `idx+1`. The index `idx` does not refer to the slice index,
but `mc.parameters.safe_mult` times the slice index.
"""
@bm function add_slice_sequence_left(mc::DQMC, idx::Int)
    @inbounds begin
        copyto!(mc.stack.curr_U, mc.stack.u_stack[idx])

        # println("Adding slice seq left $idx = ", mc.stack.ranges[idx])
        for slice in mc.stack.ranges[idx]
            multiply_slice_matrix_left!(mc, mc.model, slice, mc.stack.curr_U)
        end

        vmul!(mc.stack.tmp1, mc.stack.curr_U, Diagonal(mc.stack.d_stack[idx]))
        udt_AVX_pivot!(
            mc.stack.u_stack[idx + 1], mc.stack.d_stack[idx + 1], mc.stack.tmp1,
            mc.stack.pivot, mc.stack.tempv
        )
        vmul!(mc.stack.t_stack[idx + 1], mc.stack.tmp1, mc.stack.t_stack[idx])
    end
end

"""
    add_slice_sequence_right(mc::DQMC, idx)

Computes the next `mc.parameters.safe_mult` slice matrix products from the current
`idx+1` and writes them to `idx`. The index `idx` does not refer to the slice
index, but `mc.parameters.safe_mult` times the slice index.
"""
@bm function add_slice_sequence_right(mc::DQMC, idx::Int)
    @inbounds begin
        copyto!(mc.stack.curr_U, mc.stack.u_stack[idx + 1])

        for slice in reverse(mc.stack.ranges[idx])
            multiply_daggered_slice_matrix_left!(mc, mc.model, slice, mc.stack.curr_U)
        end

        vmul!(mc.stack.tmp1, mc.stack.curr_U, Diagonal(mc.stack.d_stack[idx + 1]))
        udt_AVX_pivot!(
            mc.stack.u_stack[idx], mc.stack.d_stack[idx], mc.stack.tmp1, mc.stack.pivot, mc.stack.tempv
        )
        vmul!(mc.stack.t_stack[idx], mc.stack.tmp1, mc.stack.t_stack[idx + 1])
    end
end

################################################################################
### Green's function calculation
################################################################################

"""
    calculate_greens_AVX!(Ul, Dl, Tl, Ur, Dr, Tr, G[, pivot, temp])

Calculates the effective Greens function matrix `G` from two UDT decompositions
`Ul, Dl, Tl` and `Ur, Dr, Tr`. Additionally a `pivot` vector can be given. Note
that all inputs will be overwritten.

The UDT should follow from a set of slice_matrix multiplications, such that
`Ur, Dr, Tr = udt(B(slice)' ⋯ B(M)')` and `Ul, Dl, Tl = udt(B(slice-1) ⋯ B(1))`.
The computed Greens function is then given as `G = inv(I + Ul Dl Tl Tr Dr Ur)`
and computed here.

`Ul, Tl, Ur, Tr, G` should be square matrices, `Dl, Dr` real Vectors (from
Diagonal matrices), `pivot` an integer Vector and `temp` a Vector with the same
element type as the matrices.
"""
@bm function calculate_greens_AVX!(
        Ul, Dl, Tl, Ur, Dr, Tr, G::AbstractArray{T},
        pivot = Vector{Int64}(undef, length(Dl)),
        temp = Vector{T}(undef, length(Dl))
    ) where T
    # @bm "B1" begin
        # Used: Ul, Dl, Tl, Ur, Dr, Tr
        # TODO: [I + Ul Dl Tl Tr^† Dr Ur^†]^-1
        # Compute: Dl * ((Tl * Tr) * Dr) -> Tr * Dr * G   (UDT)
        vmul!(G, Tl, adjoint(Tr))
        vmul!(Tr, G, Diagonal(Dr))
        vmul!(G, Diagonal(Dl), Tr)
        udt_AVX_pivot!(Tr, Dr, G, pivot, temp, Val(false)) # Dl available
    # end

    # @bm "B2" begin
        # Used: Ul, Ur, G, Tr, Dr  (Ul, Ur, Tr unitary (inv = adjoint))
        # TODO: [I + Ul Tr Dr G Ur^†]^-1
        #     = [(Ul Tr) ((Ul Tr)^-1 (G Ur^†) + Dr) (G Ur)]^-1
        #     = Ur G^-1 [(Ul Tr)^† Ur G^-1 + Dr]^-1 (Ul Tr)^†
        # Compute: Ul Tr -> Tl
        #          (Ur G^-1) -> Ur
        #          ((Ul Tr)^† Ur G^-1) -> Tr
        vmul!(Tl, Ul, Tr)
        rdivp!(Ur, G, Ul, pivot) # requires unpivoted udt decompostion (Val(false))
        vmul!(Tr, adjoint(Tl), Ur)
    # end

    # @bm "B3" begin
        # Used: Tl, Ur, Tr, Dr
        # TODO: Ur [Tr + Dr]^-1 Tl^† -> Ur [Tr]^-1 Tl^†
        rvadd!(Tr, Diagonal(Dr))
    # end

    # @bm "B4" begin
        # Used: Ur, Tr, Tl
        # TODO: Ur [Tr]^-1 Tl^† -> Ur [Ul Dr Tr]^-1 Tl^†
        #    -> Ur Tr^-1 Dr^-1 Ul^† Tl^† -> Ur Tr^-1 Dr^-1 (Tl Ul)^†
        # Compute: Ur Tr^-1 -> Ur,  Tl Ul -> Tr
        udt_AVX_pivot!(Ul, Dr, Tr, pivot, temp, Val(false)) # Dl available
        rdivp!(Ur, Tr, G, pivot) # requires unpivoted udt decompostion (false)
        vmul!(Tr, Tl, Ul)
    # end

    # @bm "B5" begin
        @turbo for i in eachindex(Dr)
            Dl[i] = 1.0 / Dr[i]
        end
    # end

    # @bm "B6" begin
        # Used: Ur, Tr, Dl, Ul, Tl
        # TODO: (Ur Dl) Tr^† -> G
        vmul!(Ul, Ur, Diagonal(Dl))
        vmul!(G, Ul, adjoint(Tr))
    # end
end

"""
    calculate_greens(mc::DQMC)

Computes the effective greens function from the current state of the stack and
saves the result to `mc.stack.greens`.

This assumes the `mc.stack.Ul, mc.stack.Dl, mc.stack.Tl = udt(B(slice-1) ⋯ B(1))` and
`mc.stack.Ur, mc.stack.Dr, mc.stack.Tr = udt(B(slice)' ⋯ B(M)')`.

This should only used internally.
"""
@bm function calculate_greens(mc::DQMC, output::AbstractMatrix = mc.stack.greens)
    calculate_greens_AVX!(
        mc.stack.Ul, mc.stack.Dl, mc.stack.Tl,
        mc.stack.Ur, mc.stack.Dr, mc.stack.Tr,
        output, mc.stack.pivot, mc.stack.tempv
    )
    output
end

"""
    calculate_greens(mc::DQMC, slice[, output=mc.stack.greens, safe_mult])

Compute the effective equal-time greens function from scratch at a given `slice`.

This does not invalidate the stack, but it does overwrite `mc.stack.greens`.
"""
@bm function calculate_greens(
        mc::DQMC, slice::Int, output::AbstractMatrix = mc.stack.greens,
        conf::AbstractArray = mc.conf, safe_mult::Int = mc.parameters.safe_mult
    )
    copyto!(mc.stack.curr_U, I)
    copyto!(mc.stack.Ur, I)
    mc.stack.Dr .= one(eltype(mc.stack.Dr))
    copyto!(mc.stack.Tr, I)

    # Calculate Ur,Dr,Tr=B(slice)' ... B(M)'
    if slice+1 <= mc.parameters.slices
        start = slice+1
        stop = mc.parameters.slices
        for k in reverse(start:stop)
            if mod(k,safe_mult) == 0
                multiply_daggered_slice_matrix_left!(mc, mc.model, k, mc.stack.curr_U, conf)
                vmul!(mc.stack.tmp1, mc.stack.curr_U, Diagonal(mc.stack.Dr))
                udt_AVX_pivot!(mc.stack.curr_U, mc.stack.Dr, mc.stack.tmp1, mc.stack.pivot, mc.stack.tempv)
                copyto!(mc.stack.tmp2, mc.stack.Tr)
                vmul!(mc.stack.Tr, mc.stack.tmp1, mc.stack.tmp2)
            else
                multiply_daggered_slice_matrix_left!(mc, mc.model, k, mc.stack.curr_U, conf)
            end
        end
        vmul!(mc.stack.tmp1, mc.stack.curr_U, Diagonal(mc.stack.Dr))
        udt_AVX_pivot!(mc.stack.Ur, mc.stack.Dr, mc.stack.tmp1, mc.stack.pivot, mc.stack.tempv)
        copyto!(mc.stack.tmp2, mc.stack.Tr)
        vmul!(mc.stack.Tr, mc.stack.tmp1, mc.stack.tmp2)
    end

    copyto!(mc.stack.curr_U, I)
    copyto!(mc.stack.Ul, I)
    mc.stack.Dl .= one(eltype(mc.stack.Dl))
    copyto!(mc.stack.Tl, I)

    # Calculate Ul,Dl,Tl=B(slice-1) ... B(1)
    if slice >= 1
        start = 1
        stop = slice
        for k in start:stop
            if mod(k,safe_mult) == 0
                multiply_slice_matrix_left!(mc, mc.model, k, mc.stack.curr_U, conf)
                vmul!(mc.stack.tmp1, mc.stack.curr_U, Diagonal(mc.stack.Dl))
                udt_AVX_pivot!(mc.stack.curr_U, mc.stack.Dl, mc.stack.tmp1, mc.stack.pivot, mc.stack.tempv)
                copyto!(mc.stack.tmp2, mc.stack.Tl)
                vmul!(mc.stack.Tl, mc.stack.tmp1, mc.stack.tmp2)
            else
                multiply_slice_matrix_left!(mc, mc.model, k, mc.stack.curr_U, conf)
            end
        end
        vmul!(mc.stack.tmp1, mc.stack.curr_U, Diagonal(mc.stack.Dl))
        udt_AVX_pivot!(mc.stack.Ul, mc.stack.Dl, mc.stack.tmp1, mc.stack.pivot, mc.stack.tempv)
        copyto!(mc.stack.tmp2, mc.stack.Tl)
        vmul!(mc.stack.Tl, mc.stack.tmp1, mc.stack.tmp2)
    end

    return calculate_greens(mc, output)
end

################################################################################
### Stack update
################################################################################

# Green's function propagation
@inline @bm function wrap_greens!(mc::DQMC, gf, curr_slice::Int, direction::Int)
    if direction == -1
        multiply_slice_matrix_inv_left!(mc, mc.model, curr_slice - 1, gf)
        multiply_slice_matrix_right!(mc, mc.model, curr_slice - 1, gf)
    else
        multiply_slice_matrix_left!(mc, mc.model, curr_slice, gf)
        multiply_slice_matrix_inv_right!(mc, mc.model, curr_slice, gf)
    end
    nothing
end

@bm function propagate(mc::DQMC)
    @inbounds if mc.stack.direction == 1
        if mod(mc.stack.current_slice, mc.parameters.safe_mult) == 0
            mc.stack.current_slice +=1 # slice we are going to
            if mc.stack.current_slice == 1
                copyto!(mc.stack.Ur, mc.stack.u_stack[1])
                copyto!(mc.stack.Dr, mc.stack.d_stack[1])
                copyto!(mc.stack.Tr, mc.stack.t_stack[1])
                copyto!(mc.stack.u_stack[1], I)
                mc.stack.d_stack[1] .= one(eltype(mc.stack.d_stack[1]))
                copyto!(mc.stack.t_stack[1], I)
                copyto!(mc.stack.Ul, mc.stack.u_stack[1])
                copyto!(mc.stack.Dl, mc.stack.d_stack[1])
                copyto!(mc.stack.Tl, mc.stack.t_stack[1])

                calculate_greens(mc) # greens_1 ( === greens_{m+1} )

            elseif 1 < mc.stack.current_slice <= mc.parameters.slices
                idx = Int((mc.stack.current_slice - 1)/mc.parameters.safe_mult)

                copyto!(mc.stack.Ur, mc.stack.u_stack[idx+1])
                copyto!(mc.stack.Dr, mc.stack.d_stack[idx+1])
                copyto!(mc.stack.Tr, mc.stack.t_stack[idx+1])
                add_slice_sequence_left(mc, idx)
                copyto!(mc.stack.Ul, mc.stack.u_stack[idx+1])
                copyto!(mc.stack.Dl, mc.stack.d_stack[idx+1])
                copyto!(mc.stack.Tl, mc.stack.t_stack[idx+1])

                if mc.parameters.check_propagation_error
                    copyto!(mc.stack.greens_temp, mc.stack.greens)
                end

                # Should this be mc.stack.greens_temp?
                # If so, shouldn't this only run w/ mc.parameters.all_checks = true?
                wrap_greens!(mc, mc.stack.greens_temp, mc.stack.current_slice - 1, 1)

                calculate_greens(mc) # greens_{slice we are propagating to}

                if mc.parameters.check_propagation_error
                    # OPT: could probably be optimized through explicit loop
                    greensdiff = maximum(abs.(mc.stack.greens_temp - mc.stack.greens))
                    if greensdiff > 1e-7
                        push!(mc.analysis.propagation_error, greensdiff)
                        mc.parameters.silent || @printf(
                            "->%d \t+1 Propagation instability\t %.1e\n",
                            mc.stack.current_slice, greensdiff
                        )
                    end
                end

            else # we are going to mc.parameters.slices+1
                idx = mc.stack.n_elements - 1
                add_slice_sequence_left(mc, idx)
                mc.stack.direction = -1
                mc.stack.current_slice = mc.parameters.slices+1 # redundant
                propagate(mc)
            end

        else
            # Wrapping
            wrap_greens!(mc, mc.stack.greens, mc.stack.current_slice, 1)
            mc.stack.current_slice += 1
        end

    else # mc.stack.direction == -1
        if mod(mc.stack.current_slice-1, mc.parameters.safe_mult) == 0
            mc.stack.current_slice -= 1 # slice we are going to
            if mc.stack.current_slice == mc.parameters.slices
                copyto!(mc.stack.Ul, mc.stack.u_stack[end])
                copyto!(mc.stack.Dl, mc.stack.d_stack[end])
                copyto!(mc.stack.Tl, mc.stack.t_stack[end])
                copyto!(mc.stack.u_stack[end], I)
                mc.stack.d_stack[end] .= one(eltype(mc.stack.d_stack[end]))
                copyto!(mc.stack.t_stack[end], I)
                copyto!(mc.stack.Ur, mc.stack.u_stack[end])
                copyto!(mc.stack.Dr, mc.stack.d_stack[end])
                copyto!(mc.stack.Tr, mc.stack.t_stack[end])

                calculate_greens(mc) # greens_{mc.parameters.slices+1} === greens_1

                # wrap to greens_{mc.parameters.slices}
                wrap_greens!(mc, mc.stack.greens, mc.stack.current_slice + 1, -1)

            elseif 0 < mc.stack.current_slice < mc.parameters.slices
                idx = Int(mc.stack.current_slice / mc.parameters.safe_mult) + 1

                copyto!(mc.stack.Ul, mc.stack.u_stack[idx])
                copyto!(mc.stack.Dl, mc.stack.d_stack[idx])
                copyto!(mc.stack.Tl, mc.stack.t_stack[idx])
                add_slice_sequence_right(mc, idx)
                copyto!(mc.stack.Ur, mc.stack.u_stack[idx])
                copyto!(mc.stack.Dr, mc.stack.d_stack[idx])
                copyto!(mc.stack.Tr, mc.stack.t_stack[idx])

                if mc.parameters.check_propagation_error
                    copyto!(mc.stack.greens_temp, mc.stack.greens)
                end

                calculate_greens(mc)

                if mc.parameters.check_propagation_error
                    # OPT: could probably be optimized through explicit loop
                    greensdiff = maximum(abs.(mc.stack.greens_temp - mc.stack.greens))
                    if greensdiff > 1e-7
                        push!(mc.analysis.propagation_error, greensdiff)
                        mc.parameters.silent || @printf(
                            "->%d \t-1 Propagation instability\t %.1e\n",
                            mc.stack.current_slice, greensdiff
                        )
                    end
                end

                wrap_greens!(mc, mc.stack.greens, mc.stack.current_slice + 1, -1)

            else # we are going to 0
                idx = 1
                add_slice_sequence_right(mc, idx)
                mc.stack.direction = 1
                mc.stack.current_slice = 0 # redundant
                propagate(mc)
            end

        else
            # Wrapping
            wrap_greens!(mc, mc.stack.greens, mc.stack.current_slice, -1)
            mc.stack.current_slice -= 1
        end
    end
    nothing
end


module ACASX_Common
using EncounterDBN
using PilotResponse
using DynamicModel
using WorldModel
using Sensor
using CASCoordination
using CollisionAvoidanceSystem
using Simulator
using RLESUtils, Observers

addObserver(sim, f::Function) = add_observer(sim.observer, f)
addObserver(sim, tag::AbstractString, f::Function) = add_observer(sim.observer, tag, f)
clearObservers!(sim) = empty!(sim.observer)

function initialize(sim)
  wm, aem, pr, adm, cas, sr = sim.wm, sim.em, sim.pr, sim.dm, sim.cas, sim.sr
  sim.t_index = 0
  EncounterDBN.initialize(aem)

  for i = 1:sim.params.num_aircraft
    #TODO: clean up this structure
    initial = EncounterDBN.getInitialState(aem, i)
    @notify_observer(sim.observer,"Initial", Any[i, sim.t_index, aem])

    Sensor.initialize(sr[i])
    CollisionAvoidanceSystem.initialize(cas[i])
    PilotResponse.initialize(pr[i])
    state = DynamicModel.initialize(adm[i], initial)
    WorldModel.initialize(wm, i, state)

    @notify_observer(sim.observer,"CAS_info", Any[i, sim.cas[i]])
  end

  # reset miss distances
  sim.vmd = typemax(Float64)
  sim.hmd = typemax(Float64)
  sim.md = typemax(Float64)
  sim.md_time = 0

  #reset the probability
  sim.step_logProb = 0.0
  @notify_observer(sim.observer, "initialize", Any[sim])
  return
end

function update(sim)
  wm, aem, pr, adm, cas, sr = sim.wm, sim.em, sim.pr, sim.dm, sim.cas, sim.sr

  sim.t_index += 1
  sim.step_logProb = 0.0 #track the log probabilities in this update

  cmdLogProb = EncounterDBN.update(aem)
  sim.step_logProb += cmdLogProb #TODO: decompose this by aircraft?
  states = WorldModel.getAll(wm)
  @notify_observer(sim.observer,"WorldModel", Any[sim.t_index, wm])

  #check and update miss distances
  vhdist = getvhdist(sim.wm)
  mds = getMissDistance(sim.params.nmac_h, sim.params.nmac_r, vhdist)
  md, index = findmin(mds)
  if md < sim.md
    sim.vmd, sim.hmd = vhdist[index]
    sim.md = md
    sim.md_time = states[1].t #report time instead of index
  end

  for i = 1:sim.params.num_aircraft
    @notify_observer(sim.observer, "Dynamics", Any[i, sim.t_index, adm[i]])

    #intended command
    command = EncounterDBN.get(aem, i)
    @notify_observer(sim.observer, "Command", Any[i, sim.t_index, command])

    output = Sensor.update(sr[i], states)
    @notify_observer(sim.observer, "Sensor",Any[i, sim.t_index, sr[i]])

    RA = CollisionAvoidanceSystem.update(cas[i], output)
    @notify_observer(sim.observer, "CAS", Any[i, sim.t_index, cas[i]])

    response = PilotResponse.update(pr[i], command, RA, states[i])
    sim.step_logProb += response.logProb
    @notify_observer(sim.observer, "Response", Any[i, sim.t_index, pr[i]])

    state = DynamicModel.update(adm[i], response)
    WorldModel.update(wm, i, state)
  end
  WorldModel.updateAll(wm)

  @notify_observer(sim.observer, "logProb", Any[sim.t_index, sim.step_logProb])

  sim.label_as_nmac = NMAC_occurred(sim) #the same for now... no filters
  @notify_observer(sim.observer, "update", Any[sim])
  return (exp(sim.step_logProb), NMAC_occurred(sim), sim.md)
end

function getvhdist(wm::AbstractWorldModel)
  states = WorldModel.getAll(wm) #states::Vector{ASWMState}
  #[(vdist,hdist)]
  vhdist = [(abs(s2.h - s1.h), norm([(s2.x - s1.x), (s2.y - s1.y)])) for s1 in states, s2 in states]
  for i = 1:length(states)
    vhdist[i, i] = (typemax(Float64), typemax(Float64)) #make selfs big to ease finding min dist
  end
  return vhdist
end

isterminal(sim) = (sim.params.end_on_nmac && NMAC_occurred(sim)) || sim.t_index >= sim.params.max_steps
NMAC_occurred(sim) = sim.hmd <= sim.params.nmac_r && sim.vmd <= sim.params.nmac_h
getMissDistance(nmac_h::Float64, nmac_r::Float64, vhmd) = map((vh) -> max(vh[2] * (nmac_h / nmac_r), vh[1]), vhmd)

end #module


#=
Idmrg environments are only to be used internally.
They have to be updated manually, without any kind of checks
=#

struct IDMRGEnv{H,V<:AbstractTensorMap}
    opp :: H
    lw :: PeriodicArray{V,2}
    rw :: PeriodicArray{V,2}
end

function IDMRGEnv(state::Union{MPSMultiline,InfiniteMPS},env)
    state === env.dependency || recalculate!(env,state);
    IDMRGEnv(env.opp,deepcopy(env.lw),deepcopy(env.rw));
end

leftenv(envs::IDMRGEnv,pos::Int) = envs.lw[pos,:];
leftenv(envs::IDMRGEnv,row::Int,col::Int) = envs.lw[row,col];
leftenv(envs::IDMRGEnv,pos::Int,state::InfiniteMPS) = envs.lw[pos,:];
function setleftenv!(envs::IDMRGEnv,pos,lw)
    envs.lw[pos,:] = lw[:]
end
function setleftenv!(envs::IDMRGEnv,row,col,val)
    envs.lw[row,col] = val;
end

rightenv(envs::IDMRGEnv,row::Int,col::Int) = envs.rw[row,col];
rightenv(envs::IDMRGEnv,pos::Int) = envs.rw[pos,:];
rightenv(envs::IDMRGEnv,pos::Int,state::InfiniteMPS) = envs.rw[pos,:];
function setrightenv!(envs::IDMRGEnv,pos,rw)
    envs.rw[pos,:] = rw[:]
end
function setrightenv!(envs::IDMRGEnv,row,col,val)
    envs.rw[row,col] = val;
end


using ArgParse
using Random
using JLD
using NPZ
include("./DEAC.jl")

function parse_commandline()
    s = ArgParseSettings()

    @add_arg_table s begin
        "--temperature", "-T"
            help = "Temperature of system."
            arg_type = Float64
            default = 0.0
        "--number_of_generations", "-N"
            help = "Number of generations before genetic algorithm quits."
            arg_type = Int64
            default = 100000
        "--population_size", "-P"
            help = "Size of initial population"
            arg_type = Int64
            default = 512
        "--genome_size", "-M"
            help = "Size of genome."
            arg_type = Int64
            default = 600
        "--omega_max"
            help = "Maximum frequency to explore."
            arg_type = Float64
            default = 60.0
        "--smooth"
            help = "Use smoothing factor in fitness."
            action = :store_true
        "--normalize"
            help = "Normalize spectrum to the zeroeth moment."
            action = :store_true
        "--use_inverse_first_moment"
            help = "Calculate inverse first moment from ISF data and use it in fitness."
            action = :store_true
        "--first_moment"
            help = "FIXME First moment."
            arg_type = Float64
            default = -1.0
        "--third_moment"
            help = "FIXME Third moment."
            arg_type = Float64
            default = -1.0
        "--third_moment_error"
            help = "FIXME Third moment."
            arg_type = Float64
            default = 0.01
        "--crossover_probability", "-r"
            help = "Initial probability for parent gene to become mutant vector gene."
            arg_type = Float64
            default = 0.90
        "--self_adapting_crossover_probability", "-u"
            help = "Probability for `crossover_probability` to mutate."
            arg_type = Float64
            default = 0.10
        "--differential_weight", "-F"
            help = "Initial weight factor when creating mutant vector."
            arg_type = Float64
            default = 0.90
        "--self_adapting_differential_weight_probability", "-v"
            help = "Probability for `differential_weight` to mutate."
            arg_type = Float64
            default = 0.10
        "--self_adapting_differential_weight_shift", "-l"
            help = "If `self_adapting_differential_weight_probability` mutate, new value is `l + m*rand()`."
            arg_type = Float64
            default = 0.10
        "--self_adapting_differential_weight", "-m"
            help = "If `self_adapting_differential_weight_probability` mutate, new value is `l + m*rand()`."
            arg_type = Float64
            default = 0.90
        "--reject"
            help = "Name of rejection function."
            arg_type = String
            default = "smallerFit"
        "--frequency_bins_method"
            help = "Name of function to generate frequency bins."
            arg_type = String
            default = "fixed_frequency_bins"
        "--load_frequency_bins_filename"
            help = "A JLD file containing frequency_bins to load."
            arg_type = String
            default = "./frequency.jld"
        "--stop_minimum_fitness"
            help = "Stopping criteria, if minimum fitness is below `stop_minimum_fitness` stop evolving."
            arg_type = Float64
            default = 1.0
        "--seed"
            help = "Seed to pass to random number generator."
            arg_type = Int64
            default = 1
        "--save_state"
            help = "Save state of DEAC algorithm. Saves the random number generator, population, and population fitness."
            action = :store_true
        "--save_file_dir"
            help = "Directory to save results in."
            arg_type = String
            default = "./deacresults"
        "--track_stats"
            help = "Track minimum fitness and other stats."
            action = :store_true
        "--number_of_blas_threads"
            help = "Number of BLAS threads to utilize if > 0 otherwise uses Julia default value."
            arg_type = Int64
            default = 0
        "qmc_data"
            help = "File containing quantum Monte Carlo data with columns: IMAGINARY_TIME, INTERMEDIATE_SCATTERING_FUNCTION, ERROR"
            arg_type = String
            required = true
    end

    return parse_args(s)
end

function checkMoves(argname::String,s::Array{String,1},l::Array{String,1})
    for ss in s
        checkMoves(argname,ss,l)
    end
end

function checkMoves(argname::String,s::String,l::Array{String,1})
    if !(s in l)
        print("$s is not a valid parameter for $argname. Valid parameters are: ")
        println(l)
        error("Failed to validate argument: $argname")
    end
nothing
end

function main()
    start = time();
    parsed_args = parse_commandline()

    # create data directory
    try
        mkpath(parsed_args["save_file_dir"]);
    catch
        nothing
    end
    save_dir = parsed_args["save_file_dir"];

    checkMoves("reject",parsed_args["reject"],DEAC.DEACreject.functionNames)
    checkMoves("frequency_bins_method",parsed_args["frequency_bins_method"],DEAC.DEACfrequency.functionNames)
    get_frequency_bins = getfield(DEAC.DEACfrequency, Symbol(parsed_args["frequency_bins_method"]));

    if parsed_args["frequency_bins_method"] == "load_frequency_bins"
        frequency_bins_filename = parsed_args["load_frequency_bins_filename"];
        println("loading frequency_bins from $(frequency_bins_filename) (will override genome_size and omega_max");
        frequency_bins = get_frequency_bins(frequency_bins_file);
    else
        frequency_bins = get_frequency_bins(0.0,parsed_args["omega_max"],parsed_args["genome_size"]);
    end
    #FIXME ensure frequency odd
    if size(frequency_bins,1)%2 == 0
        throw(AssertionError("frequency space must be odd FIXME"))
    end

    _ext = splitext(parsed_args["qmc_data"])[2];
    if _ext == ".npz"
        qmcdata=NPZ.npzread(parsed_args["qmc_data"]);
    elseif _ext == ".jld"
        qmcdata = load(parsed_args["qmc_data"]);
    else
        throw(AssertionError("qmc_data must be *.jld or *.npz"));
    end
    tau = qmcdata["tau"];
    isf = qmcdata["isf"];
    err = qmcdata["error"];

    u4,omega,minS,minP,minFit,total_generations,
    avgFitness,minFitness,
    stdFitness,P,
    fitnessP,rng,
    crossover_probs,differential_weights = DEAC.deac( tau,isf,err,frequency_bins,
                           use_inverse_first_moment = parsed_args["use_inverse_first_moment"],
                           smooth = parsed_args["smooth"],
                           normalize = parsed_args["normalize"],
                           first_moment = parsed_args["first_moment"],
                           third_moment = parsed_args["third_moment"],
                           third_moment_error = parsed_args["third_moment_error"],
                           temperature = parsed_args["temperature"],
                           number_of_generations = parsed_args["number_of_generations"],
                           population_size = parsed_args["population_size"],
                           crossoverProb = parsed_args["crossover_probability"],
                           SAcrossoverProb = parsed_args["self_adapting_crossover_probability"],
                           differentialWeight = parsed_args["differential_weight"],
                           SAdifferentialWeightProb = parsed_args["self_adapting_differential_weight_probability"],
                           SAdifferentialWeightShift = parsed_args["self_adapting_differential_weight_shift"],
                           SAdifferentialWeight = parsed_args["self_adapting_differential_weight"],
                           rejectFunc = parsed_args["reject"],
                           stop_minimum_fitness = parsed_args["stop_minimum_fitness"],
                           seed = parsed_args["seed"],
                           track_stats = parsed_args["track_stats"],
                           number_of_blas_threads = parsed_args["number_of_blas_threads"])
    seed = lpad(parsed_args["seed"],4,'0');
    elapsed = time() - start;
    filename = "$(save_dir)/deac_results_$(seed)_$u4.jld";
    println("Saving results to $filename");
    save(filename,
         "u4",u4,
         "frequency",omega,
         "dsf",minS,
         "minP",minP,
         "minFit",minFit,
         "total_generations",total_generations,
         "elapsed_time",elapsed);

    if parsed_args["track_stats"]
        filename = "$(save_dir)/deac_stats_$(seed)_$u4.jld";
        println("Saving stats to $filename");
        save(filename,
             "u4",u4,
             "avgFitness",avgFitness,
             "minFitness",minFitness,
             "stdFitness",stdFitness)
    end

    filename = "$(save_dir)/deac_params_$(seed)_$u4.jld";
    println("Saving parameters to $filename");
    save(filename,
         "u4",u4,
         "tau",tau,
         "isf",isf,
         "isf_error",err,
         "frequency",frequency_bins,
         "first_moment",parsed_args["first_moment"],
         "third_moment",parsed_args["third_moment"],
         "third_moment_error",parsed_args["third_moment_error"],
         "temperature",parsed_args["temperature"],
         "number_of_generations",parsed_args["number_of_generations"],
         "population_size",parsed_args["population_size"],
         "genome_size",parsed_args["genome_size"],
         "omega_max",parsed_args["omega_max"],
         "crossoverProb",parsed_args["crossover_probability"],
         "SAcrossoverProb",parsed_args["self_adapting_crossover_probability"],
         "differentialWeight",parsed_args["differential_weight"],
         "SAdifferentialWeightProb",parsed_args["self_adapting_differential_weight_probability"],
         "SAdifferentialWeightShift",parsed_args["self_adapting_differential_weight_shift"],
         "SAdifferentialWeight",parsed_args["self_adapting_differential_weight"],
         "rejectFunc",parsed_args["reject"],
         "frequency_bins_method",parsed_args["frequency_bins_method"],
         "load_frequency_bins_filename",parsed_args["load_frequency_bins_filename"],
         "stop_minimum_fitness",parsed_args["stop_minimum_fitness"],
         "seed",parsed_args["seed"])
    if parsed_args["save_state"]
        filename = "$(save_dir)/deac_state_$(seed)_$(seed_resample)_$u4.jld";
        println("Saving state to $filename");
        save(filename,
             "u4",u4,
             "P",P,
             "fitness",fitnessP,
             "rng",rng,
             "crossover_probs",crossover_probs,
             "differential_weights",differential_weights)
    end
    nothing
end

main()


module OCIM0
#=
This module serves to load the OCIM0 matrix and grid.
These are loaded from the public, persistant URL in FigShare.
The Julia BSON format version of the OCIM0 was created with the code
in the GitHub repository https://github.com/briochemc/OceanCirculations.
=#

using SparseArrays          # For sparse matrix
using DataDeps              # For storage location of data
using BSON                  # For saving circulation as BSON format
using Unitful, UnitfulAstro # for units
using Reexport
@reexport using OceanGrids            # To store the grid

function fallback_download(remotepath, localdir)
    @assert(isdir(localdir))
    filename = basename(remotepath)  # only works for URLs with filename as last part of name
    localpath = joinpath(localdir, filename)
    Base.download(remotepath, localpath)
    return localpath
end

# Create registry entry for OCIM in JLD2 format
function register_OCIM0()
    register(
        DataDep(
            "AIBECS_OCIM0.1",
            """
            References:
            - Primeau, F. W., Holzer, M., and DeVries, T. (2013), Southern Ocean nutrient trapping and the efficiency of the biological pump, J. Geophys. Res. Oceans, 118, 2547–2564, doi:10.1002/jgrc.20181.
            - DeVries, T. and F. Primeau, 2011: Dynamically and Observationally Constrained Estimates of Water-Mass Distributions and Ages in the Global Ocean. J. Phys. Oceanogr., 41, 2381–2401, https://doi.org/10.1175/JPO-D-10-05011.1
            """,
            "https://files.figshare.com/17302670/OCIM0.1.bson",
            "dad4238521a414eb947d3b7630560912638854e5b4e6ca6791b0a0bbeb35807e",
            fetch_method = fallback_download
        )
    )
    return nothing
end

"""
    load

Returns wet3d, grd, and T (in that order) from FigShare repository.
"""
function load()
    print("Loading OCIM0.1")
    register_OCIM0()
    bson_file = @datadep_str string("AIBECS_OCIM0.1/", "OCIM0.1.bson")
    BSON.@load bson_file T grid wet3D
    println(" ✔")
    @info """You are about to use OCIM0.1 model.
          If you use it for research, please cite:

          - Primeau, F. W., Holzer, M., and DeVries, T. (2013), Southern Ocean nutrient trapping and the efficiency of the biological pump, J. Geophys. Res. Oceans, 118, 2547–2564, doi:10.1002/jgrc.20181.
          - DeVries, T. and F. Primeau, 2011: Dynamically and Observationally Constrained Estimates of Water-Mass Distributions and Ages in the Global Ocean. J. Phys. Oceanogr., 41, 2381–2401, doi:10.1175/JPO-D-10-05011.1

          You can find the corresponding BibTeX entries in the CITATION.bib file
          at the root of the AIBECS.jl package repository.
          (Look for the "DeVries_Primeau_2011" and "Primeau_etal_2013" keys.)
          """
    return wet3D, grid, ustrip.(T)
end

end # end module

export OCIM0


## ExoplanetsSysSim/src/target.jl
## (c) 2015 Eric B. Ford

#using Distributions

immutable KeplerTarget
  #sys::PlanetarySystem   # Make array for planetary systems aroud multiple stars in one target?
  sys::Vector{PlanetarySystemAbstract}
  cdpp::Array{Float64,2} # fractional, not ppm; 2D to allow for multiple time scales, months, quarters or seasons/spacecraft rotation angles
                           # QUERY: Do we want a separate CDPP for SC?  Or will CDPP's in different months be for LC/SC depending on this variable?
                            # QUERY: Should this be moved to KeplerTargetObs?
  contam::Float64     # QUERY: Do we want/need this, since we're able to generate multiple stars in a single target?
  data_span::Float64
  duty_cycle::Float64
  #channel::Int64         # E.g., if we cared which Kepler channel the target fell on
  #has_sc::Vector{Bool}   # TODO OPT: Make Immutable Vector or BitArray for speed?  QUERY: Should this go in KeplerTargetObs?
  #                       # QUERY: Do we want a separate CDPP for SC?  Or will CDPP's in different months be for LC/SC depending on this variable?
  #ra::Float64           # E.g., if we cared about position on sky     QUERY:  Should we replace with galactic longitude and latitute?
  #dec::Floa64           #
end
num_planets(t::KeplerTarget) = sum( num_planets, t.sys)
flux(t::KeplerTarget) = sum(flux,t.sys)+t.contam

function draw_asymmetric_normal(mu::Real, sig_plus::Real, sig_minus::Real)
  stdn = rand()
  mu + ( (stdn>=0.0) ? sig_plus*stdn : sig_minus*stdn )
end

function generate_kepler_target_from_table(sim_param::SimParam)
  #const  generate_star = get_function(sim_param,"generate_star")
  const  generate_planetary_system = get_function(sim_param,"generate_planetary_system")
  const use_star_table_sigmas = false

  max_star_id = num_usable_in_star_table()
  @assert(1<=max_star_id)
  star_id = rand(1:max_star_id)
  mass = 0.0
  dens = 0.0
  radius = 0.0
  if use_star_table_sigmas
    while !(0.0<mass<100.0)
       mass = draw_asymmetric_normal( star_table(star_id,:mass), star_table(star_id,:mass_err1), star_table(star_id,:mass_err2) )
    end
    while !(0.0<dens<1000.0)
      dens = draw_asymmetric_normal( star_table(star_id,:dens), star_table(star_id,:dens_err1), star_table(star_id,:dens_err2) )
    end
    while !(0.0<radius<10000.0)
      radius = draw_asymmetric_normal( star_table(star_id,:radius), star_table(star_id,:radius_err1), star_table(star_id,:radius_err2) )
    end
  else
    mass   = star_table(star_id,:mass)
    dens   = star_table(star_id,:dens)
    radius = star_table(star_id,:radius)
  end
  star = SingleStar(radius,mass,1.0, star_id)        # TODO SCI: Allow for blends, binaries, etc.
  cdpp = 1.0e-6 * star_table(star_id, :rrmscdpp04p5) * sqrt(4.5/24.0 / LC_duration )  # TODO SCI: Allow for multiple timescales
  contam = 0.0 # rand(LogNormal(1.0e-3,1.0))      # TODO SCI: Come up with better description of Kepler targets, maybe draw from real contaminations
  data_span = star_table(star_id, :dataspan)
  duty_cycle = star_table(star_id, :dutycycle)
  # ch = rand(DiscreteUniform(1,84))
  ps = generate_planetary_system(star, sim_param)
  return KeplerTarget([ps],fill(cdpp,num_cdpp_timescales,num_quarters),contam,data_span,duty_cycle) #,ch )
end

function generate_kepler_target_simple(sim_param::SimParam)
  const  generate_star = get_function(sim_param,"generate_star")
  const  generate_planetary_system = get_function(sim_param,"generate_planetary_system")
  const star::StarAbstract = generate_star(sim_param)

  const mean_log_cdpp = 4.9759601617565465   # mean frmo star table
  const stddev_log_cdpp = 0.6704860437536709 # std dev from star table
  rrmscdpp_5hr = exp(mean_log_cdpp+stddev_log_cdpp*randn())
  cdpp = 1.0e-6 * rrmscdpp_5hr * sqrt(5.0/24.0 / LC_duration )
  contam = 0.0 # rand(LogNormal(1.0e-3,1.0))   # TODO SCI: Come up with better description of Kepler targets, maybe draw from real contaminations
  #ch = rand(DiscreteUniform(1,84))
  ps = generate_planetary_system(star, sim_param)
  return KeplerTarget([ps],fill(cdpp,num_cdpp_timescales,num_quarters),contam,mission_data_span,mission_duty_cycle) #,ch)
end

function test_target(sim_param::SimParam)
  generate_kepler_target_simple(sim_param)
  setup_star_table(sim_param)
  generate_kepler_target_from_table(sim_param)
end


# The code in this file is a small port from scikit-learn's and numpy's
# library which is distributed under the 3-Clause BSD license.
# The rest of DecisionTree.jl is released under the MIT license.

# written by Poom Chiarawongse <eight1911@gmail.com>

module treeregressor
    include("../util.jl")

    import Random
    export fit

    mutable struct NodeMeta{S}
        l           :: NodeMeta{S}  # right child
        r           :: NodeMeta{S}  # left child
        label       :: Float64      # most likely label
        feature     :: Int          # feature used for splitting
        threshold   :: S            # threshold value
        is_leaf     :: Bool
        depth       :: Int
        region      :: UnitRange{Int} # a slice of the samples used to decide the split of the node
        features    :: Vector{Int}    # a list of features not known to be constant
        split_at    :: Int            # index of samples
        function NodeMeta{S}(
            features :: Vector{Int},
            region   :: UnitRange{Int},
            depth    :: Int) where S
        node = new{S}()
        node.depth = depth
        node.region = region
        node.features = features
        node.is_leaf = false
        node
    end
    end

    struct Tree{S}
        root   :: NodeMeta{S}
        labels :: Vector{Int}
    end

    # find an optimal split that satisfy the given constraints
    # (max_depth, min_samples_split, min_purity_increase)
    function _split!(
            X                   :: AbstractMatrix{S}, # the feature array
            Y                   :: AbstractVector{Float64}, # the label array
            W                   :: AbstractVector{U},
            node                :: NodeMeta{S}, # the node to split
            max_features        :: Int, # number of features to consider
            max_depth           :: Int, # the maximum depth of the resultant tree
            min_samples_leaf    :: Int, # the minimum number of samples each leaf needs to have
            min_samples_split   :: Int, # the minimum number of samples in needed for a split
            min_purity_increase :: Float64, # minimum purity needed for a split
            indX                :: AbstractVector{Int}, # an array of sample indices,
                                                # we split using samples in indX[node.region]
            # the two arrays below are given for optimization purposes
            Xf                  :: AbstractVector{S},
            Yf                  :: AbstractVector{Float64},
            Wf                  :: AbstractVector{U},
            rng                 :: Random.AbstractRNG) where {S, U}

        region = node.region
        n_samples = length(region)
        r_start = region.start - 1

        @inbounds @simd for i in 1:n_samples
            Yf[i] = Y[indX[i + r_start]]
            Wf[i] = W[indX[i + r_start]]
        end

        tssq = zero(U)
        tsum = zero(U)
        wsum = zero(U)
        @inbounds @simd for i in 1:n_samples
            tssq += Wf[i]*Yf[i]*Yf[i]
            tsum += Wf[i]*Yf[i]
            wsum += Wf[i]
        end

        node.label =  tsum / wsum
        if (min_samples_leaf * 2 >  n_samples
         || min_samples_split    >  n_samples
         || max_depth            <= node.depth
          # equivalent to old_purity > -1e-7
         || tsum * node.label    > -1e-7 * wsum + tssq)
            node.is_leaf = true
            return
        end

        features = node.features
        n_features = length(features)
        best_purity = typemin(U)
        best_feature = -1
        threshold_lo = X[1]
        threshold_hi = X[1]

        indf = 1
        # the number of new constants found during this split
        n_constant = 0
        # true if every feature is constant
        unsplittable = true
        # the number of non constant features we will see if
        # only sample n_features used features
        # is a hypergeometric random variable
        total_features = size(X, 2)

        # this is the total number of features that we expect to not
        # be one of the known constant features. since we know exactly
        # what the non constant features are, we can sample at 'non_constants_used'
        # non constant features instead of going through every feature randomly.
        non_constants_used = util.hypergeometric(n_features, total_features-n_features, max_features, rng)
        @inbounds while (unsplittable || indf <= non_constants_used) && indf <= n_features
            feature = let
                indr = rand(rng, indf:n_features)
                features[indf], features[indr] = features[indr], features[indf]
                features[indf]
            end

            rssq = tssq
            lssq = zero(U)
            rsum = tsum
            lsum = zero(U)

            @simd for i in 1:n_samples
                Xf[i] = X[indX[i + r_start], feature]
            end

            # sort Yf and indX by Xf
            util.q_bi_sort!(Xf, indX, 1, n_samples, r_start)
            @simd for i in 1:n_samples
                Yf[i] = Y[indX[i + r_start]]
                Wf[i] = W[indX[i + r_start]]
            end
            nl, nr = zero(U), wsum
            # lo and hi are the indices of
            # the least upper bound and the greatest lower bound
            # of the left and right nodes respectively
            hi = 0
            last_f = Xf[1]
            is_constant = true
            while hi < n_samples
                lo = hi + 1
                curr_f = Xf[lo]
                hi = (lo < n_samples && curr_f == Xf[lo+1]
                    ? searchsortedlast(Xf, curr_f, lo, n_samples, Base.Order.Forward)
                    : lo)

                (lo != 1) && (is_constant = false)
                # honor min_samples_leaf
                if lo-1 >= min_samples_leaf && n_samples - (lo-1) >= min_samples_leaf
                    unsplittable = false
                    purity = (rsum * rsum / nr) + (lsum * lsum / nl)
                    if purity > best_purity && !isapprox(purity, best_purity)
                        # will take average at the end, if possible
                        threshold_lo = last_f
                        threshold_hi = curr_f
                        best_purity  = purity
                        best_feature = feature
                    end
                end

                # update, lssq, rssq, lsum, rsum in the direction
                # that would require the smaller number of iterations
                if (hi << 1) < n_samples + lo # i.e., hi - lo < n_samples - hi
                    @simd for i in lo:hi
                        nr   -= Wf[i]
                        rsum -= Wf[i]*Yf[i]
                        rssq -= Wf[i]*Yf[i]*Yf[i]
                    end
                else
                    nr = rsum = rssq = zero(U)
                    @simd for i in (hi+1):n_samples
                        nr   += Wf[i]
                        rsum += Wf[i]*Yf[i]
                        rssq += Wf[i]*Yf[i]*Yf[i]
                    end
                end
                lsum = tsum - rsum
                lssq = tssq - rssq
                nl   = wsum - nr

                last_f = curr_f
            end

            # keep track of constant features to be used later.
            if is_constant
                n_constant += 1
                features[indf], features[n_constant] = features[n_constant], features[indf]
            end

            indf += 1
        end

        # no splits honor min_samples_leaf
        @inbounds if (unsplittable
                || best_purity - tsum * node.label < min_purity_increase * wsum)
            node.is_leaf = true
            return
        else
            # new_purity - old_purity < stop.min_purity_increase
            @simd for i in 1:n_samples
                Xf[i] = X[indX[i + r_start], best_feature]
            end

            try
                node.threshold = (threshold_lo + threshold_hi) / 2.0
            catch
                node.threshold = threshold_hi
            end
            # split the samples into two parts: ones that are greater than
            # the threshold and ones that are less than or equal to the threshold
            #                                 ---------------------
            # (so we partition at threshold_lo instead of node.threshold)
            node.split_at = util.partition!(indX, Xf, threshold_lo, region)
            node.feature = best_feature
            node.features = features[(n_constant+1):n_features]
        end

    end

    @inline function fork!(node :: NodeMeta{S}) where S
        ind = node.split_at
        region = node.region
        features = node.features
        # no need to copy because we will copy at the end
        node.l = NodeMeta{S}(features, region[    1:ind], node.depth + 1)
        node.r = NodeMeta{S}(features, region[ind+1:end], node.depth + 1)
    end

    function _fit(
            X                     :: AbstractMatrix{S},
            Y                     :: AbstractVector{Float64},
            W                     :: AbstractVector{U},
            max_features          :: Int,
            max_depth             :: Int,
            min_samples_leaf      :: Int,
            min_samples_split     :: Int,
            min_purity_increase   :: Float64,
            rng=Random.GLOBAL_RNG :: Random.AbstractRNG) where {S, U}

        n_samples, n_features = size(X)

        Yf  = Array{Float64}(undef, n_samples)
        Xf  = Array{S}(undef, n_samples)
        Wf  = Array{U}(undef, n_samples)

        indX = collect(1:n_samples)
        root = NodeMeta{S}(collect(1:n_features), 1:n_samples, 0)
        stack = NodeMeta{S}[root]

        @inbounds while length(stack) > 0
            node = pop!(stack)
            _split!(
                X, Y, W,
                node,
                max_features,
                max_depth,
                min_samples_leaf,
                min_samples_split,
                min_purity_increase,
                indX,
                Xf, Yf, Wf,
                rng)
            if !node.is_leaf
                fork!(node)
                push!(stack, node.r)
                push!(stack, node.l)
            end
        end
        return (root, indX)
    end

    function fit(;
            X                     :: AbstractMatrix{S},
            Y                     :: AbstractVector{Float64},
            W                     :: Union{Nothing, AbstractVector{U}},
            max_features          :: Int,
            max_depth             :: Int,
            min_samples_leaf      :: Int,
            min_samples_split     :: Int,
            min_purity_increase   :: Float64,
            rng=Random.GLOBAL_RNG :: Random.AbstractRNG) where {S, U}

        n_samples, n_features = size(X)
        if W == nothing
            W = fill(1.0, n_samples)
        end

        util.check_input(
            X,
            Y,
            W,
            max_features,
            max_depth,
            min_samples_leaf,
            min_samples_split,
            min_purity_increase)

        root, indX = _fit(
            X,
            Y,
            W,
            max_features,
            max_depth,
            min_samples_leaf,
            min_samples_split,
            min_purity_increase,
            rng)

        return Tree{S}(root, indX)

    end
end


Recipes = Dict{AbstractString, Recipe}()

macro IN(iname, iqty)
	:(r.ins[$iname] = $iqty / r.time)
end

macro OUT(iname, iqty)
	:(r.outs[$iname] = $iqty / r.time)
end

macro FOUTS(K) :(sum([@FOUT(F, $K) for F in @Recipes])) end
macro FINS(K) :(sum([@FIN(F, $K) for F in @Recipes])) end

macro FCON(R, I) :(getvalue(Factories[$R]) * get(Recipes[$R].ins, $I, 0)) end
macro FCONS(K) :(sum([@FCON(F, $K) for F in @Recipes])) end

macro Recipes() :(collect(keys(Recipes))) end
macro FIN(F, R) :(Factories[$F] * get(Recipes[$F].ins, $R, 0)) end
macro FOUT(F, R) :(Factories[$F] * get(Recipes[$F].outs, $R, 0))end
macro FOUTS(K) :(sum([@FOUT(F, $K) for F in @Recipes])) end
macro FINS(K) :(sum([@FIN(F, $K) for F in @Recipes])) end

macro minsec(n) :((60($n), ($n), 60 * $n * mins)) end
macro PerSec(R)
	return quote
		r = get(Recipes, $R, nothing)
		if r == nothing
			1
		else
			1 / Recipes[$R].time
		end
	end
end

r = Recipes["high-tech-science-pack"] = Recipe(14, 5.5)
@IN "battery" 1
@IN "processing-unit" 3
@IN "speed-module" 1
@IN "copper-cable" 30
@OUT "high-tech-science-pack" 2.8

r = Recipes["battery"] = Recipe(5, 2.5)
@IN "sulfuric-acid" 20
@IN "iron-plate" 1
@IN "copper-plate" 1
@OUT "battery" 1.2

r = Recipes["processing-unit"] = Recipe(10, 5.5)
@IN "electronic-circuit" 20
@IN "advanced-circuit" 2
@IN "sulfuric-acid" 5
@OUT "processing-unit" 1.4

r = Recipes["advanced-circuit"] = Recipe(6, 5.5)
@IN "electronic-circuit" 2
@IN "plastic-bar" 2
@IN "copper-cable" 4
@OUT "advanced-circuit" 1.4

r = Recipes["electronic-circuit"] = Recipe(0.5, 5.5)
@IN "iron-plate" 1
@IN "copper-cable" 3
@OUT "electronic-circuit" 1.4

r = Recipes["speed-module"] = Recipe(15, 8.75)
@IN "advanced-circuit" 5
@IN "electronic-circuit" 5
@OUT "speed-module" 1

r = Recipes["copper-cable"] = Recipe(0.5, 5.5)
@IN "copper-plate" 1
@OUT "copper-cable" 2.8

r = Recipes["science-pack-3"] = Recipe(12, 5.5)
@IN "advanced-circuit" 1
@IN "engine-unit" 1
@IN "electric-mining-drill" 1
@OUT "science-pack-3" 1

r = Recipes["engine-unit"] = Recipe(10, 5.5)
@IN "steel-plate" 1
@IN "iron-gear-wheel" 1
@IN "pipe" 2
@OUT "engine-unit" 1

r = Recipes["steel-plate"] = Recipe(17.5, 9.4)
@IN "iron-plate" 5
@OUT "steel-plate" 1.2

r = Recipes["iron-gear-wheel"] = Recipe(0.5, 5.5)
@IN "iron-plate" 1
@OUT "iron-gear-wheel" 1.4

r = Recipes["pipe"] = Recipe(0.5, 5.5)
@IN "iron-plate" 1
@OUT "pipe" 1.4

r = Recipes["electric-mining-drill"] = Recipe(2, 8.75)
@IN "electronic-circuit" 3
@IN "iron-gear-wheel" 5
@IN "iron-plate" 10
@OUT "electric-mining-drill" 1

r = Recipes["plastic-bar"] = Recipe(1, 2.5)
@IN "coal" 1
@IN "petroleum-gas" 20
@OUT "plastic-bar" 1.3

r = Recipes["science-pack-2"] = Recipe(6, 5.5)
@IN "inserter" 1
@IN "transport-belt" 1
@OUT "science-pack-2" 1.4

r = Recipes["inserter"] = Recipe(0.5, 8.75)
@IN "electronic-circuit" 1
@IN "iron-gear-wheel" 1
@IN "iron-plate" 1
@OUT "inserter" 1

r = Recipes["transport-belt"] = Recipe(0.5, 8.75)
@IN "iron-gear-wheel" 1
@IN "iron-plate" 1
@OUT "transport-belt" 2

r = Recipes["science-pack-1"] = Recipe(5, 5.5)
@IN "copper-plate" 1
@IN "iron-gear-wheel" 1
@OUT "science-pack-1" 1.4

r = Recipes["production-science-pack"] = Recipe(14, 5.5)
@IN "electric-engine-unit" 1
@IN "assembling-machine-1" 1
@IN "electric-furnace" 1
@OUT "production-science-pack" 2

r = Recipes["electric-engine-unit"] = Recipe(10, 5.5)
@IN "engine-unit" 1
@IN "electronic-circuit" 2
@IN "lubricant" 15
@OUT "electric-engine-unit" 1.4

r = Recipes["assembling-machine-1"] = Recipe(0.5, 8.75)
@IN "electronic-circuit" 3
@IN "iron-gear-wheel" 5
@IN "iron-plate" 9
@OUT "assembling-machine-1" 1

r = Recipes["electric-furnace"] = Recipe(5, 8.75)
@IN "steel-plate" 10
@IN "advanced-circuit" 5
@IN "stone-brick" 10
@OUT "electric-furnace" 1

r = Recipes["stone-brick"] = Recipe(3.5, 9.4)
@IN "stone" 2
@OUT "stone-brick" 1

r = Recipes["military-science-pack"] = Recipe(10, 5.5)
@IN "piercing-rounds-magazine" 1
@IN "grenade" 1
@IN "gun-turret" 1
@OUT "military-science-pack" 2

r = Recipes["piercing-rounds-magazine"] = Recipe(3, 8.75)
@IN "firearm-magazine" 1
@IN "steel-plate" 1
@IN "copper-plate" 5
@OUT "piercing-rounds-magazine" 1

r = Recipes["firearm-magazine"] = Recipe(1, 8.75)
@IN "iron-plate" 4
@OUT "firearm-magazine" 1

r = Recipes["grenade"] = Recipe(8, 8.75)
@IN "coal" 10
@IN "iron-plate" 5
@OUT "grenade" 1

r = Recipes["gun-turret"] = Recipe(8, 8.75)
@IN "copper-plate" 10
@IN "iron-gear-wheel" 10
@IN "iron-plate" 20
@OUT "gun-turret" 1