immutable MPI_2D_Config <: MPI_areaConfig
  ##  Voodoo numbers controlling data layout.
  ##  sNx :: No. X points in sub-grid.
  ##  sNy :: No. Y points in sub-grid.
  ##  OLx :: Overlap extent in X.
  ##  OLy :: Overlat extent in Y.
  ##  nPx :: No. of processes to use in X.
  ##  nPy :: No. of processes to use in Y.
  sN :: Tuple{Int64,Int64}
  OL :: Tuple{Int64,Int64}
  nP :: Tuple{Int64,Int64}
  rank :: Int64
  size :: Int64
  function MPI_2D_Config(n :: Tuple{Int64,Int64}, l :: Tuple{Int64,Int64}, p :: Tuple{Int64,Int64})
    @assert n[1] > l[1]
    @assert n[2] > l[2]
    comm = MPI.COMM_WORLD
    new(n,l,p, MPI.Comm_rank(comm), MPI.Comm_size(comm))
  end
end

function MPI2D_InitialArray!(p :: MPI_2D_Config )
  return fill(0.0, )
end

function MPI2D_GatherAll!(p :: MPI_2D_Config, T :: Array{Float64,2}, Twhole :: Array{Float64,2})
  comm = MPI.COMM_WORLD
  (OLx, OLy) = p.OL
  (sNx, sNy) = p.sN
  (nPx, nPy) = p.nP
  rank = p.rank

  if (rank!=0)
    ##  Sending to Coupler
    sreq = MPI.Isend(T,0, rank+32,comm)
    MPI.Waitall!([sreq;])
  else
    Ti = fill(NaN, size(T))
    Twhole[1:sNx, 1:sNy] = T[OLx+(1:sNx),OLy+(1:sNy)]
    for ip = 1:(nPx*nPy-1)
      ix = mod(ip,nPx)
      iy = round(Int64, (ip-ix)/nPx)
      rreq = MPI.Irecv!(Ti, ip, ip+32, comm)
      MPI.Waitall!([rreq;])
      Twhole[ix*sNx+ (1:sNx),iy*sNy+(1:sNy)] = Ti[OLx+(1:sNx),OLy + (1:sNy)]
    end
  end
  return Twhole
end

function MPI2D_DistributeCore!(p :: MPI_2D_Config, T :: Array{Float64,2},  Twhole :: Array{Float64,2})
  comm = MPI.COMM_WORLD
  (OLx, OLy) = p.OL
  (sNx, sNy) = p.sN
  (nPx, nPy) = p.nP
  rank = p.rank

  if (rank != 0)
    ##  Receiving From Coupler
    Ti = fill(NaN, p.sN)
    csreq = MPI.Irecv!(Ti,0, rank+32,comm)
    MPI.Waitall!([csreq;])
    T[OLx+(1:sNx), OLy+(1:sNy)] = Ti
  else
    Ti = fill(NaN, size(T))
    T[(1+OLx):(OLx+sNx),(1+OLy):(OLy+sNy)] =  Twhole[1:sNx, 1:sNy]
    ## Send to Workers
    for ip = 1:(nPx*nPy-1)
      ix = mod(ip,nPx)
      iy = round(Int64, (ip-ix)/nPx)
      Ti = fill(NaN, p.sN)
      Ti[:,:] = Twhole[ix*sNx+ (1:sNx),iy*sNy+(1:sNy)]
      sreq = MPI.Isend(Ti, ip, ip+32, comm)
      MPI.Waitall!([sreq;])
    end
  end
  return Twhole
end

function MPI2D_DistributeBoundary!(p :: MPI_2D_Config, T :: Array{Float64,2},  Twhole :: Array{Float64,2})
  comm = MPI.COMM_WORLD
  #= MPI.Barrier(comm) =#
  (OLx, OLy) = p.OL
  (sNx, sNy) = p.sN
  (nPx, nPy) = p.nP
  rank = p.rank

  if (rank!=0)

    crreq = Array(MPI.Request,4)
    Twest = fill(NaN, (OLx, sNy))
    Tnorth = fill(NaN,(sNx, OLy))
    Teast = fill(NaN, (OLx, sNy))
    Tsouth = fill(NaN, (sNx, OLy))

    ##  Receiving from Coupler
    crreq[1] = MPI.Irecv!(Twest,0, 4*rank+1, comm)
    crreq[2] = MPI.Irecv!(Tnorth,0, 4*rank+2, comm)
    crreq[3] = MPI.Irecv!(Teast,0, 4*rank+3, comm)
    crreq[4] = MPI.Irecv!(Tsouth,0, 4*rank+4, comm)
    MPI.Waitall!(crreq)
    T[1:OLx,OLy+(1:sNy)] = Twest
    T[OLx+(1:sNx),sNy+OLy+(1:OLy)] = Tnorth
    T[sNx+OLx+(1:OLx),OLy+(1:sNy)] = Teast
    T[OLx+(1:sNx),1:OLy] = Tsouth
  else

    Twest = Twhole[sNx*nPx-OLx+(1:OLx),1:sNy]
    Tnorth = Twhole[1:sNx, mod(sNy+(1:OLy)-1, sNy*nPy)+1]
    Teast  = Twhole[mod(sNx+(1:OLx)-1, sNx*nPx )+1,1:sNy]
    Tsouth = Twhole[1:sNx, mod(sNy*nPy-OLy+(1:OLy)-1,sNy*nPy)+1]

    T[1:OLx,OLy+(1:sNy)] = Twest
    T[OLx+(1:sNx),sNy+OLy+(1:OLy)] = Tnorth
    T[sNx+OLx+(1:OLx),OLy+(1:sNy)] = Teast
    T[OLx+(1:sNx),1:OLy] = Tsouth

    sreq = Array(MPI.Request,4)
    for ip = 1:(nPx*nPy-1)
      ix = mod(ip,nPx)
      iy = round(Int64, (ip-ix)/nPx)

      # Sending West Boundary
      iwx = mod((ix-1), nPx)
      iwy = iy
      pendingx = (iwx+1)*sNx-OLx
      pendingy = (iwy)*sNy
      Twest = Twhole[pendingx+(1:OLx), pendingy+(1:sNy)]
      sreq[1] = MPI.Isend(Twest, ip, ip*4+1, comm)

      # Sending North Boundary
      iwx = ix
      iwy = mod(iy+1,nPy)
      pendingx = iwx*sNx
      pendingy = iwy*sNy
      Tnorth = Twhole[pendingx+(1:sNx), pendingy+(1:OLy)]
      sreq[2] = MPI.Isend(Tnorth, ip, ip*4+2, comm)

      # Sending East Boundary
      iwx = mod(ix+1,nPx)
      iwy = iy
      pendingx = iwx*sNx
      pendingy = iwy*sNy
      Twest = Twhole[pendingx+(1:OLx), pendingy+(1:sNy)]
      sreq[3] = MPI.Isend(Teast, ip, ip*4+3, comm)

      # Sending South Boundary
      iwx = ix
      iwy = mod(iy-1,nPy)
      pendingx = iwx*sNx
      pendingy = (iwy+1)*sNy-OLy
      Twest = Twhole[pendingx+(1:sNx), pendingy+(1:OLy)]
      sreq[4] = MPI.Isend(Tsouth, ip, ip*4+4, comm)

      MPI.Waitall!(sreq)

    end
  end
  return T
end

function MPI2D_GatherAll!(p :: MPI_2D_Config, T :: Array{Float64,3}, Twhole :: Array{Float64,3})
  for ii = 1:size(T,3)
    T1 = T[:,:,ii]
    Twhole1 = Twhole[:,:,ii]
    MPI2D_GatherAll!(p,T1, Twhole1)
  end
  return Twhole1
end

function MPI2D_DistributeCore!(p :: MPI_2D_Config, T :: Array{Float64,3}, Twhole :: Array{Float64,3})
  for ii = 1:size(T,3)
    T1 = T[:,:,ii]
    Twhole1 = Twhole[:,:,ii]
    MPI2D_DistributeCore!(p,T1,  Twhole1)
  end
  return Twhole1
end

function MPI2D_DistributeBoundary!(p :: MPI_2D_Config, T :: Array{Float64,3},  Twhole :: Array{Float64,3})
  for ii = 1:size(T,3)
    T1 = T[:,:,ii]
    Twhole1 = Twhole[:,:,ii]
    MPI2D_DistributeBoundary!(p,T1, Twhole1)
  end
  return Twhole1
end

export MPI_2D_Config
export MPI2D_GatherAll!
export MPI2D_DistributeCore!
export MPI2D_DistributeBoundary!


# Canvas: a thing upon which other things are placed.

export Canvas, Units, Rotation, canvas, deferredcanvas,
       draw, drawpart, set_unit_box, set_box

# A box giving the coordinate system used by a canvas.
type Units
    x0::Float64
    y0::Float64
    width::Float64
    height::Float64

    function Units()
        new(0.0, 0.0, 1.0, 1.0)
    end

    function Units(width::Number,
                   height::Number)
        new(0.0, 0.0, width, height)
    end

    function Units(x0::Number,
                   y0::Number,
                   width::Number,
                   height::Number)
        new(x0, y0, width, height)
    end
end

# Note: Units has almost the same representation as BoundingBox, except no unit
# is associated with the numbers.
function convert(::Type{BoundingBox}, u::Units)
    BoundingBox(u.x0, u.y0, u.width, u.height)
end

function convert(::Type{Units}, u::BoundingBox)
    Units(u.x0.value, u.y0.value, u.width.value, u.height.value)
end

# Canvases are containers for forms and other canvases with an associated
# coordinate transform.
abstract Canvas

# An identity element for the canvas monoid.
type EmptyCanvas <: Canvas end
const empty_canvas = EmptyCanvas()

# A deferred canvas is a function that produces a canvas and is evaluated when
# the graphic is drawn. This is useful when the layout of the contents of a
# canvas depends on its size in absolute coordinates, which is only known, in
# general at draw time.
#
# The function `f` must have the signature:
#   f(box::BoundingBox, unit_box::BoundingBox)
#
# Where box is the bounding box in absolute coordinates of the parent canvas,
# and unit_box is its unit box.
#
# Another way to think of this: a canvas in to an expression what a
# deferredcanvas is to a macro.
#
type DeferredCanvas <: Canvas
    f::Function
    order::Integer
end

deferredcanvas(f::Function) = DeferredCanvas(f, 0)
deferredcanvas(f::Function, order::Integer) = DeferredCanvas(f, order)

# non-empty tree of canvases.
type CanvasTree <: Canvas
    box::BoundingBox
    form::Form
    property::Property
    unit_box::Units
    rot::Rotation
    children::List{Canvas}

    # Z-order of this canvas relative to its siblings.
    order::Integer

    # True if this canvas should use its parent's coordinate system.
    units_inherited::Bool

    # True if children of the canvas should be clipped by its bounding box.
    clip::Bool

    function CanvasTree(box::BoundingBox,
                        form::Form,
                        property::Property,
                        unit_box::Units,
                        rot::Rotation,
                        children::List{Canvas},
                        order::Integer,
                        units_inherited::Bool,
                        clip::Bool)

        new(box, form, property, unit_box, rot,
            children, order, units_inherited, clip)
    end

    function CanvasTree(x0::MeasureOrNumber=0.0w,
                        y0::MeasureOrNumber=0.0h,
                        width::MeasureOrNumber=1.0w,
                        height::MeasureOrNumber=1.0h;
                        unit_box=Units(),
                        rotation=Rotation(),
                        units_inherited=false,
                        order=0,
                        clip=false)
        new(BoundingBox(x0, y0, width, height),
                empty_form,
                empty_property,
                unit_box,
                rotation,
                ListNil{Canvas}(),
                order,
                units_inherited,
                clip)
    end

    # shallow copy constructor
    function CanvasTree(c::CanvasTree)
        new(c.box, c.form, c.property, c.unit_box,
            c.rot, c.children, c.order, c.units_inherited, c.clip)
    end
end

# Alias constructor functions
const canvas = CanvasTree

copy(c::CanvasTree) = CanvasTree(c)

# Return a new Canvas with unit_box substituted.
function set_unit_box(c::CanvasTree, unit_box::Units)
    c = copy(c)
    c.unit_box = unit_box
    c
end

# Return a new Canvas with the box substituted
function set_box(c::CanvasTree, box::BoundingBox)
    c = copy(c)
    c.box = box
    c
end

function show(io::IO, c::CanvasTree)
    print(io, "Canvas(")
    show(io, c.children)
    print(io, ")")
end

# Similar to dump(), but briefer
function contents(io, c::CanvasTree, n::Int, indent)
    lc = length(c.children)
    lf = length(c.form)
    println(indent, "Canvas with ", lc, lc == 1 ? " child" : " children",
            " and ", lf, lf == 1 ? " form" : " forms")
    if n > 0
        # Children
        i = 1
        for cc in c.children
            contents(io, cc, n - 1, string(indent, "  "))
            if i > 10
                println(io, indent, "  ...")
                break
            end
            i += 1
        end
        # Form
        contents(io, c.form, n, indent)
    end
end
contents(io, c::CanvasTree, n::Int) = contents(io, c, n, "")
contents(io, c::CanvasTree) = contents(io, c, 10, "")
contents(c::CanvasTree, args...) = contents(OUTPUT_STREAM, c, args...)

