@testset "KeyedDataset" begin
    @testset "Construction" begin
        @testset "Invalid" begin
            # Test that you can construct an invalid dataset if you really want to
            @test_throws KeyAlignmentError KeyedDataset(
                OrderedSet(Pattern[(:__, :time)]),
                LittleDict(
                    (:val1,) => KeyedArray(rand(4); time=1:4),
                    (:val2,) => KeyedArray(rand(4); time=2:5),
                ),
            )

            ds = KeyedDataset(
                OrderedSet(Pattern[(:__, :time)]),
                LittleDict(
                    (:val1,) => KeyedArray(rand(4); time=1:4),
                    (:val2,) => KeyedArray(rand(4); time=2:5),
                ),
                false
            )
            @test_throws KeyAlignmentError validate(ds)
        end

        @testset "Empty" begin
            expected = KeyedDataset(OrderedSet{Pattern}(), LittleDict{Tuple, KeyedArray}())
            @test KeyedDataset() == expected

            patterns = Pattern[(:train, :_, :target)]
            expected = KeyedDataset(OrderedSet(patterns), LittleDict{Tuple, KeyedArray}())
            @test KeyedDataset(; constraints=patterns) == expected
        end

        @testset "KeyedArrays" begin
            ds = KeyedDataset(
                :val1 => KeyedArray(
                    rand(4, 3, 2);
                    time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                    loc=1:3,
                    obj=[:a, :b],
                ),
                :val2 => KeyedArray(
                    rand(4, 3, 2) .+ 1.0;
                    time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                    loc=1:3,
                    obj=[:a, :b],
                ),
            )

            # Test that we successfully extracted the dims
            @test issetequal([:time, :loc, :obj], dimnames(ds))

            # Test generated constraints
            @test constraintmap(ds) == LittleDict(
                Pattern((:__, :time)) => Set([(:val1, :time), (:val2, :time)]),
                Pattern((:__, :loc)) => Set([(:val1, :loc), (:val2, :loc)]),
                Pattern((:__, :obj)) => Set([(:val1, :obj), (:val2, :obj)]),
            )

            # Test that we have a data dict entry for each value column
            @test issetequal([(:val1,), (:val2,)], keys(ds.data))

            @testset "Variable Keys" begin
                # Test construction with different key types and lengths
                ds = KeyedDataset(
                    "val1" => KeyedArray(
                        rand(4, 3, 2);
                        time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                        loc=1:3,
                        obj=[:a, :b],
                    ),
                    (:group1, 2) => KeyedArray(
                        rand(4, 3, 2) .+ 1.0;
                        time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                        loc=1:3,
                        obj=[:a, :b],
                    ),
                )

                # Test that we successfully extracted the dims
                @test issetequal([:time, :loc, :obj], dimnames(ds))
                # Test that we have a data dict entry for each value column
                @test issetequal([("val1",), (:group1, 2)], keys(ds.data))
            end
        end

        @testset "Flatten" begin
            # Technically this test is just showing how you can use the `flatten` function
            # to construct a KeyedDatasets from an existing nested structure of KeyedArrays.
            @testset "NamedTuples" begin
                # In this case, the resulting keys from flatten need to be symbols with
                # an `:_` delimiter
                data = (
                    group1 = (
                        a = KeyedArray(
                            rand(4, 3, 2);
                            time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                            loc=1:3,
                            obj=[:a, :b],
                        ),
                        b = KeyedArray(
                            rand(4, 2);
                            time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                            label=["x", "y"],
                        )
                    ),
                    group2 = (
                        a = KeyedArray(
                            rand(4, 3, 2) .+ 1.0;
                            time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                            loc=1:3,
                            obj=[:a, :b],
                        ),
                        b = KeyedArray(
                            rand(4, 2) .+ 1.0;
                            time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                            label=["x", "y"],
                        )
                    )
                )
                ds = KeyedDataset(flatten(data)...)

                # Test that we successfully extracted the dims
                @test issetequal([:time, :loc, :obj, :label], dimnames(ds))

                # Test generated constraints
                @test constraintmap(ds) == LittleDict(
                    Pattern((:__, :time)) => Set([
                        (:group1, :a, :time),
                        (:group1, :b, :time),
                        (:group2, :a, :time),
                        (:group2, :b, :time),
                    ]),
                    Pattern((:__, :loc)) => Set([(:group1, :a, :loc), (:group2, :a, :loc)]),
                    Pattern((:__, :obj)) => Set([(:group1, :a, :obj), (:group2, :a, :obj)]),
                    Pattern((:__, :label)) => Set([
                        (:group1, :b, :label),
                        (:group2, :b, :label),
                    ]),
                )

                # Test that we successfully extracted the flattened kwargs.
                @test issetequal(
                    [(:group1, :a), (:group1, :b), (:group2, :a), (:group2, :b)], keys(ds.data)
                )

                # Test an example where we pass a delimiter to flatten to a named tuple,
                # that we can use as kwargs...
                ds = KeyedDataset(; flatten(data, :_)...)

                @test constraintmap(ds) == LittleDict(
                    Pattern((:__, :time)) => Set([
                        (:group1_a, :time),
                        (:group1_b, :time),
                        (:group2_a, :time),
                        (:group2_b, :time),
                    ]),
                    Pattern((:__, :loc)) => Set([(:group1_a, :loc), (:group2_a, :loc)]),
                    Pattern((:__, :obj)) => Set([(:group1_a, :obj), (:group2_a, :obj)]),
                    Pattern((:__, :label)) => Set([(:group1_b, :label), (:group2_b, :label)]),
                )

                @test issetequal(
                    [(:group1_a,), (:group1_b,), (:group2_a,), (:group2_b,)], keys(ds.data)
                )
            end
            @testset "Pairs" begin
                # In this case, we have the option to having tuple keys which allows for
                # multi-dimensional style indexing.
                data = [
                    :group1 => [
                        :a => KeyedArray(
                            rand(4, 3, 2);
                            time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                            loc=1:3,
                            obj=[:a, :b],
                        ),
                        :b => KeyedArray(
                            rand(4, 2);
                            time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                            label=["x", "y"],
                        )
                    ],
                    :group2 => [
                        :a => KeyedArray(
                            rand(4, 3, 2) .+ 1.0;
                            time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                            loc=1:3,
                            obj=[:a, :b],
                        ),
                        :b => KeyedArray(
                            rand(4, 2) .+ 1.0;
                            time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                            label=["x", "y"],
                        )
                    ]
                ]
                ds = KeyedDataset(flatten(data)...)

                # Test that we successfully extracted the dims
                @test issetequal([:time, :loc, :obj, :label], dimnames(ds))

                # Test that we successfully extracted the flattened pairs as tuples.
                @test issetequal(
                    [(:group1, :a), (:group1, :b), (:group2, :a), (:group2, :b)], keys(ds.data)
                )

                # We can still choose to flatten to a single symbol if we want.
                ds = KeyedDataset(flatten(data, :_)...)

                @test issetequal(
                    [(:group1_a,), (:group1_b,), (:group2_a,), (:group2_b,)], keys(ds.data)
                )

                @test constraintmap(ds) == LittleDict(
                    Pattern((:__, :time)) => Set([
                        (:group1_a, :time),
                        (:group1_b, :time),
                        (:group2_a, :time),
                        (:group2_b, :time),
                    ]),
                    Pattern((:__, :loc)) => Set([(:group1_a, :loc), (:group2_a, :loc)]),
                    Pattern((:__, :obj)) => Set([(:group1_a, :obj), (:group2_a, :obj)]),
                    Pattern((:__, :label)) => Set([(:group1_b, :label), (:group2_b, :label)]),
                )
            end

            @testset "Mixed key types" begin
                # In this case, we have the option to having tuple keys which allows for
                # multi-dimensional style indexing.
                data = [
                    :group1 => [
                        "a" => KeyedArray(
                            rand(4, 3, 2);
                            time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                            loc=1:3,
                            obj=[:a, :b],
                        ),
                        "b" => KeyedArray(
                            rand(4, 2);
                            time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                            label=["x", "y"],
                        )
                    ],
                    :group2 => [
                        "a" => KeyedArray(
                            rand(4, 3, 2) .+ 1.0;
                            time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                            loc=1:3,
                            obj=[:a, :b],
                        ),
                        "b" => KeyedArray(
                            rand(4, 2) .+ 1.0;
                            time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                            label=["x", "y"],
                        )
                    ]
                ]
                ds = KeyedDataset(flatten(data)...)

                # Test that we successfully extracted the dims
                @test issetequal([:time, :loc, :obj, :label], dimnames(ds))

                # Test that we successfully extracted the flattened pairs as tuples.
                @test issetequal(
                    [(:group1, "a"), (:group1, "b"), (:group2, "a"), (:group2, "b")], keys(ds.data)
                )
            end
        end
    end
    @testset "show" begin
        ds = KeyedDataset(
            :val1 => KeyedArray(
                rand(4, 3, 2);
                time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                loc=1:3,
                obj=[:a, :b],
            ),
            :val2 => KeyedArray(
                rand(4, 3, 2) .+ 1.0;
                time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                loc=1:3,
                obj=[:a, :b],
            ),
        )

        # This is likely to change in the future, so we keep this test simple
        s = sprint(show, ds)
        @test startswith(s, "KeyedDataset")
        @test occursin("2 components", s)
        @test occursin("3 constraints", s)

        # Having unused constraints is fine since we might add them later.
        s = sprint(show, KeyedDataset(pairs(ds.data)...; constraints=Pattern[(:__, :foo)]))
        @test occursin("2 components", s)
        @test occursin("1 constraints", s)

        # While possible to construct it's rather pointless to have a dimension path
        # that can map to multiple constraints.
        bad_ds = KeyedDataset(
            pairs(ds.data)...;
            constraints=Pattern[
                (:__, :time),
                (:val1, :time),
            ],
        )
        @test_throws ArgumentError sprint(show, bad_ds)
    end

    @testset "dimpaths" begin
        ds = KeyedDataset(
            :val1 => KeyedArray(
                rand(4, 3, 2);
                time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                loc=1:3,
                obj=[:a, :b],
            ),
            :val2 => KeyedArray(
                rand(4, 3, 2) .+ 1.0;
                time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                loc=1:3,
                obj=[:a, :b],
            ),
        )

        @test issetequal(
            dimpaths(ds),
            [
                (:val1, :time),
                (:val1, :loc),
                (:val1, :obj),
                (:val2, :time),
                (:val2, :loc),
                (:val2, :obj),
            ],
        )

        @test issetequal(dimpaths(ds, Pattern(:__, :time)), [(:val1, :time), (:val2, :time)])
    end

    @testset "dimnames" begin
        ds = KeyedDataset(
            :val1 => KeyedArray(
                rand(4, 3, 2);
                time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                loc=1:3,
                obj=[:a, :b],
            ),
            :val2 => KeyedArray(
                rand(4, 3, 2) .+ 1.0;
                time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                id=1:3,
                obj=[:a, :b],
            ),
        )
        @test issetequal(dimnames(ds), [:time, :loc, :obj, :id])
    end

    @testset "axiskeys" begin
        ds = KeyedDataset(
            :val1 => KeyedArray(
                rand(4, 3, 2);
                time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                loc=1:3,
                obj=[:a, :b],
            ),
            :val2 => KeyedArray(
                rand(4, 3, 2) .+ 1.0;
                time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                id=1:3,
                obj=[:a, :b],
            ),
        )

        @test issetequal(
            axiskeys(ds),
            [
                DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                1:3,
                [:a, :b],
            ],
        )

        @test axiskeys(ds, (:val1, :time)) == DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14)
        @test unique(axiskeys(ds, :time)) == [DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14)]
    end

    @testset "validate" begin
        ds = KeyedDataset(
            :val1 => KeyedArray(
                rand(4, 3, 2);
                time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                loc=1:3,
                obj=[:a, :b],
            ),
            :val2 => KeyedArray(
                rand(4, 3, 2) .+ 1.0;
                time=DateTime(2021, 1, 1, 11):Hour(1):DateTime(2021, 1, 1, 14),
                id=1:3,
                obj=[:a, :b],
            ),
        )

        @test validate(ds)
        @test validate(ds, Pattern(:__, :time))

        # Intentionally break the internal keys
        ds.data[(:val1,)] = KeyedArray(
            rand(4, 3, 2);
            time=DateTime(2021, 1, 2, 11):Hour(1):DateTime(2021, 1, 2, 14),
            loc=1:3,
            obj=[:a, :b],
        )

        @test_throws KeyAlignmentError validate(ds)
        @test_throws KeyAlignmentError validate(ds, Pattern(:__, :time))
        @test validate(ds, Pattern(:__, :obj))
    end
end


"""
    range32(start, stop, step)

StepRangeLen but for Float32.
"""
function range32(start::Real, stop::Real, step::Real)
  res = range(Float32(start), stop=Float32(stop), step = Float32(step))
  return StepRangeLen{Float32,Float32,Float32}(res)
end


export Nested

struct Nested{T}
    value::T
end

# function Nested(nt)
#     x = keysort(nt)
#     T = typeof(x)
#     Nested{T}(x)
# end

Nested(;kwargs...) = Nested((;kwargs...))

import Base

function Base.show(io::IO, x::Nested)
    io = IOContext(io, :compact => true)
    print(io, "Nested(", x.value, ")")
end

lenses(x::Nested) = lenses(x.value)

function schema(N::Type{Nested{X}}) where {X}
    return Nested(schema(X))
end

@generated function Base.empty(x::Nested)
    e = _empty(schema(x))
    return e
end

function _empty(x::Nested)
    ℓ = lenses(x)
    n = length(ℓ)
    set(x, batch(ℓ...), ntuple(i -> □, n))
end


module STL

struct Vertex
    x::Float32
    y::Float32
    z::Float32
end

const Normal = Vertex

struct Triangle
    normal::Normal
    v1::Vertex
    v2::Vertex
    v3::Vertex
