Constraints

# Constraints

This page explains how to write various types of constraints in JuMP. Before reading further, please make sure you are familiar with JuMP models, and JuMP Variables. For nonlinear constraints, see Nonlinear Modeling instead.

JuMP is based on the MathOptInterface (MOI) API. Because of this, JuMP thinks of a constraint as the restriction that the output of a function belongs to a set. For example, instead of representing a constraint $a^\top x \le b$ as a less-than-or-equal-to constraint, JuMP models this as the scalar affine function $a^\top x$ belonging to the less-than set $(-\infty, b]$. Thus, instead of a less-than-or-equal-to constraint, we consider this constraint to be a scalar affine -in- less than constraint. More generally, we use the shorthand function-in-set to refer to constraints composed of different types of functions and sets. In the rest of this page, we will introduce the different types of functions and sets that JuMP knows about as needed. You can read more details about this function-in-set concept in the MOI documentation.

Note

The examples use MOI as an alias for the MathOptInterface module. This alias is defined by using JuMP. You may also define it in your code by

import MathOptInterface
const MOI = MathOptInterface

## The @constraint macro

Constraints are added to a JuMP model using the @constraint macro. Here is an example of how to add the constraint $2x \le 1$ to a JuMP model:

julia> @constraint(model, con, 2x <= 1)
con : 2 x <= 1.0

Wasn't that easy! Let's unpack what happened, because just like @variable there are a few subtle things going on.

1. The mathematical constraint $2x \le 1$ was added to the model.
2. A Julia variable called con was created that is a reference to the constraint.
3. This Julia variable was stored in model and can be accessed by model[:con].
4. JuMP set the name attribute (the one that is shown when printing) of the constraint to "con".

Just like the Julia variables created in @variable, con can be bound to a different value. For example:

julia> con
con : 2 x <= 1.0

julia> con = 1
1

julia> con
1

However, the reference can be retrieved by querying the model using the symbolic name:

julia> con = model[:con]
con : 2 x <= 1.0

julia> con
con : 2 x <= 1.0

Because the named variables and constraints are stored in the same namespace, creating a constraint with the same name as a variable or an existing constraint will result in an error. To overcome this limitation, it is possible to create anonymous constraints, just like it is possible to create Anonymous JuMP variables. This is done by dropping the second argument to @constraint:

julia> con = @constraint(model, 2x <= 1)
2 x <= 1.0

It is also possible use different comparison operators (e.g., >= and ==) to create the following types of constraints:

julia> @constraint(model, 2x >= 1)
2 x >= 1.0

julia> @constraint(model, 2x == 1)
2 x = 1.0

julia> @constraint(model, 1 <= 2x <= 3)
2 x ∈ [1.0, 3.0]

Note that JuMP normalizes the constraints by moving all of the terms containing variables to the left-hand side, and all of the constant terms to the right-hand side. Thus, we get:

julia> @constraint(model, 2x + 1 <= 4x + 4)
-2 x <= 3.0

## The @constraints macro

Like @variables, there is a "plural" version of the @constraint macro:

julia> @constraints(model, begin
2x <=  1
x >= -1
end)

julia> print(model)
Feasibility
Subject to
x ≥ -1.0
2 x ≤ 1.0

## Duality

JuMP adopts the notion of conic duality from MOI. For linear programs, a feasible dual on a >= constraint is nonnegative and a feasible dual on a <= constraint is nonpositive. If the constraint is an equality constraint, it depends on which direction is binding.

Note

JuMP's definition of duality is independent of the objective sense. That is, the sign of feasible duals associated with a constraint depends on the direction of the constraint and not whether the problem is maximization or minimization. This is a different convention from linear programming duality in some common textbooks.

The dual value associated with a constraint in the most recent solution can be accessed using the dual function. You can use the has_duals function to check whether the model has a dual solution available to query. For example:

julia> model = Model();

julia> @variable(model, x)
x

julia> @constraint(model, con, x <= 1)
con : x <= 1.0

julia> has_duals(model)
false
julia> @objective(model, Min, -2x)
-2 x

julia> optimize!(model)

julia> has_duals(model)
true

julia> dual(con)
-2.0

julia> @objective(model, Max, 2x)
2 x

julia> optimize!(model)

julia> dual(con)
-2.0

To help users who may be less familiar with conic duality, JuMP provides the shadow_price function which returns a value that can be interpreted as the improvement in the objective in response to an infinitesimal relaxation (on the scale of one unit) in the right-hand side of the constraint. shadow_price can be used only on linear constraints with a <=, >=, or == comparison operator.

In the example above, dual(con) returned -2.0 regardless of the optimization sense. However, in the second case when the optimization sense is Max, shadow_price returns:

julia> shadow_price(con)
2.0

To query the dual variables associated with a variable bound, first obtain a constraint reference using one of UpperBoundRef, LowerBoundRef, or FixRef, and then call dual on the returned constraint reference. Note that in linear programming, the duals on variable bounds are also called the reduced costs (although the sign might differ from the one you expect).

## Constraint names

The name, i.e. the value of the MOI.ConstraintName attribute, of a constraint can be obtained by name(::JuMP.ConstraintRef) and set by set_name(::JuMP.ConstraintRef, ::String).

name(con_ref::ConstraintRef)

Get a constraint's name attribute.

set_name(con_ref::ConstraintRef, s::AbstractString)

Set a constraint's name attribute.