compose(canvas::Canvas) = canvas

# Compositions of canvases is tree joining by making b a subtree of a.
#     a      b             a___
#    / \    / \    --->   / \  \
#   X   Y  U   V         X   Y  b
#                              / \
#                             U   V
function compose(canvases::Canvas...)
    root = empty_canvas
    for canvas in canvases
        if canvas === empty_canvas
            continue
        elseif root === empty_canvas
            root = copy(canvas)
        else
            root.children = cons(canvas, root.children)
        end
    end
    root
end

# Composition of forms into canvases
function compose(a::CanvasTree, b::Form)
    CanvasTree(a.box, combine(a.form, b), a.property,
               a.unit_box, a.rot, a.children, a.order, a.units_inherited, a.clip)
end

function compose(a::EmptyCanvas, b::Form)
    a
end

function compose(a::CanvasTree, b::Property)
    CanvasTree(a.box, a.form, combine(a.property, b),
               a.unit_box, a.rot, a.children, a.order, a.units_inherited, a.clip)
end

function compose(a::EmptyCanvas, b::Property)
    a
end

function compose(a::DeferredCanvas, b::Union(Canvas, Form, Property))
    error("Error: Deferred canvases cannot be composed with.")
end

# Draw a realization of the graphic on a given backend.
#
# Args:
#   backend: Backend on which to draw the graphic.
#   root_canvas: Root node of the graphic.
#
# Returns:
#   nothing
#
# Effects:
#   The root_canvas will be finished after the call to draw. If you wish to draw
#   multiple canvases, use `drawpart`.
#
function draw(backend::Backend, root_canvas::Canvas)
    drawpart(backend, root_canvas)
    finish(backend)
end

# Draw without finishing the backend.
function drawpart(backend::Backend, root_canvas::Canvas)
    S = {(root_canvas, NativeTransform(), BoundingBox(), root_box(backend))}
    while !isempty(S)
        s = pop!(S)

        if s == :POP_PROPERTY
            pop_property(backend)
            continue
        end

        (canvas, parent_t, unit_box, parent_box) = s

        if canvas === empty_canvas
            continue
        end

        if typeof(canvas) == DeferredCanvas
            abs_parent_box =
                BoundingBox(convert(SimpleMeasure{MillimeterUnit}, parent_box.x0),
                            convert(SimpleMeasure{MillimeterUnit}, parent_box.y0),
                            convert(SimpleMeasure{MillimeterUnit}, parent_box.width),
                            convert(SimpleMeasure{MillimeterUnit}, parent_box.height))

            canvas = canvas.f(abs_parent_box, unit_box)
            if !(typeof(canvas) <: Canvas)
                error("Error: A deferred canvas function did not evaluate to a canvas.")
            end
        end

        box = native_measure(canvas.box, parent_t, unit_box, parent_box, backend)
        rot = native_measure(canvas.rot, parent_t, unit_box, box, backend)
        t = combine(rot, parent_t)

        if !canvas.units_inherited
            unit_box = convert(BoundingBox, canvas.unit_box)
        end

        property = canvas.property
        if canvas.clip
            property |= clip(Point(box.x0, box.y0),
                             Point(box.x0 + box.width, box.y0),
                             Point(box.x0 + box.width, box.y0 + box.height),
                             Point(box.x0, box.y0 + box.height))
        end

        if !is(property, empty_property)
            push!(S, :POP_PROPERTY)
            push_property(backend, native_measure(property, t, unit_box,
                                                  box, backend))
        end

        draw(backend, t, unit_box, box, canvas.form)

        ordered_children = Array((Integer, Integer, Canvas), length(canvas.children))
        for (i, child) in enumerate(canvas.children)
            # The tuple here includes i so canvases are never compared to break
            # ties.
            ordered_children[i] = (child.order, i, child)
        end
        sort!(ordered_children)
        reverse!(ordered_children)

        for (_, _, child) in ordered_children
            push!(S, (child, t, unit_box, box))
        end
    end
end


module SuiteSparseGraphBLAS
__precompile__(true)

using Libdl: dlsym, dlopen, dlclose

# Allow users to specify a non-Artifact shared lib.
using Preferences
include("find_binary.jl")
const libgraphblas_handle = Ref{Ptr{Nothing}}()
@static if artifact_or_path == "default"
    using SSGraphBLAS_jll
    const libgraphblas = SSGraphBLAS_jll.libgraphblas
else
    const libgraphblas = artifact_or_path
end

using SparseArrays
using SparseArrays: nonzeroinds
using MacroTools
using LinearAlgebra
using Random: randsubseq, default_rng, AbstractRNG, GLOBAL_RNG
using SpecialFunctions: lgamma, gamma, erf, erfc
using Base.Broadcast
include("abstracts.jl")
include("libutils.jl")
include("lib/LibGraphBLAS.jl")
using .libgb
include("operators/libgbops.jl")

# Globals

include("types.jl")
include("gbtypes.jl")

include("constants.jl")

include("operators/operatorutils.jl")
include("operators/unaryops.jl")
include("operators/binaryops.jl")
include("operators/monoids.jl")
include("operators/semirings.jl")
include("operators/selectops.jl")
include("descriptors.jl")
using .UnaryOps
using .BinaryOps
using .Monoids
using .Semirings

# Create typed operators
_createunaryops()
_createbinaryops()
_createmonoids()
_createsemirings()

include("operators/oplist.jl")
include("indexutils.jl")

include("operations/extract.jl")
include("scalar.jl")
include("vector.jl")
include("matrix.jl")
include("random.jl")

include("operations/operationutils.jl")
include("operations/transpose.jl")
include("operations/mul.jl")
include("operations/ewise.jl")
include("operations/map.jl")
include("operations/select.jl")
include("operations/reduce.jl")
include("operations/kronecker.jl")
include("operations/concat.jl")
include("operations/resize.jl")

include("print.jl")
include("import.jl")
include("export.jl")
include("pack.jl")
include("unpack.jl")
include("options.jl")
#EXPERIMENTAL
include("operations/argminmax.jl")
include("operations/broadcasts.jl")
include("chainrules/chainruleutils.jl")
include("chainrules/mulrules.jl")
include("chainrules/ewiserules.jl")
include("chainrules/maprules.jl")
include("chainrules/reducerules.jl")
include("chainrules/selectrules.jl")
include("chainrules/constructorrules.jl")
#include("random.jl")
include("misc.jl")
include("asjulia.jl")
include("sparsemat.jl")
export SparseArrayCompat
export libgb
export UnaryOps, BinaryOps, Monoids, Semirings #Submodules
export UnaryOp, BinaryOp, Monoid, Semiring #UDFs
export Descriptor #Types
export gbset, gbget
export xtype, ytype, ztype, validtypes #Determine input/output types of operators
export GBScalar, GBVector, GBMatrix #arrays
export lgamma, gamma, erf, erfc #reexport of SpecialFunctions.

# Function arguments not found elsewhere in Julia
#UnaryOps not found in Julia/stdlibs.
export frexpe, frexpx, positioni, positionj
#BinaryOps not found in Julia/stdlibs.
export second, rminus, pair, ∨, ∧, lxor, fmod, firsti,
    firstj, secondi, secondj
#SelectOps not found in Julia/stdlibs
export offdiag

export clear!, extract, extract!, subassign!, assign!, hvcat! #array functions

#operations
export mul, select, select!, eadd, eadd!, emul, emul!, map, map!, gbtranspose, gbtranspose!,
gbrand, eunion, eunion!
# Reexports from LinAlg
export diag, diagm, mul!, kron, kron!, transpose, reduce, tril, triu

# Reexports from SparseArrays
export nnz, sprand, findnz, nonzeros, nonzeroinds

function __init__()
    @static if artifact_or_path != "default"
        libgraphblas_handle[] = dlopen(libgraphblas)
    else
        #The artifact does dlopen for us.
        libgraphblas_handle[] = SSGraphBLAS_jll.libgraphblas_handle
    end
    _load_globaltypes()
    # We initialize GraphBLAS by giving it Julia's GC wrapped memory management functions.
    # In the future this should hopefully allow us to do no-copy passing of arrays between Julia and SS:GrB.
    # In the meantime it helps Julia respond to memory pressure from SS:GrB and finalize things in a timely fashion.
    libgb.GxB_init(libgb.GrB_NONBLOCKING, cglobal(:jl_malloc), cglobal(:jl_calloc), cglobal(:jl_realloc), cglobal(:jl_free))
    gbset(:nthreads, Sys.CPU_THREADS ÷ 2)
    # Eagerly load selectops constants.
    _loadselectops()
    # Set printing done by SuiteSparse:GraphBLAS to base-1 rather than base-0.
    gbset(BASE1, 1)
    atexit() do
        # Finalize the lib, for now only frees a small internal memory pool.
        libgb.GrB_finalize()
        @static if artifact_or_path != "default"
            dlclose(libgraphblas_handle[])
        end
    end
end

include("operators/ztypes.jl")
end #end of module


using PyCall

@pyimport keras.models as models
@pyimport yaml as pyyaml

abstract Policy
# Policy must have:
# choose_move(board::Board, policy::Policy)

type RandomPolicy <: Policy end

function choose_move(board::Board, policy::RandomPolicy)
    candidates = legal_moves(board)
    rand(candidates)
end

###
# Keras Policy
# Training code in keras.jl
###

type KerasNetwork <: Policy
    # Ick - is there some way of improving typing here?
    model::Module
    features::Vector{Function}  # Feature extractors to run
    # Make it callable if given a raw object
    KerasNetwork(model::PyCall.PyObject, features) = new(pywrap(model), features)
    KerasNetwork(model::Module, features) = new(model, features)
end

"Load a Keras based policy from `folder` with `name`"
function KerasNetwork(folder::AbstractString, name::AbstractString)
    println(STDERR, "Loading Keras model from $(joinpath(folder, name))")
    open(joinpath(folder, string(name, ".yml"))) do file
        yaml = readall(file)
        model = pywrap(models.model_from_yaml(yaml))
        model.load_weights(joinpath(folder, string(name, ".hf5")))

        # Need to compile in order to predict anything even if we aren't training
        model.compile(loss="categorical_crossentropy", optimizer="adadelta", metrics=["accuracy"])

        # Read in extra data on the feature maps this model was trained on
        # Do this with pyyaml because the yaml has !!python directives
        features = pyyaml.load(yaml)["features"]
        features = map(x -> eval(symbol(x)), features)

        println(STDERR, "Loaded model")
        KerasNetwork(model, features)
    end
end

# A policy takes a board and outputs a probability distribution over moves

function choose_move(board::Board, policy::KerasNetwork)
    if board.last_move == PASS_MOVE
        return PASS_MOVE
    end
    moves = move_probs(board, policy)
    color = current_player(board)
    for move in moves
        point = pointindex(move)
        if is_legal(board, point, color)
            return point
        end
    end
    return PASS_MOVE
end

function move_probs(board::Board, policy::KerasNetwork)
    X = get_features(board, features=policy.features)
    X = to_python_array(X, size(X)..., 1) # Pad it out to create a batch
    probs = policy.model.predict(X)[:]
    sortperm(probs, rev=true)
end


# This script generates a `.hpp` file (C++ header) containing
# an LDPC code stored in compressed sparse column (CSC) format.
# See command line help for how to use it.

using SparseArrays
using LinearAlgebra
using DelimitedFiles

using ArgParse # Must be installed (use the Pkg package manager)

header = raw"""
// This is an automatically generated file.
// It contains row indices that should be combined to reduce the number of syndrome bits
// of an LDPC code in a rate-adaptive manner.
// These row indices are obtained via optimization of the particular LDPC matrix.

#include <cstdint>
#include <array>

namespace AutogenRateAdapt {

"""

struct ValidPath
    val::String
    ValidPath(s::AbstractString) = isfile(s) ? new(s) : error("No such file: '$s'")
end

function parse_my_args(args)
    s = ArgParseSettings(
        "Create a C++ code-file containing line indices for combination,
        to be applied to a given LDPC code in order to achieve rate adaption."
        ; allow_ambiguous_opts=false)

    @add_arg_table! s begin
        "rate_adaption_file_path"
            required = true
            arg_type = ValidPath
            help = "Path to file containing the line indices for rate adaption."
        "--output_path"
            default = "autogen_rate_adaption.hpp"
            arg_type = String
            help = "Path to put the automatically generated C++ file containing the rate adaption."
        "--debug_use_only_part"
            help = "If true, only a small part of the provided data processed."
            action = :store_true
    end

    parsed_args = parse_args(args, s)
    println("Parsed args:")
    for (key,val) in parsed_args
        println("  $key  =>  $(repr(val))")
    end

    return abspath(parsed_args["rate_adaption_file_path"].val),
            abspath(parsed_args["output_path"]),
            parsed_args["debug_use_only_part"]
end