end

function parse(path::AbstractString)
    open(path) do stl
        skip(stl, 80)  # skip header

        trianglecount = read(stl, UInt32)
        triangles = Array{Triangle, 1}(undef, trianglecount)

        for i in 1:trianglecount
            normal = Normal(read(stl, Float32), read(stl, Float32), read(stl, Float32))
            v1 = Vertex(read(stl, Float32), read(stl, Float32), read(stl, Float32))
            v2 = Vertex(read(stl, Float32), read(stl, Float32), read(stl, Float32))
            v3 = Vertex(read(stl, Float32), read(stl, Float32), read(stl, Float32))
            triangles[i] = Triangle(normal, v1, v2, v3)

            skip(stl, 2)  # skip attribute byte count
        end

        @assert eof(stl)
        return triangles
    end
end

export parse

end # module


# Function to load data from CSV file. Empty lines indicate end of a sequence.
function load(path::ASCIIString)
    stream = open(path, "r")
    X = Array{XT,1}[ XT[ ] ]
    Y = Array{YT,1}[ YT[ ] ]
    for line in map(strip, eachline(stream))
        if isempty(line)
            push!(X, XT[ ])
            push!(Y, YT[ ])
        else
            x1, x2, y = split(line, ',')
            push!(X[end], [ float(x1), float(x2) ])
            push!(Y[end], y)
        end
    end
    close(stream)
    return X, Y
end

function colorize(col::Symbol, str::ASCIIString)
    get(Base.text_colors, col, :normal) * str * get(Base.text_colors, :normal, :normal)
end


struct SubNetwork
  inputs::Vector{Union{SubNetwork,AbstractTensor}}
end

SubNetwork(inputs::Union{SubNetwork,AbstractTensor}...) = SubNetwork(collect(inputs))

function get_leaves(trees::Vector{SubNetwork})
  return [get_leaves(tree) for tree in trees]
end

function get_leaves(tree::SubNetwork)
  return mapreduce(get_leaves, vcat, tree.inputs)
end

get_leaves(tensor::AbstractTensor) = tensor

ITensors.inds(node::SubNetwork) = noncommoninds(get_leaves(node)...)

# Returns a vector of noncommon indices
ITensors.noncommoninds(node::Union{AbstractTensor,SubNetwork}) = collect(inds(node))

function ITensors.noncommoninds(nodes::Union{AbstractTensor,SubNetwork}...)
  return symdiff(map(inds, nodes)...)
end

function neighboring_tensors(subnetwork::SubNetwork, tensor_list::Vector{<:AbstractTensor})
  subnet_inds = ITensors.inds(subnetwork)
  is_neighbor(t) = length(intersect(subnet_inds, inds(t))) > 0
  return [t for t in tensor_list if is_neighbor(t)]
end

@non_differentiable SubNetwork(inputs::Union{SubNetwork,AbstractTensor}...)


function add_dual_vars_in_dual_cones(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike,
                                     primal_dual_map::PrimalDualMap{T}, dual_names::DualNames,
                                     con_types::Vector{Tuple{DataType, DataType}}) where T
    dual_obj_affine_terms = Dict{VI, T}()
    for (F, S) in con_types
        for ci in MOI.get(primal_model, MOI.ListOfConstraintIndices{F,S}()) # Constraints of type {F, S}
            # Add dual variable to dual cone
            # Fill a dual objective dictionary
            # Fill the primal_con_dual_var dictionary
            ci_dual = add_dual_variable(dual_model, primal_model, dual_names,
                                        primal_dual_map.primal_con_dual_var, dual_obj_affine_terms, ci)
            push_to_primal_con_dual_con!(primal_dual_map.primal_con_dual_con, ci, ci_dual)
            push_to_primal_con_constants!(primal_model, primal_dual_map.primal_con_constants, ci)
        end
    end
    return dual_obj_affine_terms
end

# Utils for primal_con_constants dict
function push_to_primal_con_constants!(primal_model::MOI.ModelLike, primal_con_constants::Dict{CI, Vector{T}},
                                      ci::CI{F, S}) where {T, F <: MOI.AbstractScalarFunction, S <: MOI.AbstractScalarSet}
    push!(primal_con_constants, ci => get_scalar_term(primal_model, ci))
    return
end

function push_to_primal_con_constants!(primal_model::MOI.ModelLike, primal_con_constants::Dict{CI, Vector{T}},
                                       ci::CI{F, S}) where {T, F <: MOI.AbstractVectorFunction, S <: MOI.AbstractVectorSet}
    return # No constants need to be passed to the DualOptimizer in this case, don't need to push zero to the dict
end

# Utils for primal_con_dual_con dict
function push_to_primal_con_dual_con!(primal_con_dual_con::Dict{CI, CI}, ci::CI, ci_dual::CI)
    push!(primal_con_dual_con, ci => ci_dual)
    return
end

function push_to_primal_con_dual_con!(primal_con_dual_con::Dict{CI, CI}, ci::CI, ci_dual::Nothing)
    return # Don't put in the dict a nothing value
end

# Utils for dual_obj_affine_terms dict
function push_to_dual_obj_aff_terms!(primal_model::MOI.ModelLike, dual_obj_affine_terms::Dict{VI, T}, vi::VI,
                                     ci::CI{F, S}, i::Int) where {T, F <: MOI.AbstractFunction, S <: MOI.AbstractSet}
    s = get_set(primal_model, ci)
    value = set_dot(i, s, T)*get_scalar_term(primal_model, i, ci)
    if !iszero(value) # If value is different than 0 add to the dictionary
        push!(dual_obj_affine_terms, vi => value)
    end
    return
end

function push_to_dual_obj_aff_terms!(primal_model::MOI.ModelLike, dual_obj_affine_terms::Dict{VI, T}, vi::VI,
                                     ci::CI{VVF, S}, i::Int) where {T, S <: MOI.AbstractSet}
    return # It is zero so don't push to the dual_obj_affine_terms
end

function _add_dual_variable(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, dual_names::DualNames,
                            primal_con_dual_var::Dict{CI, Vector{VI}}, dual_obj_affine_terms::Dict{VI, T},
                            ci::CI{F, S}) where {T, F <: MOI.AbstractFunction,
                                                    S <: MOI.AbstractSet}

    # Change to add_constrained_varibales in MOI 0.9.0
    # because of https://github.com/guilhermebodin/Dualization.jl/issues/9
    vis = MOI.add_variables(dual_model, get_ci_row_dimension(primal_model, ci)) # Add as many variables as the dimension of the constraint
    push!(primal_con_dual_var, ci => vis) # Add the map of the added dual variable to the relationated constraint
    # Get constraint name
    ci_name = MOI.get(primal_model, MOI.ConstraintName(), ci)
    # Add each vi to the dictionary
    for (i, vi) in enumerate(vis)
        push_to_dual_obj_aff_terms!(primal_model, dual_obj_affine_terms, vi, ci, i)
        set_dual_variable_name(dual_model, vi, i, ci_name,
                               dual_names.dual_variable_name_prefix)
    end
    return vis
end

function add_dual_variable(dual_model::MOI.ModelLike, primal_model::MOI.ModelLike, dual_names::DualNames,
                           primal_con_dual_var::Dict{CI, Vector{VI}}, dual_obj_affine_terms::Dict{VI, T},
                           ci::CI{F, S}) where {T, F <: MOI.AbstractFunction,
                                                   S <: MOI.AbstractSet}

    vis = _add_dual_variable(dual_model, primal_model, dual_names,
                             primal_con_dual_var, dual_obj_affine_terms, ci)
    return add_dual_cone_constraint(dual_model, primal_model, vis, ci)
end

function set_dual_variable_name(dual_model::MOI.ModelLike, vi::VI, i::Int, ci_name::String, prefix::String)
    MOI.set(dual_model, MOI.VariableName(), vi, prefix*ci_name*"_$i")
    return
end


function showpixel(a::Image, x::Int, y::Int)
    c = color(a, x, y)
    hex(c.r, 2)*hex(c.g, 2)*hex(c.b, 2)
end

w = 5
h = 7
a = Image(w, h)

purple = Color(0xff, 0, 0xff)
green = Color(0, 0xff, 0)

fill!(a, purple)
splat!(a, 2, 3, green)

for i in 1:h
    for j in 1:w
        print(showpixel(a, j, i), " ")
    end
    println()
end


nindexes(N::Int) = N == 1 ? "1 index" : "$N indexes"

type FilledExtrapolation{T,N,ITP<:AbstractInterpolation,IT,GT,FT} <: AbstractExtrapolation{T,N,ITP,IT,GT}
    itp::ITP
    fillvalue::FT
end

function FilledExtrapolation{T,N,IT,GT}(itp::AbstractInterpolation{T,N,IT,GT}, fillvalue)
    FilledExtrapolation{T,N,typeof(itp),IT,GT,typeof(fillvalue)}(itp, fillvalue)
end

"""
`extrapolate(itp, fillvalue)` creates an extrapolation object that returns the `fillvalue` any time the indexes in `itp[x1,x2,...]` are out-of-bounds.
"""
extrapolate{T,N,IT,GT}(itp::AbstractInterpolation{T,N,IT,GT}, fillvalue) = FilledExtrapolation(itp, convert(eltype(itp), fillvalue))

@generated function getindex{T,N,ITP,IT,GT,FT}(fitp::FilledExtrapolation{T,N,ITP,IT,GT,FT}, args::Number...)
    n = length(args)
    n == N || return error("Must index $(N)-dimensional interpolation objects with $(nindexes(N))")
    Tret = FT<:Number ? getindex_return_type(ITP, args) : FT
    meta = Expr(:meta, :inline)
    quote
        $meta
        # Check to see if we're in the extrapolation region, i.e.,
        # out-of-bounds in an index
        @nexprs $N d->((args[d] < lbound(fitp,d) || args[d] > ubound(fitp, d)) && return convert($Tret, fitp.fillvalue))
        # In the interpolation region
        return getindex(fitp.itp,args...)
    end
end

getindex{T}(fitp::FilledExtrapolation{T,1}, x::Number, y::Int) = y == 1 ? fitp[x] : throw(BoundsError())

lbound(etp::FilledExtrapolation, d) = lbound(etp.itp, d)
ubound(etp::FilledExtrapolation, d) = ubound(etp.itp, d)


# The contents of this file are slightly modified from InvertedIndices.jl: https://github.com/mbauman/InvertedIndices.jl/blob/7c97b19e6c75043f231f9d8e6661886b688259f1/src/InvertedIndices.jl
# Available under the following MIT "Expat" License:
#
# > Copyright (c) 2017: Matt Bauman.
# >
# > Permission is hereby granted, free of charge, to any person obtaining a copy
# > of this software and associated documentation files (the "Software"), to deal
# > in the Software without restriction, including without limitation the rights
# > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# > copies of the Software, and to permit persons to whom the Software is
# > furnished to do so, subject to the following conditions:
# >
# > The above copyright notice and this permission notice shall be included in all
# > copies or substantial portions of the Software.
# >
# > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# > SOFTWARE.
# >

# A very simple and primitive static array to avoid allocations while fulfilling the indexing API
struct TupleVector{T<:Tuple{Vararg{Int,N}} where N} <: AbstractVector{Int}
    data::T
end
Base.size(::TupleVector{<:NTuple{N}}) where {N} = (N,)
@inline function Base.getindex(t::TupleVector, i::Int)
    @boundscheck checkbounds(t, i)
    return @inbounds t.data[i]
end


function integer_regex(fm::CSVFormat)
    if fm.thousands_separator == nothing
        return r"^\s*-?\d+\s*$"
    else
        if fm.thousands_separator == '.'
            return r"^\s*-?((\d{1,3}\.)?(\d{3}\.)*\d{3}|\d{1,3})\s*$"
        elseif fm.thousands_separator == ','
            return r"^\s*-?((\d{1,3},)?(\d{3},)*\d{3}|\d{1,3})\s*$"
        else
            error("thousands_separator ($(fm.thousands_separator)) not supported.")
        end
    end
end

function float_regex(fm::CSVFormat)
    if fm.thousands_separator == nothing
        if fm.decimal_separator == '.'
            return r"^\s*-?(\d+(\.\d*)?|\d*\.\d+)\s*$"
        elseif fm.decimal_separator == ','
            return r"^\s*-?(\d+(,\d*)?|\d*,\d+)\s*$"
        else
            error("Decimal separator not supported: $(fm.decimal_separator)")
        end
    else
        if fm.thousands_separator == '.' && fm.decimal_separator == ','
            return r"^\s*-?((\d{1,3}\.)?(\d{3}\.)*\d{3}|\d{1,3})(,\d*)?\s*$"
        elseif fm.thousands_separator == ',' && fm.decimal_separator == '.'
            return r"^\s*-?((\d{1,3},)?(\d{3},)*\d{3}|\d{1,3})(\.\d*)?\s*$"
        else
            error("Combination of thousands_separator '$(fm.thousands_separator)' and decimal_separator '$(fm.decimal_separator)' not supported.")
        end
    end
end

mutable struct InferenceState
    datatype::DataType
    allow_missing::Bool
end

InferenceState() = InferenceState(Any, false)

function datatype(s::InferenceState)
    if s.allow_missing
        return Union{Missing, s.datatype}
    else
        return s.datatype
    end
end

Base.:(==)(s1::InferenceState, s2::InferenceState) = s1.datatype == s2.datatype && s1.allow_missing == s2.allow_missing

function infer_type(value::String, format::CSVFormat, state::InferenceState=InferenceState(), fr::Regex=float_regex(format), ir::Regex=integer_regex(format)) :: InferenceState
    inferred_type = Any
    inferred_missing = false

    if ismissingstr(value, format)
        inferred_missing = true
    else
        # If it's already a String, there's nothing to improve with inference
        if state.datatype == String
            inferred_type = String
        else

            if occursin(ir, value)
                inferred_type = Int
            elseif occursin(fr, value)
                inferred_type = Float64
            else
                try
                    Date(value, format.date_format)
                    inferred_type = Date
                catch e
                    inferred_type = String
                end
            end
        end
    end

    # Promote type
    if type_order(inferred_type) > type_order(state)
        state.datatype = inferred_type
    end

    # Promote Missing
    state.allow_missing = state.allow_missing || inferred_missing
    return state