The constraint can also be retrieved from its name using constraint_by_name.

constraint_by_name(model::AbstractModel,
name::String)::Union{ConstraintRef, Nothing}

Returns the reference of the constraint with name attribute name or Nothing if no constraint has this name attribute. Throws an error if several constraints have name as their name attribute.

constraint_by_name(model::AbstractModel,
name::String,
F::Type{<:Union{AbstractJuMPScalar,
Vector{<:AbstractJuMPScalar},
MOI.AbstactFunction}},
S::Type{<:MOI.AbstractSet})::Union{ConstraintRef, Nothing}

Similar to the method above, except that it throws an error if the constraint is not an F-in-S contraint where F is either the JuMP or MOI type of the function, and S is the MOI type of the set. This method is recommended if you know the type of the function and set since its returned type can be inferred while for the method above (i.e. without F and S), the exact return type of the constraint index cannot be inferred.

julia> using JuMP

julia> model = Model()
A JuMP Model
Feasibility problem with:
Variables: 0
Model mode: AUTOMATIC
CachingOptimizer state: NO_OPTIMIZER
Solver name: No optimizer attached.

julia> @variable(model, x)
x

julia> @constraint(model, con, x^2 == 1)
con : x² = 1.0

julia> constraint_by_name(model, "kon")

julia> constraint_by_name(model, "con")
con : x² = 1.0

julia> constraint_by_name(model, "con", AffExpr, MOI.EqualTo{Float64})

julia> constraint_by_name(model, "con", QuadExpr, MOI.EqualTo{Float64})
con : x² = 1.0

## Constraint containers

So far, we've added constraints one-by-one. However, just like Variable containers, JuMP provides a mechanism for building groups of constraints compactly. References to these groups of constraints are returned in containers. Three types of constraint containers are supported: Arrays, DenseAxisArrays, and SparseAxisArrays. We explain each of these in the following.

### Arrays

One way of adding a group of constraints compactly is the following:

julia> @constraint(model, con[i = 1:3], i * x <= i + 1)
3-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},1}:
con[1] : x <= 2.0
con[2] : 2 x <= 3.0
con[3] : 3 x <= 4.0

JuMP returns references to the three constraints in an Array that is bound to the Julia variable con. This array can be accessed and sliced as you would with any Julia array:

julia> con[1]
con[1] : x <= 2.0

julia> con[2:3]
2-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},1}:
con[2] : 2 x <= 3.0
con[3] : 3 x <= 4.0

Anonymous containers can also be constructed by dropping the name (e.g. con) before the square brackets:

julia> @constraint(model, [i = 1:2], i * x <= i + 1)
2-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},1}:
x <= 2.0
2 x <= 3.0

Just like @variable, JuMP will form an Array of constraints when it can determine at parse time that the indices are one-based integer ranges. Therefore con[1:b] will create an Array, but con[a:b] will not. A special case is con[Base.OneTo(n)] which will produce an Array. If JuMP cannot determine that the indices are one-based integer ranges (e.g., in the case of con[a:b]), JuMP will create a DenseAxisArray instead.

### DenseAxisArrays

The syntax for constructing a DenseAxisArray of constraints is very similar to the syntax for constructing a DenseAxisArray of variables.

julia> @constraint(model, con[i = 1:2, j = 2:3], i * x <= j + 1)
2-dimensional DenseAxisArray{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},2,...} with index sets:
Dimension 1, Base.OneTo(2)
Dimension 2, 2:3
And data, a 2×2 Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},2}:
con[1,2] : x <= 3.0    con[1,3] : x <= 4.0
con[2,2] : 2 x <= 3.0  con[2,3] : 2 x <= 4.0

### SparseAxisArrays

The syntax for constructing a SparseAxisArray of constraints is very similar to the syntax for constructing a SparseAxisArray of variables.

julia> @constraint(model, con[i = 1:2, j = 1:2; i != j], i * x <= j + 1)
JuMP.Containers.SparseAxisArray{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},2,Tuple{Int64,Int64}} with 2 entries:
[1, 2]  =  con[1,2] : x <= 3.0
[2, 1]  =  con[2,1] : 2 x <= 2.0

### Forcing the container type

When creating a container of constraints, JuMP will attempt to choose the tightest container type that can store the constraints. However, because this happens at parse time, it does not always make the best choice. Just like in @variable, we can force the type of container using the container keyword. For syntax and the reason behind this, take a look at the variable docs.

## Vectorized constraints

We can also add constraints to JuMP using vectorized linear algebra. For example:

julia> @variable(model, x[i=1:2])
2-element Array{VariableRef,1}:
x[1]
x[2]

julia> A = [1 2; 3 4]
2×2 Array{Int64,2}:
1  2
3  4

julia> b = [5, 6]
2-element Array{Int64,1}:
5
6

julia> @constraint(model, con, A * x .== b)
2-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.EqualTo{Float64}},ScalarShape},1}:
x[1] + 2 x[2] == 5.0
3 x[1] + 4 x[2] == 6.0
Note

Make sure to use Julia's dot syntax in front of the comparison operators (e.g. .==, .>=, and .<=). If you use a comparison without the dot, an error will be thrown.

Instead of adding an array of ScalarAffineFunction-in-EqualTo constraints, we can instead construct a VectorAffineFunction-in-Nonnegatives constraint as follows:

julia> @constraint(model, A * x - b in MOI.Nonnegatives(2))
[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] in MathOptInterface.Nonnegatives(2)

In addition to the Nonnegatives set, MOI defines a number of other vector-valued sets such as Nonpositives. See the MOI documentation for more information.

Note also that for the first time we have used an explicit function-in-set description of the constraint. Read more about this representation for constraints in the MOI documentation.

## Constraints on a single variable

In Variables, we saw how to modify the variable bounds, as well as add binary and integer restrictions to the domain of each variable. This can also be achieved using the @constraint macro. For example, MOI.ZeroOne() restricts the domain to $\{0, 1\}$:

julia> @constraint(model, x in MOI.ZeroOne())
x binary

and MOI.Integer() restricts to the domain to the integers $\mathbb{Z}$:

julia> @constraint(model, x in MOI.Integer())
x integer

JuMP also supports modeling semi-continuous variables, whose domain is $\{0\} ∪ [l, u]$, using the MOI.Semicontinuous set:

julia> @constraint(model, x in MOI.Semicontinuous(1.5, 3.5))
x in MathOptInterface.Semicontinuous{Float64}(1.5, 3.5)

as well as semi-integer variables, whose domain is $\{0\} ∪ \{l, l+1, \dots, u\}$, using the MOI.Semiinteger set:

julia> @constraint(model, x in MOI.Semiinteger(1.0, 3.0))
x in MathOptInterface.Semiinteger{Float64}(1.0, 3.0)

In addition to affine functions, JuMP also supports constraints with quadratic terms. (For more general nonlinear functions, see Nonlinear Modeling.) For example:

julia> @variable(model, x[i=1:2])
2-element Array{VariableRef,1}:
x[1]
x[2]

julia> @variable(model, t >= 0)
t

julia> @constraint(model, x[1]^2 + x[2]^2 <= t^2)
x[1]² + x[2]² - t² <= 0.0

Note that this quadratic constraint (including the lower bound on t) is equivalent to a second order cone constraint where ||x[1]^2 + x[2]^2||\_2 ≤ t and t ≥ 0. Instead of writing out the quadratic expansion, we can pass JuMP the constraint in function-in-set form. To do so, we need to define the function and the set.

The function is a vector of variables:

julia> [t, x[1], x[2]]
3-element Array{VariableRef,1}:
t
x[1]
x[2]

Note that the variable t comes first, followed by the x arguments. The set is an instance of SecondOrderCone: SecondOrderCone(). Thus, we can add the second order cone constraint as follows:

julia> @constraint(model, [t, x[1], x[2]] in SecondOrderCone())
[t, x[1], x[2]] in MathOptInterface.SecondOrderCone(3)

JuMP also supports the RotatedSecondOrderCone which requires the addition of a perspective variable u. The rotated second order cone constraints the variables t, u, and x such that: ||x[1]^2 + x[2]^2||\_2 ≤ t × u and t, u ≥ 0. It can be added as follows:

julia> @variable(model, u)
u

julia> @constraint(model, [t, u, x[1], x[2]] in RotatedSecondOrderCone())
[t, u, x[1], x[2]] in MathOptInterface.RotatedSecondOrderCone(4)

In addition to the second order cone and rotated second order cone, MOI defines a number of other conic sets such as the exponential and power cones. See the MathOptInterface documentation for more information.

## Constraints on a collection of variables

In addition to constraining the domain of a single variable, JuMP supports placing constraints of a subset of the variables. We already saw an example of this in the Quadratic constraints section when we constrained a vector of variables to belong to the second order cone.

In a special ordered set of type I (often denoted SOS-I), at most one variable can take a non-zero value. We can construct SOS-I constraints using the MOI.SOS1 set:

julia> @variable(model, x[1:3])
3-element Array{VariableRef,1}:
x[1]
x[2]
x[3]

julia> @constraint(model, x in MOI.SOS1([1.0, 2.0, 3.0]))
[x[1], x[2], x[3]] in MathOptInterface.SOS1{Float64}([1.0, 2.0, 3.0])

Note that we have to pass MOI.SOS1 a weight vector. This vector implies an ordering on the variables. If the decision variables are related and have a physical ordering (e.g., they correspond to the size of a factory to be built, and the SOS-I constraint enforces that only one factory can be built), then the weight vector, although not used directly in the constraint, can help the solver make a better decision in the solution process.

This ordering is more important in a special ordered set of type II (SOS-II), in which at most two values can be non-zero, and if there are two non-zeros, they must be consecutive according to the ordering. For example, in the following constraint, the possible non-zero pairs are (x[1] and x[3]) and (x[2] and x[3]):

julia> @constraint(model, x in MOI.SOS2([3.0, 1.0, 2.0]))
[x[1], x[2], x[3]] in MathOptInterface.SOS2{Float64}([3.0, 1.0, 2.0])

## Indicator constraints

JuMP provides a special syntax for creating indicator constraints, that is, enforce a constraint to hold depending on the value of a binary variable. In order to constrain the constraint x + y <= 1 to hold when a binary variable a is one, use the following syntax:

julia> @variable(model, x)
x

julia> @variable(model, y)
y

julia> @variable(model, a, Bin)
a

julia> @constraint(model, a => {x + y <= 1})
a => {x + y ≤ 1.0}

If instead the constraint should hold when a is zero, simply add a ! or ¬ before the binary variable.

julia> @constraint(model, !a => {x + y <= 1})
!a => {x + y ≤ 1.0}

