Defining a new set

The easiest way to extend the behavior of MathOptInterface is to design a new set. The purpose of this tutorial is to explain how to define a new set and some of the associated nuances.

Defining a new function is an order of magnitude (if not two) harder than defining a new set. Don't consider it as an option unless you have already tried to support your behavior as a set.

As a motivation for this tutorial, we consider the LinMax constraint type, which appears often in constraint programming:

\[t = \max(f_1(x), f_2(x), \ldots, f_N(x))\]

The first step to design a new set for MathOptInterface is to define the mathematical relationship you want to model as a function-in-set $f(x) \in S$.

Your initial thought for representing the LinMax constraint in MathOptInterface may be to represent it as:

\[F(x) \in LinMax(t)\]

where $F(x)$ is the vector-valued function created by concatenating the $f_i$ functions. This formulation violates a basic rule of MathOptInterface:

Rule

Sets cannot contain references to decision variables or other constraints.

Instead, we could model the LinMax constraint as:

\[[t, f_1(x), f_2(x), \ldots, f_N(x)] \in LinMax(N+1)\]

In the language of MathOptInterface, this is a AbstractVectorFunction in the LinMax set of dimension $N+1$. The type of the function depends on the types of the component scalar functions, with a special convention that the first element in the function is interpretable as a VariableIndex t.

Now LinMax can be trivially defined as a new AbstractVectorSet:

julia> import MathOptInterface as MOIjulia> struct LinMax <: MOI.AbstractVectorSet           dimension::Int       end

and it can immediately be used in MathOptInterface:

julia> model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}());julia> t = MOI.VariableIndex(1);julia> x = MOI.VariableIndex.(2:3);julia> F = 1.0 .* x .+ 2.0;julia> g = MOI.Utilities.operate(vcat, Float64, t, F...);julia> MOI.add_constraint(model, g, LinMax(3))MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64}, LinMax}(1)julia> print(model)FeasibilitySubject to:VectorAffineFunction{Float64}-in-LinMax ┌              ┐ │0.0 + 1.0 v[1]│ │2.0 + 1.0 v[2]│ │2.0 + 1.0 v[3]│ └              ┘ ∈ LinMax(3)