end

type_order(state::InferenceState) = type_order(state.datatype)
type_order(::Type{Any}) = 0
type_order(::Type{Int}) = 1
type_order(::Type{Float64}) = 2
type_order(::Type{Date}) = 3
type_order(::Type{String}) = 4

function infer_schema(raw::Array{String, 2}, format::CSVFormat=CSVFormat(), header::Bool=true) :: Schema
    FST_DATAROW = header ? 2 : 1
    fr = float_regex(format)
    ir = integer_regex(format)
    local s::InferenceState
    (rows, cols) = size(raw)
    @assert rows >= FST_DATAROW "Empty data"

    schema_header = Vector{Symbol}(undef, cols)
    if header
        @inbounds for c in 1:cols
            schema_header[c] = Symbol(raw[1,c])
        end
    else
        @inbounds for c in 1:cols
            schema_header[c] = Symbol(c)
        end
    end

    schema_datatypes = Vector{Type}(undef, cols)

    @inbounds for c in 1:cols
        s = InferenceState()

        for r in FST_DATAROW:rows
            infer_type(raw[r,c], format, s, fr, ir)
        end

        schema_datatypes[c] = datatype(s)
    end

    return Schema(schema_header, schema_datatypes)
end

function infer_schema(input, format::CSVFormat=CSVFormat(); header::Bool=true, use_mmap::Bool=false)
    raw = readdlm(input, format.dlm, String; use_mmap=use_mmap)
    return infer_schema(raw, format, header)
end


mutable struct JointEstimationPolyaGammaModel
    parameters::Vector{<:AbstractParameters}
    latents::Vector{<:AbstractLatent}
    responses_per_item::Vector{Vector{Float64}}
    responses_per_examinee::Vector{Vector{Float64}}
    n_index::Vector{Vector{Int64}}
    i_index::Vector{Vector{Int64}}
    item_sampling::Bool
    examinee_sampling::Bool
    ext_opt_settings::Vector{Float64}

    JointEstimationPolyaGammaModel(parameters, latents, responses_per_item, responses_per_examinee, n_index, i_index,item_sampling, examinee_sampling, ext_opt_settings) =
     new(parameters, latents, responses_per_item, responses_per_examinee, n_index, i_index, item_sampling, examinee_sampling, ext_opt_settings)
end


@testset "Testing Reco submodule" begin

fnRecoV1 = "./data/mdf/reco_V1.mdf"
fnRecoV2 = "./data/mdf/reco_V2.mdf"
fnRecoV2b = "./data/mdf/reco_V2b.mdf"

mdfv1 = MPIFile(fnRecoV1)
@test typeof(mdfv1) <: MDFFileV1

saveasMDF(fnRecoV2, fnRecoV1)

mdfv2 = MPIFile(fnRecoV2)
@test typeof(mdfv2) <: MDFFileV2

c1 = loadRecoData(fnRecoV1)
c2 = loadRecoData(fnRecoV2)
saveRecoData(fnRecoV2b,c1)

mdfv2b = MPIFile(fnRecoV2b)
@test typeof(mdfv2b) <: MDFFileV2

for mdf in (mdfv1,mdfv2,mdfv2b)
  @info "Test $mdf"
  @test recoSize(mdf) == [40,40,1]
  @test norm(recoFovCenter(mdf) - [0.0,0.0,0.0]) < eps() # Image conversion fails
  @test recoFov(mdf) == [0.044,0.044,0.001]
  @test recoOrder(mdf) == "xyz"
  @test recoPositions(mdf) == nothing

  @test size( recoData(mdf) ) == (1, 1600, 1)
end

@test c1 == c2

end


module TopoChains

using Functors

export FuncTopo, @functopo_str, @functopo
export TopoChain

include("code.jl")
include("topology.jl")
include("topochain.jl")

Functors.functor(s::TopoChain) = s.models, m -> TopoChain(s.topo, m...)

end


function VectorizationBase.vectorizable(ScA::ScatteredArray{E,M,T,N,Np1}) where {E,M,T,N,Np1}
    ptr_data = pointer(ScA.data)
    VectorizedScatteredArray{E,M,T,N,Np1}(ptr_data, size(ScA.data))
end

const ScatteredVector{E,M,T} = ScatteredArray{E,M,T,1,2}
const ScatteredMatrix{E,M,T} = ScatteredArray{E,M,T,2,3}

function ScA_setindex_quote(N, L)
    q = quote $(Expr(:meta, :inline)) end
    inds = [:(i[$j]) for j ∈ 1:N]
    for j ∈ 1:L
        push!(q.args, Expr(:call, :setindex!, :(ScA.data), :(v[LinearStorage(), $j]), inds..., j))
    end
    q
end

@generated function Base.setindex!(ScA::ScatteredArray{E,M,T,N,Np1}, v::T,
                                   # i::Vararg{<:Integer,N}) where {T,E,M,N,Np1}
                                   i::Vararg{<:Integer,N}) where {E,M,T,N,Np1}
    ScA_setindex_quote(N, type_length(T))
end

@generated function Base.setindex!(ScA::ScatteredArray{E,M,T,N,Np1}, v::T,
                                    # i::CartesianIndex{N}) where {T,E,M,N,Np1}
                                    i::MultiDimIndex{N}) where {E,M,T,N,Np1}
    ScA_setindex_quote(N, type_length(T))
end

@generated function Base.getindex(ScA::ScatteredArray{E,M,T,N,Np1}, i::Vararg{<:Integer,N}) where {E,M,T,N,Np1}
    inds = [:(i[$n]) for n in 1:N]
    ind_expr = [Expr(:call, :getindex, :(ScA.data), inds..., j) for j in 1:type_length(T)]
    quote
        $(Expr(:meta, :inline))
        $(construct_expr(T, ind_expr))
    end
end

@generated function Base.getindex(ScA::ScatteredArray{E,M,T,N,Np1},
                        i::MultiDimIndex{N}) where {E,M,T,N,Np1}
    inds = [:(i[$n]) for n in 1:N]
    ind_expr = [Expr(:call, :getindex, :(ScA.data), inds..., j) for j in 1:type_length(T)]
    quote
        $(Expr(:meta, :inline))
        $(construct_expr(T, ind_expr))
    end
end

function scattered_array_view_inds(V, N)
    inds = Expr[]
    i = 0
    for n ∈ 1:N
        if V.parameters[n] == Colon
            i += 1
            push!(inds, :(i[$i]))
        else
            push!(inds, :(ScA.view[$n]))
        end
    end
    inds
end

@generated function Base.getindex(ScA::ScatteredArrayView{E,M,T,new_N,N,Np1,V},
                        i::Vararg{<:Any,new_N}) where {E,M,T,new_N,N,Np1,V}
    inds = scattered_array_view_inds(V, N)
    # The strategy is to calculate the first index, as well as the stride between indices.
    # After the first index is calculated, the remaining indices can be calcualted much more
    # efficiently via the stide.
    q = quote
        $(Expr(:meta, :inline))
        stride = $(Expr(:call, :*, [:(ScA.full_size[$n]) for n ∈ 1:N]...))
        ind_1 = sub2ind(ScA.full_size, $(Expr(:tuple,inds...,1)))
    end
    for j ∈ 2:type_length(T)
        push!(q.args, :($(Symbol(:ind_,j)) = ind_1 + stride * $(j-1)))
    end
    push!(q.args, construct_expr(T, [:(unsafe_load(ScA.ptr, $(Symbol(:ind_,j)))) for j ∈ 1:type_length(T)]))
    q
end
@generated function Base.setindex!(ScA::ScatteredArrayView{E,M,T,new_N,N,Np1,V}, v,
                        i::Vararg{<:Any,new_N}) where {E,M,T,new_N,N,Np1,V}
    inds = scattered_array_view_inds(V, N)
    q = quote
        $(Expr(:meta, :inline))
        stride = $(Expr(:call, :*, [:(ScA.full_size[$n]) for n ∈ 1:N]...))
        ind_1 = sub2ind(ScA.full_size, $(Expr(:tuple,inds...,1)))
        unsafe_store!(ScA.ptr, v[LinearStorage(), 1], ind_1)
    end
    for j ∈ 2:type_length(T)
        push!(q.args, quote
            unsafe_store!(ScA.ptr, v[LinearStorage(), $j], ind_1 + stride * $(j-1))
        end)
    end
    push!(q.args, :(v))
    q
end
@inline function Base.view(ScA::ScatteredArray{E,M,T,N,Np1}, i::Vararg{<:Any,N}) where {E,M,T,N,Np1}
    ScatteredArrayView(ScA, i)
end
@generated function ScatteredArrayView(ScA::ScatteredArray{E,M,T,N,Np1}, i::V) where {E,M,T,N,Np1,V}
    new_N = 0
    new_size = Expr(:tuple)
    for n ∈ 1:N
        if V.parameters[n] == Colon
            new_N += 1
            push!(new_size.args, :(full_size[$n]))
        end
    end
    quote
        full_size = size(ScA.data)
        ScatteredArrayView{$E,$M,$T,$new_N,$N,$Np1,$V}(pointer(ScA.data), $new_size, full_size, i)
    end
end

# @inline function SArray{Tuple{S},T,N,L}(vs::Vararg{SIMDPirates.SVec{W,T},L}) where {S,T,N,L,W}
#     SArray{S}(vs...)
# end

function scattered_vload_quote(N, W, E, T, V)
    inds = [:(i[$n]) for n in 1:N]
    quote
        $(Expr(:meta,:inline))
        $(construct_expr(T, [Expr(:call, :vload, V, :(vScA.ptr),
            Expr(:call, :sub2ind, :(vScA.size), Expr(:tuple, inds..., j))) for j in 1:type_length(T)]))
    end
end

@generated function SIMDPirates.vload(::Type{SIMDPirates.SVec{W,E}}, vScA::VectorizedScatteredArray{E,M,T,N,Np1},
                                                # i::CartesianIndex{N}) where {E,M,T,N,Np1,W}
                                                i::Vararg{<:Integer,N}) where {E,M,T,N,Np1,W}
    scattered_vload_quote(N, W, E, T, SIMDPirates.SVec{W,E})
end
@generated function SIMDPirates.vload(::Type{SIMDPirates.SVec{W,E}}, vScA::VectorizedScatteredArray{E,M,T,N,Np1},
                                                # i::Vararg{<:Integer,N}) where {E,M,T,N,Np1,W}
                                                i::MultiDimIndex{N}) where {E,M,T,N,Np1,W}
    scattered_vload_quote(N, W, E, T, SIMDPirates.SVec{W,E})
end

@generated function SIMDPirates.vload(::Type{SIMDPirates.SVec{W,E}}, vScA::VectorizedScatteredArray{E,M,T,N,Np1},
                                                # i::Vararg{<:Integer,N}) where {E,M,T,N,Np1,W}
                                                i::Integer) where {E,M,T,N,Np1,W}
    quote
        $(Expr(:meta,:inline))
        $(construct_expr(T, [Expr(:call, :vload, SIMDPirates.SVec{W,E}, :(vScA.ptr),
            :(i + length(vScA)*$(j-1)) ) for j in 1:type_length(T)]))
    end
end
@generated function SIMDPirates.vload(
                ::Type{SIMDPirates.SVec{W,E}}, vScA::VectorizedScatteredArray{E,M,T,N,Np1}
            ) where {E,M,T,N,Np1,W}
    quote
        $(Expr(:meta,:inline))
        $(construct_expr(T, [Expr(:call, :vload, SIMDPirates.SVec{W,E}, :(vScA.ptr + length(vScA)*$(sizeof(E)*(j-1))) ) for j in 1:type_length(T)]))
    end
end
@generated function SIMDPirates.vload(
                ::Type{SIMDPirates.SVec{W,Ef1}}, vScA::VectorizedScatteredArray{Ef2,M,T,N,Np1}
            ) where {Ef1,Ef2,M,T,N,Np1,W}
    quote
        $(Expr(:meta,:inline))
        $(construct_expr(T,
            [Expr(:call, :convert, SVec{W,Ef1},
                Expr(:call, :vload, SIMDPirates.SVec{W,Ef2}, :(vScA.ptr + length(vScA)*$(sizeof(Ef2)*(j-1))) )) for j in 1:type_length(T)
        ])
        )
    end
end

@inline function Base.:+(i::Integer, v::VectorizedScatteredArray{E,M,T,N,Np1}) where {E,M,T,N,Np1}
    VectorizedScatteredArray{E,M,T,N,Np1}(v.ptr + sizeof(E) * i, v.size)
end
@inline function Base.:+(v::VectorizedScatteredArray{E,M,T,N,Np1}, i::Integer) where {E,M,T,N,Np1}
    VectorizedScatteredArray{E,M,T,N,Np1}(v.ptr + sizeof(E) * i, v.size)
end

function Base.copyto!(ScA::ScatteredArray{E,M,T,N,Np1}, A::AbstractArray{T}) where {E,M,T,N,Np1}
    @boundscheck size(ScA) == size(A) || throwdimensionmismatcherror(size(ScA), size(A))
    @inbounds for i in eachindex(A)
        ScA[i] = A[i]
    end
    ScA
end

@generated function ScatteredArray(A::AbstractArray{T,N}) where {E,M,T <: AbstractArray{E,M},N}
    isbitstype(T) || thrownotisbitserror(T)
    TL = type_length(T)
    quote
        ScA = ScatteredArray{$E,$M,$T,$N,$(N+1)}(
            Array{$E}(undef, $(Expr(:tuple, [:(size(A,$n)) for n ∈ 1:N]..., TL)) )
        )
        @inbounds copyto!(ScA, A)
    end