## Semidefinite constraints

JuMP provides a special syntax for constraining a matrix to be symmetric positive semidefinite (PSD) with the @SDconstraint macro. In the context of this macro, the inequality A >= B between two square matrices A and B is understood as constraining A - B to be symmetric positive semidefinite.

julia> @variable(model, x)
x

julia> @SDconstraint(model, [x 2x; 3x 4x] >= ones(2, 2))
[x - 1    2 x - 1;
3 x - 1  4 x - 1] ∈ PSDCone()

Solvers supporting such constraints usually expect to be given a matrix that is symbolically symmetric, that is, for which the expression in corresponding off-diagonal entries are the same. In our example, the expressions of entries (1, 2) and (2, 1) are respectively 2x - 1 and 3x - 1 which are different. To bridge the gap between the constraint modeled and what the solver expects, JuMP creates an equality constraint 3x - 1 == 2x - 1 and constrains the symmetric matrix [x - 1, 2 x - 1, 2 x - 1, 4 x - 1] to be positive semidefinite.

Note

If the matrix provided is already symbolically symmetric, the equality constrains are equivalent to 0 = 0 and are not added. In practice, if all coefficients are smaller than 1e-10, the constraint is ignored, if all coefficients are smaller than 1e-8 but some are larger than 1e-10, it is ignored but a warning is displayed, otherwise if at least one coefficient is larger than 1e-8, the constraint is added.

If the matrix is known to be symmetric, the PSD constraint can be added as follows:

julia> using LinearAlgebra

julia> @constraint(model, Symmetric([x 2x; 2x 4x] - ones(2, 2)) in PSDCone())
[x - 1    2 x - 1;
2 x - 1  4 x - 1] ∈ PSDCone()

Note that the lower triangular entries are silently ignored even if they are different so use it with caution:

julia> cref = @constraint(model, Symmetric([x 2x; 3x 4x]) in PSDCone())
[x    2 x;
2 x  4 x] ∈ PSDCone()

julia> jump_function(constraint_object(cref))
3-element Array{GenericAffExpr{Float64,VariableRef},1}:
x
2 x
4 x

julia> moi_set(constraint_object(cref))
MathOptInterface.PositiveSemidefiniteConeTriangle(2)

Note that as @SDconstraint(model, A >= B) constrains A - B to be symmetric positive semidefinite, even if A is a matrix of variables and B is a matrix of zeros, A - B will be a matrix of affine expressions. For instance, in the example below, the function is VectorAffineFunction instead of VectorOfVariables.

julia> typeof(@SDconstraint(model, [x x; x x] >= zeros(2, 2)))
ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64},MathOptInterface.PositiveSemidefiniteConeSquare},SquareMatrixShape}

Moreover, the Symmetric structure can be lost in the operation A - B. For instance, in the example below, the set is PositiveSemidefiniteConeSquare instead of PositiveSemidefiniteConeTriangle.

julia> typeof(@SDconstraint(model, Symmetric([x x; x x]) >= zeros(2, 2)))
ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64},MathOptInterface.PositiveSemidefiniteConeSquare},SquareMatrixShape}

To create a constraint on the vector of variables with the @SDconstraint macro, use the 0 symbol. The following three syntax are equivalent:

• @SDconstraint(model, A >= 0),
• @SDconstraint(model, 0 <= A) and
• @constraint(model, A in PSDCone()).
julia> typeof(@SDconstraint(model, [x x; x x] >= 0))
ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables,MathOptInterface.PositiveSemidefiniteConeSquare},SquareMatrixShape}

julia> typeof(@SDconstraint(model, 0 <= Symmetric([x x; x x])))
ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables,MathOptInterface.PositiveSemidefiniteConeTriangle},SymmetricMatrixShape}

As the syntax is recognized at parse time, using a variable with value zero does not work:

julia> a = 0
0

julia> @SDconstraint(model, [x x; x x] >= a)
ERROR: Operation - between Array{VariableRef,2} and Int64 is not allowed. You should use broadcast.
[...]

## Constraint modifications

A common paradigm, especially in linear programming, is to repeatedly solve a model with different coefficients.

### Modifying a constant term

Use set_normalized_rhs to modify the right-hand side (constant) term of a constraint. Use normalized_rhs to query the right-hand side term.

julia> @constraint(model, con, 2x <= 1)
con : 2 x <= 1.0

julia> set_normalized_rhs(con, 3)

julia> con
con : 2 x <= 3.0

julia> normalized_rhs(con)
3.0
Note

JuMP normalizes constraints into a standard form by moving all constant terms onto the right-hand side of the constraint.

@constraint(model, 2x - 1 <= 2)

will be normalized to

@constraint(model, 2x <= 3)

set_normalized_rhs sets the right-hand side term of the normalized constraint.

If constraints are complicated, e.g., they are composed of a number of components, each of which has a constant term, then it may be difficult to calculate what the right-hand side term should be in the standard form.

For this situation, JuMP includes the ability to fix variables to a value using the fix function. Fixing a variable sets its lower and upper bound to the same value. Thus, changes in a constant term can be simulated by adding a dummy variable and fixing it to different values. Here is an example:

julia> @variable(model, const_term)
const_term

julia> @constraint(model, con, 2x <= const_term + 1)
con : 2 x - const_term <= 1.0

julia> fix(const_term, 1.0)