function write_cpp_constexpr_rate_adaption(
    rate_adaption_rows::AbstractArray{T, 2} where T <: Integer,
    output_cpp_path::AbstractString
    ; only_debug_mode = false
    )
    size(rate_adaption_rows, 2) == 2 || throw(
        ArgumentError("Expecting input of shape N x 2, where N is the number of time that two rows are combined.
                        Received shape $(size(rate_adaption_rows))"))

    if only_debug_mode  # make input data smaller...
        cut_at = minimum([10, size(rate_adaption_rows)...])
        rate_adaption_rows = rate_adaption_rows[1:cut_at, :]
    end

    max_row_idx = maximum(rate_adaption_rows)
    if log2(max_row_idx) < 16
        row_idx_type = "std::uint16_t"
    elseif log2(max_row_idx) < 32
        row_idx_type = "std::uint32_t"
    elseif log2(max_row_idx) < 64
        row_idx_type = "std::uint64_t"
    else
        throw(ArgumentError("Input insanely large? Has $max_row_idx entries..."))
    end

    open(output_cpp_path, "w+") do f
        print(f, header)

        println(f, """
        constexpr std::size_t num_combined_rows = $(length(rate_adaption_rows));\n
        constexpr std::array<$row_idx_type, num_combined_rows> rows = {""")

        for (i, idx) in enumerate(transpose(rate_adaption_rows))
            print(f, "0x$(string(idx, base=16))")
            if i != length(rate_adaption_rows)
                print(f, ",")
            end
            if mod(i, 100) == 0
                println(f, "")  # for formatting.
            end
        end
        println(f, "\n};\n")

        println(f, "} // namespace RALDPC")
    end
end

function read_rate_adaption_file(file_path::AbstractString)
    return DelimitedFiles.readdlm(file_path, ',', Int)
end

function write_array_to_csv(arr::AbstractArray, out_path::AbstractString)
    open(out_path, "w") do io
       writedlm(io, arr, ',')
    end
end

function main(args)
    rate_adaption_file_path, output_path, only_debug_mode = parse_my_args(args)
    rate_adaption_rows = read_rate_adaption_file(rate_adaption_file_path)
    write_cpp_constexpr_rate_adaption(rate_adaption_rows, output_path; only_debug_mode)

    @info "Saved C++ file ($(Base.stat(output_path).size) bytes) at '$(output_path)'."
end

main(ARGS)


BroadcastStyle(::Type{<:judiMultiSourceVector}) = ArrayStyle{judiMultiSourceVector}()

function similar(bc::Broadcast.Broadcasted{ArrayStyle{judiMultiSourceVector}}, ::Type{ElType}) where ElType
    # Scan the inputs for the ArrayAndChar:
    A = find_msv(bc)
    return similar(A, ElType)
end

"`A = find_msv(As)` returns the first judiMultiSourceVector among the arguments."
find_msv(bc::Base.Broadcast.Broadcasted) = find_msv(bc.args)
find_msv(args::Tuple) = find_msv(find_msv(args[1]), Base.tail(args))
find_msv(x) = x
find_msv(::Tuple{}) = nothing
find_msv(a::judiMultiSourceVector, rest) = a
find_msv(::Any, rest) = find_msv(rest)

extrude(x::judiMultiSourceVector) = extrude(x.data)

# Add broadcasting by hand due to the per source indexing
for func ∈ [:lmul!, :rmul!, :rdiv!, :ldiv!]
    @eval begin
        $func(ms::judiMultiSourceVector, x::Number) = $func(ms.data, x)
        $func(x::Number, ms::judiMultiSourceVector) = $func(x, ms.data)
    end
end

# Broadcasted custom type
check_compat() = true
get_src(ms::judiMultiSourceVector, j) = ms.data[j]
get_src(v::Vector{<:Array}, j) = v[j]
get_src(v::Array{T, N}, j) where {T<:Number, N} = v
get_src(n::Number, j) = n

struct MultiSource <: Base.AbstractBroadcasted
    m1
    m2
    op
end

function materialize(bc::MultiSource)
    m1, m2 = materialize(bc.m1), materialize(bc.m2)
    ms = similar(find_msv((m1, m2)))
    check_compat(m1, m2)
    for i=1:ms.nsrc
        ms.data[i] = materialize(broadcasted(bc.op, get_src(m1, i), get_src(m2, i)))
    end
    ms
end

function materialize!(ms::judiMultiSourceVector, bc::MultiSource)
    m1, m2 = materialize(bc.m1), materialize(bc.m2)
    check_compat(ms, m1)
    check_compat(ms, m2)
    for i=1:ms.nsrc
        broadcast!(bc.op, ms.data[i], get_src(m1, i), get_src(m2, i))
    end
    nothing
end

for op ∈ [:+, :-, :*, :/]
    # Two multi source vectors
    @eval begin
        $(op)(ms1::judiMultiSourceVector, ms2::judiMultiSourceVector) = materialize(broadcasted($op, ms1, ms2))
    end
    # External types and broadcasting
    for LT in [Number, Real, Vector{<:Array}, Array{<:Number, <:Integer}]
        # +/*/... julia type vs multi source
        @eval $(op)(ms1::$(LT), ms2::judiMultiSourceVector) = materialize(broadcasted($op, ms1, ms2))
        @eval $(op)(ms1::judiMultiSourceVector, ms2::$(LT)) = materialize(broadcasted($op, ms1, ms2))
        # broadcasted julia type vs multi source vector or broadcast
        for MS in [judiMultiSourceVector, MultiSource]
            @eval broadcasted(::typeof($op), ms1::$(LT), ms2::$(MS)) = MultiSource(ms1, ms2, $op)
            @eval broadcasted(::typeof($op), ms1::$(MS), ms2::$(LT)) = MultiSource(ms1, ms2, $op)
        end
    end
    # multi source with multi source
    @eval broadcasted(::typeof($op), ms1::judiMultiSourceVector, ms2::MultiSource) = MultiSource(ms1, ms2, $op)
    @eval broadcasted(::typeof($op), ms1::MultiSource, ms2::judiMultiSourceVector) = MultiSource(ms1, ms2, $op)
    @eval broadcasted(::typeof($op), ms1::judiMultiSourceVector, ms2::judiMultiSourceVector) = MultiSource(ms1, ms2, $op)
    @eval broadcasted(::typeof($op), ms1::MultiSource, ms2::MultiSource) = MultiSource(ms1, ms2, $op)
end


#
# This file is part of module
#   GenerateMediumDict (ModiaMedia/dict/GenerateMediumDict.jl)
#

dict["MoistAir"] = ModiaMedia.MoistAir(fluidData["H2O"],
                                       fluidData["N2"],
                                       singleGasesData["H2O"],
                                       singleGasesData["Air"])


## Add Linear constraints

# add single constraint

function add_constr!(model::Model, inds::IVec, coeffs::FVec, rel::Cchar, rhs::Float64)
    length(inds) == length(coeffs) || error("Inconsistent argument dimensions.")
    ret = @grb_ccall(addconstr, Cint, (
        Ptr{Void},    # model
        Cint,         # numnz
        Ptr{Cint},    # cind
        Ptr{Float64}, # cvals
        Cchar,        # sense
        Float64,      # rhs
        Ptr{UInt8}    # name
        ),
        model, length(inds), inds - 1, coeffs, rel, rhs, C_NULL)
    if ret != 0
        throw(GurobiError(model.env, ret))
    end
    nothing
end

function add_constr!(model::Model, inds::Vector, coeffs::Vector, rel::GChars, rhs::Real)
    add_constr!(model, ivec(inds), fvec(coeffs), cchar(rel), Float64(rhs))
end

function add_constr!(model::Model, coeffs::Vector, rel::GChars, rhs::Real)
    inds = find(coeffs)
    vals = coeffs[inds]
    add_constr!(model, inds, vals, rel, rhs)
end

# add multiple constraints

function add_constrs!(model::Model, cbegins::IVec, inds::IVec, coeffs::FVec, rel::CVec, rhs::FVec)
    m = length(cbegins)
    nnz = length(inds)
    (m == length(rel) == length(rhs) && nnz == length(coeffs)) || error("Inconsistent argument dimensions.")

    if m > 0
        ret = @grb_ccall(addconstrs, Cint, (
            Ptr{Void},    # model
            Cint,         # num constraints
            Cint,         # num non-zeros
            Ptr{Cint},    # cbeg
            Ptr{Cint},    # cind
            Ptr{Float64}, # cval
            Ptr{Cchar},   # sense
            Ptr{Float64}, # rhs
            Ptr{UInt8}    # names
            ),
            model, m, nnz, cbegins - 1, inds - 1, coeffs,
            rel, rhs, C_NULL)

        if ret != 0
            throw(GurobiError(model.env, ret))
        end
    end
    nothing
end

function add_constrs!(model::Model, cbeg::Vector, inds::Vector, coeffs::Vector, rel::GCharOrVec, rhs::Vector)
    add_constrs!(model, ivec(cbeg), ivec(inds), fvec(coeffs), cvecx(rel, length(cbeg)), fvec(rhs))
end

function add_constrs_t!(model::Model, At::SparseMatrixCSC{Float64}, rel::GCharOrVec, b::Vector)
    n, m = size(At)
    (m == length(b) && n == num_vars(model)) || error("Incompatible argument dimensions.")
    add_constrs!(model, At.colptr[1:At.n], At.rowval, At.nzval, rel, b)
end

function add_constrs_t!(model::Model, At::Matrix{Float64}, rel::GCharOrVec, b::Vector)
    n, m = size(At)
    (m == length(b) && n == num_vars(model)) || error("Incompatible argument dimensions.")
    add_constrs_t!(model, sparse(At), rel, b)
end

function add_constrs!(model::Model, A::CoeffMat, rel::GCharOrVec, b::Vector{Float64})
    m, n = size(A)
    (m == length(b) && n == num_vars(model)) || error("Incompatible argument dimensions.")
    add_constrs_t!(model, transpose(A), rel, b)
end

# add single range constraint

function add_rangeconstr!(model::Model, inds::IVec, coeffs::FVec, lb::Float64, ub::Float64)
    ret = @grb_ccall(addrangeconstr, Cint, (
        Ptr{Void},    # model
        Cint,         # numnz
        Ptr{Cint},    # cind
        Ptr{Float64}, # cvals
        Float64,      # lower
        Float64,      # upper
        Ptr{UInt8}    # name
        ),
        model, length(inds), inds - 1, coeffs, lb, ub, C_NULL)
    if ret != 0
        throw(GurobiError(model.env, ret))
    end
    nothing
end

function add_rangeconstr!(model::Model, inds::Vector, coeffs::Vector, lb::Real, ub::Real)
    add_rangeconstr!(model, ivec(inds), fvec(coeffs), Float64(lb), Float64(ub))
end

function add_rangeconstr!(model::Model, coeffs::Vector, lb::Real, ub::Real)
    inds = find(coeffs)
    vals = coeffs[inds]
    add_rangeconstr!(model, inds, vals, lb, ub)
end

# add multiple range constraints

function add_rangeconstrs!(model::Model, cbegins::IVec, inds::IVec, coeffs::FVec, lb::FVec, ub::FVec)
    m = length(cbegins)
    nnz = length(inds)
    (m == length(lb) == length(ub) && nnz == length(coeffs)) || error("Incompatible argument dimensions.")

    if m > 0
        ret = @grb_ccall(addrangeconstrs, Cint, (
            Ptr{Void},    # model
            Cint,         # num constraints
            Cint,         # num non-zeros
            Ptr{Cint},    # cbeg
            Ptr{Cint},    # cind
            Ptr{Float64}, # cval
            Ptr{Float64}, # lower
            Ptr{Float64}, # upper
            Ptr{UInt8}    # names
            ),
            model, m, nnz, cbegins - 1, inds - 1, coeffs, lb, ub, C_NULL)

        if ret != 0
            throw(GurobiError(model.env, ret))
        end
    end
    nothing
end

function add_rangeconstrs!(model::Model, cbeg::Vector, inds::Vector, coeffs::Vector, lb::Vector, ub::Vector)
    add_rangeconstrs!(model, ivec(cbeg), ivec(inds), fvec(coeffs), fvec(lb), fvec(ub))
end

function add_rangeconstrs_t!(model::Model, At::SparseMatrixCSC{Float64}, lb::Vector, ub::Vector)
    add_rangeconstrs!(model, At.colptr[1:At.n], At.rowval, At.nzval, lb, ub)
end

function add_rangeconstrs_t!(model::Model, At::Matrix{Float64}, lb::Vector, ub::Vector)
    add_rangeconstrs_t!(model, sparse(At), lb, ub)
end

function add_rangeconstrs!(model::Model, A::CoeffMat, lb::Vector, ub::Vector)
    m, n = size(A)
    (m == length(lb) == length(ub) && n == num_vars(model)) || error("Incompatible argument dimensions.")

    add_rangeconstrs_t!(model, transpose(A), lb, ub)
end

function get_constrmatrix(model::Model)
    nnz = get_intattr(model, "NumNZs")
    m = num_constrs(model)
    n = num_vars(model)
    numnzP = Array(Cint, 1)
    cbeg = Array(Cint, m+1)
    cind = Array(Cint, nnz)
    cval = Array(Cdouble, nnz)
    ret = @grb_ccall(getconstrs, Cint, (
                     Ptr{Void},
                     Ptr{Cint},
                     Ptr{Cint},
                     Ptr{Cint},
                     Ptr{Cdouble},
                     Cint,
                     Cint
                     ),
                     model, numnzP, cbeg, cind, cval, 0, m)
    if ret != 0
        throw(GurobiError(model.env, ret))
    end
    cbeg[end] = nnz
    I = Array(Int64, nnz)
    J = Array(Int64, nnz)
    V = Array(Float64, nnz)
    for i in 1:length(cbeg)-1
        for j in (cbeg[i]+1):cbeg[i+1]
            I[j] = i
            J[j] = cind[j]+1
            V[j] = cval[j]
        end
    end
    return sparse(I, J, V, m, n)
end

const GRB_SOS_TYPE1 = convert(Cint, 1)
const GRB_SOS_TYPE2 = convert(Cint, 2)

function add_sos!(model::Model, sostype::Symbol, idx::Vector{Int}, weight::Vector{Cdouble})
    ((nelem = length(idx)) == length(weight)) || error("Index and weight vectors of unequal length")
    (sostype == :SOS1) ? (typ = GRB_SOS_TYPE1) : ( (sostype == :SOS2) ? (typ = GRB_SOS_TYPE2) : error("Invalid SOS constraint type") )
    ret = @grb_ccall(addsos, Cint, (
                     Ptr{Void},
                     Cint,
                     Cint,
                     Ptr{Cint},
                     Ptr{Cint},
                     Ptr{Cint},
                     Ptr{Cdouble}
                     ),
                     model, convert(Cint, 1), convert(Cint, nelem), Cint[typ], Cint[0], convert(Vector{Cint}, idx-1), weight)
    if ret != 0
        throw(GurobiError(model.env, ret))
    end
end

del_constrs!{T<:Real}(model::Model, idx::T) = del_constrs!(model, Cint[idx])
del_constrs!{T<:Real}(model::Model, idx::Vector{T}) = del_constrs!(model, convert(Vector{Cint},idx))
function del_constrs!(model::Model, idx::Vector{Cint})
    numdel = length(idx)
    ret = @grb_ccall(delconstrs, Cint, (
                     Ptr{Void},
                     Cint,
                     Ptr{Cint}),
                     model, convert(Cint,numdel), idx.-1)
    if ret != 0
        throw(GurobiError(model.env, ret))
    end
end


#production and acquisition

function ICCost!(options::zefiroOptions,sizes::zefiroSizes,data::zefiroData,output::zefiroOutput)
    key = (options.postprocess ? "postprocessing" : "evaluating")
    println(key*" port cost")
    port!(options,sizes,data,output)
    println(key*" installationOfTheComponents cost")
    installationOfTheComponents!(options,sizes,data,output)
    println(key*" commissioning cost")
    commissioning!(options,sizes,data,output)
    println(key*" insurance cost")
    insurance!(options,sizes,data,output)

    nothing
end

function port!(options::zefiroOptions,sizes::zefiroSizes,data::zefiroData,output::zefiroOutput)

    if options.port == 0
        if options.postprocess
            nothing
        else
            for i in 1:sizes.winds,j in 1:sizes.turbines
                output.CAPEX[i,j] += (data.Cport[i] + data.N1d[j]*data.Lr[i])*data.turbineQuantity[j]
                output.port[i,j] += (data.Cport[i] + data.N1d[j]*data.Lr[i])*data.turbineQuantity[j]
            end
        end
    else
        error("port mode not found.")
    end
    nothing

end

function installationOfTheComponents!(options::zefiroOptions,sizes::zefiroSizes,data::zefiroData,output::zefiroOutput)

    if options.installationOfTheComponents == 0
        if options.postprocess
            nothing
        else
            output.CAPEX .+= transpose(data.Ccomp).*transpose(data.turbineQuantity)
            output.installationOfTheComponents .+= transpose(data.Ccomp).*transpose(data.turbineQuantity)
        end
    else
        error("installationOfTheComponents mode not found.")
    end
    nothing

end

function commissioning!(options::zefiroOptions,sizes::zefiroSizes,data::zefiroData,output::zefiroOutput)

    if options.commissioning == 0
        if options.postprocess
            nothing
        else
            output.CAPEX .+= transpose(data.Lcom.*data.Ncom).*transpose(data.turbineQuantity)
            output.commissioning .+= transpose(data.Lcom.*data.Ncom).*transpose(data.turbineQuantity)
        end
    else
        error("commissioning mode not found.")
    end
    nothing

end

function insurance!(options::zefiroOptions,sizes::zefiroSizes,data::zefiroData,output::zefiroOutput)

    if options.insurance == 0
        if options.postprocess
            nothing
        else
            for i in 1:sizes.winds,j in 1:sizes.turbines
                output.CAPEX[i,j] += data.Cins[i]*data.IC[j]
                output.insurance[i,j] += data.Cins[i]*data.IC[j]
            end
        end
    else
        error("insurance mode not found.")
    end
    nothing

end


module DistributedFlux

using Flux, MPI
using TensorBoardLogger, Logging
using ProgressMeter: next!, Progress
using Flux.Optimise: Params, @progress, batchmemaybe, StopException, update!

include("functor.jl")
include("orthogonal.jl")
include("padding.jl")
include("layers.jl")

call(f, xs...) = f(xs...)
runall(f) = f
runall(fs::AbstractVector) = x -> foreach(call, fs, x)

bcast!(x) = MPI.Initialized() ? MPI.Bcast!(x, 0, MPI.COMM_WORLD) : x

function allreduce!(x)
    MPI.Initialized() || return x
    x′ = zero(x)
    MPI.Allreduce!(x, x′, MPI.SUM, MPI.COMM_WORLD)
    x .= x′ ./ MPI.Comm_size(MPI.COMM_WORLD)
end

unwrap(x) = hasproperty(x, :data) ? x.data : x

function allreduce!(xs, gs)
    for x in xs
        allreduce!(unwrap(gs[x]))
    end
end

function Flux.train!(loss, ps, data, opt, gradient = Flux.gradient; cb_step = x -> (), cb_epoch = x -> (), logger = TBLogger(), verbose = false, sync = false)
    sync && foreach(bcast! ∘ unwrap, ps)
    ps = Params(ps)
    cb_step = runall(cb_step)
    cb_epoch = runall(cb_epoch)
    l̄ = 0f0
    ndata = length(data)
    prog = Progress(ndata)
    @progress for (n, d) in enumerate(data)
        try
            local l
            gs = gradient(ps) do
                l = loss(batchmemaybe(d)...)
            end
            sync && allreduce!(ps, gs)
            update!(opt, ps, gs)
            l̄ = ((n - 1) * l̄ + l) / n
            if verbose
                prog.desc = "$n/$ndata "
                next!(prog, showvalues = [(:loss, l), (:avgloss, l̄)])
                with_logger(logger) do
                    @info "train" loss=l avgloss=l̄
                end
            end
            cb_step((step = n, loss = l, avgloss = l̄))
        catch ex
            if ex isa StopException
                break
            else
                rethrow(ex)
            end
        end
    end
    cb_epoch((avgloss = l̄,))
    return l̄
end

end


module Augmentor

using RecipesBase
using Requires
using Images
using AffineTransforms
using ColorTypes
using PiecewiseAffineTransforms
using Interpolations

export

    rotate_expand,
    rotate_crop,

    DisplacementField,
        uniform_displacement,
        gaussian_displacement,

    DisplacementMesh,

    ImageTransformation,
        Either,
        NoOp,
        FlipX,
        FlipY,
        Rotate90,
        Rotate180,
        Rotate270,
        Resize,
        CropRatio,
        CropSize,
        RCropSize,
        RCropRatio,
        Crop,
        Zoom,
        Scale,
        RandomDisplacement,
        SmoothedDisplacement,

    multiplier,

    ImageSource,
        DirImageSource,

    Pipeline,

    transform

include("common.jl")

include("rotate.jl")
include("crop.jl")

include("imagetransformation.jl")

include("displacementfield.jl")
include("displacementmesh.jl")

include("transformations/flipx.jl")
include("transformations/flipy.jl")
include("transformations/resize.jl")
include("transformations/cropratio.jl")
include("transformations/cropsize.jl")
include("transformations/rcropsize.jl")
include("transformations/rcropratio.jl")
include("transformations/crop.jl")
include("transformations/zoom.jl")
include("transformations/scale.jl")
include("transformations/rotate90.jl")
include("transformations/rotate180.jl")
include("transformations/rotate270.jl")
include("transformations/randomdisplacement.jl")
include("transformations/smootheddisplacement.jl")

include("imagesource.jl")
include("dirimagesource.jl")

include("pipeline.jl")
include("linearpipeline.jl")

# Lazy loading for package integrations
@require MLDataUtils include("integrations/MLDataUtils.jl")

end


abstract type AbstractOperationModel end

struct DefaultOpModel<:AbstractOperationModel end

mutable struct ModelReference
    transmission::Type{<:PM.AbstractPowerModel}
    devices::Dict{Symbol, DeviceModel}
    branches::Dict{Symbol, DeviceModel}
    services::Dict{Symbol, ServiceModel}
end

"""
    ModelReference(::Type{T}) where {T<:PM.AbstractPowerFormulation}

Creates a model reference of the Power Formulation, devices, branches, and services.

# Arguments
-`model::Type{T} where {T<:PM.AbstractPowerFormulation} = CopperPlatePowerModel`:
-`devices::Dict{Symbol, DeviceModel} = devices`: device dictionary
-`branches::Dict{Symbol, BranchModel} = branches`: branch dictionary
-`services::Dict{Symbol, ServiceModel} = services`: service dictionary

# Example
```julia
model_ref= ModelReference(CopperPlatePowerModel, devices, branches, services)
```
"""
function ModelReference(::Type{T}) where {T<:PM.AbstractPowerModel}

    return  ModelReference(T,
                           Dict{Symbol, DeviceModel}(),
                           Dict{Symbol, DeviceModel}(),
                           Dict{Symbol, ServiceModel}())

end

mutable struct OperationModel{M<:AbstractOperationModel}
    model_ref::ModelReference
    sys::PSY.System
    canonical::CanonicalModel
end

"""
    OperationModel(::Type{M},
    model_ref::ModelReference,
    sys::PSY.System;
    optimizer::Union{Nothing, JuMP.OptimizerFactory}=nothing,
    kwargs...) where {M<:AbstractOperationModel,
                      T<:PM.AbstractPowerFormulation}

This builds the optimization model and populates the operation model

# Arguments
-`::Type{M} where {M<:AbstractOperationModel, T<:PM.AbstractPowerFormulation} = TestOptModel`:
The abstract operation model type
-`model_ref::ModelReference = model_ref`: The model reference made up of transmission, devices,
                                          branches, and services.
-`sys::PSY.System = c_sys5`: the system created using Power Systems

# Output
-`op_model::OperationModel`: The operation model contains the model type, model, Power
Systems system, and optimization model.

# Example
```julia
model_ref= ModelReference(CopperPlatePowerModel, devices, branches, services)
OpModel = OperationModel(TestOptModel, model_ref, c_sys5_re; PTDF = PTDF5, optimizer = GLPK_optimizer)
```

# Accepted Key Words
-`verbose::Bool = true`: verbose default is true
-`PTDF::PTDF = PTDF`: Passes the PTDF matrix into the optimization model
-`optimizer::union{Nothing,JuMP.OptimizerFactory} = GLPK_optimizer`: The optimizer gets passed
into the optimization model the default is nothing.
-`sequential_runs::Bool = false`: tells the model to do sequential runs
-`initial_conditions::DICKDA = DICKDA()`: default of Dict{ICKey, Array{InitialCondition}}
-`parameters::Bool = false`: enable JuMP parameters
-`forecast::Bool = true`: if true, forecast collects the time steps in Power Systems,
if false it runs for one time step
-`initial_time::Dates.DateTime = PSY.get_forecasts_initial_time(sys)`: initial time of forecast
"""
function OperationModel(::Type{M},
                        model_ref::ModelReference,
                        sys::PSY.System;
                        optimizer::Union{Nothing, JuMP.OptimizerFactory}=nothing,
                        kwargs...) where {M<:AbstractOperationModel}

    op_model = OperationModel{M}(model_ref,
                          sys,
                          CanonicalModel(model_ref.transmission, sys, optimizer; kwargs...))

    build_op_model!(op_model; kwargs...)

    return  op_model

end
"""
    OperationModel(op_model::Type{M},
                    ::Type{T},
                    sys::PSY.System;
                    kwargs...) where {M<:AbstractOperationModel,
                                    T<:PM.AbstractPowerFormulation}

This uses the Abstract Power Formulation to build the model reference and
the optimization model and populates the operation model struct.

# Arguments
-`op_model::Type{M} = where {M<:AbstractOperationModel`: Defines the type of the operation model
-`::Type{T} where T<:PM.AbstractPowerFormulation`: The power formulation used for model ref & optimization model
-`sys::PSY.System = c_sys5`: the system created in Power Systems

# Output
-`op_model::OperationModel`: The operation model contains the model type, model, Power
Systems system, and optimization model.

# Example
```julia
model_ref= ModelReference(CopperPlatePowerModel, devices, branches, services)
OpModel = OperationModel(TestOptModel, model_ref, c_sys5_re; PTDF = PTDF5, optimizer = GLPK_optimizer)
```

# Accepted Key Words
-`verbose::Bool = true`: verbose default is true
-`PTDF::PTDF = PTDF`: Passes the PTDF matrix into the optimization model
-`optimizer::union{Nothing,JuMP.OptimizerFactory} = GLPK_optimizer`: The optimizer gets passed
into the optimization model the default is nothing.
-`sequential_runs::Bool = false`: tells the model to do sequential runs
-`initial_conditions::DICKDA = DICKDA()`: default of Dict{ICKey, Array{InitialCondition}}
-`parameters::Bool = false`: enable JuMP parameters
-`forecast::Bool = true`: if true, forecast collects the time steps in Power Systems,
if false it runs for one time step
-`initial_time::Dates.DateTime = PSY.get_forecasts_initial_time(sys)`: initial time of forecast

"""
function OperationModel(::Type{M},
                        ::Type{T},
                        sys::PSY.System;
                        kwargs...) where {M<:AbstractOperationModel,
                                          T<:PM.AbstractPowerModel}

    optimizer = get(kwargs, :optimizer, nothing)

    return OperationModel{M}(ModelReference(T),
                          sys,
                          CanonicalModel(T, sys, optimizer; kwargs...))

end
"""
    OperationModel(::Type{T},
                    sys::PSY.System;
                    kwargs...) where {M<:AbstractOperationModel,
                                    T<:PM.AbstractPowerFormulation}

This uses the Abstract Power Formulation to build the model reference and
the optimization model and populates the operation model struct.

***Note:*** the abstract operation model is set to the default operation model

# Arguments
-`op_model::Type{M} = where {M<:AbstractOperationModel`: Defines the type of the operation model
-`::Type{T} where T<:PM.AbstractPowerFormulation`: The power formulation used for model ref & optimization model
-`sys::PSY.System = c_sys5`: the system created in Power Systems

# Output
-`op_model::OperationModel`: The operation model contains the model type, model, Power
Systems system, and optimization model.

# Example
```julia
model_ref= ModelReference(CopperPlatePowerModel, devices, branches, services)
OpModel = OperationModel(TestOptModel, model_ref, c_sys5_re; PTDF = PTDF5, optimizer = GLPK_optimizer)
```

# Accepted Key Words
-`verbose::Bool = true`: verbose default is true
-`PTDF::PTDF = PTDF`: Passes the PTDF matrix into the optimization model
-`optimizer::union{Nothing,JuMP.OptimizerFactory} = GLPK_optimizer`: The optimizer gets passed
into the optimization model the default is nothing.
-`sequential_runs::Bool = false`: tells the model to do sequential runs
-`initial_conditions::DICKDA = DICKDA()`: default of Dict{ICKey, Array{InitialCondition}}
-`parameters::Bool = false`: enable JuMP parameters
-`forecast::Bool = true`: if true, forecast collects the time steps in Power Systems,
if false it runs for one time step
-`initial_time::Dates.DateTime = PSY.get_forecasts_initial_time(sys)`: initial time of forecast

"""
function OperationModel(::Type{T},
                        sys::PSY.System;
                        kwargs...) where {T<:PM.AbstractPowerModel}

    return OperationModel(DefaultOpModel,
                         T,
                         sys; kwargs...)

end

get_transmission_ref(op_model::OperationModel) = op_model.model_ref.transmission
get_devices_ref(op_model::OperationModel) = op_model.model_ref.devices
get_branches_ref(op_model::OperationModel) = op_model.model_ref.branches
get_services_ref(op_model::OperationModel) = op_model.model_ref.services
get_system(op_model::OperationModel) = op_model.sys

function set_transmission_ref!(op_model::OperationModel{M},
                               transmission::Type{T}; kwargs...) where {T<:PM.AbstractPowerModel,
                                                                        M<:AbstractOperationModel}

    # Reset the canonical
    op_model.model_ref.transmission = transmission
    op_model.canonical = CanonicalModel(transmission,
                                        op_model.sys,
                                        op_model.canonical.optimizer_factory; kwargs...)

    build_op_model!(op_model; kwargs...)

    return
end

function set_devices_ref!(op_model::OperationModel{M},
                          devices::Dict{Symbol, DeviceModel}; kwargs...) where M<:AbstractOperationModel

    # Reset the canonical
    op_model.model_ref.devices = devices
    op_model.canonical = CanonicalModel(op_model.model_ref.transmission,
                                        op_model.sys,
                                        op_model.canonical.optimizer_factory; kwargs...)

    build_op_model!(op_model; kwargs...)

    return
end

function set_branches_ref!(op_model::OperationModel{M},
                           branches::Dict{Symbol, DeviceModel}; kwargs...) where M<:AbstractOperationModel

    # Reset the canonical
    op_model.model_ref.branches = branches
    op_model.canonical = CanonicalModel(op_model.model_ref.transmission,
                                        op_model.sys,
                                        op_model.canonical.optimizer_factory; kwargs...)

    build_op_model!(op_model; kwargs...)

    return
end

function set_services_ref!(op_model::OperationModel{M},
                           services::Dict{Symbol, DeviceModel}; kwargs...) where M<:AbstractOperationModel

    # Reset the canonical
    op_model.model_ref.services = services
    op_model.canonical = CanonicalModel(op_model.model_ref.transmission,
                                        op_model.sys,
                                        op_model.canonical.optimizer_factory; kwargs...)

    build_op_model!(op_model; kwargs...)

    return
end

function set_device_model!(op_model::OperationModel{M},
                           name::Symbol,
                           device::DeviceModel{D, B}; kwargs...) where {D<:PSY.Injection,
                                                                        B<:AbstractDeviceFormulation,
                                                                        M<:AbstractOperationModel}

    if haskey(op_model.model_ref.devices, name)
        op_model.model_ref.devices[name] = device
        op_model.canonical = CanonicalModel(op_model.model_ref.transmission,
                                            op_model.sys,
                                            op_model.canonical.optimizer_factory; kwargs...)
        build_op_model!(op_model; kwargs...)
    else
        error("Device Model with name $(name) doesn't exist in the model")
    end

    return

end

function set_branch_model!(op_model::OperationModel{M},
                           name::Symbol,
                           branch::DeviceModel{D, B}; kwargs...) where {D<:PSY.Branch,
                                                                        B<:AbstractDeviceFormulation,
                                                                        M<:AbstractOperationModel}

    if haskey(op_model.model_ref.branches, name)
        op_model.model_ref.branches[name] = branch
        op_model.canonical = CanonicalModel(op_model.model_ref.transmission,
                                            op_model.sys,
                                            op_model.canonical.optimizer_factory; kwargs...)
        build_op_model!(op_model; kwargs...)
    else
        error("Branch Model with name $(name) doesn't exist in the model")
    end

    return

end

function set_services_model!(op_model::OperationModel{M},
                             name::Symbol,
                             service::DeviceModel; kwargs...) where M<:AbstractOperationModel

    if haskey(op_model.model_ref.devices, name)
        op_model.model_ref.services[name] = service
        op_model.canonical = CanonicalModel(op_model.model_ref.transmission,
                                            op_model.sys,
                                            op_model.canonical.optimizer_factory; kwargs...)
        build_op_model!(op_model; kwargs...)
    else
        error("Branch Model with name $(name) doesn't exist in the model")
    end

    return

end

function construct_device!(op_model::OperationModel,
                           name::Symbol,
                           device_model::DeviceModel;
                           kwargs...)

    if haskey(op_model.model_ref.devices, name)
        error("Device with model name $(name) already exists in the Opertaion Model")
    end

    op_model.model_ref.devices[name] = device_model

    construct_device!(op_model.canonical,
                     get_system(op_model),
                      device_model,
                      get_transmission_ref(op_model);
                      kwargs...)

    JuMP.@objective(op_model.canonical.JuMPmodel,
                    MOI.MIN_SENSE,
                    op_model.canonical.cost_function)

    return

end

function construct_network!(op_model::OperationModel,
                            system_formulation::Type{T};
                            kwargs...) where {T<:PM.AbstractPowerModel}

    construct_network!(op_model.canonical, get_system(op_model), T; kwargs...)

    return
end

function get_initial_conditions(op_model::OperationModel)
    return op_model.canonical.initial_conditions
end

function get_initial_conditions(op_model::OperationModel,
                                ic::InitialConditionQuantity,
                                device::PSY.Device)

    canonical = op_model.canonical
    key = ICKey(ic, device)

    return get_ini_cond(canonical, key)

end


"""
    Buffer

Struct used for reading out vertices from Action struct.
Contains symmetry related flags, (asymptotic) kernel specification and interpolation parameters.
* `exchange_flag :: Bool`  : flag for site [(i0, j) <-> (j, i0)] and spin [(μ, ν) <-> (ν, μ)] exchange
* `map_flag      :: Bool`  : flag for channel mapping s <-> u
* `sgn_μν        :: Bool`  : sign function for combined spin indices
* `sgn_μ         :: Bool`  : sign function for first spin index
* `sgn_ν         :: Bool`  : sign function for second spin index
* `kernel        :: Int64` : specification of asymptotic kernel to be interpolated
* `p1            :: Param` : interpolation parameters for bosonic frequency argument
* `p2            :: Param` : interpolation parameters for first fermionic frequency argument
* `p3            :: Param` : interpolation parameters for second fermionic frequency argument
"""
struct Buffer
    exchange_flag :: Bool
    map_flag      :: Bool
    sgn_μν        :: Bool
    sgn_μ         :: Bool
    sgn_ν         :: Bool
    kernel        :: Int64
    p1            :: Param
    p2            :: Param
    p3            :: Param
end

# function to invert flag
function set_flag(
    flag :: Bool
    )    :: Bool

    if flag
        flag = false
    else
        flag = true
    end

    return flag
end

# generate buffer dummy
function get_buffer_empty() :: Buffer

    b = Buffer(false, false, false, false, false, 0, get_param_empty(), get_param_empty(), get_param_empty())

    return b
end

# generate generic access buffer for Action struct given flags
function get_buffer(
    w             :: Float64,
    v             :: Float64,
    vp            :: Float64,
    Ω             :: Vector{Float64},
    ν             :: Vector{Float64},
    exchange_flag :: Bool,
    map_flag      :: Bool,
    sgn_μν        :: Bool,
    sgn_μ         :: Bool,
    sgn_ν         :: Bool
    )             :: Buffer

    if Ω[end] < abs(w)
        return get_buffer_empty()
    else
        if ν[end] < abs(v)
            if ν[end] < abs(vp)
                # interpolation for q1
                return Buffer(exchange_flag, map_flag, sgn_μν, sgn_μ, sgn_ν, 1, get_param(w, Ω), get_param_empty(), get_param_empty())
            else
                # interpolation for q2_2
                return Buffer(exchange_flag, map_flag, sgn_μν, sgn_μ, sgn_ν, 3, get_param(w, Ω), get_param_empty(), get_param(vp, ν))
            end
        else
            if ν[end] < abs(vp)
                # interpolation for q2_1
                return Buffer(exchange_flag, map_flag, sgn_μν, sgn_μ, sgn_ν, 2, get_param(w, Ω), get_param(v, ν), get_param_empty())
            else
                # interpolation for q3
                return Buffer(exchange_flag, map_flag, sgn_μν, sgn_μ, sgn_ν, 4, get_param(w, Ω), get_param(v, ν), get_param(vp, ν))
            end
        end
    end
end

# generate access buffer for s channel of Action struct
function get_buffer_s(
    w  :: Float64,
    v  :: Float64,
    vp :: Float64,
    m  :: Mesh
    )  :: Buffer

    # init flags
    exchange_flag = false
    map_flag      = false
    sgn_μν        = false
    sgn_μ         = false
    sgn_ν         = false

    # do -w -> w
    if w < 0.0
        w             *= -1.0
        exchange_flag  = set_flag(exchange_flag)
    end

    # do -v -> v
    if v < 0.0
        v             *= -1.0
        exchange_flag  = set_flag(exchange_flag)
        map_flag       = set_flag(map_flag)
        sgn_μ          = set_flag(sgn_μ)
    end

    # do -vp -> vp
    if vp < 0.0
        vp       *= -1.0
        map_flag  = set_flag(map_flag)
        sgn_ν     = set_flag(sgn_ν)
    end

    return get_buffer(w, v, vp, m.Ωs, m.νs, exchange_flag, map_flag, sgn_μν, sgn_μ, sgn_ν)
end

# generate access buffer for t channel of Action struct
function get_buffer_t(
    w  :: Float64,
    v  :: Float64,
    vp :: Float64,
    m  :: Mesh
    )  :: Buffer

    # init flags
    exchange_flag = false
    map_flag      = false
    sgn_μν        = false
    sgn_μ         = false
    sgn_ν         = false

    # do -w -> w
    if w < 0.0
        w      *= -1.0
        sgn_μν  = set_flag(sgn_μν)
    end

    # do -v -> v
    if v < 0.0
        v     *= -1.0
        sgn_μ  = set_flag(sgn_μ)
    end

    # do -vp -> vp
    if vp < 0.0
        vp    *= -1.0
        sgn_ν  = set_flag(sgn_ν)
    end

    return get_buffer(w, v, vp, m.Ωt, m.νt, exchange_flag, map_flag, sgn_μν, sgn_μ, sgn_ν)
end

# generate access buffer for u channel of Action struct
function get_buffer_u(
    w  :: Float64,
    v  :: Float64,
    vp :: Float64,
    m  :: Mesh
    )  :: Buffer

    # init flags
    exchange_flag = false
    map_flag      = false
    sgn_μν        = false
    sgn_μ         = false
    sgn_ν         = false

    # do -w -> w
    if w < 0.0
        w             *= -1.0
        exchange_flag  = set_flag(exchange_flag)
        sgn_μν         = set_flag(sgn_μν)
    end

    # do -v -> v
    if v < 0.0
        v             *= -1.0
        exchange_flag  = set_flag(exchange_flag)
        map_flag       = set_flag(map_flag)
        sgn_ν          = set_flag(sgn_ν)
    end

    # do -vp -> vp
    if vp < 0.0
        vp       *= -1.0
        map_flag  = set_flag(map_flag)
        sgn_ν     = set_flag(sgn_ν)
    end

    return get_buffer(w, v, vp, m.Ωs, m.νs, exchange_flag, map_flag, sgn_μν, sgn_μ, sgn_ν)
end


# Sun 30 Dec 2012 09:44:00 PM EST
#
# NeHe Tut 19 - Particle engine (triangle strips)
#
# Q - quit
# L - turn lights on/off
# F - change texture filter (linear, nearest, mipmap)
# PageUp/Down - move camera closer/further away
# Up/Down - increase/decrease x-rotation speed
# Left/Right - increase/decrease y-rotation speed
# Home/End - increase/decrease the amount of "friction" on particles
# Enter - turn rainbow effect on/off
# Space - step through colors for rainbow effect

# load necessary GL/SDL routines

using OpenGL
@OpenGL.version "1.0"
@OpenGL.load
using SDL

# initialize variables

bpp       = 16
wintitle  = "NeHe Tut 19"
icontitle = "NeHe Tut 19"
width     = 640
height    = 480

colors = [1.0  0.5  0.5;
          1.0  0.75 0.5;
          1.0  1.0  0.5;
          0.75 1.0  0.5;
          0.5  1.0  0.5;
          0.5  1.0  0.75;
          0.5  1.0  1.0;
          0.5  0.75 1.0;
          0.5  0.5  1.0;
          0.75 0.5  1.0;
          1.0  0.5  1.0;
          1.0  0.5  0.75]

rainbow       = true
slowdown      = 2.0
xspeed        = 0
yspeed        = 0
zoom          = -40.0
color         = 1
delay         = 0

MAX_PARTICLES = 1000

type particle
    active::Bool
    life::Float64
    fade::Float64
    red::Float64
    green::Float64
    blue::Float64
    xPos::Float64
    yPos::Float64
    zPos::Float64
    xSpeed::Float64
    ySpeed::Float64
    zSpeed::Float64
    xGrav::Float64
    yGrav::Float64
    zGrav::Float64
end

particles = [particle(true,                   # Julia doesn't like it when you try to initialize an empty array of
                      1.0,                    # a composite type and try to fill it afterwards, so we
                      rand(1:100)/1000+0.003,  # start with a 1-element vector and tack on values
                      colors[1,1],
                      colors[1,2],
                      colors[1,3],
                      0.0,
                      0.0,
                      0.0,
                      (rand(1:50)-26.0)*10.0,
                      (rand(1:50)-26.0)*10.0,
                      (rand(1:50)-26.0)*10.0,
                      0.0,
                      -0.8,
                      0.0)]

for loop = 2:MAX_PARTICLES
    active = true
    life   = 1.0
    fade   = rand(1:100)/1000+0.003
    red    = colors[loop%size(colors,1)+1,1]
    green  = colors[loop%size(colors,1)+1,2]
    blue   = colors[loop%size(colors,1)+1,3]
    xPos   = 0.0
    yPos   = 0.0
    zPos   = 0.0
    xSpeed = (rand(1:50)-26.0)*10.0
    ySpeed = (rand(1:50)-26.0)*10.0
    zSpeed = (rand(1:50)-26.0)*10.0
    xGrav  = 0.0
    yGrav  = -0.8
    zGrav  = 0.0
    particles = push!(particles, particle(active,life,fade,red,green,blue,xPos,yPos,zPos,xSpeed,ySpeed,zSpeed,xGrav,yGrav,zGrav))
end

keystate_checked   = false
lastkeycheckTime   = 0
key_repeatinterval = 75 #ms

# open SDL window with an OpenGL context

SDL_Init(SDL_INIT_VIDEO)
#videoInfo = SDL_GetVideoInfo()
videoFlags = (SDL_OPENGL | SDL_GL_DOUBLEBUFFER | SDL_HWPALETTE | SDL_RESIZABLE)
#if videoInfo.hw_available
    videoFlags = (videoFlags | SDL_HWSURFACE)
#else
    #videoFlags = (videoFlags | SDL_SWSURFACE)
#end
#if videoInfo.blit_hw
    videoFlags = (videoFlags | SDL_HWACCEL)
#end
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1)
SDL_SetVideoMode(width, height, bpp, videoFlags)
SDL_WM_SetCaption(wintitle, icontitle)

glViewport(0, 0, width, height)
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glDisable(GL_DEPTH_TEST)
glShadeModel(GL_SMOOTH)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST)

