Skip to content

Commit

Permalink
all_constraints and list_of_constraint_types (#1805)
Browse files Browse the repository at this point in the history
* all_constraints and list_of_constraint_types

* tidy

* typo fix

* Fix rebase quirks
  • Loading branch information
mlubin committed Jan 30, 2019
1 parent c240909 commit a7a63c7
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 9 deletions.
64 changes: 56 additions & 8 deletions docs/src/constraints.md
Expand Up @@ -596,8 +596,50 @@ false

## Accessing constraints from a model

TODO: Describe constraints vs. `ConstraintRef`s. Describe `JuMP.constraint_object`.
Describe how to access all constraints in a model.
You can query the types of constraints currently present in the model by calling
[`list_of_constraint_types`](@ref). Then, given a function and set type, use
[`all_constraints`](@ref) to access a list of constraint references for
constraints of this type. Then use [`constraint_object`](@ref) to get an
instance of an [`AbstractConstraint`](@ref) object, either
[`ScalarConstraint`](@ref) or [`VectorConstraint`](@ref) that stores the
constraint data.

```jldoctest
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}:
(VariableRef, MathOptInterface.Integer)
(VariableRef, MathOptInterface.GreaterThan{Float64})
(GenericAffExpr{Float64,VariableRef}, MathOptInterface.LessThan{Float64})
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> 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> 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)
```


## Reference
Expand All @@ -608,14 +650,20 @@ Describe how to access all constraints in a model.
SecondOrderCone
RotatedSecondOrderCone
PSDCone
JuMP.shadow_price
JuMP.set_coefficient
JuMP.is_valid
shadow_price
set_coefficient
is_valid
JuMP.delete
JuMP.LowerBoundRef
JuMP.UpperBoundRef
JuMP.FixRef
LowerBoundRef
UpperBoundRef
FixRef
ConstraintRef
list_of_constraint_types
all_constraints
constraint_object
AbstractConstraint
ScalarConstraint
VectorConstraint
```

## Constructing constraints without adding them to the model
Expand Down
117 changes: 116 additions & 1 deletion src/constraints.jl
Expand Up @@ -269,7 +269,14 @@ end

#############################################################################
# AbstractConstraint
# Abstract base type for all constraint types
"""
abstract type AbstractConstraint
An abstract base type for all constraint types. `AbstractConstraint`s store the
function and set directly, unlike [`ConstraintRef`](@ref)s that are merely
references to constraints stored in a model. `AbstractConstraint`s do not need
to be attached to a model.
"""
abstract type AbstractConstraint end

"""
Expand Down Expand Up @@ -359,6 +366,21 @@ Returns the MOI set of dimension `dim` corresponding to the JuMP set `s`.
"""
function moi_set end

"""
constraint_object(ref::ConstraintRef)
Return the underlying constraint data for the constraint referenced by `ref`.
"""
function constraint_object end

"""
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](@ref Constraints) on JuMP's representation of
constraints for more background.
"""
struct ScalarConstraint{F <: AbstractJuMPScalar,
S <: MOI.AbstractScalarSet} <: AbstractConstraint
func::F
Expand All @@ -368,6 +390,7 @@ end
jump_function(constraint::ScalarConstraint) = constraint.func
moi_set(constraint::ScalarConstraint) = constraint.set
shape(::ScalarConstraint) = ScalarShape()

function constraint_object(ref::ConstraintRef{Model, MOICON{FuncType, SetType}}) where
{FuncType <: MOI.AbstractScalarFunction, SetType <: MOI.AbstractScalarSet}
model = ref.model
Expand All @@ -379,6 +402,16 @@ function check_belongs_to_model(c::ScalarConstraint, model)
check_belongs_to_model(c.func, model)
end

"""
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`](@ref) matching the form in which
the constraint was constructed (e.g., by using matrices or flat vectors).
See also the [documentation](@ref Constraints) on JuMP's representation of
constraints.
"""
struct VectorConstraint{F <: AbstractJuMPScalar,
S <: MOI.AbstractVectorSet,
Shape <: AbstractShape} <: AbstractConstraint
Expand Down Expand Up @@ -587,3 +620,85 @@ function shadow_price(constraint::ConstraintRef{Model, MOICON{F, S}}
return shadow_price_less_than_(dual_val, sense)
end
end

function _error_if_not_concrete_type(t)
if !isconcretetype(t)
error("`$t` is not a concrete type. Did you miss a type parameter?")
end
return
end

"""
all_constraints(model::Model, function_type, set_type)::Vector{VariableRef}
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.
See also [`list_of_constraint_types`](@ref).
# Example
```jldoctest
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
```
"""
function all_constraints(model::Model,
function_type::Type{<:AbstractJuMPScalar},
set_type::Type{<:MOI.AbstractSet})
_error_if_not_concrete_type(function_type)
_error_if_not_concrete_type(set_type)
# TODO: Support JuMP's set helpers like SecondOrderCone().
f_type = moi_function_type(function_type)
constraint_ref_type = ConstraintRef{Model, MOICON{f_type, set_type},
ScalarShape}
result = constraint_ref_type[]
for idx in MOI.get(model, MOI.ListOfConstraintIndices{f_type, set_type}())
push!(result, ConstraintRef(model, idx, ScalarShape()))
end
return result
end

# TODO: Support vector function types. This is blocked by not having the shape
# information available.

"""
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
```jldoctest
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}:
(VariableRef, MathOptInterface.ZeroOne)
(VariableRef, MathOptInterface.GreaterThan{Float64})
(GenericAffExpr{Float64,VariableRef}, MathOptInterface.LessThan{Float64})
"""
function list_of_constraint_types(model::Model)
list = MOI.get(model, MOI.ListOfConstraints())
return [(jump_function_type(model, f), s) for (f,s) in list]
end
1 change: 1 addition & 0 deletions src/print.jl
Expand Up @@ -386,6 +386,7 @@ Write to `io` a summary of the number of constraints.
function show_constraints_summary(io::IO, model::Model)
for (F, S) in MOI.get(model, MOI.ListOfConstraints())
num_constraints = MOI.get(model, MOI.NumberOfConstraints{F, S}())
# TODO: Print jump_function_type(model, F) instead of F.
println(io, "`$F`-in-`$S`: $num_constraints constraint",
plural(num_constraints))
end
Expand Down
27 changes: 27 additions & 0 deletions test/constraint.jl
Expand Up @@ -377,6 +377,33 @@ end

@testset "Constraints for JuMP.Model" begin
constraints_test(Model, JuMP.VariableRef)
@testset "all_constraints (scalar)" begin
model = Model()
@variable(model, x >= 0)
ref = all_constraints(model, VariableRef, MOI.GreaterThan{Float64})
@test ref == [LowerBoundRef(x)]
aff_constraints = all_constraints(model, AffExpr,
MOI.GreaterThan{Float64})
@test isempty(aff_constraints)
err = ErrorException("`MathOptInterface.GreaterThan` is not a " *
"concrete type. Did you miss a type parameter?")
@test_throws err all_constraints(model, AffExpr,
MOI.GreaterThan)
err = ErrorException("`GenericAffExpr` is not a concrete type. " *
"Did you miss a type parameter?")
@test_throws err all_constraints(model, GenericAffExpr,
MOI.ZeroOne)
end
# TODO: all_constraints (vector)
@testset "list_of_constraint_types" begin
model = Model()
@variable(model, x >= 0, Bin)
@constraint(model, 2x <= 1)
constraint_types = list_of_constraint_types(model)
@test Set(constraint_types) == Set([(VariableRef, MOI.ZeroOne),
(VariableRef, MOI.GreaterThan{Float64}),
(AffExpr, MOI.LessThan{Float64})])
end
end

@testset "Constraints for JuMPExtension.MyModel" begin
Expand Down

0 comments on commit a7a63c7

Please sign in to comment.