The constraint con is now equivalent to 2x <= 2.

Note

Even though const_term is fixed, it is still a decision variable. Thus, const_term * x is bilinear. Fixed variables are not replaced with constants when communicating the problem to a solver.

Another option is to use add_to_function_constant. The constant given is added to the function of a func-in-set constraint. In the following example, adding 2 to the function has the effect of removing 2 to the right-hand side:

julia> @constraint(model, con, 2x <= 1)
con : 2 x <= 1.0

julia> con
con : 2 x <= -1.0

julia> normalized_rhs(con)
-1.0

In the case of interval constraints, the constant is removed in each bounds.

julia> @constraint(model, con, 0 <= 2x + 1 <= 2)
con : 2 x ∈ [-1.0, 1.0]

julia> con
con : 2 x ∈ [-4.0, -2.0]

### Modifying a variable coefficient

To modify the coefficients for a linear term in a constraint (but notably not yet the coefficients on a quadratic term), use set_normalized_coefficient. To query the current coefficient, use normalized_coefficient.

julia> @constraint(model, con, 2x[1] + x[2] <= 1)
con : 2 x[1] + x[2] ≤ 1.0

julia> set_normalized_coefficient(con, x[2], 0)

julia> con
con : 2 x[1] ≤ 1.0

julia> normalized_coefficient(con, x[2])
0.0
Note

JuMP normalizes constraints into a standard form by moving all terms involving variables onto the left-hand side of the constraint.

@constraint(model, 2x <= 1 - x)

will be normalized to

@constraint(model, 3x <= 1)

set_normalized_coefficient sets the coefficient of the normalized constraint.

## Constraint deletion

Constraints can be deleted from a model using delete. Just like variable references, it is possible to check if a constraint reference is valid using is_valid. Here is an example of deleting a constraint:

julia> @constraint(model, con, 2x <= 1)
con : 2 x <= 1.0

julia> is_valid(model, con)
true

julia> delete(model, con)

julia> is_valid(model, con)
false

## Accessing constraints from a model

You can query the types of constraints currently present in the model by calling list_of_constraint_types. Then, given a function and set type, use num_constraints to access the number of constraints of this type and all_constraints to access a list of their references. Then use constraint_object to get an instance of an AbstractConstraint object, either ScalarConstraint or VectorConstraint, that stores the constraint data.

julia> model = Model();

julia> @variable(model, x[i=1:2] >= i, Int);

julia> @constraint(model, x[1] + x[2] <= 1);

julia> list_of_constraint_types(model)
3-element Array{Tuple{DataType,DataType},1}:
(GenericAffExpr{Float64,VariableRef}, MathOptInterface.LessThan{Float64})
(VariableRef, MathOptInterface.GreaterThan{Float64})
(VariableRef, MathOptInterface.Integer)

julia> num_constraints(model, VariableRef, MOI.Integer)
2

julia> all_constraints(model, VariableRef, MOI.Integer)
2-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.SingleVariable,MathOptInterface.Integer},ScalarShape},1}:
x[1] integer
x[2] integer

julia> num_constraints(model, VariableRef, MOI.GreaterThan{Float64})
2

julia> all_constraints(model, VariableRef, MOI.GreaterThan{Float64})
2-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.SingleVariable,MathOptInterface.GreaterThan{Float64}},ScalarShape},1}:
x[1] ≥ 1.0
x[2] ≥ 2.0

julia> num_constraints(model, GenericAffExpr{Float64,VariableRef}, MOI.LessThan{Float64})
1

julia> less_than_constraints = all_constraints(model, GenericAffExpr{Float64,VariableRef}, MOI.LessThan{Float64})
1-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},1}:
x[1] + x[2] ≤ 1.0

julia> con = constraint_object(less_than_constraints[1])
ScalarConstraint{GenericAffExpr{Float64,VariableRef},MathOptInterface.LessThan{Float64}}(x[1] + x[2], MathOptInterface.LessThan{Float64}(1.0))

julia> con.func
x[1] + x[2]

julia> con.set
MathOptInterface.LessThan{Float64}(1.0)

## Complementarity constraints

A mixed complementarity constraint F(x) ⟂ x consists of finding x in the interval [lb, ub], such that the following holds:

• F(x) == 0 if lb < x < ub
• F(x) >= 0 if lb == x
• F(x) <= 0 if x == ub

For more information, see the MOI.Complements documentation.

JuMP supports mixed complementarity constraints via complements(F(x), x) or F(x) ⟂ x in the @constraint macro. The interval set [lb, ub] is obtained from the variable bounds on x.

For example, to define the problem 2x - 1 ⟂ x with x ∈ [0, ∞), do:

julia> @variable(model, x >= 0)
x

julia> @constraint(model, 2x - 1 ⟂ x)
[2 x - 1, x] ∈ MathOptInterface.Complements(1)

This problem has a unique solution at x = 0.5.

The perp operator ⟂ can be entered in most editors (and the Julia REPL) by typing \perp<tab>.

An alternative approach that does not require the ⟂ symbol uses the complements function as follows:

julia> @constraint(model, complements(2x - 1, x))
[2 x - 1, x] ∈ MathOptInterface.Complements(1)

In both cases, the mapping F(x) is supplied as the first argument, and the matching variable x is supplied as the second.

Vector-valued complementarity constraints are also supported:

julia> @variable(model, -2 <= y[1:2] <= 2)
2-element Array{VariableRef,1}:
y[1]
y[2]