glMatrixMode(GL_PROJECTION)
glLoadIdentity()

gluPerspective(45.0,width/height,0.1,200.0)

glMatrixMode(GL_MODELVIEW)

# load textures from images

tex   = Array(Uint32,1) # generating 1 texture

img, w, h = glimread(expanduser("~/.julia/SDL/Examples/NeHe/tut19/Particle.bmp"))

glGenTextures(1,tex)
glBindTexture(GL_TEXTURE_2D,tex[1])
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexImage2D(GL_TEXTURE_2D, 0, 3, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, img)

# enable texture mapping & blending

glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE)
glColor(1.0, 1.0, 1.0, 0.5)

glBindTexture(GL_TEXTURE_2D,tex[1])

# main drawing loop

while true
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()

    for loop = 1:MAX_PARTICLES
        if particles[loop].active
            x = particles[loop].xPos
            y = particles[loop].yPos
            z = particles[loop].zPos+zoom
            glColor(particles[loop].red, particles[loop].green, particles[loop].blue, particles[loop].life)
            glBegin(GL_TRIANGLE_STRIP)
                glTexCoord(1, 1)
                glVertex(x+0.5, y+0.5, z)
                glTexCoord(0, 1)
                glVertex(x-0.5, y+0.5, z)
                glTexCoord(1, 0)
                glVertex(x+0.5, y-0.5, z)
                glTexCoord(0, 0)
                glVertex(x-0.5, y-0.5, z)
            glEnd()
            particles[loop].xPos   += particles[loop].xSpeed/(slowdown*1000)
            particles[loop].yPos   += particles[loop].ySpeed/(slowdown*1000)
            particles[loop].zPos   += particles[loop].zSpeed/(slowdown*1000)
            particles[loop].xSpeed += particles[loop].xGrav
            particles[loop].ySpeed += particles[loop].yGrav
            particles[loop].zSpeed += particles[loop].zGrav
            particles[loop].life   -= particles[loop].fade
            if particles[loop].life < 0.0
                particles[loop].life   = 1.0
                particles[loop].fade   = (rand(1:100))/1000+0.003
                particles[loop].xPos   = 0.0
                particles[loop].yPos   = 0.0
                particles[loop].zPos   = 0.0
                particles[loop].xSpeed = xspeed+rand(1:60)-32.0
                particles[loop].ySpeed = yspeed+rand(1:60)-30.0
                particles[loop].zSpeed = rand(1:60)-30.0
                particles[loop].red    = colors[color,1]
                particles[loop].green  = colors[color,2]
                particles[loop].blue   = colors[color,3]
            end
            if saved_keystate == true
                if keystate[SDLK_8] == true && particles[loop].yGrav < 1.5
                    particles[loop].yGrav +=0.01
                elseif keystate[SDLK_2] == true && particles[loop].yGrav > -1.5
                    particles[loop].yGrav -=0.01
                elseif keystate[SDLK_4] == true && particles[loop].xGrav < 1.5
                    particles[loop].xGrav +=0.01
                elseif keystate[SDLK_6] == true && particles[loop].xGrav > -1.5
                    particles[loop].xGrav -=0.01
                elseif keystate[SDLK_TAB] == true
                    particles[loop].xPos   = 0.0
                    particles[loop].yPos   = 0.0
                    particles[loop].zPos   = 0.0
                    particles[loop].xSpeed = (rand(1:52)-26.0)*10
                    particles[loop].ySpeed = (rand(1:50)-25.0)*10
                    particles[loop].zSpeed = (rand(1:50)-25.0)*10
                end
            end
        end
    end

    delay +=1

    SDL_GL_SwapBuffers()

    SDL_PumpEvents()
    if SDL_GetTicks() - lastkeycheckTime >= key_repeatinterval
        keystate         = SDL_GetKeyState()
        keystate_checked = true
        lastkeycheckTime = SDL_GetTicks()
    end

    # Sampling rates for event processing are so fast that a single key press
    # lasts through several iterations of this loop.  This means that one press
    # can be seen as 50 or more presses by the SDL event system.  To correct
    # for this, we only check keypresses every 75ms.

    if keystate_checked == true
        if keystate[SDLK_q] == true
            break
        elseif keystate[SDLK_HOME] == true
            slowdown -=0.01
        elseif keystate[SDLK_END] == true
            slowdown +=0.01
        elseif keystate[SDLK_PAGEUP] == true
            zoom +=0.1
        elseif keystate[SDLK_PAGEDOWN] == true
            zoom -=0.1
        elseif keystate[SDLK_UP] == true
            yspeed +=1.0
        elseif keystate[SDLK_DOWN] == true
            yspeed -=1.0
        elseif keystate[SDLK_LEFT] == true
            xspeed +=1.0
        elseif keystate[SDLK_RIGHT] == true
            xspeed -=1.0
        elseif keystate[SDLK_RETURN] == true
            rainbow = (rainbow ? false : true)
        elseif keystate[SDLK_SPACE] == true || (rainbow && delay > 25)
            delay = 0
            color +=1
            if color > 12
                color = 1
            end
        end
        keystate_checked = false
    end