end
@generated function ScatteredArray{E,M,T,N,Np1}(::UndefInitializer, I::Vararg{<:Integer,N}) where {E,M,T <: AbstractArray{E,M},N,Np1}
    isbitstype(T) || thrownotisbitserror(T)
    N + 1 == Np1 || throw("N + 1 = $(N + 1) != Np1 = $Np1")
    # @show T
    TL = type_length(T)
    quote
        ScatteredArray{$E,$M,$T,$N,$Np1}(
            Array{$E}(undef, $(Expr(:tuple, [:(I[$n]) for n ∈ 1:N]..., TL)) )
        )
    end
end

@generated function Base.size(ScA::ScatteredArray{E,M,T,N,Np1}) where {E,M,T,N,Np1}
    quote
        $(Expr(:meta, :inline))
        $(Expr(:tuple, [:(size(ScA,$n)) for n ∈ 1:N]...))
    end
end
@inline Base.size(ScA::ScatteredArray, n::Integer) = size(ScA.data, n)


module FixedArrayBench
# TODO, make everything work automatically and save history!

using BenchmarkTools
import FixedSizeArrays
import SIMD
import StaticArrays

include("missing_ops.jl")
const suite = BenchmarkGroup()
include("matrix.jl")
include("vector.jl")
# Define a parent BenchmarkGroup to contain our suite

function run(tunecache=true)
    vector_bench()
    if tunecache && isfile("params.jld")
        loadparams!(suite, JLD.load("params.jld", "suite"), :evals, :samples)
    else
        println("tuning...")
        tune!(suite)
        println("done tuning")
    end
    result = BenchmarkTools.run(suite)
    #JLD.save("result$(Dates.format(now(), "yyudH")).jld", "suite", result)
    result
end

end # module


# This code license is MIT: https://github.com/vtjnash/JuliaCon2019Threading/blob/master/LICENSE

using Base.Threads: @threads

function prefix_threads!(⊕, y::AbstractVector)
    l = length(y)
    k = ceil(Int, log2(l))
    # do reduce phase
    for j = 1:k
        @threads for i = 2^j:2^j:min(l, 2^k)
            @inbounds y[i] = y[i - 2^(j - 1)] ⊕ y[i]
        end
    end
    # do expand phase
    for j = (k - 1):-1:1
        @threads for i = 3*2^(j - 1):2^j:min(l, 2^k)
            @inbounds y[i] = y[i - 2^(j - 1)] ⊕ y[i]
        end
    end
    return y
end

# Example:
#   A = fill(1, 500_000)
#   prefix_threads!(+, A)


# Theories can just be vectors of rules!

macro theory(e)
    e = macroexpand(__module__, e)
    e = rmlines(e)
    if isexpr(e, :block)
        Vector{Rule}(e.args .|> x -> Rule(x; mod=__module__))
    else
        error("theory is not in form begin a => b; ... end")
    end
end

"""
A Theory is either a vector of [`Rule`](@ref) or
a compiled, callable function.
"""
const Theory = Union{Vector{Rule}, Function}


#  Copyright 2016, Iain Dunning, Joey Huchette, Miles Lubin, and contributors
#  This Source Code Form is subject to the terms of the Mozilla Public
#  License, v. 2.0. If a copy of the MPL was not distributed with this
#  file, You can obtain one at http://mozilla.org/MPL/2.0/.
#############################################################################
# JuMP
# An algebraic modelling langauge for Julia
# See http://github.com/JuliaOpt/JuMP.jl
#############################################################################
# test/print.jl
# Testing $fa pretty-printing-related functionality
#############################################################################
using JuMP, FactCheck
import JuMP.REPLMode, JuMP.IJuliaMode
import JuMP.repl, JuMP.ijulia

# Helper function to test IO methods work correctly
fname = VERSION < v"0.5.0-dev+4340" ? :writemime : :show
@eval function io_test(mode, obj, exp_str; repl=:both)
    if mode == REPLMode
        repl != :show  && @fact sprint(print, obj) --> exp_str
        repl != :print && @fact sprint(show,  obj) --> exp_str
    else
        @fact sprint($fname, "text/latex", obj) --> string("\$\$ ",exp_str," \$\$")
    end
end

facts("[print] JuMPContainer{Variable}") do
    le, ge, fa = repl[:leq], repl[:geq], repl[:for_all]
    inset, dots = repl[:in], repl[:dots]
    infty, union = repl[:infty], repl[:union]

    m = Model()

    #------------------------------------------------------------------
    # Test bound printing
    context("bound printing") do
    @variable(m,      bnd_free[2:5])
    @variable(m,      bnd_lowb[2:5] >= 2)
    @variable(m,      bnd_high[2:5] <= 5)
    @variable(m, 2 <= bnd_both[2:5] <= 5)
    @variable(m,      bnd_difflo[i=2:5] >= i)
    @variable(m,      bnd_diffup[i=2:5] <= i)
    @variable(m, i <= bnd_diffbo[i=2:5] <= 2i)
    @variable(m, i <= bnd_difflo_with_up[i=2:5] <= 5)
    @variable(m, 2 <= bnd_diffup_with_lo[i=2:5] <= i)

    io_test(REPLMode, bnd_free, "bnd_free[i] free $fa i $inset {2,3,4,5}")
    io_test(REPLMode, bnd_lowb, "bnd_lowb[i] $ge 2 $fa i $inset {2,3,4,5}")
    io_test(REPLMode, bnd_high, "bnd_high[i] $le 5 $fa i $inset {2,3,4,5}")
    io_test(REPLMode, bnd_both, "2 $le bnd_both[i] $le 5 $fa i $inset {2,3,4,5}")
    io_test(REPLMode, bnd_difflo, "bnd_difflo[i] $ge $dots $fa i $inset {2,3,4,5}")
    io_test(REPLMode, bnd_diffup, "bnd_diffup[i] $le $dots $fa i $inset {2,3,4,5}")
    io_test(REPLMode, bnd_diffbo, "$dots $le bnd_diffbo[i] $le $dots $fa i $inset {2,3,4,5}")
    io_test(REPLMode, bnd_difflo_with_up, "$dots $le bnd_difflo_with_up[i] $le 5 $fa i $inset {2,3,4,5}")
    io_test(REPLMode, bnd_diffup_with_lo, "2 $le bnd_diffup_with_lo[i] $le $dots $fa i $inset {2,3,4,5}")

    io_test(IJuliaMode, bnd_free, "bnd_free_{i} free \\quad\\forall i \\in \\{2,3,4,5\\}")
    io_test(IJuliaMode, bnd_lowb, "bnd_lowb_{i} \\geq 2 \\quad\\forall i \\in \\{2,3,4,5\\}")
    io_test(IJuliaMode, bnd_high, "bnd_high_{i} \\leq 5 \\quad\\forall i \\in \\{2,3,4,5\\}")
    io_test(IJuliaMode, bnd_both, "2 \\leq bnd_both_{i} \\leq 5 \\quad\\forall i \\in \\{2,3,4,5\\}")
    io_test(IJuliaMode, bnd_difflo, "bnd_difflo_{i} \\geq \\dots \\quad\\forall i \\in \\{2,3,4,5\\}")
    io_test(IJuliaMode, bnd_diffup, "bnd_diffup_{i} \\leq \\dots \\quad\\forall i \\in \\{2,3,4,5\\}")
    io_test(IJuliaMode, bnd_diffbo, "\\dots \\leq bnd_diffbo_{i} \\leq \\dots \\quad\\forall i \\in \\{2,3,4,5\\}")
    io_test(IJuliaMode, bnd_difflo_with_up, "\\dots \\leq bnd_difflo_with_up_{i} \\leq 5 \\quad\\forall i \\in \\{2,3,4,5\\}")
    io_test(IJuliaMode, bnd_diffup_with_lo, "2 \\leq bnd_diffup_with_lo_{i} \\leq \\dots \\quad\\forall i \\in \\{2,3,4,5\\}")
    end

    #------------------------------------------------------------------
    # Test index set printing
    context("index set printing") do
    @variable(m, rng_unit1[1:10])  # Array{Variable}
    @variable(m, rng_unit2[-2:3])  # JuMPArray
    @variable(m, rng_unit3[[1:10;]])  # JuMPDict
    @variable(m, rng_step1[1:2:10])
    @variable(m, rng_step2[-2:5:10])
    @variable(m, rng_step3[1:5:3])
    @variable(m, rng_step4[0:2:2])
    @variable(m, arr_1[[:a,:b,:c]])
    @variable(m, arr_2[[:a,1,"test"]])
    @variable(m, arr_3[[:apple,:banana,:carrot,:diamonds]])
    @variable(m, rng2_1[1:10,[:a,:b,:c]])
    @variable(m, tri_1[i=1:3,j=i:3])
    @variable(m, tri_2[i=1:3,j=-i])
    @variable(m, tri_3[(i,j)=[(i,i+2) for i in 1:5],k=i:j])
    @variable(m, iter_1[keys(Dict(:a => 1))])

    io_test(REPLMode, rng_unit1, "rng_unit1[i] free $fa i $inset {1,2,$dots,9,10}")
    io_test(REPLMode, rng_unit2, "rng_unit2[i] free $fa i $inset {-2,-1,$dots,2,3}")
    io_test(REPLMode, rng_unit3, "rng_unit3[i] free $fa i $inset {1,2,$dots,9,10}")
    io_test(REPLMode, rng_step1, "rng_step1[i] free $fa i $inset {1,3,$dots,7,9}")
    io_test(REPLMode, rng_step2, "rng_step2[i] free $fa i $inset {-2,3,8}")
    io_test(REPLMode, rng_step3, "rng_step3[i] free $fa i $inset {1}")
    io_test(REPLMode, rng_step4, "rng_step4[i] free $fa i $inset {0,2}")
    io_test(REPLMode, arr_1, "arr_1[i] free $fa i $inset {a,b,c}")
    io_test(REPLMode, arr_2, "arr_2[i] free $fa i $inset {a,1,test}")
    io_test(REPLMode, arr_3, "arr_3[i] free $fa i $inset {apple,banana,carrot,diamonds}")
    io_test(REPLMode, rng2_1, "rng2_1[i,j] free $fa i $inset {1,2,$dots,9,10}, j $inset {a,b,c}")
    io_test(REPLMode, tri_1, "tri_1[i,j] free $fa i $inset {1,2,3}, j $inset {$dots}")
    io_test(REPLMode, tri_2, "tri_2[i,j] free $fa i $inset {1,2,3}, j $inset {$dots}")
    io_test(REPLMode, tri_3, "tri_3[(i,j),k] free $fa (i,j) $inset {(1,3),(2,4),$dots,(4,6),(5,7)}, k $inset {$dots}")
    io_test(REPLMode, iter_1, "iter_1[i] free $fa i $inset {a}")

    io_test(IJuliaMode, rng_unit1, "rng_unit1_{i} free \\quad\\forall i \\in \\{1,2,\\dots,9,10\\}")
    io_test(IJuliaMode, rng_unit2, "rng_unit2_{i} free \\quad\\forall i \\in \\{-2,-1,\\dots,2,3\\}")
    io_test(IJuliaMode, rng_unit3, "rng_unit3_{i} free \\quad\\forall i \\in \\{1,2,\\dots,9,10\\}")
    io_test(IJuliaMode, rng_step1, "rng_step1_{i} free \\quad\\forall i \\in \\{1,3,\\dots,7,9\\}")
    io_test(IJuliaMode, rng_step2, "rng_step2_{i} free \\quad\\forall i \\in \\{-2,3,8\\}")
    io_test(IJuliaMode, rng_step3, "rng_step3_{i} free \\quad\\forall i \\in \\{1\\}")
    io_test(IJuliaMode, rng_step4, "rng_step4_{i} free \\quad\\forall i \\in \\{0,2\\}")
    io_test(IJuliaMode, arr_1, "arr_1_{i} free \\quad\\forall i \\in \\{a,b,c\\}")
    io_test(IJuliaMode, arr_2, "arr_2_{i} free \\quad\\forall i \\in \\{a,1,test\\}")
    io_test(IJuliaMode, arr_3, "arr_3_{i} free \\quad\\forall i \\in \\{apple,banana,carrot,diamonds\\}")
    io_test(IJuliaMode, rng2_1, "rng2_1_{i,j} free \\quad\\forall i \\in \\{1,2,\\dots,9,10\\}, j \\in \\{a,b,c\\}")
    io_test(IJuliaMode, tri_1, "tri_1_{i,j} free \\quad\\forall i \\in \\{1,2,3\\}, j \\in \\{\\dots\\}")
    io_test(IJuliaMode, tri_2, "tri_2_{i,j} free \\quad\\forall i \\in \\{1,2,3\\}, j \\in \\{\\dots\\}")
    io_test(IJuliaMode, tri_3, "tri_3_{(i,j),k} free \\quad\\forall (i,j) \\in \\{(1,3),(2,4),\\dots,(4,6),(5,7)\\}, k \\in \\{\\dots\\}")
    io_test(IJuliaMode, iter_1, "iter_1_{i} free \\quad\\forall i \\in \\{a\\}")
    end

    #------------------------------------------------------------------
    # Test category printing
    context("category printing") do
    @variable(m, cat_bin[1:3], Bin)
    @variable(m, 2 <= cat_int[1:3] <= 5, Int)
    @variable(m, cat_semiint_both[2:3] >= 2, SemiInt)
    @variable(m, i <= cat_semiint_difflow[i=2:3] <= 4, SemiInt)
    @variable(m, 2 <= cat_semiint_diffup[i=2:3] <= i, SemiInt)
    @variable(m, i <= cat_semiint_none[i=2:3] <= 2i, SemiInt)
    @variable(m, cat_semicont_both[2:3] >= 2, SemiCont)
    @variable(m, i <= cat_semicont_difflow[i=2:3] <= 4, SemiCont)
    @variable(m, 2 <= cat_semicont_diffup[i=2:3] <= i, SemiCont)
    @variable(m, i <= cat_semicont_none[i=2:3] <= 2i, SemiCont)
    @variable(m, fixed_var[i=2:3] == i)

    io_test(REPLMode, cat_bin, "cat_bin[i] $inset {0,1} $fa i $inset {1,2,3}")
    io_test(REPLMode, cat_int, "2 $le cat_int[i] $le 5, integer, $fa i $inset {1,2,3}")
    io_test(REPLMode, cat_semiint_both, "cat_semiint_both[i] $inset {2,$dots,$infty} $union {0} $fa i $inset {2,3}")
    io_test(REPLMode, cat_semiint_difflow, "cat_semiint_difflow[i] $inset {$dots,$dots,4} $union {0} $fa i $inset {2,3}")
    io_test(REPLMode, cat_semiint_diffup, "cat_semiint_diffup[i] $inset {2,$dots,$dots} $union {0} $fa i $inset {2,3}")
    io_test(REPLMode, cat_semiint_none, "cat_semiint_none[i] $inset {$dots,$dots,$dots} $union {0} $fa i $inset {2,3}")
    io_test(REPLMode, cat_semicont_both, "cat_semicont_both[i] $inset [2,$infty] $union {0} $fa i $inset {2,3}")
    io_test(REPLMode, cat_semicont_difflow, "cat_semicont_difflow[i] $inset [$dots,4] $union {0} $fa i $inset {2,3}")
    io_test(REPLMode, cat_semicont_diffup, "cat_semicont_diffup[i] $inset [2,$dots] $union {0} $fa i $inset {2,3}")
    io_test(REPLMode, cat_semicont_none, "cat_semicont_none[i] $inset [$dots,$dots] $union {0} $fa i $inset {2,3}")
    io_test(REPLMode, fixed_var, "fixed_var[i] = $dots $fa i $inset {2,3}")

    io_test(IJuliaMode, cat_bin, "cat_bin_{i} \\in \\{0,1\\} \\quad\\forall i \\in \\{1,2,3\\}")
    io_test(IJuliaMode, cat_int, "2 \\leq cat_int_{i} \\leq 5, \\in \\mathbb{Z}, \\quad\\forall i \\in \\{1,2,3\\}")
    io_test(IJuliaMode, cat_semiint_both, "cat_semiint_both_{i} \\in \\{2,\\dots,\\infty\\} \\cup \\{0\\} \\quad\\forall i \\in \\{2,3\\}")
    io_test(IJuliaMode, cat_semiint_difflow, "cat_semiint_difflow_{i} \\in \\{\\dots,\\dots,4\\} \\cup \\{0\\} \\quad\\forall i \\in \\{2,3\\}")
    io_test(IJuliaMode, cat_semiint_diffup, "cat_semiint_diffup_{i} \\in \\{2,\\dots,\\dots\\} \\cup \\{0\\} \\quad\\forall i \\in \\{2,3\\}")
    io_test(IJuliaMode, cat_semiint_none, "cat_semiint_none_{i} \\in \\{\\dots,\\dots,\\dots\\} \\cup \\{0\\} \\quad\\forall i \\in \\{2,3\\}")
    io_test(IJuliaMode, cat_semicont_both, "cat_semicont_both_{i} \\in \\[2,\\infty\\] \\cup \\{0\\} \\quad\\forall i \\in \\{2,3\\}")
    io_test(IJuliaMode, cat_semicont_difflow, "cat_semicont_difflow_{i} \\in \\[\\dots,4\\] \\cup \\{0\\} \\quad\\forall i \\in \\{2,3\\}")
    io_test(IJuliaMode, cat_semicont_diffup, "cat_semicont_diffup_{i} \\in \\[2,\\dots\\] \\cup \\{0\\} \\quad\\forall i \\in \\{2,3\\}")
    io_test(IJuliaMode, cat_semicont_none, "cat_semicont_none_{i} \\in \\[\\dots,\\dots\\] \\cup \\{0\\} \\quad\\forall i \\in \\{2,3\\}")
    io_test(IJuliaMode, fixed_var, "fixed_var_{i} = \\dots \\quad\\forall i \\in \\{2,3\\}")
    end

    #------------------------------------------------------------------
    # Tests for particular issues
    context("Empty JuMPContainer printing (#124)") do
    @variable(m, empty_free[1:0])
    io_test(REPLMode, empty_free, "Empty Array{Variable} (no indices)")
    io_test(IJuliaMode, empty_free, "Empty Array{Variable} (no indices)")
    @variable(m, empty_set[[]])
    io_test(REPLMode, empty_set, "empty_set (no indices)")
    io_test(IJuliaMode, empty_set, "empty_set (no indices)")
    end