julia> M = [1 2; 3 4]
2×2 Array{Int64,2}:
1  2
3  4

julia> q = [5, 6]
2-element Array{Int64,1}:
5
6

julia> @constraint(model, M * y + q ⟂ y)
[y[1] + 2 y[2] + 5, 3 y[1] + 4 y[2] + 6, y[1], y[2]] ∈ MathOptInterface.Complements(2)

## Reference

@constraint(m::Model, expr)

Add a constraint described by the expression expr.

@constraint(m::Model, ref[i=..., j=..., ...], expr)

Add a group of constraints described by the expression expr parametrized by i, j, ...

The expression expr can either be

• of the form func in set constraining the function func to belong to the set set which is either a MathOptInterface.AbstractSet or one of the JuMP shortcuts SecondOrderCone, RotatedSecondOrderCone and PSDCone, e.g. @constraint(model, [1, x-1, y-2] in SecondOrderCone()) constrains the norm of [x-1, y-2] be less than 1;
• of the form a sign b, where sign is one of ==, ≥, >=, ≤ and <= building the single constraint enforcing the comparison to hold for the expression a and b, e.g. @constraint(m, x^2 + y^2 == 1) constrains x and y to lie on the unit circle;
• of the form a ≤ b ≤ c or a ≥ b ≥ c (where ≤ and <= (resp. ≥ and >=) can be used interchangeably) constraining the paired the expression b to lie between a and c;
• of the forms @constraint(m, a .sign b) or @constraint(m, a .sign b .sign c) which broadcast the constraint creation to each element of the vectors.

Note for extending the constraint macro

Each constraint will be created using add_constraint(m, build_constraint(_error, func, set)) where

• _error is an error function showing the constraint call in addition to the error message given as argument,
• func is the expression that is constrained
• and set is the set in which it is constrained to belong.

For expr of the first type (i.e. @constraint(m, func in set)), func and set are passed unchanged to build_constraint but for the other types, they are determined from the expressions and signs. For instance, @constraint(m, x^2 + y^2 == 1) is transformed into add_constraint(m, build_constraint(_error, x^2 + y^2, MOI.EqualTo(1.0))).

To extend JuMP to accept new constraints of this form, it is necessary to add the corresponding methods to build_constraint. Note that this will likely mean that either func or set will be some custom type, rather than e.g. a Symbol, since we will likely want to dispatch on the type of the function or set appearing in the constraint.

@SDconstraint(model::Model, expr)

Add a semidefinite constraint described by the expression expr.

@SDconstraint(model::Model, ref[i=..., j=..., ...], expr)

Add a group of semidefinite constraints described by the expression expr parametrized by i, j, ...

The expression expr needs to be of the form a sign b where sign is ⪰, ≥, >=, ⪯, ≤ or <= and a and b are square matrices. It constrains the square matrix x (or -x if the sign is ⪯, ≤ or <=) to be symmetric and positive semidefinite where

• x = a, if b is the symbol 0,
• x = -b, if a is the symbol 0,
• otherwise, x = a - b.

By default, we check numerical symmetry of the matrix x, and if symmetry is violated by some arbitrary amount, we add explicit equality constraints. You can use Symmetric(x) in PSDCone() with the @constraint macro to skip these checks if you know the matrix must be symmetric; see PSDCone for more information.

Examples

The following constrains the matrix [x-1 2x-2; -3 x-4] to be symmetric and positive semidefinite, that is, it constrains 2x-2 to be equal to -3 and constrains all eigenvalues of the matrix to be nonnegative.

julia> model = Model();

julia> @variable(model, x)
x

julia> a = [x 2x
0  x];

julia> b = [1 2
3 4];

julia> cref = @SDconstraint(model, a ⪰ b)
[x - 1  2 x - 2;
-3     x - 4  ] ∈ PSDCone()

julia> jump_function(constraint_object(cref))
4-element Array{GenericAffExpr{Float64,VariableRef},1}:
x - 1
-3
2 x - 2
x - 4

julia> moi_set(constraint_object(cref))
MathOptInterface.PositiveSemidefiniteConeSquare(2)

In the set PositiveSemidefiniteConeSquare(2) in the last output, Square means that the matrix is passed as a square matrix as the corresponding off-diagonal entries need to be constrained to be equal. A similar set PositiveSemidefiniteConeTriangle exists which only uses the upper triangular part of the matrix assuming that it is symmetric, see PSDCone to see how to use it.

SecondOrderCone

Second order cone object that can be used to constrain the euclidean norm of a vector x to be less than or equal to a nonnegative scalar t. This is a shortcut for the MathOptInterface.SecondOrderCone.

Examples

The following constrains $\|(x-1, x-2)\|_2 \le t$ and $t \ge 0$:

julia> model = Model();

julia> @variable(model, x)
x

julia> @variable(model, t)
t

julia> @constraint(model, [t, x-1, x-2] in SecondOrderCone())
[t, x - 1, x - 2] ∈ MathOptInterface.SecondOrderCone(3)
RotatedSecondOrderCone

Rotated second order cone object that can be used to constrain the square of the euclidean norm of a vector x to be less than or equal to $2tu$ where t and u are nonnegative scalars. This is a shortcut for the MathOptInterface.RotatedSecondOrderCone.

Examples

The following constrains $\|(x-1, x-2)\|_2 \le 2tx$ and $t, x \ge 0$:

julia> model = Model();

julia> @variable(model, x)
x

julia> @variable(model, t)
t

julia> @constraint(model, [t, x, x-1, x-2] in RotatedSecondOrderCone())
[t, x, x - 1, x - 2] ∈ MathOptInterface.RotatedSecondOrderCone(4)
PSDCone

Positive semidefinite cone object that can be used to constrain a square matrix to be positive semidefinite in the @constraint macro. If the matrix has type Symmetric then the columns vectorization (the vector obtained by concatenating the columns) of its upper triangular part is constrained to belong to the MOI.PositiveSemidefiniteConeTriangle set, otherwise its column vectorization is constrained to belong to the MOI.PositiveSemidefiniteConeSquare set.

Examples

Consider the following example:

julia> model = Model();

julia> @variable(model, x)
x

julia> a = [ x 2x
2x  x];

julia> b = [1 2
2 4];

julia> cref = @SDconstraint(model, a ⪰ b)
[x - 1    2 x - 2;
2 x - 2  x - 4  ] ∈ PSDCone()

julia> jump_function(constraint_object(cref))
4-element Array{GenericAffExpr{Float64,VariableRef},1}:
x - 1
2 x - 2
2 x - 2
x - 4

julia> moi_set(constraint_object(cref))
MathOptInterface.PositiveSemidefiniteConeSquare(2)

We see in the output of the last command that the matrix the vectorization of the matrix is constrained to belong to the PositiveSemidefiniteConeSquare.

julia> using LinearAlgebra # For Symmetric

julia> cref = @constraint(model, Symmetric(a - b) in PSDCone())
[x - 1    2 x - 2;
2 x - 2  x - 4  ] ∈ PSDCone()

julia> jump_function(constraint_object(cref))
3-element Array{GenericAffExpr{Float64,VariableRef},1}:
x - 1
2 x - 2
x - 4

julia> moi_set(constraint_object(cref))
MathOptInterface.PositiveSemidefiniteConeTriangle(2)

As we see in the output of the last command, the vectorization of only the upper triangular part of the matrix is constrained to belong to the PositiveSemidefiniteConeSquare.

shadow_price(con_ref::ConstraintRef)

Return the change in the objective from an infinitesimal relaxation of the constraint.

This value is computed from dual and can be queried only when has_duals is true and the objective sense is MIN_SENSE or MAX_SENSE (not FEASIBILITY_SENSE). For linear constraints, the shadow prices differ at most in sign from the dual value depending on the objective sense.

Notes

• The function simply translates signs from dual and does not validate the conditions needed to guarantee the sensitivity interpretation of the shadow price. The caller is responsible, e.g., for checking whether the solver converged to an optimal primal-dual pair or a proof of infeasibility.
• The computation is based on the current objective sense of the model. If this has changed since the last solve, the results will be incorrect.
• Relaxation of equality constraints (and hence the shadow price) is defined based on which sense of the equality constraint is active.
normalized_coefficient(con_ref::ConstraintRef, variable::VariableRef)

Return the coefficient associated with variable in constraint after JuMP has normalized the constraint into its standard form. See also set_normalized_coefficient.

set_normalized_coefficient(con_ref::ConstraintRef, variable::VariableRef, value)

Set the coefficient of variable in the constraint constraint to value.

Note that prior to this step, JuMP will aggregate multiple terms containing the same variable. For example, given a constraint 2x + 3x <= 2, set_normalized_coefficient(con, x, 4) will create the constraint 4x <= 2.

model = Model()
@variable(model, x)
@constraint(model, con, 2x + 3x <= 2)
set_normalized_coefficient(con, x, 4)
con

# output

con : 4 x <= 2.0
normalized_rhs(con_ref::ConstraintRef)

Return the right-hand side term of con_ref after JuMP has converted the constraint into its normalized form. See also set_normalized_rhs.

set_normalized_rhs(con_ref::ConstraintRef, value)

Set the right-hand side term of constraint to value.

Note that prior to this step, JuMP will aggregate all constant terms onto the right-hand side of the constraint. For example, given a constraint 2x + 1 <= 2, set_normalized_rhs(con, 4) will create the constraint 2x <= 4, not 2x + 1 <= 4.

julia> @constraint(model, con, 2x + 1 <= 2)
con : 2 x <= 1.0

julia> set_normalized_rhs(con, 4)

julia> con
con : 2 x <= 4.0
add_to_function_constant(constraint::ConstraintRef, value)

Add value to the function constant term.

Note that for scalar constraints, JuMP will aggregate all constant terms onto the right-hand side of the constraint so instead of modifying the function, the set will be translated by -value. For example, given a constraint 2x <= 3, add_to_function_constant(c, 4) will modify it to 2x <= -1.

Examples

For scalar constraints, the set is translated by -value:

julia> @constraint(model, con, 0 <= 2x - 1 <= 2)
con : 2 x ∈ [1.0, 3.0]

julia> con
con : 2 x ∈ [-3.0, -1.0]

For vector constraints, the constant is added to the function:

julia> @constraint(model, con, [x + y, x, y] in SecondOrderCone())
con : [x + y, x, y] in MOI.SecondOrderCone(3)

julia> add_to_function_constant(con, [1, 2, 2])

julia> con
con : [x + y + 1, x + 2, y + 2] in MOI.SecondOrderCone(3)
is_valid(model::Model, con_ref::ConstraintRef{Model})