end


#@nospecialize

function determine_variables(expr)::Set{Symbol}
    variables = Set{Symbol}()
    ignored_symbols = Set{Symbol}()
    push!(ignored_symbols, :target)
    prewalk(expr) do x
        if x isa Symbol
            x ∈ ignored_symbols || push!(variables, x)
        elseif x isa Expr
            if x.head === :(=)
                LHS = first(x.args)
                if LHS isa Symbol
                    push!(ignored_symbols, LHS)
                elseif LHS isa Expr && LHS.head === :ref
                    push!(ignored_symbols, first(LHS.args))
                end
            elseif x.head === :call
                push!(ignored_symbols, first(x.args))
            end
        end
        return x
    end
    variables
end

function no_fmadd_dist_call(y, dargs)
    dist = Expr(:call, first(dargs), y)
    for i ∈ 2:length(dargs)
        push!(dist.args, dargs[i])
    end
    Expr(:(=), :target, Expr(:call, Expr(:(.), :ProbabilityModels, QuoteNode(:vadd)), :target, dist))
end
function fmadd_dist_call(y, dargs)
    farg₁ = dargs[2]
    # farg₁ must be an expr of the form (X * β + α)
    (farg₁ isa Expr && farg₁.head === :call) || return no_fmadd_dist_call(y, dargs)
    args = farg₁.args
    Nargs = length(args)
    Nargs > 2 || return no_fmadd_dist_call(y, dargs)
    if first(args) !== :+
        if first(dargs) === :Normal && first(args) === :* && length(args) == 3
            dist = Expr(:call, :Normal, y, args[2], args[3])
            for i ∈ 3:length(dargs)
                push!(dist.args, dargs[i])
            end
            Expr(:(=), :target, Expr(:call, Expr(:(.), :ProbabilityModels, QuoteNode(:vadd)), :target, dist))
        else
            return no_fmadd_dist_call(y, dargs)
        end
    end
    j = 2
    while j ≤ Nargs
        argsⱼ = args[j]
        if argsⱼ isa Expr && (first(argsⱼ.args) === :*) && length(argsⱼ.args) == 3
            break
        end
        j += 1
    end
    j > Nargs && return no_fmadd_dist_call(y, dargs)
    # if we're here, j is the index of the mul
    argsⱼ = args[j].args
    dist = Expr(:call, first(dargs), y, argsⱼ[2], argsⱼ[3])
    if Nargs == 3
        push!(dist.args, args[j == 2 ? 3 : 2])
    else #Nargs > 3
        for i ∈ 2:Nargs
            i == j || push!(dist.args, args[i])
        end
    end
    for i ∈ 3:length(dargs)
        push!(dist.args, dargs[i])
    end
    Expr(:(=), :target, Expr(:call, Expr(:(.), :ProbabilityModels, QuoteNode(:vadd)), :target, dist))