end

facts("[print] JuMPContainer{Number}") do
    # The same output for REPL and IJulia, so only testing one
    mod = Model()
    @variable(mod, i*j <= w[i=9:10, [:Apple,5,:Banana], j=-1:+1] <= i*j)
    @variable(mod, i*j*k <= x[i=9:11,j=99:101,k=3:4] <= i*j*k)
    @variable(mod, i*j <= y[i=9:11,j=i:11] <= i*j)
    @variable(mod, j <= z[i=[:a,'b'],j=1:3] <= j)
    solve(mod)

    # Deal with hashing variations
    if hash(5) < hash(:Apple)
        io_test(REPLMode, getvalue(w), """
w: 3 dimensions:
[ 9,:,:]
  [ 9, Apple,:]
    [ 9, Apple,-1] = -9.0
    [ 9, Apple, 0] = 0.0
    [ 9, Apple, 1] = 9.0
  [ 9,     5,:]
    [ 9,     5,-1] = -9.0
    [ 9,     5, 0] = 0.0
    [ 9,     5, 1] = 9.0
  [ 9,Banana,:]
    [ 9,Banana,-1] = -9.0
    [ 9,Banana, 0] = 0.0
    [ 9,Banana, 1] = 9.0
[10,:,:]
  [10, Apple,:]
    [10, Apple,-1] = -10.0
    [10, Apple, 0] = 0.0
    [10, Apple, 1] = 10.0
  [10,     5,:]
    [10,     5,-1] = -10.0
    [10,     5, 0] = 0.0
    [10,     5, 1] = 10.0
  [10,Banana,:]
    [10,Banana,-1] = -10.0
    [10,Banana, 0] = 0.0
    [10,Banana, 1] = 10.0
""", repl=:print)
    else
        io_test(REPLMode, getvalue(w), """
w: 3 dimensions:
[ 9,:,:]
  [ 9, Apple,:]
    [ 9, Apple,-1] = -9.0
    [ 9, Apple, 0] = 0.0
    [ 9, Apple, 1] = 9.0
  [ 9,     5,:]
    [ 9,     5,-1] = -9.0
    [ 9,     5, 0] = 0.0
    [ 9,     5, 1] = 9.0
  [ 9,Banana,:]
    [ 9,Banana,-1] = -9.0
    [ 9,Banana, 0] = 0.0
    [ 9,Banana, 1] = 9.0
[10,:,:]
  [10, Apple,:]
    [10, Apple,-1] = -10.0
    [10, Apple, 0] = 0.0
    [10, Apple, 1] = 10.0
  [10,     5,:]
    [10,     5,-1] = -10.0
    [10,     5, 0] = 0.0
    [10,     5, 1] = 10.0
  [10,Banana,:]
    [10,Banana,-1] = -10.0
    [10,Banana, 0] = 0.0
    [10,Banana, 1] = 10.0
""", repl=:print)
    end

    io_test(REPLMode, getvalue(x), """
x: 3 dimensions:
[ 9,:,:]
  [ 9, 99,:]
    [ 9, 99,3] = 2673.0
    [ 9, 99,4] = 3564.0
  [ 9,100,:]
    [ 9,100,3] = 2700.0
    [ 9,100,4] = 3600.0
  [ 9,101,:]
    [ 9,101,3] = 2727.0
    [ 9,101,4] = 3636.0
[10,:,:]
  [10, 99,:]
    [10, 99,3] = 2970.0
    [10, 99,4] = 3960.0
  [10,100,:]
    [10,100,3] = 3000.0
    [10,100,4] = 4000.0
  [10,101,:]
    [10,101,3] = 3030.0
    [10,101,4] = 4040.0
[11,:,:]
  [11, 99,:]
    [11, 99,3] = 3267.0
    [11, 99,4] = 4356.0
  [11,100,:]
    [11,100,3] = 3300.0
    [11,100,4] = 4400.0
  [11,101,:]
    [11,101,3] = 3333.0
    [11,101,4] = 4444.0
""", repl=:print)

    io_test(REPLMode, getvalue(y), """
y: 2 dimensions, 6 entries:
 [ 9, 9] = 81.0
 [ 9,10] = 90.0
 [ 9,11] = 99.0
 [10,10] = 100.0
 [10,11] = 110.0
 [11,11] = 121.0""")

    io_test(REPLMode, getvalue(z), """
z: 2 dimensions:
[a,:]
  [a,1] = 1.0
  [a,2] = 2.0
  [a,3] = 3.0
[b,:]
  [b,1] = 1.0
  [b,2] = 2.0
  [b,3] = 3.0
""")

end

facts("[print] SOS constraints") do
    modS = Model()
    a = [1,2,3]
    @variable(modS, x[1:3], Bin)
    addSOS1(modS, [a[i]x[i] for i in 1:3])
    s1 = JuMP.SOSConstraint([x[i] for i in 1:3],
                            [a[i] for i in 1:3], :SOS1)
    io_test(REPLMode, s1, "SOS1: {1 x[1], 2 x[2], 3 x[3]}")
    io_test(IJuliaMode, s1, "SOS1: \\{1 x[1], 2 x[2], 3 x[3]\\}")

    b = [5,4,7,2,1]
    @variable(modS, y[1:5], Bin)
    s2 = JuMP.SOSConstraint([y[i] for i in 1:5],
                            [b[i] for i in 1:5], :SOS2)
    io_test(REPLMode, s2, "SOS2: {5 y[1], 4 y[2], 7 y[3], 2 y[4], 1 y[5]}")
    io_test(IJuliaMode, s2, "SOS2: \\{5 y[1], 4 y[2], 7 y[3], 2 y[4], 1 y[5]\\}")
end

facts("[print] Model") do
    le, ge, fa = repl[:leq], repl[:geq], repl[:for_all]
    inset, dots = repl[:in], repl[:dots]
    infty, union = repl[:infty], repl[:union]
    Vert, sub2 = repl[:Vert], repl[:sub2]

    #------------------------------------------------------------------

    mod_1 = Model()
    @variable(mod_1, a>=1)
    @variable(mod_1, b<=1)
    @variable(mod_1, -1<=c<=1)
    @variable(mod_1, a1>=1,Int)
    @variable(mod_1, b1<=1,Int)
    @variable(mod_1, -1<=c1<=1,Int)
    @variable(mod_1, x, Bin)
    @variable(mod_1, y)
    @variable(mod_1, z, Int)
    @variable(mod_1, sos[1:3], Bin)
    @variable(mod_1, 2 <= si <= 3, SemiInt)
    @variable(mod_1, 2 <= sc <= 3, SemiCont)
    @variable(mod_1, fi == 9)
    @objective(mod_1, Max, a - b + 2a1 - 10x)
    @constraint(mod_1, a + b - 10c - 2x + c1 <= 1)
    @constraint(mod_1, a*b <= 2)
    addSOS1(mod_1, [i*sos[i] for i in 1:3])
    @constraint(mod_1, norm(sos) + a <= 1)

    io_test(REPLMode, mod_1, """
Max a - b + 2 a1 - 10 x
Subject to
 a + b - 10 c - 2 x + c1 $le 1
 a*b - 2 $le 0
 SOS1: {1 sos[1], 2 sos[2], 3 sos[3]}
 $(Vert)[sos[1],sos[2],sos[3]]$(Vert)$(sub2) $le -a + 1
 sos[i] $inset {0,1} $fa i $inset {1,2,3}
 a $ge 1
 b $le 1
 -1 $le c $le 1
 a1 $ge 1, integer
 b1 $le 1, integer
 -1 $le c1 $le 1, integer
 x $inset {0,1}
 y free
 z free, integer
 si $inset {2,$dots,3} $union {0}
 sc $inset [2,3] $union {0}
 fi = 9
""", repl=:print)

    io_test(REPLMode, mod_1, """
Maximization problem with:
 * 1 linear constraint
 * 1 quadratic constraint
 * 1 SOS constraint
 * 1 SOC constraint
 * 15 variables: 4 binary, 4 integer, 1 semicontinuous, 1 semi-integer
Solver is default solver""", repl=:show)

    io_test(IJuliaMode, mod_1, """
\\begin{alignat*}{1}\\max\\quad & a - b + 2 a1 - 10 x\\\\
\\text{Subject to} \\quad & a + b - 10 c - 2 x + c1 \\leq 1\\\\
 & a\\times b - 2 \\leq 0\\\\
 & SOS1: \\{1 sos[1], 2 sos[2], 3 sos[3]\\}\\\\
 & \\Vert[sos_{1},sos_{2},sos_{3}]\\Vert_2 $le -a + 1\\\\
 & sos_{i} \\in \\{0,1\\} \\quad\\forall i \\in \\{1,2,3\\}\\\\
 & a \\geq 1\\\\
 & b \\leq 1\\\\
 & -1 \\leq c \\leq 1\\\\
 & a1 \\geq 1, \\in \\mathbb{Z}\\\\
 & b1 \\leq 1, \\in \\mathbb{Z}\\\\
 & -1 \\leq c1 \\leq 1, \\in \\mathbb{Z}\\\\
 & x \\in \\{0,1\\}\\\\
 & y free\\\\
 & z free, \\in \\mathbb{Z}\\\\
 & si \\in \\{2,\\dots,3\\} \\cup \\{0\\}\\\\
 & sc \\in \\[2,3\\] \\cup \\{0\\}\\\\
 & fi = 9\\\\
\\end{alignat*}
""")

    #------------------------------------------------------------------

    mod_2 = Model()
    @variable(mod_2, x, Bin)
    @variable(mod_2, y, Int)
    @constraint(mod_2, x*y <= 1)

    io_test(REPLMode, mod_2, """
Feasibility problem with:
 * 0 linear constraints
 * 1 quadratic constraint
 * 2 variables: 1 binary, 1 integer
Solver is default solver""", repl=:show)

    mod_2 = Model()
    @variable(mod_2, x)
    @constraint(mod_2, x <= 3)

    io_test(REPLMode, mod_2, """
Feasibility problem with:
 * 1 linear constraint
 * 1 variable
Solver is default solver""", repl=:show)

    #------------------------------------------------------------------

    mod_3 = Model()

    @variable(mod_3, x[1:5])
    @NLconstraint(mod_3, x[1]*x[2] == 1)
    @NLconstraint(mod_3, x[3]*x[4] == 1)
    @NLconstraint(mod_3, x[5]*x[1] == 1)
    @NLobjective(mod_3, Min, x[1]*x[3])

    io_test(REPLMode, mod_3, """
Min (nonlinear expression)
Subject to
 3 nonlinear constraints
 x[i] free $fa i $inset {1,2,$dots,4,5}
""", repl=:print)
    io_test(REPLMode, mod_3, """
Minimization problem with:
 * 0 linear constraints
 * 3 nonlinear constraints
 * 5 variables
Solver is default solver""", repl=:show)
    io_test(IJuliaMode, mod_3, """
\\begin{alignat*}{1}\\min\\quad & (nonlinear expression)\\\\
\\text{Subject to} \\quad & 3 nonlinear constraints\\\\
 & x_{i} free \\quad\\forall i \\in \\{1,2,\\dots,4,5\\}\\\\
\\end{alignat*}
""", repl=:print)
end

