Extensions

JuMP provides a variety of ways to extend the basic modeling functionality.

Define a new set

To define a new set for JuMP, subtype MOI.AbstractScalarSet or MOI.AbstractVectorSet and implement Base.copy for the set. That's it!

struct _NewVectorSet <: MOI.AbstractVectorSet
    dimension::Int
end
Base.copy(x::_NewVectorSet) = x

model = Model()
@variable(model, x[1:2])
@constraint(model, x in _NewVectorSet(2))

# output

[x[1], x[2]] ∈ _NewVectorSet(2)

However, for vector-sets, this requires the user to specify the dimension argument to their set, even though we could infer it from the length of x!

You can make a more user-friendly set by subtyping AbstractVectorSet and implementing moi_set.

struct NewVectorSet <: JuMP.AbstractVectorSet end
JuMP.moi_set(::NewVectorSet, dim::Int) = _NewVectorSet(dim)

model = Model()
@variable(model, x[1:2])
@constraint(model, x in NewVectorSet())

# output

[x[1], x[2]] ∈ _NewVectorSet(2)

Extend @variable

Just as Bin and Int create binary and integer variables, you can extend the @variable macro to create new types of variables. Here is an explanation by example, where we create a AddTwice type, that creates a tuple of two JuMP variables instead of a single variable.

First, create a new struct. This can be anything. Our struct holds a VariableInfo object that stores bound information, and whether the variable is binary or integer.

julia> struct AddTwice
           info::JuMP.VariableInfo
       end

Second, implement build_variable, which takes ::Type{AddTwice} as an argument, and returns an instance of AddTwice. Note that you can also receive keyword arguments.

julia> function JuMP.build_variable(
           _err::Function,
           info::JuMP.VariableInfo,
           ::Type{AddTwice};
           kwargs...
       )
           println("Can also use $kwargs here.")
           return AddTwice(info)
       end

Third, implement add_variable, which takes the instance of AddTwice from the previous step, and returns something. Typically, you will want to call add_variable here. For example, our AddTwice call is going to add two JuMP variables.

julia> function JuMP.add_variable(
           model::JuMP.Model,
           duplicate::AddTwice,
           name::String,
       )
           a = JuMP.add_variable(
               model,
               JuMP.ScalarVariable(duplicate.info),
               name * "_a",
            )
           b = JuMP.add_variable(
               model,
               JuMP.ScalarVariable(duplicate.info),
               name * "_b",
            )
           return (a, b)
       end

Now AddTwice can be passed to @variable just like Bin or Int. However, now it adds two variables instead of one!

julia> model = Model();

julia> @variable(model, x[i=1:2], AddTwice, kw=i)
Can also use Base.Iterators.Pairs(:kw=>1) here.
Can also use Base.Iterators.Pairs(:kw=>2) here.
2-element Array{Tuple{VariableRef,VariableRef},1}:
 (x[1]_a, x[1]_b)
 (x[2]_a, x[2]_b)

julia> num_variables(model)
4

julia> first(x[1])
x[1]_a

julia> last(x[2])
x[2]_b

Extend @constraint

The @constraint macro always calls the same three functions:

  • parse_constraint: is called at parsing time, it parses the constraint expression and returns a build_constraint call expression;
  • build_constraint: given the functions and sets involved in the constraints, it returns a AbstractConstraint;
  • add_constraint: given the model, the AbstractConstraint constructed in build_constraint and the constraint name, it stores them in the model and returns a ConstraintRef.

Adding methods to these functions is the recommended way to extend the @constraint macro.

Adding parse_constraint methods

Work in progress.

Adding build_constraint methods

There are typically two choices when creating a build_constraint method, either return an AbstractConstraint already supported by the model, i.e. ScalarConstraint or VectorConstraint, or a custom AbstractConstraint with a corresponding add_constraint method (see Adding add_constraint methods).

Adding add_constraint methods

Work in progress.

Adding an extra positional argument

We can also extend @constraint to handle additional positional arguments that effectively "tag" a particular constraint type and/or pass along additional information that we may want. For example, we can make a MyConstrType that modifies affine equalities:

julia> model = Model(); @variable(model, x);

julia> struct MyConstrType end

julia> function JuMP.build_constraint(
            _error::Function,
            f::JuMP.GenericAffExpr,
            set::MOI.EqualTo,
            extra::Type{MyConstrType};
            d = 0,
       )
            new_set = MOI.LessThan(set.value + d)
            return JuMP.build_constraint(_error, f, new_set)
       end

julia> @constraint(model, my_con, x == 0, MyConstrType, d = 2)
my_con : x ≤ 2.0

Note that only a single positional argument can be given to a particular constraint. Extensions that seek to pass multiple arguments (e.g., Foo and Bar) should combine them into one argument type (e.g., FooBar).

Shapes

Shapes allow vector constraints, which are represented as flat vectors in MOI, to retain a matrix shape at the JuMP level. There is a shape field in VectorConstraint that can be set in build_constraint and that is used to reshape the result computed in value and dual.

Extend @objective

Work in progress.

Adding a bridge

Work in progress.

Defining new JuMP models

Work in progress.