end
function dist_call(x)
    distcall::Expr = x.args[3]
    distcall.head === :call || return x
    length(distcall.args) < 2 && return x
    y = x.args[2]
    dargs = distcall.args
    if first(dargs) ∈ ProbabilityDistributions.FMADD_DISTRIBUTIONS
        fmadd_dist_call(y, dargs)
    else
        no_fmadd_dist_call(y, dargs)
    end
end

function translate_sampling_statement(x)
    x isa Expr || return x
    if x.head === :(+=)
        return Expr(:(=), x.args[1], Expr(:+, x.args[1], x.args[2]))
    elseif x.head === :call && first(x.args) === :~
        dist_call(x)
    else
        return x
    end
end

translate_sampling_statements(expr::Expr)::Expr = prewalk(translate_sampling_statement, expr)

# This does not work, because eval only looks in the current module, and doesn't accept a module argument (ie, Main)
# function interpolate_globals(expr)::Expr
#     postwalk(expr) do x
#         if x isa Expr && x.head == :$
#             return eval(first(x.args))
#         else
#             return x
#         end
#     end
# end
maybeget(d::Dict{K,V}, ex) where {K,V} = ex isa K ? get(d, ex, ex) : ex
ssa_sym(i::Int) = Symbol("##SSAValue##$(i)##")
function ssa_to_sym(expr)
    postwalk(expr) do ex
        if ex isa Core.SSAValue
            return ssa_sym(ex.id)
        elseif ex isa GlobalRef
            return Expr(:., Symbol(ex.mod), QuoteNode(ex.name))
        else
            return ex
        end
    end