facts("[print] changing variable categories") do
    le, ge, fa = repl[:leq], repl[:geq], repl[:for_all]
    inset, dots = repl[:in], repl[:dots]
    infty, union = repl[:infty], repl[:union]

    mod = Model()
    @variable(mod, x[1:3])
    @variable(mod, y[i=1:3,i:3])
    setcategory(x[3], :SemiCont)
    setcategory(y[1,3], :Int)

    io_test(REPLMode, mod, """
Min 0
Subject to
 x[1] free
 x[2] free
 x[3] $inset [-$infty,$infty] $union {0}
 y[1,1] free
 y[1,2] free
 y[1,3] free, integer
 y[2,2] free
 y[2,3] free
 y[3,3] free
""", repl=:print)

    io_test(IJuliaMode, mod, """
\\begin{alignat*}{1}\\min\\quad & 0\\\\
\\text{Subject to} \\quad & x_{1} free\\\\
 & x_{2} free\\\\
 & x_{3} \\in \\[-\\infty,\\infty\\] \\cup \\{0\\}\\\\
 & y_{1,1} free\\\\
 & y_{1,2} free\\\\
 & y_{1,3} free, \\in \\mathbb{Z}\\\\
 & y_{2,2} free\\\\
 & y_{2,3} free\\\\
 & y_{3,3} free\\\\
\\end{alignat*}
""")
end

facts("[print] expressions") do
    # Most of the expression logic is well covered by test/operator.jl
    # This is really just to check IJulia printing for expressions
    le, ge = repl[:leq], repl[:geq]

    #------------------------------------------------------------------
    mod = Model()
    @variable(mod, x[1:5])
    @variable(mod, y[i=2:4,j=i:5])
    @variable(mod, z)

    @constraint(mod, x[1] + 2*y[2,3] <= 3)
    io_test(REPLMode, mod.linconstr[end], "x[1] + 2 y[2,3] $le 3")
    io_test(IJuliaMode, mod.linconstr[end], "x_{1} + 2 y_{2,3} \\leq 3")

    @constraint(mod, (x[1]+x[2])*(y[2,2]+3.0) <= 1)
    io_test(REPLMode, mod.quadconstr[end], "x[1]*y[2,2] + x[2]*y[2,2] + 3 x[1] + 3 x[2] - 1 $le 0")
    io_test(IJuliaMode, mod.quadconstr[end], "x_{1}\\times y_{2,2} + x_{2}\\times y_{2,2} + 3 x_{1} + 3 x_{2} - 1 \\leq 0")

    @constraint(mod, (y[2,2]+3.0)*(x[1]+x[2]) <= 1)
    io_test(REPLMode, mod.quadconstr[end], "x[1]*y[2,2] + x[2]*y[2,2] + 3 x[1] + 3 x[2] - 1 $le 0")
    io_test(IJuliaMode, mod.quadconstr[end], "x_{1}\\times y_{2,2} + x_{2}\\times y_{2,2} + 3 x_{1} + 3 x_{2} - 1 \\leq 0")
end

facts("[print] Variable") do
    m = Model()
    @variable(m, 0 <= x <= 2, inconstraints=ConstraintRef{Model,LinearConstraint}[], objective=0.0, coefficients=Float64[] )

    @fact    getname(x) --> "x"
    io_test(REPLMode,   x, "x")
    io_test(IJuliaMode, x, "x")

    setname(x, "x2")
    @fact    getname(x) --> "x2"
    io_test(REPLMode,   x, "x2")
    io_test(IJuliaMode, x, "x2")

    setname(x, "")
    @fact    getname(x) --> "col_1"
    io_test(REPLMode,   x, "col_1")
    io_test(IJuliaMode, x, "col_1")

    @variable(m, z[1:2,3:5])
    @fact       getname(z[1,3]) --> "z[1,3]"
    io_test(REPLMode,   z[1,3],    "z[1,3]")
    io_test(IJuliaMode, z[1,3],    "z_{1,3}")
    @fact       getname(z[2,4]) --> "z[2,4]"
    io_test(REPLMode,   z[2,4],    "z[2,4]")
    io_test(IJuliaMode, z[2,4],    "z_{2,4}")
    @fact       getname(z[2,5]) --> "z[2,5]"
    io_test(REPLMode,   z[2,5],    "z[2,5]")
    io_test(IJuliaMode, z[2,5],    "z_{2,5}")

    @variable(m, w[3:9,["red","blue","green"]])
    @fact    getname(w[7,"green"]) --> "w[7,green]"
    io_test(REPLMode,   w[7,"green"], "w[7,green]")
    io_test(IJuliaMode, w[7,"green"], "w_{7,green}")

    rng = 2:5
    @variable(m, v[rng,rng,rng,rng,rng,rng,rng])
    a_v = v[4,5,2,3,2,2,4]
    @fact    getname(a_v) --> "v[4,5,2,3,2,2,4]"
    io_test(REPLMode,   a_v, "v[4,5,2,3,2,2,4]")
    io_test(IJuliaMode, a_v, "v_{4,5,2,3,2,2,4}")
end

facts("[print] User-created Array{Variable}") do
    m = Model()
    @variable(m, x)
    @variable(m, y)

    v = [x,y,x]
    A = [x y; y x]
    if VERSION >= v"0.5.0-"
        io_test(REPLMode,   v, "JuMP.Variable[x,y,x]")
        io_test(IJuliaMode, v, "JuMP.Variable[x,y,x]")
    else
        io_test(REPLMode,   v, "[x,y,x]")
        io_test(IJuliaMode, v, "[x,y,x]")
    end

    if VERSION >= v"0.5.0-dev+3642"
        io_test(REPLMode,   A, "JuMP.Variable[x y; y x]")
        io_test(IJuliaMode, A, "JuMP.Variable[x y; y x]")
    else
        io_test(REPLMode,   A, """
2x2 Array{JuMP.Variable,2}:
 x  y
 y  x""")
        io_test(IJuliaMode, A, """
2x2 Array{JuMP.Variable,2}:
 x  y
 y  x""")
    end
end

facts("[print] basename keyword argument") do
    m = Model()
    @variable(m, x, basename="foo")
    @variable(m, y[1:3], basename=:bar)
    num = 123
    @variable(m, z[[:red,:blue]], basename="color_$num")
    @variable(m, v[1:2,1:2], SDP, basename=string("i","$num",num))
    @variable(m, w[1:3,1:3], Symmetric, basename="symm")

    io_test(REPLMode,   x, "foo")
    io_test(IJuliaMode, x, "foo")
    io_test(REPLMode,   y[2], "bar[2]")
    io_test(IJuliaMode, y[2], "bar_{2}")
    io_test(REPLMode,   z[:red], "color_123[red]")
    io_test(IJuliaMode, z[:red], "color_123_{red}")
    io_test(REPLMode,   v[2,1], "i123123[1,2]")
    io_test(IJuliaMode, v[2,1], "i123123_{1,2}")
    io_test(REPLMode,   w[1,3], "symm[1,3]")
    io_test(IJuliaMode, w[1,3], "symm_{1,3}")
end


function save(f::Stream{format"PLY_BINARY"}, msh::AbstractMesh)
    io = stream(f)
    points = decompose(Point{3, Float32}, msh)
    faces = decompose(GLTriangleFace, msh)

    n_points = length(points)
    n_faces = length(faces)

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

    # write the vertices and faces
    write(io, points)

    for f in faces
        write(io, convert(UInt8, 3))
        write(io, raw.(f)...)
    end
    close(io)
end

function save(f::Stream{format"PLY_ASCII"}, msh::AbstractMesh)
    io = stream(f)
    points = coordinates(msh)
    meshfaces = faces(msh)

    n_faces = length(points)
    n_points = length(meshfaces)

    # write the header
    write(io, "ply\n")
    write(io, "format ascii 1.0\n")
    write(io, "element vertex $n_faces\n")
    write(io, "property float x\nproperty float y\nproperty float z\n")
    write(io, "element face $n_points\n")
    write(io, "property list uchar int vertex_index\n")
    write(io, "end_header\n")

    # write the vertices and faces
    for v in points
        println(io, join(Point{3, Float32}(v), " "))
    end
    for f in meshfaces
        println(io, length(f), " ", join(raw.(ZeroIndex.(f)), " "))
    end
    close(io)
end

function load(fs::Stream{format"PLY_ASCII"}; facetype=GLTriangleFace, pointtype=Point3f0)
    io = stream(fs)
    n_points = 0
    n_faces = 0

    properties = String[]

    # read the header
    line = readline(io)

    while !startswith(line, "end_header")
        if startswith(line, "element vertex")
            n_points = parse(Int, split(line)[3])
        elseif startswith(line, "element face")
            n_faces = parse(Int, split(line)[3])
        elseif startswith(line, "property")
            push!(properties, line)
        end
        line = readline(io)
    end

    faceeltype = eltype(facetype)
    points = Array{pointtype}(undef, n_points)
    #faces = Array{FaceType}(undef, n_faces)
    faces = facetype[]

    # read the data
    for i = 1:n_points
        points[i] = pointtype(parse.(eltype(pointtype), split(readline(io)))) # line looks like: "-0.018 0.038 0.086"
    end

    for i = 1:n_faces
        line = split(readline(io))
        len = parse(Int, popfirst!(line))
        if len == 3
            push!(faces, NgonFace{3, faceeltype}(reinterpret(ZeroIndex{UInt32}, parse.(UInt32, line)))) # line looks like: "3 0 1 3"
        elseif len == 4
            push!(faces, convert_simplex(facetype, QuadFace{faceeltype}(reinterpret(ZeroIndex{UInt32}, parse.(UInt32, line))))...) # line looks like: "4 0 1 2 3"
        end
    end
    return Mesh(points, faces)
end

function load(fs::Stream{format"PLY_BINARY"}; facetype=GLTriangleFace, pointtype=Point3f0)
    io = stream(fs)
    n_points = 0
    n_faces = 0

    properties = String[]

    # read the header
    line = readline(io)

    while !startswith(line, "end_header")
        if startswith(line, "element vertex")
            n_points = parse(Int, split(line)[3])
        elseif startswith(line, "element face")
            n_faces = parse(Int, split(line)[3])
        elseif startswith(line, "property")
            push!(properties, line)
        end
        line = readline(io)
    end

    faceeltype = eltype(facetype)
    points = Array{pointtype}(undef, n_points)
    #faces = Array{FaceType}(undef, n_faces)
    faces = facetype[]

    # read the data
    for i = 1:n_points
        points[i] = pointtype(read(io, Float32), read(io, Float32), read(io, Float32))
    end

    for i = 1:n_faces
        len = read(io, UInt8)
        indices = reinterpret(ZeroIndex{UInt32}, [ read(io, UInt32) for _ in 1:len ])
        if len == 3
            push!(faces, NgonFace{3, faceeltype}(indices)) # line looks like: "3 0 1 3"
        elseif len == 4
            push!(faces, convert_simplex(facetype, QuadFace{faceeltype}(indices))...) # line looks like: "4 0 1 2 3"
        end
    end

    return Mesh(points, faces)
end


using Pkg
cd(joinpath(@__DIR__, ".."))
Pkg.activate(".")

mkpath("results")

open("results/bench.csv", "w") do io
  println(io, "Benchmark, Package, Time")
end

j = `$(Base.julia_cmd()) -O3`

@info "Running Baseline Benchmarks"
run(`$j --project=src/baseline src/baseline/baseline.jl`)

@info "Running Flux Benchmarks"
run(`$j --project=src/flux src/flux/flux.jl`)

@info "Running Knet Benchmarks"
run(`$j --project=src/knet src/knet/knet.jl`)

@info "Running Zygote Benchmarks"
run(`$j --project=src/zygote src/zygote/zygote.jl`)


""" Draw wiring diagrams (aka string diagrams) using TikZ.
"""
module TikZWiringDiagrams
export to_tikz

using Match

using ...Doctrines: ObExpr, HomExpr, dom, codom, head, args, compose, id
using ...Syntax: GATExpr, show_latex
using ...WiringDiagrams
import ..TikZ