Return true if constraint_ref refers to a valid constraint in model.

is_valid(model::Model, variable_ref::VariableRef)

Return true if variable refers to a valid variable in model.

delete(model::Model, con_ref::ConstraintRef)

Delete the constraint associated with constraint_ref from the model model.

delete(model::Model, con_refs::Vector{<:ConstraintRef})

Delete the constraints associated with con_refs from the model model. Solvers may implement specialized methods for deleting multiple constraints of the same concrete type, i.e., when isconcretetype(eltype(con_refs)). These may be more efficient than repeatedly calling the single constraint delete method.

delete(model::Model, variable_ref::VariableRef)

Delete the variable associated with variable_ref from the model model.

delete(model::Model, variable_refs::Vector{VariableRef})

Delete the variables associated with variable_refs from the model model. Solvers may implement methods for deleting multiple variables that are more efficient than repeatedly calling the single variable delete method.

LowerBoundRef(v::VariableRef)

Return a constraint reference to the lower bound constraint of v. Errors if one does not exist.

UpperBoundRef(v::VariableRef)

Return a constraint reference to the upper bound constraint of v. Errors if one does not exist.

FixRef(v::VariableRef)

Return a constraint reference to the constraint fixing the value of v. Errors if one does not exist.

ConstraintRef

Holds a reference to the model and the corresponding MOI.ConstraintIndex.

list_of_constraint_types(model::Model)

Return a list of tuples of the form (F, S) where F is a JuMP function type and S is an MOI set type such that all_constraints(model, F, S) returns a nonempty list.

Example

julia> model = Model();

julia> @variable(model, x >= 0, Bin);

julia> @constraint(model, 2x <= 1);

julia> list_of_constraint_types(model)
3-element Array{Tuple{DataType,DataType},1}:
(GenericAffExpr{Float64,VariableRef}, MathOptInterface.LessThan{Float64})
(VariableRef, MathOptInterface.GreaterThan{Float64})
(VariableRef, MathOptInterface.ZeroOne)
all_constraints(model::Model, function_type, set_type)::Vector{<:ConstraintRef}

Return a list of all constraints currently in the model where the function has type function_type and the set has type set_type. The constraints are ordered by creation time.

Example

julia> model = Model();

julia> @variable(model, x >= 0, Bin);

julia> @constraint(model, 2x <= 1);

julia> all_constraints(model, VariableRef, MOI.GreaterThan{Float64})
1-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.SingleVariable,MathOptInterface.GreaterThan{Float64}},ScalarShape},1}:
x ≥ 0.0

julia> all_constraints(model, VariableRef, MOI.ZeroOne)
1-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.SingleVariable,MathOptInterface.ZeroOne},ScalarShape},1}:
x binary

julia> all_constraints(model, AffExpr, MOI.LessThan{Float64})
1-element Array{ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}},ScalarShape},1}:
2 x ≤ 1.0
num_constraints(model::Model, function_type, set_type)::Int64

Return the number of constraints currently in the model where the function has type function_type and the set has type set_type.

Example

julia> model = Model();

julia> @variable(model, x >= 0, Bin);

julia> @variable(model, y);

julia> @constraint(model, y in MOI.GreaterThan(1.0));

julia> @constraint(model, y <= 1.0);

julia> @constraint(model, 2x <= 1);

julia> num_constraints(model, VariableRef, MOI.GreaterThan{Float64})
2

julia> num_constraints(model, VariableRef, MOI.ZeroOne)
1

julia> num_constraints(model, AffExpr, MOI.LessThan{Float64})
2
constraint_object(con_ref::ConstraintRef)

Return the underlying constraint data for the constraint referenced by ref.

abstract type AbstractConstraint

An abstract base type for all constraint types. AbstractConstraints store the function and set directly, unlike ConstraintRefs that are merely references to constraints stored in a model. AbstractConstraints do not need to be attached to a model.

struct ScalarConstraint

The data for a scalar constraint. The func field containts a JuMP object representing the function and the set field contains the MOI set. See also the documentation on JuMP's representation of constraints for more background.

struct VectorConstraint

The data for a vector constraint. The func field containts a JuMP object representing the function and the set field contains the MOI set. The shape field contains an AbstractShape matching the form in which the constraint was constructed (e.g., by using matrices or flat vectors). See also the documentation on JuMP's representation of constraints.

index(cr::ConstraintRef)::MOI.ConstraintIndex

Return the index of the constraint that corresponds to cr in the MOI backend.

optimizer_index(cr::ConstraintRef{Model})::MOI.ConstraintIndex

Return the index of the constraint that corresponds to cr in the optimizer model. It throws NoOptimizer if no optimizer is set and throws an ErrorException if the optimizer is set but is not attached or if the constraint is bridged.

## Constructing constraints without adding them to the model

For advanced use cases.

@build_constraint(constraint_expr)

Constructs a ScalarConstraint or VectorConstraint using the same machinery as @constraint but without adding the constraint to a model.

Constraints using broadcast operators like x .<= 1 are also supported and will create arrays of ScalarConstraint or VectorConstraint.

Examples

model = Model();
@variable(model, x);
@build_constraint(2x >= 1)

# output
ScalarConstraint{GenericAffExpr{Float64,VariableRef},MathOptInterface.GreaterThan{Float64}}(2 x, MathOptInterface.GreaterThan{Float64}(1.0))