end
function subssasyms!(q, ex, substitutions, i)
    ex = postwalk(ex) do x
        y = ssa_to_sym(x)
        maybeget(substitutions, y)
    end
    push!(q.args, :($(ssa_sym(i)) = $ex))
    nothing
end
function flatten_expression(expr)::Expr
    lowered_array = Meta.lower(ProbabilityModels, expr).args
    substitutions = Dict{Symbol,Expr}()
    assignments = Dict{Symbol,Symbol}()
    @assert length(lowered_array) == 1
    lowered = first(lowered_array).code
    q = quote end
    for i ∈ 1:length(lowered) - 1 # skip return statement
        ex = lowered[i]
        if ex isa Expr
            if ex.head === :call
                if first(ex.args) === :(:)
                    push!(q.args, :($(ssa_sym(i)) = ProbabilityModels.PaddedMatrices.Static{($(ex.args[2]),$(ex.args[3]))}()))
                elseif ex.args[1] isa GlobalRef && ex.args[1].name == :getproperty
                    substitutions[ssa_sym(i)] = Expr(:., ex.args[2], ex.args[3])
                else
                    subssasyms!(q, ex, substitutions, i)
                end
            elseif ex.head === :(=)
                assignments[ssa_to_sym(ex.args[2])] = ex.args[1]
            else
                subssasyms!(q, ex, substitutions, i)
            end
        else
            subssasyms!(q, ex, substitutions, i)
        end
    end
    q = postwalk(q) do ex
        maybeget(assignments, ex)
    end
    # println(q)
    q
end

function drop_const_assignment!(first_pass, tracked_vars, ex, LHS, RHS, verbose)
    if RHS isa Expr && RHS.head === :call
        f = first(RHS.args)
        A = @view(RHS.args[2:end])
        if f ∈ ProbabilityDistributions.DISTRIBUTION_DIFF_RULES
            track_tup = Expr(:tuple,)
            for a ∈ A
                if a ∈ tracked_vars
                    push!(track_tup.args, true)
                    push!(tracked_vars, LHS)
                else
                    push!(track_tup.args, false)
                end
            end
            if verbose
                printstring = "distribution $f (ret: $LHS): "
                push!(first_pass, :(println($printstring)))
            end
            push!(first_pass, :($LHS = ProbabilityModels.ProbabilityDistributions.$f(Val{$track_tup}(), $(A...))))
            verbose && push!(first_pass, :(println($LHS)))
        else
            for a ∈ A
                if a ∈ tracked_vars
                    push!(tracked_vars, LHS)
                    break
                end
            end
            if f === :add
                push!(first_pass, :( $LHS = ProbabilityModels.vadd($(A...))))
            else
                push!(first_pass, ex)
            end
        end
    else
        A = RHS.args
        for a ∈ A
            if a ∈ tracked_vars
                push!(tracked_vars, out)
                break
            end
        end
        push!(first_pass, ex)
    end
end

"""
This pass is for when we aren't taking partial derivatives.
"""
function constant_drop_pass!(first_pass::Vector{Any}, expr, tracked_vars, verbose = false)
    for x ∈ expr.args
        if !(x isa Expr)
            push!(first_pass, x)
            continue
        end
        ex::Expr = x
        if ex.head === :(=)
            drop_const_assignment!(first_pass, tracked_vars, ex, ex.args[1], ex.args[2], verbose)
        else
            push!(first_pass, ex)
        end
    end
    nothing
end

types_to_vals(::Type{T}) where {T} = Val{T}()
types_to_vals(v) = v
types_to_vals(A::AbstractArray{Union{Missing,T}}) where {T} = DistributionParameters.maybe_missing(A)
extract_typeval(::Type{Val{T}}) where {T} = T
extract_typeval(::Type{Val{Type{T}}}) where {T} = T

function load_and_constrain_quote(ℓ, model_name, variables, variable_type_names, θ, Tθ, T)
    Nparam = gensym(:number_parameters)
    logjac = gensym(:logjac)
    θq = quote
        @generated function ProbabilityModels.DistributionParameters.constrain($ℓ::$(model_name){$Nparam, $(variable_type_names...)}, $θ::AbstractVector{$T}, ::Val{$logjac} = Val{false}()) where {$Nparam, $T, $logjac, $(variable_type_names...)}
            return_partials = false
            first_pass = quote end
            push!(first_pass.args, Expr(:(=), Symbol("##θparameter##"), Expr(:call, :(ProbabilityModels.vectorizable), $(QuoteNode(θ)))))
            second_pass = quote end
            transformed_params = Expr(:tuple,)
            return_expr = $logjac ? Expr(:tuple, transformed_params, :target) : transformed_params
        end
    end
    v = Symbol("##storage_vector##")
    θq_vec = quote
        @generated function ProbabilityModels.DistributionParameters.constrain!(
            $v::AbstractVector{$T}, $ℓ::$(model_name){$Nparam, $(variable_type_names...)}, $θ::AbstractVector{$T}
        ) where {$Nparam, $T, $(variable_type_names...)}
            return_partials = false
            first_pass = quote end
            push!(first_pass.args, Expr(:(=), Symbol("##stack_pointer##"), Expr(:call, :(ProbabilityModels.StackPointer), Expr(:call, :pointer, Symbol("##storage_vector##")))))
            push!(first_pass.args, Expr(:(=), Symbol("##θparameter##"), Expr(:call, :(ProbabilityModels.vectorizable), $(QuoteNode(θ)))))
            second_pass = quote end
        end
    end
    param_names = gensym(:param_names)
    param_names_quote = quote
        function ProbabilityModels.DistributionParameters.parameter_names($ℓ::$(model_name){$Nparam, $(variable_type_names...)}) where {$Nparam, $(variable_type_names...)}
            $param_names = String[]
            $([quote
               if $v <: Val
               # @show $v
               append!($param_names, ProbabilityModels.DistributionParameters.parameter_names(ProbabilityModels.extract_typeval($v), $(QuoteNode(v)))::Vector{String})
               elseif $v <: ProbabilityModels.DistributionParameters.MissingDataArray
               #append!($param_names, ProbabilityModels.DistributionParameters.parameter_names($v, $(QuoteNode(v)))::Vector{String})
               append!($param_names, ProbabilityModels.DistributionParameters.parameter_names($ℓ.$(variables[i]), $(QuoteNode(v)))::Vector{String})
               end
               end for (i,v) ∈ enumerate(variable_type_names)]...)
            $param_names
        end
    end
    Nconstrainedparam = Symbol("##number_of_parameters##")
    constrained_length_quote = quote
        @generated function ProbabilityModels.DistributionParameters.constrained_length($ℓ::$(model_name){$Nparam, $(variable_type_names...)}) where {$Nparam, $(variable_type_names...)}
            $Nconstrainedparam = 0
            $([quote
               if $v <: Val
                   $Nconstrainedparam += ProbabilityModels.PaddedMatrices.type_length(ProbabilityModels.extract_typeval($v))
               elseif $v <: ProbabilityModels.DistributionParameters.MissingDataArray
                   $Nconstrainedparam += ProbabilityModels.PaddedMatrices.type_length($v)
               end
               end for v ∈ variable_type_names]...)
            $Nconstrainedparam
        end
    end
    θq_body = θq.args[end].args[end].args[end].args;
    θq_vec_body = θq_vec.args[end].args[end].args[end].args;
    for i ∈ eachindex(variables)
        load_data = Expr(:quote, :($(variables[i]) = $ℓ.$(variables[i])))
        load_incomplete_data = Expr(:quote, Expr(:(=), Symbol("##incomplete##", variables[i]), :($ℓ.$(variables[i]))))
        push!(θq_body, quote
          if $(variable_type_names[i]) <: Val
              ProbabilityModels.DistributionParameters.load_parameter!(
                  first_pass.args, second_pass.args, $(QuoteNode(variables[i])),
                  ProbabilityModels.extract_typeval($(variable_type_names[i])),
                  false, ProbabilityModels, nothing, $logjac, true
              )
              push!(transformed_params.args, Expr(:(=), $(QuoteNode(variables[i])), $(QuoteNode(variables[i]))))
          elseif $(variable_type_names[i]) <: ProbabilityModels.DistributionParameters.MissingDataArray
              push!(first_pass.args, $load_incomplete_data)
              ProbabilityModels.DistributionParameters.load_missing_as_vector!(
                  first_pass.args, second_pass.args, $(QuoteNode(variables[i])), ($(variable_type_names[i])),
                  false, ProbabilityModels, nothing, $logjac, true
              )
              push!(transformed_params.args, Expr(:(=), $(QuoteNode(variables[i])), $(QuoteNode(variables[i]))))
          end
        end)
        push!(θq_vec_body, quote
            if $(variable_type_names[i]) <: Val
                ProbabilityModels.DistributionParameters.load_parameter!(first_pass.args, second_pass.args, $(QuoteNode(variables[i])), ProbabilityModels.extract_typeval($(variable_type_names[i])), false, ProbabilityModels, Symbol("##stack_pointer##"), false, true)
            #     if ProbabilityModels.extract_typeval($(variable_type_names[i])) <: ProbabilityModels.DistributionParameters.RealFloat
            #         push!(first_pass.args, Expr(:call, :(ProbabilityModels.store!),
            #                               Expr(:call, :pointer, Symbol("##stack_pointer##"), $(QuoteNode(T))), $(QuoteNode(variables[i]))))
            #         push!(first_pass.args, Expr(:(+=), Symbol("##stack_pointer##"), Expr(:call, :sizeof, $(QuoteNode(T)) )))
            #   end
              elseif $(variable_type_names[i]) <: ProbabilityModels.DistributionParameters.MissingDataArray
                  push!(first_pass.args, $load_incomplete_data)
                  ProbabilityModels.DistributionParameters.load_missing_as_vector!(
                      first_pass.args, second_pass.args, $(QuoteNode(variables[i])), ($(variable_type_names[i])),
                      false, ProbabilityModels, Symbol("##stack_pointer##"), false, true
                  )
            end
       end)
    end
    push!(θq_body, :(push!(first_pass.args, return_expr)))
    push!(θq_vec_body, Expr(:call, :push!, :(first_pass.args), QuoteNode(Symbol("##storage_vector##"))))
    push!(θq_body, :(first_pass))
    # push!(θq_body, :(QuoteNode(first_pass)))
    push!(θq_vec_body, :(first_pass))
    θq, θq_vec, param_names_quote, constrained_length_quote
end