# Data types
############

""" A wire (edge) in a TikZ wiring diagram.
"""
struct Wire
  label::String
  reverse::Bool
  Wire(label::String; reverse::Bool=false) = new(label, reverse)
end

""" A bundle of wires in a TikZ wiring diagram.

A graphical representation of an object.
"""
const Wires = Vector{Wire}

""" A port on a box in a TikZ wiring diagram.
"""
struct Port
  wire::Wire
  anchor::String
  angle::Int
  show_label::Bool
  Port(wire::Wire, anchor::String; angle::Int=0, show_label::Bool=true) =
    new(wire, anchor, angle, show_label)
end

""" A box in a TikZ wiring diagram.

A `Box` is a graphical representation of a morphism, and need not be rendered
as a geometric box (rectangle).
"""
struct Box
  node::TikZ.Node
  inputs::Vector{Port}
  outputs::Vector{Port}
end

# Wiring diagrams
#################

""" Draw a wiring diagram in TikZ for the given morphism expression.

The diagram is constructed recursively, mirroring the structure of the formula.
This is achieved by nesting TikZ pictures in TikZ nodes recursively--a feature
not officially supported by TikZ but that is nonetheless in widespread use.

Warning: Since our implementation uses the `remember picture` option, LaTeX must
be run at least *twice* to fully render the picture. See (TikZ Manual,
Sec 17.13).
"""
function to_tikz(f::HomExpr;
    font_size::Number=12, line_width::String="0.4pt", math_mode::Bool=true,
    arrowtip::String="", labels::Bool=false,
    box_size::Number=2, box_style::Dict=Dict(),
    sequence_sep::Number=2, parallel_sep::Number=0.5)::TikZ.Picture
  # Parse arguments.
  # XXX: Storing the style parameters as a global variable is bad practice, but
  # passing them around is really inconvenient. Is there a better approach?
  global style = Dict(
    :math_mode => math_mode, :arrowtip => !isempty(arrowtip), :labels => labels,
    :box_size => box_size, :box_style => box_style,
    :sequence_sep => sequence_sep, :parallel_sep => parallel_sep,
  )

  # Draw input and output arrows by composing identities on either side of f.
  f_seq = collect(HomExpr, head(f) == :compose ? args(f) : [f])
  if head(f) == :id
    insert!(f_seq, 1, id(dom(f)))
  else
    if head(dom(f)) != :munit
      insert!(f_seq, 1, id(dom(f)))
    end
    if head(codom(f)) != :munit
      push!(f_seq, id(codom(f)))
    end
  end

  # Create node for extended morphism.
  box_tikz = length(f_seq) == 1 ? box("n", first(f_seq)) : sequence("n", f_seq)

  # Create picture with this single node.
  props = [
    TikZ.Property("remember picture"),
    TikZ.Property("font",
                  "{\\fontsize{$font_size}{$(round(1.2*font_size;digits=2))}}"),
    TikZ.Property("container/.style", "{inner sep=0}"),
    TikZ.Property("every path/.style", "{solid, line width=$line_width}"),
  ]
  if !isempty(arrowtip)
    decoration = "{markings, mark=at position 0.5 with {\\arrow{$arrowtip}}}"
    push!(props, TikZ.Property("decoration", decoration))
  end
  if math_mode
    append!(props, [ TikZ.Property("execute at begin node", "\$"),
                     TikZ.Property("execute at end node", "\$") ])
  end
  TikZ.Picture(box_tikz.node; props=props)
end

""" Create wires for an object expression.
"""
function wires(A::ObExpr)::Wires
  error("TikZ wires not implement for $(typeof(A))")
end

""" Create box for a morphism expression.
"""
function box(name::String, f::HomExpr)::Box
  error("TikZ box not implement for $(typeof(f))")
end

# Elements of wiring diagrams
#############################

""" A rectangle, the default style for generators.
"""
function rect(name::String, content::String, dom::Wires, codom::Wires;
              padding::String="0.333em", rounded::Bool=true)::Box
  dom_ports = box_ports(dom, name, dir="west", angle=180)
  codom_ports = box_ports(codom, name, dir="east", angle=0)
  size = box_size(max(length(dom_ports), length(codom_ports)))

  box_style = style[:box_style]
  props = [
    TikZ.Property("draw"),
    TikZ.Property("solid"),
    TikZ.Property("inner sep", get(box_style, :padding, padding)),
    TikZ.Property("rectangle"),
    TikZ.Property(get(box_style, :rounded, rounded) ?
                  "rounded corners" : "sharp corners"),
    TikZ.Property("minimum height", "$(size)em"),
  ]
  node = TikZ.Node(name; content=content, props=props)
  Box(node, dom_ports, codom_ports)
end
function rect(name::String, f::HomExpr{:generator}; kw...)::Box
  rect(name, label(f), wires(dom(f)), wires(codom(f)); kw...)
end

""" A trapezium node, the default style for generators in dagger categories.

The node content is a nested TikZ picture that contains a single visible node.
Nesting pictures even at this level may seem crazy, but it's the only way I know
to get a bounding box on the inner node, regardless of its shape, *before* it's
rendered.
"""
function trapezium(name::String, content::String, dom::Wires, codom::Wires;
                   padding::String="0.333em", rounded::Bool=true,
                   angle::Int=80, reverse::Bool=false)::Box
  dom_ports = box_ports(dom, name, dir="west", angle=180)
  codom_ports = box_ports(codom, name, dir="east", angle=0)
  size = box_size(max(length(dom_ports), length(codom_ports)))

  box_style = style[:box_style]
  props = [
    TikZ.Property("draw"),
    TikZ.Property("solid"),
    TikZ.Property("inner sep", get(box_style, :padding, padding)),
    TikZ.Property("trapezium"),
    TikZ.Property("trapezium angle", string(get(box_style, :angle, angle))),
    TikZ.Property("trapezium stretches body"),
    TikZ.Property("shape border rotate", reverse ? "90" : "270"),
    TikZ.Property(get(box_style, :rounded, rounded) ?
                  "rounded corners" : "sharp corners"),
    # Actually the height because of rotation.
    TikZ.Property("minimum width", "$(size)em"),
  ]
  node = TikZ.Node("$name box"; content=content, props=props)
  picture = TikZ.Picture(node)

  props = [ TikZ.Property("container") ]
  node = TikZ.Node(name; content=picture, props=props)
  Box(node, dom_ports, codom_ports)
end
function trapezium(name::String, f::HomExpr{:generator}; kw...)::Box
  trapezium(name, label(f), wires(dom(f)), wires(codom(f)); kw...)
end

""" A triangle node.

Supports any number of inputs and outputs, but looks best with at most one
input and at most one output.
"""
function triangle(name::String, content::String, dom::Wires, codom::Wires;
                  padding::String="0.333em", rounded::Bool=false,
                  reverse::Bool=false)::Box
  dom_ports = [ Port(w, "$name.west", angle=180) for w in dom ]
  codom_ports = [ Port(w, "$name.east", angle=0) for w in codom ]
  box_style = style[:box_style]
  props = [
    TikZ.Property("draw"),
    TikZ.Property("solid"),
    TikZ.Property("inner sep", get(box_style, :padding, padding)),
    TikZ.Property("isosceles triangle"),
    TikZ.Property("isosceles triangle stretches"),
    TikZ.Property("shape border rotate", reverse ? "180" : "0"),
    TikZ.Property(get(box_style, :rounded, rounded) ?
                  "rounded corners" : "sharp corners"),
    TikZ.Property("minimum height", "$(box_size(1))em"),
  ]
  node = TikZ.Node(name; content=content, props=props)
  Box(node, dom_ports, codom_ports)
end
function triangle(name::String, f::HomExpr{:generator}; kw...)::Box
  triangle(name, label(f), wires(dom(f)), wires(codom(f)); kw...)
end

""" Straight lines, used to draw identity morphisms.
"""
function lines(name::String, wires::Wires)::Box
  dom_ports = box_ports(wires, name, dir="center", angle=180)
  codom_ports = box_ports(wires, name, dir="center", angle=0)
  height = box_size(length(wires))
  props = [ TikZ.Property("minimum height", "$(height)em") ]
  node = TikZ.Node(name; props=props)
  Box(node, dom_ports, codom_ports)
end
lines(name::String, A::ObExpr) = lines(name, wires(A))

""" Boxes in sequence, used to draw compositions.
"""
function sequence(name::String, homs::Vector)::Box
  sequence_sep = style[:sequence_sep]
  edge_props = style[:arrowtip] ?
    [ TikZ.Property("postaction", "{decorate}") ] : []
  edge_node_props = [
    TikZ.Property("above", "0.25em"),
    TikZ.Property("midway")
  ]

  mors = [ box("$name$i", g) for (i,g) in enumerate(homs) ]
  stmts = TikZ.Statement[ mors[1].node ]
  for i = 2:length(mors)
    push!(mors[i].node.props,
          TikZ.Property("right=$(sequence_sep)em of $name$(i-1))"))
    push!(stmts, mors[i].node)
    for j = 1:length(mors[i].inputs)
      src_port = mors[i-1].outputs[j]
      tgt_port = mors[i].inputs[j]

      # Create edge node for label. We use the source port, not the target port
      # (see also `GraphvizWiring`).
      wire = src_port.wire
      node = if (style[:labels] && src_port.show_label && tgt_port.show_label)
        TikZ.EdgeNode(content=wire.label, props=edge_node_props)
      else
        nothing
      end

      # Create path operation and draw edge.
      if wire.reverse
        src_port, tgt_port = tgt_port, src_port
      end
      op = TikZ.PathOperation("to"; props=[
        TikZ.Property("out", string(src_port.angle)),
        TikZ.Property("in", string(tgt_port.angle)),
      ])
      edge = TikZ.Edge(src_port.anchor, tgt_port.anchor;
                       op=op, props=edge_props, node=node)
      push!(stmts, edge)
    end
  end

  props = [ TikZ.Property("container") ]
  node = TikZ.Node(name; content=TikZ.Picture(stmts...), props=props)
  Box(node, first(mors).inputs, last(mors).outputs)
end

""" Boxes in parallel, used to draw monoidal products.
"""
function parallel(name::String, homs::Vector)::Box
  parallel_sep = style[:parallel_sep]

  mors = []
  for (i,g) in enumerate(homs)
    mor = box("$name$i", g)
    if i > 1
      push!(mor.node.props,
            TikZ.Property("below=$(parallel_sep)em of $name$(i-1)"))
    end
    push!(mors, mor)
  end
  stmts = TikZ.Statement[ mor.node for mor in mors ]

  props = [ TikZ.Property("container") ]
  node = TikZ.Node(name; content=TikZ.Picture(stmts...), props=props)
  inputs = vcat([ mor.inputs for mor in mors]...)
  outputs = vcat([ mor.outputs for mor in mors]...)
  Box(node, inputs, outputs)
end

""" Cross two wires.

Used to draw braid morphisms in symmatric monoidal categories.
"""
function crossing(name::String, wire1::Wire, wire2::Wire)
  dom = [ Port(wire1, "$name.center", angle=135),
          Port(wire2, "$name.center", angle=225) ]
  codom = [ Port(wire2, "$name.center", angle=45),
            Port(wire1, "$name.center", angle=315) ]
  props = [
    TikZ.Property("minimum height", "$(box_size(2))em")
  ]
  node = TikZ.Node(name; props=props)
  Box(node, dom, codom)
end
crossing(name::String, A::ObExpr) = crossing(name, wires(A)...)

""" A junction of wires drawn as a circle.

Used to morphisms in internal monoids and comonoids.

Implemented using a small, visible node for the point and a big, invisible node
as a spacer. FIXME: Is there a more elegant way to achieve the desired margin?
"""
function junction_circle(name::String, wires_in::Wires, wires_out::Wires;
                         fill::Bool=true)
  dom = circle_ports(wires_in, "$name point", "west";
                     show_label = length(wires_in) <= 1)
  codom = circle_ports(wires_out, "$name point", "east";
                       show_label = length(wires_out) <= 1)
  size = box_size(max(length(dom), length(codom)))
  pic = TikZ.Picture(
    TikZ.Node("$name box"; props=[
      TikZ.Property("minimum height", "$(size)em"),
    ]),
    TikZ.Node("$name point"; props=[
      TikZ.Property("draw"),
      TikZ.Property(fill ? "fill" : "solid"),
      TikZ.Property("circle"),
      TikZ.Property("minimum size", "0.333em"),
      TikZ.Property("above", "0 of $name box.center"),
      TikZ.Property("anchor", "center"),
    ]),
  )
  node = TikZ.Node(name; content=pic, props=[TikZ.Property("container")])
  Box(node, dom, codom)
end
function junction_circle(name::String, dom::ObExpr, codom::ObExpr; kw...)
  junction_circle(name, wires(dom), wires(codom); kw...)
end
function junction_circle(name::String, f::HomExpr; kw...)
  junction_circle(name, dom(f), codom(f); kw...)
end

""" A cup.

Used to draw counit morphisms in compact closed categories.
"""
function cup(name::String, wire1::Wire, wire2::Wire)
  ports = [ Port(wire1, "$name.center", angle=90, show_label=false),
            Port(wire2, "$name.center", angle=270, show_label=false) ]
  props = [
    TikZ.Property("minimum height", "$(box_size(2))em")
  ]
  node = TikZ.Node(name; props=props)
  Box(node, ports, [])
end
cup(name::String, A::ObExpr) = cup(name, wires(A)...)

""" A cap.

Used to draw unit morphisms in compact closed categories.
"""
function cap(name::String, wire1::Wire, wire2::Wire)
  ports = [ Port(wire1, "$name.center", angle=90, show_label=false),
            Port(wire2, "$name.center", angle=270, show_label=false) ]
  props = [
    TikZ.Property("minimum height", "$(box_size(2))em")
  ]
  node = TikZ.Node(name; props=props)
  Box(node, [], ports)
