Skip to content

Commit

Permalink
Add constraint by name (#1701)
Browse files Browse the repository at this point in the history
* Add constraint_by_name

* 💄 Fix style of set_name doc

* 📝 Add docstring for set_name(::ConstraintRef, ...

* 📝 Reference variable doc for name/set_name

* 📝 Add documentation section on constraint names

* 📝 name -> name attribute

* ✏️ have -> has

* ✏️ Remove useless dot

* 📝 Add their

* 📝 Improve F-in-S constraint_by_name method doc

* 📝 Fix doctests
  • Loading branch information
blegat authored and mlubin committed Dec 22, 2018
1 parent ff5032a commit a6a9c6e
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 22 deletions.
16 changes: 16 additions & 0 deletions docs/src/constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,22 @@ DocTestSetup = quote
end
```

## Constraint names

The name, i.e. the value of the `MOI.ConstraintName` attribute, of a constraint
can be obtained by [`JuMP.name(::JuMP.ConstraintRef)`](@ref) and set by
[`JuMP.set_name(::JuMP.ConstraintRef, ::String)`](@ref).
```@docs
name(::JuMP.ConstraintRef{Model, <:JuMP.MOI.ConstraintIndex})
set_name(::JuMP.ConstraintRef{Model, <:JuMP.MOI.ConstraintIndex}, ::String)
```

The constraint can also be retrieved from its name using
[`JuMP.constraint_by_name`](@ref).
```@docs
constraint_by_name
```

## Constraint containers

So far, we've added constraints one-by-one. However, just like
Expand Down
7 changes: 4 additions & 3 deletions docs/src/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,11 @@ julia> JuMP.fix_value(x)
## Variable names

The name, i.e. the value of the `MOI.VariableName` attribute, of a variable can
obtained by [`JuMP.name`](@ref) and set by [`JuMP.set_name`](@ref).
be obtained by [`JuMP.name(::JuMP.VariableRef)`](@ref) and set by
[`JuMP.set_name(::JuMP.VariableRef, ::String)`](@ref).
```@docs
name
set_name
name(::JuMP.VariableRef)
set_name(::JuMP.VariableRef, ::String)
```
.

Expand Down
3 changes: 3 additions & 0 deletions src/aff_expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ function MOI.VectorAffineFunction(affs::Vector{AffExpr})
MOI.VectorAffineFunction(terms, constant)
end
moi_function(a::Vector{<:GenericAffExpr}) = MOI.VectorAffineFunction(a)
function moi_function_type(::Type{Vector{Aff}}) where {T, Aff <: GenericAffExpr{T}}
return MOI.VectorAffineFunction{T}
end

# Copy an affine expression to a new model by converting all the
# variables to the new model's variables
Expand Down
97 changes: 96 additions & 1 deletion src/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,107 @@ Base.broadcastable(cref::ConstraintRef) = Ref(cref)
"""
name(v::ConstraintRef)
Get a constraint's name.
Get a constraint's name attribute.
"""
name(cr::ConstraintRef{Model,<:MOICON}) = MOI.get(cr.model, MOI.ConstraintName(), cr)

"""
set_name(v::ConstraintRef, s::AbstractString)
Set a constraint's name attribute.
"""
set_name(cr::ConstraintRef{Model,<:MOICON}, s::String) = MOI.set(cr.model, MOI.ConstraintName(), cr, s)

"""
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.
```jldoctest objective_function; setup = :(using JuMP), filter = r"Stacktrace:.*"s
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> JuMP.constraint_by_name(model, "kon")
julia> JuMP.constraint_by_name(model, "con")
con : x² = 1.0
julia> JuMP.constraint_by_name(model, "con", AffExpr, JuMP.MOI.EqualTo{Float64})
julia> JuMP.constraint_by_name(model, "con", QuadExpr, JuMP.MOI.EqualTo{Float64})
con : x² = 1.0
```
"""
function constraint_by_name end

function constraint_by_name(model::Model, name::String)
index = MOI.get(backend(model), MOI.ConstraintIndex, name)
if index isa Nothing
return nothing
else
return constraint_ref_with_index(model, index)
end
end

function constraint_by_name(model::Model, name::String,
F::Type{<:MOI.AbstractFunction},
S::Type{<:MOI.AbstractSet})
index = MOI.get(backend(model), MOI.ConstraintIndex{F, S}, name)
if index isa Nothing
return nothing
else
return constraint_ref_with_index(model, index)
end
end
function constraint_by_name(model::Model, name::String,
F::Type{<:Union{ScalarType,
Vector{ScalarType}}},
S::Type) where ScalarType <: AbstractJuMPScalar
return constraint_by_name(model, name, moi_function_type(F), S)
end

# Creates a ConstraintRef with default shape
function constraint_ref_with_index(model::AbstractModel,
index::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction,
<:MOI.AbstractScalarSet})
return ConstraintRef(model, index, ScalarShape())
end
function constraint_ref_with_index(model::AbstractModel,
index::MOI.ConstraintIndex{<:MOI.AbstractVectorFunction,
<:MOI.AbstractVectorSet})
return ConstraintRef(model, index, VectorShape())
end

"""
delete(model::Model, constraint_ref::ConstraintRef)
Expand Down
3 changes: 3 additions & 0 deletions src/quad_expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,9 @@ function MOI.VectorQuadraticFunction(quads::Vector{QuadExpr})
MOI.VectorQuadraticFunction(lin_terms, quad_terms, constants)
end
moi_function(a::Vector{<:GenericQuadExpr}) = MOI.VectorQuadraticFunction(a)
function moi_function_type(::Type{Vector{Quad}}) where {T, Quad <: GenericQuadExpr{T}}
return MOI.VectorQuadraticFunction{T}
end


# Copy a quadratic expression to a new model by converting all the
Expand Down
10 changes: 5 additions & 5 deletions src/variables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,14 @@ end
"""
name(v::VariableRef)::String
Get a variable's name.
Get a variable's name attribute.
"""
name(v::VariableRef) = MOI.get(owner_model(v), MOI.VariableName(), v)

"""
set_name(v::VariableRef,s::AbstractString)
set_name(v::VariableRef, s::AbstractString)
Set a variable's name.
Set a variable's name attribute.
"""
function set_name(v::VariableRef, s::String)
return MOI.set(owner_model(v), MOI.VariableName(), v, s)
Expand All @@ -228,8 +228,8 @@ end
name::String)::Union{AbstractVariableRef, Nothing}
Returns the reference of the variable with name attribute `name` or `Nothing` if
no variable have this name attribute. Throws an error if several variables have
`name` as name attribute.
no variable has this name attribute. Throws an error if several variables have
`name` as their name attribute.
```jldoctest objective_function; setup = :(using JuMP), filter = r"Stacktrace:.*"s
julia> model = Model()
Expand Down
34 changes: 31 additions & 3 deletions test/JuMPExtension.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ mutable struct MyModel <: JuMP.AbstractModel
nextconidx::Int # Next constraint index is nextconidx+1
constraints::Dict{ConstraintIndex,
JuMP.AbstractConstraint} # Map conidx -> variable
con_to_name::Dict{ConstraintIndex, String} # Map conidx -> name
con_to_name::Dict{ConstraintIndex, String} # Map conidx -> name
name_to_con::Union{Dict{String, ConstraintIndex},
Nothing} # Map name -> conidx
objectivesense::MOI.OptimizationSense
objective_function::JuMP.AbstractJuMPScalar
obj_dict::Dict{Symbol, Any} # Same that JuMP.Model's field `obj_dict`
function MyModel()
new(0, Dict{Int, JuMP.AbstractVariable}(),
Dict{Int, String}(), Dict{String, Int}(), # Variables
Dict{Int, String}(), nothing, # Variables
0, Dict{ConstraintIndex, JuMP.AbstractConstraint}(),
Dict{ConstraintIndex, String}(), # Constraints
Dict{ConstraintIndex, String}(), nothing, # Constraints
MOI.FEASIBILITY_SENSE,
zero(JuMP.GenericAffExpr{Float64, MyVariableRef}),
Dict{Symbol, Any}())
Expand Down Expand Up @@ -269,6 +271,32 @@ end
JuMP.name(cref::MyConstraintRef) = cref.model.con_to_name[cref.index]
function JuMP.set_name(cref::MyConstraintRef, name::String)
cref.model.con_to_name[cref.index] = name
cref.model.name_to_con = nothing
end
function JuMP.constraint_by_name(model::MyModel, name::String)
if model.name_to_con === nothing
# Inspired from MOI/src/Utilities/model.jl
model.name_to_con = Dict{String, ConstraintIndex}()
for (con, con_name) in model.con_to_name
if haskey(model.name_to_con, con_name)
# -1 is a special value that means this string does not map to
# a unique constraint name.
model.name_to_con[con_name] = ConstraintIndex(-1)
else
model.name_to_con[con_name] = con
end
end
end
index = get(model.name_to_con, name, nothing)
if index isa Nothing
return nothing
elseif index.value == -1
error("Multiple constraints have the name $name.")
else
# We have no information on whether this is a vector constraint
# or a scalar constraint
return JuMP.ConstraintRef(model, index, JuMP.ScalarShape())
end
end

end
62 changes: 52 additions & 10 deletions test/constraint.jl
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
function constraints_test(ModelType::Type{<:JuMP.AbstractModel})
function test_constraint_name(constraint, name, F::Type, S::Type)
@test JuMP.name(constraint) == name
model = constraint.model
@test constraint.index == JuMP.constraint_by_name(model, name).index
if !(model isa JuMPExtension.MyModel)
@test constraint.index == JuMP.constraint_by_name(model, name, F, S).index
end
end

function constraints_test(ModelType::Type{<:JuMP.AbstractModel},
VariableRefType::Type{<:JuMP.AbstractVariableRef})
AffExprType = JuMP.GenericAffExpr{Float64, VariableRefType}
QuadExprType = JuMP.GenericQuadExpr{Float64, VariableRefType}

@testset "SingleVariable constraints" begin
m = ModelType()
@variable(m, x)

# x <= 10.0 doesn't translate to a SingleVariable constraint because
# the LHS is first subtracted to form x - 10.0 <= 0.
@constraint(m, cref, x in MOI.LessThan(10.0))
@test JuMP.name(cref) == "cref"
test_constraint_name(cref, "cref", JuMP.VariableRef,
MOI.LessThan{Float64})
c = JuMP.constraint_object(cref)
@test c.func == x
@test c.set == MOI.LessThan(10.0)

@variable(m, y[1:2])
@constraint(m, cref2[i=1:2], y[i] in MOI.LessThan(float(i)))
@test JuMP.name(cref2[1]) == "cref2[1]"
test_constraint_name(cref2[1], "cref2[1]", JuMP.VariableRef,
MOI.LessThan{Float64})
c = JuMP.constraint_object(cref2[1])
@test c.func == y[1]
@test c.set == MOI.LessThan(1.0)
Expand Down Expand Up @@ -41,7 +56,7 @@ function constraints_test(ModelType::Type{<:JuMP.AbstractModel})
cref = @constraint(m, 2x <= 10)
@test JuMP.name(cref) == ""
JuMP.set_name(cref, "c")
@test JuMP.name(cref) == "c"
test_constraint_name(cref, "c", JuMP.AffExpr, MOI.LessThan{Float64})

c = JuMP.constraint_object(cref)
@test JuMP.isequal_canonical(c.func, 2x)
Expand Down Expand Up @@ -83,7 +98,7 @@ function constraints_test(ModelType::Type{<:JuMP.AbstractModel})
@variable(m, y)

@constraint(m, cref, 1.0 <= x + y + 1.0 <= 2.0)
@test JuMP.name(cref) == "cref"
test_constraint_name(cref, "cref", JuMP.AffExpr, MOI.Interval{Float64})

c = JuMP.constraint_object(cref)
@test JuMP.isequal_canonical(c.func, x + y)
Expand Down Expand Up @@ -210,7 +225,8 @@ function constraints_test(ModelType::Type{<:JuMP.AbstractModel})
@test c.shape isa JuMP.SquareMatrixShape

@constraint(m, sym_ref, Symmetric([x 1; 1 -y] - [1 x; x -2]) in PSDCone())
@test JuMP.name(sym_ref) == "sym_ref"
test_constraint_name(sym_ref, "sym_ref", Vector{AffExpr},
MOI.PositiveSemidefiniteConeTriangle)
c = JuMP.constraint_object(sym_ref)
@test JuMP.isequal_canonical(c.func[1], x-1)
@test JuMP.isequal_canonical(c.func[2], 1-x)
Expand All @@ -219,7 +235,8 @@ function constraints_test(ModelType::Type{<:JuMP.AbstractModel})
@test c.shape isa JuMP.SymmetricMatrixShape

@SDconstraint(m, cref, [x 1; 1 -y] [1 x; x -2])
@test JuMP.name(cref) == "cref"
test_constraint_name(cref, "cref", Vector{AffExpr},
MOI.PositiveSemidefiniteConeSquare)
c = JuMP.constraint_object(cref)
@test JuMP.isequal_canonical(c.func[1], x-1)
@test JuMP.isequal_canonical(c.func[2], 1-x)
Expand All @@ -230,7 +247,8 @@ function constraints_test(ModelType::Type{<:JuMP.AbstractModel})

@SDconstraint(m, iref[i=1:2], 0 [x+i x+y; x+y -y])
for i in 1:2
@test JuMP.name(iref[i]) == "iref[$i]"
test_constraint_name(iref[i], "iref[$i]", Vector{AffExpr},
MOI.PositiveSemidefiniteConeSquare)
c = JuMP.constraint_object(iref[i])
@test JuMP.isequal_canonical(c.func[1], x+i)
@test JuMP.isequal_canonical(c.func[2], x+y)
Expand All @@ -247,6 +265,30 @@ function constraints_test(ModelType::Type{<:JuMP.AbstractModel})
@test_macro_throws ErrorException @SDconstraint(m, [x 1; 1 -y] == [1 x; x -2])
end

@testset "Constraint name" begin
model = ModelType()
@variable(model, x)
@constraint(model, con, x^2 == 1)
test_constraint_name(con, "con", QuadExprType, MOI.EqualTo{Float64})
JuMP.set_name(con, "kon")
@test JuMP.constraint_by_name(model, "con") isa Nothing
test_constraint_name(con, "kon", QuadExprType, MOI.EqualTo{Float64})
y = @constraint(model, kon, [x^2, x] in SecondOrderCone())
err(name) = ErrorException("Multiple constraints have the name $name.")
@test_throws err("kon") JuMP.constraint_by_name(model, "kon")
JuMP.set_name(kon, "con")
test_constraint_name(con, "kon", QuadExprType, MOI.EqualTo{Float64})
test_constraint_name(kon, "con", Vector{QuadExprType},
MOI.SecondOrderCone)
JuMP.set_name(con, "con")
@test_throws err("con") JuMP.constraint_by_name(model, "con")
@test JuMP.constraint_by_name(model, "kon") isa Nothing
JuMP.set_name(kon, "kon")
test_constraint_name(con, "con", QuadExprType, MOI.EqualTo{Float64})
test_constraint_name(kon, "kon", Vector{QuadExprType},
MOI.SecondOrderCone)
end

@testset "Useful PSD error message" begin
model = ModelType()
@variable(model, X[1:2, 1:2])
Expand Down Expand Up @@ -308,11 +350,11 @@ function constraints_test(ModelType::Type{<:JuMP.AbstractModel})
end

@testset "Constraints for JuMP.Model" begin
constraints_test(Model)
constraints_test(Model, JuMP.VariableRef)
end

@testset "Constraints for JuMPExtension.MyModel" begin
constraints_test(JuMPExtension.MyModel)
constraints_test(JuMPExtension.MyModel, JuMPExtension.MyVariableRef)
end

@testset "Modifications" begin
Expand Down

0 comments on commit a6a9c6e

Please sign in to comment.