function generate_generated_funcs_expressions(model_name, expr)
    # Determine the set of variables that are either parameters or data, after interpolating any globals inserted via `$`
    # expr = interpolate_globals(expr)]
    variable_set = determine_variables(expr)
    variables = sort!([v for v ∈ variable_set]) # ensure order is constant
    variable_type_names = [Symbol("##Type##", v) for v ∈ variables]

    Nparam = Symbol("##number_of_parameters##")
    stride = gensym(:LDA)
    L1 = gensym(:L)
    L2 = gensym(:L)
    T = Symbol("##element_type##")

    var_vartype_pairs = [:( $(variables[i])::$(variable_type_names[i]) ) for i ∈ eachindex(variables)]
    struct_quote = quote
        struct $model_name{$Nparam, $(variable_type_names...)} <: ProbabilityModels.AbstractProbabilityModel{$Nparam}
            $(var_vartype_pairs...)
        end
    end
    precomp = gensym(:precompile)
    # precomp_quote = if VERSION >= v"1.3.0-alpha.0" && ProbabilityModels.NTHREADS[] > 1
    #     tlag = gensym(:tlag); tl = gensym(:tl)
    #     quote
    #         $tlag = Threads.@spawn precompile(ProbabilityModels.logdensity_and_gradient!, (ProbabilityModels.PtrVector{$Nparam, Float64, $Nparam, false}, $model_name{$Nparam,$(variable_type_names...)}, ProbabilityModels.PtrVector{$Nparam, Float64, $Nparam, false}, ProbabilityModels.StackPointer))
    #         $tl = Threads.@spawn precompile(ProbabilityModels.logdensity, ($model_name{$Nparam,$(variable_type_names...)}, ProbabilityModels.PtrVector{$Nparam, Float64, $Nparam, false}, ProbabilityModels.StackPointer))
    #         Threads.@spawn precompile(ProbabilityModels.InplaceDHMC.mcmc_with_warmup!, (
    #             ProbabilityModels.VectorizedRNG.PtrPCG{4}, ProbabilityModels.StackPointer, ProbabilityModels.PaddedMatrices.DynamicPtrVector{Float64}, ProbabilityModels.PaddedMatrices.DynamicPtrVector{ProbabilityModels.InplaceDHMC.TreeStatisticsNUTS}, $model_name{$Nparam, $(variable_type_names...)}, Int))
    #         wait($tlag); wait($tl)
    #     end
    # else
    #     quote
    #         precompile(ProbabilityModels.logdensity_and_gradient!, (ProbabilityModels.PtrVector{$Nparam, Float64, $Nparam, false}, $model_name{$Nparam,$(variable_type_names...)}, ProbabilityModels.PtrVector{$Nparam, Float64, $Nparam, false}, ProbabilityModels.StackPointer))
    #         precompile(ProbabilityModels.logdensity, ($model_name{$Nparam,$(variable_type_names...)}, ProbabilityModels.PtrVector{$Nparam, Float64, $Nparam, false}, ProbabilityModels.StackPointer))
    #     end
    # end
    precomp_quote = quote
        precompile(ProbabilityModels.logdensity_and_gradient!, (ProbabilityModels.PtrVector{$Nparam, Float64, $Nparam, false}, $model_name{$Nparam,$(variable_type_names...)}, ProbabilityModels.PtrVector{$Nparam, Float64, $Nparam, false}, ProbabilityModels.StackPointer))
        precompile(ProbabilityModels.logdensity, ($model_name{$Nparam,$(variable_type_names...)}, ProbabilityModels.PtrVector{$Nparam, Float64, $Nparam, false}, ProbabilityModels.StackPointer))
    end
    struct_kwarg_quote = quote
        function $model_name{$Nparam}( $(var_vartype_pairs...)) where {$Nparam, $(variable_type_names...)}
            $model_name{$Nparam,$(variable_type_names...)}($(variables...))
        end
        function $model_name{$Nparam}($precomp::Bool, $(var_vartype_pairs...)) where {$Nparam, $(variable_type_names...)}
            if $precomp
                $precomp_quote
            end
            $model_name{$Nparam,$(variable_type_names...)}($(variables...))
        end
        function $model_name($precomp::Bool = true; $(var_vartype_pairs...)) where {$(variable_type_names...)}
            $Nparam = 0
            # @show $(variables...)
            # @show $(variable_type_names...)
            $([quote
                if isa(ProbabilityModels.types_to_vals($v), Val) || isa($v, ProbabilityModels.DistributionParameters.MissingDataArray)
                   $Nparam += ProbabilityModels.PaddedMatrices.param_type_length($v)
               elseif isa($v, AbstractArray{Union{Missing,T}} where T)
                    $v = ProbabilityModels.DistributionParameters.maybe_missing($v)
                   $Nparam += ProbabilityModels.PaddedMatrices.param_type_length($v)
                end
            end for v ∈ variables]...)
            $model_name{$Nparam}($precomp, $([:(ProbabilityModels.types_to_vals($v)) for v ∈ variables]...))
        end
    end
    # Translate the sampling statements, and then flatten the expression to remove nesting.
    expr = translate_sampling_statements(expr) |>
                flatten_expression# |>
    # The plan in this definition is to make each keyword arg default to the appropriate field of ℓ
    # This allows one to optionally override in the case of a single evaluation.
    # Additionally, it will throw an error if one of the required fields was not defined.
    V = gensym(:V)
    ℓ = Symbol("####ℓ#data####")# gensym(:ℓ)
    θ = Symbol("##θ_parameter_vector##")#gensym(:θ)
    Tθ = gensym(:Tθ)
    constrain_quote, cvq, pn, clq  = load_and_constrain_quote(ℓ, model_name, variables, variable_type_names, θ, Tθ, T)
    base_stack_pointer = STACK_POINTER_REF[]# + 9VectorizationBase.REGISTER_SIZE
    stack_pointer_expr = NTHREADS[] == 1 ? base_stack_pointer : :($base_stack_pointer + (Threads.threadid()-1)*$(LOCAL_STACK_SIZE[]))
    # we have to split these, because of dispatch ambiguity errors
    θq_value = quote
        @generated function ProbabilityModels.logdensity(
                    $ℓ::$(model_name){$Nparam, $(variable_type_names...)},
                    $θ::ProbabilityModels.PtrVector{$Nparam, $T, $Nparam, false},
                    $(Symbol("##stack_pointer##"))::ProbabilityModels.StackPointer = $stack_pointer_expr
        ) where {$Nparam, $T, $(variable_type_names...)}

            return_partials = false
            TLθ = $Nparam
            model_parameters = Symbol[]
            first_pass = Any[]
            second_pass = Any[]
        end
    end
    θq_valuegradient = quote
        @generated function ProbabilityModels.logdensity_and_gradient!(
                        $(Symbol("##∂θ_parameter_vector##"))::ProbabilityModels.PtrVector{$Nparam, $T, $Nparam, false},
                        $ℓ::$(model_name){$Nparam, $(variable_type_names...)},
                        $θ::ProbabilityModels.PtrVector{$Nparam, $T, $Nparam, false},
                        $(Symbol("##initial_stack_pointer##"))::ProbabilityModels.StackPointer = $stack_pointer_expr
        ) where {$Nparam, $T, $(variable_type_names...)}

            return_partials = true
            TLθ = $Nparam
            model_parameters = Symbol[]
            first_pass = Any[]
            second_pass = Any[]
        end
    end
    for (θq, return_partials) ∈ ((θq_value, false), (θq_valuegradient, true))
        θq_body = θq.args[end].args[end].args[end].args;
        push!(θq_body, :(expr = $(Expr(:quote, deepcopy(expr)))))
        # Now, we work on assembling the function.
        for i ∈ eachindex(variables)
            load_data = Expr(:quote, :($(variables[i]) = $ℓ.$(variables[i])))
              load_data_dynamic_ptr_array = Expr(:quote,
                                         :($(variables[i]) = ProbabilityModels.PaddedMatrices.DynamicPtrArray(
                                             pointer( $ℓ.$(variables[i])),
                                             size($ℓ.$(variables[i])),
                                             size($ℓ.$(variables[i]),1)) )
                                         )
            load_data_ptr_array = Expr(:quote, :($(variables[i]) = ProbabilityModels.PaddedMatrices.PtrArray( $ℓ.$(variables[i]) ) ) )
            missingvar = Symbol("##missing##", variables[i])
            load_incomplete_data = Expr(:quote, Expr(:(=), Symbol("##incomplete##", variables[i]), :($ℓ.$(variables[i]))))
            push!(θq_body, quote
                if $(variable_type_names[i]) <: Val
                    push!(model_parameters, $(QuoteNode(variables[i])))
                  ProbabilityModels.DistributionParameters.load_parameter!(
                      first_pass, second_pass,
                      $(QuoteNode(variables[i])), ProbabilityModels.extract_typeval($(variable_type_names[i])),
                      $return_partials, ProbabilityModels, Symbol("##stack_pointer##"), true, false
                  )
                elseif $(variable_type_names[i]) <: ProbabilityModels.DistributionParameters.MissingDataArray
                    push!(model_parameters, $(QuoteNode(variables[i])))
                    push!(first_pass, $load_incomplete_data)
                  ProbabilityModels.DistributionParameters.load_parameter!(
                      first_pass, second_pass, $(QuoteNode(variables[i])), ($(variable_type_names[i])),
                      $return_partials, ProbabilityModels, Symbol("##stack_pointer##"), true, false
                  )
                elseif $(variable_type_names[i]) <: Union{Array,SubArray{<:Any,<:Any,<:Array}}
                  push!(first_pass, $load_data_dynamic_ptr_array)
                elseif $(variable_type_names[i]) <: ProbabilityModels.PaddedMatrices.AbstractMutableFixedSizeArray
                  push!(first_pass, $load_data_ptr_array)
                else
                    push!(first_pass, $load_data)
                end
            end)
        end
        if return_partials
            processing = quote
                $(verbose_models() > 0 ? :(println("Before differentiating, the model is: \n", ProbabilityModels.PaddedMatrices.simplify_expr(expr), "\n\n")) : nothing)
                ProbabilityModels.ReverseDiffExpressions.reverse_diff_pass!(first_pass, second_pass, expr, tracked_vars, :ProbabilityModels, $(verbose_models() > 1))
                expr_out = quote
                    target = ProbabilityModels.initialize_target($(Symbol("##element_type##")))
                    # $(Symbol("##stack_pointer##")) = $(Symbol("##initial_stack_pointer##"))
                    $(Symbol("##stack_pointer##")) = ProbabilityModels.StackPointer(pointer($(Symbol("##initial_stack_pointer##")), $(Symbol("##element_type##"))))
                    $(Symbol("##θparameter##")) = ProbabilityModels.vectorizable($(Symbol("##θ_parameter_vector##")))
                    # $(Symbol("##θparameter##")) = ProbabilityModels.vectorizable(ProbabilityModels.SIMDPirates.noalias!(pointer($(Symbol("##θ_parameter_vector##")))))
                    # $(Symbol("##∂θparameter##")) = ProbabilityModels.vectorizable($(Symbol("##∂θ_parameter_vector##")))
                    $(Symbol("##∂θparameter##")) = ProbabilityModels.vectorizable(pointer($(Symbol("##∂θ_parameter_vector##"))))
                end
                append!(expr_out.args, first_pass)
                append!(expr_out.args, second_pass)
                push!(expr_out.args, Expr(:(=), Symbol("##scalar_target##"), :(ProbabilityModels.vsum($(name_dict[:target])))))
                push!(expr_out.args, :((isfinite($(Symbol("##scalar_target##"))) && all(isfinite, $(Symbol("##∂θ_parameter_vector##")))) || ($(Symbol("##scalar_target##")) = typemin($(Symbol("##element_type##"))))))
                push!(expr_out.args, Symbol("##scalar_target##"))
            end
        else
            processing = quote
                ProbabilityModels.constant_drop_pass!(first_pass, expr, tracked_vars, $(verbose_models() > 1))
                pushfirst!(first_pass, Expr(:(=), Symbol("##initial_stack_pointer##"), Expr(:call, :pointer, Symbol("##stack_pointer##"))))
                expr_out = quote
                    target = ProbabilityModels.initialize_target($(Symbol("##element_type##")))
                    $(Symbol("##θparameter##")) = ProbabilityModels.vectorizable($(Symbol("##θ_parameter_vector##")))
                end
                append!(expr_out.args, first_pass)
                push!(expr_out.args, Expr(:(=), Symbol("##scalar_target##"), :(ProbabilityModels.vsum($(name_dict[:target])))))
                push!(expr_out.args, Expr(:call, :(ProbabilityModels.lifetime_end!), Symbol("##initial_stack_pointer##"), Val{8192}()))
                push!(expr_out.args, :(isfinite($(Symbol("##scalar_target##"))) ? $(Symbol("##scalar_target##")) : typemin($(Symbol("##element_type##")))))
            end
        end
        push!(θq_body, quote
            tracked_vars = Set(model_parameters)
              name_dict = Dict(:target => :target)
              $processing
              final_quote = ProbabilityModels.StackPointers.stack_pointer_pass(
                  expr_out, $(QuoteNode(Symbol("##stack_pointer##"))),
                  nothing, $(verbose_models() > 1), :ProbabilityModels
              ) |> ProbabilityModels.PaddedMatrices.simplify_expr
              $(verbose_models() > 0) && println(final_quote)
              quote
                  @inbounds begin
                      $final_quote
                  end
              end
        end)
    end
    # Now, we would like to apply
    PaddedMatrices.simplify_expr.((struct_quote, struct_kwarg_quote, θq_value, θq_valuegradient, constrain_quote, cvq, pn, clq, variables))
    #struct_quote, struct_kwarg_quote, θq_value, θq_valuegradient, constrain_quote, cvq, pn, clq, variables))
end

macro model(model_name, expr)
    # struct_quote, struct_kwarg_quote, θq_value, θq_valuegradient, constrain_q, dim_q, variables = generate_generated_funcs_expressions(model_name, expr)
    struct_quote, struct_kwarg_quote, θq_value, θq_valuegradient, constrain_q, cvq, pn, clq, variables = generate_generated_funcs_expressions(model_name, expr)
    printstring = """
        Defined model: $model_name.
        Unknowns: $(variables[1])$([", " * string(variables[i]) for i ∈ 2:length(variables)]...).
    """
    esc(quote
        # $struct_quote; $dim_q; $struct_kwarg_quote; $θq_value; $θq_valuegradient; $constrain_q;
        $struct_quote; $struct_kwarg_quote; $θq_value; $θq_valuegradient; $constrain_q; $cvq; $pn; $clq;
#        ProbabilityModels.Distributed.myid() == 1 && println($printstring)
        println($printstring)
    end)
end
# macro show_model_expr(model_name, expr)
#     struct_quote, struct_kwarg_quote, θq_value, θq_valuegradient, constrain_q, cvq, pn, clq, variables = generate_generated_funcs_expressions(model_name, expr)
#     @show struct_quote
#     @show struct_kwarg_quote
#     @show θq_value
#     @show θq_valuegradient
#     @show constrain_q
#     @show cvq
#     @show, pn
#     @show clq
#     @show variables
#     nothing
# end

# @specialize