Extending JuMP
Extending MOI
Adding a bridge
See the bridge section in the MOI manual.
JuMP.add_bridge
— Function. add_bridge(model::Model,
BridgeType::Type{<:MOI.Bridges.AbstractBridge})
Add BridgeType
to the list of bridges that can be used to transform unsupported constraints into an equivalent formulation using only constraints supported by the optimizer.
JuMP.BridgeableConstraint
— Type.struct BridgeableConstraint{C, B} <: AbstractConstraint
constraint::C
bridge_type::B
end
Constraint constraint
that can be bridged by the bridge of type bridge_type
. Adding this constraint to a model is equivalent to
add_bridge(model, bridge_type)
add_constraint(model, constraint)
Examples
Given a new scalar set type CustomSet
with a bridge CustomBridge
that can bridge F
-in-CustomSet
constraints, when the user does
model = Model()
@variable(model, x)
@constraint(model, x + 1 in CustomSet())
optimize!(model)
with an optimizer that does not support F
-in-CustomSet
constraints, the constraint will not be bridge unless he manually calls add_bridge(model, CustomBridge)
. In order to automatically add the CustomBridge
to any model to which an F
-in-CustomSet
is added, simply add the following method:
function JuMP.build_constraint(_error::Function, func::AbstractJuMPScalar,
set::CustomSet)
constraint = ScalarConstraint(func, set)
return JuMP.BridgeableConstraint(constraint, CustomBridge)
end
Note
JuMP extensions should extend JuMP.build_constraint
only if they also defined CustomSet
, for three reasons:
- It is problematic if multiple extensions overload the same JuMP method.
- A missing method will not inform the users that they forgot to load the extension module defining the
build_constraint
method. - Defining a method where neither the function nor any of the argument types are defined in the package is called type piracy and is discouraged in the Julia style guide.
```
Extending JuMP macros
In order to provide a convenient syntax for the user to create variables, constraints or set the objective of a JuMP extension, it might be required to use macros similar to @variable
, @constraint
and @objective
. It is recommended to first check whether it is possible to extend one of these three macros before creating a new one so as to leverage all their features and provide a more consistent interface to the user.
Extending the @constraint
macro
The @constraint
macro always calls the same three functions:
parse_constraint
: is called at parsing time, it parses the constraint expression and returns abuild_constraint
call expression;build_constraint
: given the functions and sets involved in the constraints, it returns aAbstractConstraint
;add_constraint
: given the model, theAbstractConstraint
constructed inbuild_constraint
and the constraint name, it stores them in the model and returns aConstraintRef
.
Adding methods to these functions is the recommended way to extend the @constraint
macro.
Adding parse_constraint
methods
JuMP.sense_to_set
— Function.sense_to_set(_error::Function, ::Val{sense_symbol})
Converts a sense symbol to a set set
such that @constraint(model, func sense_symbol 0) is equivalent to
@constraint(model, func in set)for any
func::AbstractJuMPScalar`.
Example
Once a custom set is defined you can directly create a JuMP constraint with it:
julia> struct CustomSet{T} <: MOI.AbstractScalarSet
value::T
end
julia> model = Model();
julia> @variable(model, x)
x
julia> cref = @constraint(model, x in CustomSet(1.0))
x ∈ CustomSet{Float64}(1.0)
However, there might be an appropriate sign that could be used in order to provide a more convenient syntax:
julia> JuMP.sense_to_set(::Function, ::Val{:⊰}) = CustomSet(0.0)
julia> MOIU.shift_constant(set::CustomSet, value) = CustomSet(set.value + value)
julia> cref = @constraint(model, x ⊰ 1)
x ∈ CustomSet{Float64}(1.0)
Note that the whole function is first moved to the right-hand side, then the sign is transformed into a set with zero constant and finally the constant is moved to the set with MOIU.shift_constant
.
Adding build_constraint
methods
There is 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).
JuMP.build_constraint
— Function.build_constraint(_error::Function, Q::Symmetric{V, M},
::PSDCone) where {V <: AbstractJuMPScalar,
M <: AbstractMatrix{V}}
Return a VectorConstraint
of shape SymmetricMatrixShape
constraining the matrix Q
to be positive semidefinite.
This function is used by the @constraint
macros as follows:
@constraint(model, Symmetric(Q) in PSDCone())
The form above is usually used when the entries of Q
are affine or quadratic expressions but it can also be used when the entries are variables to get the reference of the semidefinite constraint, e.g.,
@variable model Q[1:2,1:2] Symmetric
# The type of `Q` is `Symmetric{VariableRef, Matrix{VariableRef}}`
var_psd = @constraint model Q in PSDCone()
# The `var_psd` variable contains a reference to the constraint
build_constraint(_error::Function,
Q::AbstractMatrix{<:AbstractJuMPScalar},
::PSDCone)
Return a VectorConstraint
of shape SquareMatrixShape
constraining the matrix Q
to be symmetric and positive semidefinite.
This function is used by the @constraint
and @SDconstraint
macros as follows:
@constraint(model, Q in PSDCone())
@SDconstraint(model, P ⪰ Q)
The @constraint
call above is usually used when the entries of Q
are affine or quadratic expressions but it can also be used when the entries are variables to get the reference of the semidefinite constraint, e.g.,
@variable model Q[1:2,1:2]
# The type of `Q` is `Matrix{VariableRef}`
var_psd = @constraint model Q in PSDCone()
# The `var_psd` variable contains a reference to the constraint
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
.
JuMP.AbstractShape
— Type.AbstractShape
Abstract vectorizable shape. Given a flat vector form of an object of shape shape
, the original object can be obtained by reshape_vector
.
JuMP.shape
— Function.shape(c::AbstractConstraint)::AbstractShape
Return the shape of the constraint c
.
JuMP.reshape_vector
— Function.reshape_vector(vectorized_form::Vector, shape::AbstractShape)
Return an object in its original shape shape
given its vectorized form vectorized_form
.
Examples
Given a SymmetricMatrixShape
of vectorized form [1, 2, 3]
, the following code returns the matrix Symmetric(Matrix[1 2; 2 3])
:
julia> reshape_vector([1, 2, 3], SymmetricMatrixShape(2))
2×2 LinearAlgebra.Symmetric{Int64,Array{Int64,2}}:
1 2
2 3
JuMP.reshape_set
— Function.reshape_set(vectorized_set::MOI.AbstractSet, shape::AbstractShape)
Return a set in its original shape shape
given its vectorized form vectorized_form
.
Examples
Given a SymmetricMatrixShape
of vectorized form [1, 2, 3] in MOI.PositiveSemidefinieConeTriangle(2)
, the following code returns the set of the original constraint Symmetric(Matrix[1 2; 2 3]) in PSDCone()
:
julia> reshape_set(MOI.PositiveSemidefiniteConeTriangle(2), SymmetricMatrixShape(2))
PSDCone()
JuMP.dual_shape
— Function.dual_shape(shape::AbstractShape)::AbstractShape
Returns the shape of the dual space of the space of objects of shape shape
. By default, the dual_shape
of a shape is itself. See the examples section below for an example for which this is not the case.
Examples
Consider polynomial constraints for which the dual is moment constraints and moment constraints for which the dual is polynomial constraints. Shapes for polynomials can be defined as follows:
struct Polynomial
coefficients::Vector{Float64}
monomials::Vector{Monomial}
end
struct PolynomialShape <: AbstractShape
monomials::Vector{Monomial}
end
JuMP.reshape_vector(x::Vector, shape::PolynomialShape) = Polynomial(x, shape.monomials)
and a shape for moments can be defined as follows:
struct Moments
coefficients::Vector{Float64}
monomials::Vector{Monomial}
end
struct MomentsShape <: AbstractShape
monomials::Vector{Monomial}
end
JuMP.reshape_vector(x::Vector, shape::MomentsShape) = Moments(x, shape.monomials)
The dual_shape
allows to define the shape of the dual of polynomial and moment constraints:
dual_shape(shape::PolynomialShape) = MomentsShape(shape.monomials)
dual_shape(shape::MomentsShape) = PolynomialShape(shape.monomials)
JuMP.ScalarShape
— Type.ScalarShape
Shape of scalar constraints.
JuMP.VectorShape
— Type.VectorShape
Vector for which the vectorized form corresponds exactly to the vector given.
JuMP.SquareMatrixShape
— Type.SquareMatrixShape
Shape object for a square matrix of side_dimension
rows and columns. The vectorized form contains the entries of the the matrix given column by column (or equivalently, the entries of the lower-left triangular part given row by row).
JuMP.SymmetricMatrixShape
— Type.SymmetricMatrixShape
Shape object for a symmetric square matrix of side_dimension
rows and columns. The vectorized form contains the entries of the upper-right triangular part of the matrix given column by column (or equivalently, the entries of the lower-left triangular part given row by row).
Adding add_constraint
methods
JuMP.add_constraint
— Function.add_constraint(model::Model, con::AbstractConstraint, name::String="")
Add a constraint con
to Model model
and sets its name.