end
cap(name::String, A::ObExpr) = cap(name, wires(A)...)

# Helper functions
##################

""" Compute the size of a box from the number of its ports.

We use the unique formula consistent with the monoidal product, meaning that
the size of a product of generator boxes depends only on the total number of
ports, not the number of generators.
"""
function box_size(ports::Int)::Number
  m = max(1, ports)
  m * style[:box_size] + (m-1) * style[:parallel_sep]
end

""" Create ports for a rectangular box.

This mainly involves computing the locations of anchors. The anchors are
consistent with the monoidal product (see `box_size`).
"""
function box_ports(wires::Wires, name::String;
                   dir::String="center", kw...)::Vector{Port}
  box_size, parallel_sep = style[:box_size], style[:parallel_sep]
  m = length(wires)
  if m == 0
    return []
  elseif m == 1
    return [ Port(wires[1], "$name.$dir"; kw...) ]
  end

  result = []
  start = (m*box_size + (m-1)*parallel_sep) / 2
  for (i,wire) in enumerate(wires)
    offset = box_size/2 + (i-1)*(box_size+parallel_sep)
    anchor = "\$($name.$dir)+(0,$(start-offset)em)\$"
    push!(result, Port(wire, anchor; kw...))
  end
  return result
end

""" Create ports for a circular node.
"""
function circle_ports(wires::Wires, name::String, dir::String;
                      kw...)::Vector{Port}
  @assert dir in ("west", "east")
  m = length(wires)
  angles = round.(Int, range(0,stop=180,length=m+2)[2:end-1])
  if dir == "west"
    angles = mod.(angles .+ 90, 360)
  elseif dir == "east"
    angles = reverse(mod.(angles .- 90, 360))
  end
  Port[ Port(wire, "$name.$angle"; angle=angle, kw...)
            for (wire, angle) in zip(wires, angles) ]
end

# Defaults
##########

# These methods are reasonable to define for the base expression types since
# they will rarely be changed.

wires(A::ObExpr{:generator}) = [ Wire(label(A)) ]
wires(A::ObExpr{:munit}) = Wire[]
wires(A::ObExpr{:otimes}) = vcat(map(wires, args(A))...)

box(name::String, f::HomExpr{:id}) = lines(name, dom(f))
box(name::String, f::HomExpr{:compose}) = sequence(name, args(f))
box(name::String, f::HomExpr{:otimes}) = parallel(name, args(f))
box(name::String, f::HomExpr{:braid}) = crossing(name, dom(f))

function label(expr::GATExpr{:generator})::String
  sprint(style[:math_mode] ? show_latex : show, expr)
end

""" Default renderers for specific syntax systems.
"""
module Defaults
  import ..TikZWiringDiagrams: Wire, box, wires, label, rect, trapezium,
    junction_circle, cup, cap
  using Catlab.Doctrines
  using Catlab.Syntax

  generator(expr::GATExpr)::GATExpr{:generator} = first(expr)

  # Category
  box(name::String, f::FreeCategory.Hom{:generator}) = rect(name, f)

  # Dagger category
  # Assumes that daggers are fully distributed (as in this syntax system).
  Syntax = FreeDaggerCategory
  box(name::String, f::Syntax.Hom{:generator}) = trapezium(name, f)
  box(name::String, f::Syntax.Hom{:dagger}) = trapezium(name,
    label(generator(f)), wires(dom(f)), wires(codom(f)); reverse=true)

  # Symmetric monoidal category
  Syntax = FreeSymmetricMonoidalCategory
  box(name::String, f::Syntax.Hom{:generator}) = rect(name, f)

  # (Co)cartesian category
  Syntax = FreeCartesianCategory
  box(name::String, f::Syntax.Hom{:generator}) = rect(name, f)
  box(name::String, f::Syntax.Hom{:mcopy}) = junction_circle(name, f)
  box(name::String, f::Syntax.Hom{:delete}) = junction_circle(name, f)

  Syntax = FreeCocartesianCategory
  box(name::String, f::Syntax.Hom{:generator}) = rect(name, f)
  box(name::String, f::Syntax.Hom{:mmerge}) = junction_circle(name, f)
  box(name::String, f::Syntax.Hom{:create}) = junction_circle(name, f)

  # Biproduct category
  Syntax = FreeBiproductCategory
  box(name::String, f::Syntax.Hom{:generator}) = rect(name, f)
  box(name::String, f::Syntax.Hom{:mcopy}) = junction_circle(name, f)
  box(name::String, f::Syntax.Hom{:delete}) = junction_circle(name, f)
  box(name::String, f::Syntax.Hom{:mmerge}) = junction_circle(name, f)
  box(name::String, f::Syntax.Hom{:create}) = junction_circle(name, f)

  # Compact closed category
  # Assumes that duals are fully distributed (as in this syntax system).
  Syntax = FreeCompactClosedCategory
  wires(A::Syntax.Ob{:dual}) = [ Wire(label(generator(A)); reverse=true) ]
  box(name::String, f::Syntax.Hom{:generator}) = rect(name, f)
  box(name::String, f::Syntax.Hom{:dunit}) = cap(name, codom(f))
  box(name::String, f::Syntax.Hom{:dcounit}) = cup(name, dom(f))

  # Bicategory of relations
  Syntax = FreeBicategoryRelations
  box(name::String, f::Syntax.Hom{:generator}) = trapezium(name, f)
  box(name::String, f::Syntax.Hom{:dagger}) = trapezium(name,
    label(generator(f)), wires(dom(f)), wires(codom(f)); reverse=true)
  box(name::String, f::Syntax.Hom{:dunit}) = cap(name, codom(f))
  box(name::String, f::Syntax.Hom{:dcounit}) = cup(name, dom(f))
  box(name::String, f::Syntax.Hom{:mcopy}) = junction_circle(name, f; fill=false)
  box(name::String, f::Syntax.Hom{:delete}) = junction_circle(name, f; fill=false)
  box(name::String, f::Syntax.Hom{:mmerge}) = junction_circle(name, f; fill=false)
  box(name::String, f::Syntax.Hom{:create}) = junction_circle(name, f; fill=false)

  Syntax = FreeAbelianBicategoryRelations
  box(name::String, f::Syntax.Hom{:generator}) = trapezium(name, f)
  box(name::String, f::Syntax.Hom{:dagger}) = trapezium(name,
    label(generator(f)), wires(dom(f)), wires(codom(f)); reverse=true)
  box(name::String, f::Syntax.Hom{:dunit}) = cap(name, codom(f))
  box(name::String, f::Syntax.Hom{:dcounit}) = cup(name, dom(f))
  box(name::String, f::Syntax.Hom{:mcopy}) = junction_circle(name, f; fill=false)
  box(name::String, f::Syntax.Hom{:delete}) = junction_circle(name, f; fill=false)
  box(name::String, f::Syntax.Hom{:mmerge}) = junction_circle(name, f; fill=false)
  box(name::String, f::Syntax.Hom{:create}) = junction_circle(name, f; fill=false)
  box(name::String, f::Syntax.Hom{:plus}) = junction_circle(name, f; fill=true)
  box(name::String, f::Syntax.Hom{:zero}) = junction_circle(name, f; fill=true)
  box(name::String, f::Syntax.Hom{:coplus}) = junction_circle(name, f; fill=true)
  box(name::String, f::Syntax.Hom{:cozero}) = junction_circle(name, f; fill=true)
end

end


# Interface Concorde / Julia

function versTSPLIB(instance)
    sortie = open("temp/instance_TSPLIB", "w")
    write(sortie, "NAME : none")
    write(sortie, "\nCOMMENT : none")
    write(sortie, "\nTYPE : TSP")
    write(sortie, "\nDIMENSION : "*string(instance.nbPoints))
    write(sortie, "\nEDGE_WEIGHT_TYPE : EUC_2D")
    write(sortie, "\nNODE_COORD_SECTION")
    for i in 1:instance.nbPoints
        # On multiplie par 1000 car Concorde ne gère pas les flottants, cela évite donc de se retrouver avec des erreurs liées aux arrondies
        # Les distances sur certaines instances peuvent en effet être sensiblement proches
        write(sortie, "\n"*string(i)*" "*string(instance.listePoints[i].x*1000)*" "*string(instance.listePoints[i].y*1000))
    end
    write(sortie, "\nEOF")
    close(sortie);
end

function lectureSolutionLINKERN()
    # On lit la solution dans le fichier de sortie après exécution programme linkern
    f = open("temp/res")
    lignes = readlines(f)
    ordreVisite = []
    push!(ordreVisite, 1)
    for i in 2:size(lignes,1)
       push!(ordreVisite, parse.(Int64, split(lignes[i])[2]) + 1)
    end
    close(f)
    return ordreVisite
end

function lectureSolutionCONCORDE()
    # On lit la solution dans le fichier de sortie après exécution programme concorde
    f = open("temp/res")
    lignes = readlines(f)
    ordreVisite = []
    l = 2
    n = 1
    for i in 1:parse.(Int64, lignes[1])
       push!(ordreVisite, parse.(Int64, split(lignes[l])[n]) + 1)
       if mod(i,10) == 0 # 10 noeuds par ligne
           l += 1
           n = 1
       else
           n += 1
       end
    end
    push!(ordreVisite, 1)
    close(f)
    return ordreVisite
end

function resolutionConcorde(instance)
    asciiArtAvion()
    versTSPLIB(instance) # On convertit l'instance courante vers le format TSPLIB (compatible Concorde)

    println("\n Choix du type de résolution (TSP) :")
    println(" -----------------------------------")
    println("    --> 1) Exacte")
    println("    --> 2) Approchée : Lin-Kernighan (à personnaliser)")
    println("    --> 3) Approchée : Lin-Kernighan (défaut)")
    choix = choixListe(3)
    if choix == "1"
         run(pipeline(`./concorde/concorde -o temp/res temp/instance_TSPLIB`, stdout=devnull, stderr=devnull))
         ordreVisite = lectureSolutionCONCORDE()
         instance.codeResolution = 1
    elseif choix == "2"
         println("\n Construction initiale : ")
         println(" -----------------------")
         println("    --> 1) Aléatoire")
         println("    --> 2) Nearest-Neighbor")
         println("    --> 3) Glouton")
         println("    --> 4) Boruvka")
         println("    --> 5) Quick-Boruvka")
         choix = choixListe(5)
         constructionInitiale = string((parse(Int64, choix)-1))
         # Note : on repasse le choix en int pour y soustraire une unité et correspondre aux choix proposés par le programme linkern
         # 0 : Aléatoire, 1 : Nearest-Neighbor, 2 : Glouton, 3 : Boruvka, 4 : Quick-Boruvka
         # On repasse ensuite en string pour personnaliser la commande ci-dessous

         println("\n Choix du kick : ")
         println(" ---------------")
         println("    --> 1) Aléatoire")
         println("    --> 2) Géométrique")
         println("    --> 3) Close")
         println("    --> 4) Marche Aléatoire")
         choix = choixListe(4)
         kick = string((parse(Int64, choix)-1))

         print("\n Temps limite de résolution (secondes) : ")
         tempsLimite = readline()
         tempsLimite = parse(Int64, tempsLimite)

         run(pipeline(`./concorde/linkern -I $constructionInitiale -K $kick -t $tempsLimite -o temp/res temp/instance_TSPLIB`, stdout=devnull, stderr=devnull)) # On sauvegarde le résultat de la résolution dans le fichier temporaire res et on ne conserve pas l'affichage textuelle
         ordreVisite = lectureSolutionLINKERN()
         instance.codeResolution = 2
    elseif choix == "3"
        # Ici on exécute Lin-Kernighan avec les choix par défaut
        # Quick-boruvka + Random_walk kicks
        # Nombre de kicks : nombre de noeuds
        run(pipeline(`./concorde/linkern -o temp/res temp/instance_TSPLIB`, stdout=devnull, stderr=devnull))
        ordreVisite = lectureSolutionLINKERN()
        instance.codeResolution = 2
    end
    run(`./clean.sh`) # On supprime les fichiers résiduels
    # Note : on ne peut pas utiliser le pattern * (propre au shell) avec la fonction run(), il semblerait que cela va être implémenté dans les prochaines versions de Julia (vu sur discourse.julialang.org : "using * as a wildcard in backtick commands").
    # Pour cette raison, on passe par un script Shell annexe pour supprimer les fichiers temporaires
    solution = Solution(ordreVisite, [], coutTour(ordreVisite, instance), 0)
    instance.solution = solution
end

function asciiArtAvion()
    println("")
    println(" _____         ______  ")
    println("| : \\         |    \\   ")
    println("| :  `\\______|______\\_______ ")
    println(" \\'______    Concorde  \\_____\\_____ ")
    println("   \\____/-)_,---------,_____________>-- ")
    println("             \\       / ")
    println("              |     / ")
    println("              |____/__  ")
    println("")
end

########## Fonctions rapides (sans affichage ni lecture utilisateur) ##########

function concordeFast(instance) # 0 verbosité
    versTSPLIB(instance)
    run(pipeline(`./concorde/concorde -o temp/res temp/instance_TSPLIB`, stdout=devnull, stderr=devnull))
    ordreVisite = lectureSolutionCONCORDE()
    instance.codeResolution = 1
    run(`./clean.sh`) # On supprime les fichiers résiduels
    solution = Solution(ordreVisite, [], coutTour(ordreVisite, instance), 0)
    instance.solution = solution
end

function lkhFast(instance) # 0 verbosité
    versTSPLIB(instance)
    run(pipeline(`./concorde/linkern -o temp/res temp/instance_TSPLIB`, stdout=devnull, stderr=devnull))
    ordreVisite = lectureSolutionLINKERN()
    instance.codeResolution = 2
    run(pipeline(`./clean.sh`, stderr=devnull)) # On supprime les fichiers résiduels
    solution = Solution(ordreVisite, [], coutTour(ordreVisite, instance), 0)
    instance.solution = solution
end