Skip to content

Commit a6a9c6e

Browse files
blegatmlubin
authored andcommitted
Add constraint by name (#1701)
* 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
1 parent ff5032a commit a6a9c6e

File tree

8 files changed

+210
-22
lines changed

8 files changed

+210
-22
lines changed

docs/src/constraints.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,22 @@ DocTestSetup = quote
209209
end
210210
```
211211

212+
## Constraint names
213+
214+
The name, i.e. the value of the `MOI.ConstraintName` attribute, of a constraint
215+
can be obtained by [`JuMP.name(::JuMP.ConstraintRef)`](@ref) and set by
216+
[`JuMP.set_name(::JuMP.ConstraintRef, ::String)`](@ref).
217+
```@docs
218+
name(::JuMP.ConstraintRef{Model, <:JuMP.MOI.ConstraintIndex})
219+
set_name(::JuMP.ConstraintRef{Model, <:JuMP.MOI.ConstraintIndex}, ::String)
220+
```
221+
222+
The constraint can also be retrieved from its name using
223+
[`JuMP.constraint_by_name`](@ref).
224+
```@docs
225+
constraint_by_name
226+
```
227+
212228
## Constraint containers
213229

214230
So far, we've added constraints one-by-one. However, just like

docs/src/variables.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,11 @@ julia> JuMP.fix_value(x)
249249
## Variable names
250250

251251
The name, i.e. the value of the `MOI.VariableName` attribute, of a variable can
252-
obtained by [`JuMP.name`](@ref) and set by [`JuMP.set_name`](@ref).
252+
be obtained by [`JuMP.name(::JuMP.VariableRef)`](@ref) and set by
253+
[`JuMP.set_name(::JuMP.VariableRef, ::String)`](@ref).
253254
```@docs
254-
name
255-
set_name
255+
name(::JuMP.VariableRef)
256+
set_name(::JuMP.VariableRef, ::String)
256257
```
257258
.
258259

src/aff_expr.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,9 @@ function MOI.VectorAffineFunction(affs::Vector{AffExpr})
324324
MOI.VectorAffineFunction(terms, constant)
325325
end
326326
moi_function(a::Vector{<:GenericAffExpr}) = MOI.VectorAffineFunction(a)
327+
function moi_function_type(::Type{Vector{Aff}}) where {T, Aff <: GenericAffExpr{T}}
328+
return MOI.VectorAffineFunction{T}
329+
end
327330

328331
# Copy an affine expression to a new model by converting all the
329332
# variables to the new model's variables

src/constraints.jl

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,107 @@ Base.broadcastable(cref::ConstraintRef) = Ref(cref)
111111
"""
112112
name(v::ConstraintRef)
113113
114-
Get a constraint's name.
114+
Get a constraint's name attribute.
115115
"""
116116
name(cr::ConstraintRef{Model,<:MOICON}) = MOI.get(cr.model, MOI.ConstraintName(), cr)
117117

118+
"""
119+
set_name(v::ConstraintRef, s::AbstractString)
120+
121+
Set a constraint's name attribute.
122+
"""
118123
set_name(cr::ConstraintRef{Model,<:MOICON}, s::String) = MOI.set(cr.model, MOI.ConstraintName(), cr, s)
119124

125+
"""
126+
constraint_by_name(model::AbstractModel,
127+
name::String)::Union{ConstraintRef, Nothing}
128+
129+
Returns the reference of the constraint with name attribute `name` or `Nothing`
130+
if no constraint has this name attribute. Throws an error if several
131+
constraints have `name` as their name attribute.
132+
133+
constraint_by_name(model::AbstractModel,
134+
name::String,
135+
F::Type{<:Union{AbstractJuMPScalar,
136+
Vector{<:AbstractJuMPScalar},
137+
MOI.AbstactFunction}},
138+
S::Type{<:MOI.AbstractSet})::Union{ConstraintRef, Nothing}
139+
140+
Similar to the method above, except that it throws an error if the constraint is
141+
not an `F`-in-`S` contraint where `F` is either the JuMP or MOI type of the
142+
function, and `S` is the MOI type of the set. This method is recommended if you
143+
know the type of the function and set since its returned type can be inferred
144+
while for the method above (i.e. without `F` and `S`), the exact return type of
145+
the constraint index cannot be inferred.
146+
147+
```jldoctest objective_function; setup = :(using JuMP), filter = r"Stacktrace:.*"s
148+
julia> using JuMP
149+
150+
julia> model = Model()
151+
A JuMP Model
152+
Feasibility problem with:
153+
Variables: 0
154+
Model mode: AUTOMATIC
155+
CachingOptimizer state: NO_OPTIMIZER
156+
Solver name: No optimizer attached.
157+
158+
julia> @variable(model, x)
159+
x
160+
161+
julia> @constraint(model, con, x^2 == 1)
162+
con : x² = 1.0
163+
164+
julia> JuMP.constraint_by_name(model, "kon")
165+
166+
julia> JuMP.constraint_by_name(model, "con")
167+
con : x² = 1.0
168+
169+
julia> JuMP.constraint_by_name(model, "con", AffExpr, JuMP.MOI.EqualTo{Float64})
170+
171+
julia> JuMP.constraint_by_name(model, "con", QuadExpr, JuMP.MOI.EqualTo{Float64})
172+
con : x² = 1.0
173+
```
174+
"""
175+
function constraint_by_name end
176+
177+
function constraint_by_name(model::Model, name::String)
178+
index = MOI.get(backend(model), MOI.ConstraintIndex, name)
179+
if index isa Nothing
180+
return nothing
181+
else
182+
return constraint_ref_with_index(model, index)
183+
end
184+
end
185+
186+
function constraint_by_name(model::Model, name::String,
187+
F::Type{<:MOI.AbstractFunction},
188+
S::Type{<:MOI.AbstractSet})
189+
index = MOI.get(backend(model), MOI.ConstraintIndex{F, S}, name)
190+
if index isa Nothing
191+
return nothing
192+
else
193+
return constraint_ref_with_index(model, index)
194+
end
195+
end
196+
function constraint_by_name(model::Model, name::String,
197+
F::Type{<:Union{ScalarType,
198+
Vector{ScalarType}}},
199+
S::Type) where ScalarType <: AbstractJuMPScalar
200+
return constraint_by_name(model, name, moi_function_type(F), S)
201+
end
202+
203+
# Creates a ConstraintRef with default shape
204+
function constraint_ref_with_index(model::AbstractModel,
205+
index::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction,
206+
<:MOI.AbstractScalarSet})
207+
return ConstraintRef(model, index, ScalarShape())
208+
end
209+
function constraint_ref_with_index(model::AbstractModel,
210+
index::MOI.ConstraintIndex{<:MOI.AbstractVectorFunction,
211+
<:MOI.AbstractVectorSet})
212+
return ConstraintRef(model, index, VectorShape())
213+
end
214+
120215
"""
121216
delete(model::Model, constraint_ref::ConstraintRef)
122217

src/quad_expr.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,9 @@ function MOI.VectorQuadraticFunction(quads::Vector{QuadExpr})
285285
MOI.VectorQuadraticFunction(lin_terms, quad_terms, constants)
286286
end
287287
moi_function(a::Vector{<:GenericQuadExpr}) = MOI.VectorQuadraticFunction(a)
288+
function moi_function_type(::Type{Vector{Quad}}) where {T, Quad <: GenericQuadExpr{T}}
289+
return MOI.VectorQuadraticFunction{T}
290+
end
288291

289292

290293
# Copy a quadratic expression to a new model by converting all the

src/variables.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,14 +210,14 @@ end
210210
"""
211211
name(v::VariableRef)::String
212212
213-
Get a variable's name.
213+
Get a variable's name attribute.
214214
"""
215215
name(v::VariableRef) = MOI.get(owner_model(v), MOI.VariableName(), v)
216216

217217
"""
218-
set_name(v::VariableRef,s::AbstractString)
218+
set_name(v::VariableRef, s::AbstractString)
219219
220-
Set a variable's name.
220+
Set a variable's name attribute.
221221
"""
222222
function set_name(v::VariableRef, s::String)
223223
return MOI.set(owner_model(v), MOI.VariableName(), v, s)
@@ -228,8 +228,8 @@ end
228228
name::String)::Union{AbstractVariableRef, Nothing}
229229
230230
Returns the reference of the variable with name attribute `name` or `Nothing` if
231-
no variable have this name attribute. Throws an error if several variables have
232-
`name` as name attribute.
231+
no variable has this name attribute. Throws an error if several variables have
232+
`name` as their name attribute.
233233
234234
```jldoctest objective_function; setup = :(using JuMP), filter = r"Stacktrace:.*"s
235235
julia> model = Model()

test/JuMPExtension.jl

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,17 @@ mutable struct MyModel <: JuMP.AbstractModel
2020
nextconidx::Int # Next constraint index is nextconidx+1
2121
constraints::Dict{ConstraintIndex,
2222
JuMP.AbstractConstraint} # Map conidx -> variable
23-
con_to_name::Dict{ConstraintIndex, String} # Map conidx -> name
23+
con_to_name::Dict{ConstraintIndex, String} # Map conidx -> name
24+
name_to_con::Union{Dict{String, ConstraintIndex},
25+
Nothing} # Map name -> conidx
2426
objectivesense::MOI.OptimizationSense
2527
objective_function::JuMP.AbstractJuMPScalar
2628
obj_dict::Dict{Symbol, Any} # Same that JuMP.Model's field `obj_dict`
2729
function MyModel()
2830
new(0, Dict{Int, JuMP.AbstractVariable}(),
29-
Dict{Int, String}(), Dict{String, Int}(), # Variables
31+
Dict{Int, String}(), nothing, # Variables
3032
0, Dict{ConstraintIndex, JuMP.AbstractConstraint}(),
31-
Dict{ConstraintIndex, String}(), # Constraints
33+
Dict{ConstraintIndex, String}(), nothing, # Constraints
3234
MOI.FEASIBILITY_SENSE,
3335
zero(JuMP.GenericAffExpr{Float64, MyVariableRef}),
3436
Dict{Symbol, Any}())
@@ -269,6 +271,32 @@ end
269271
JuMP.name(cref::MyConstraintRef) = cref.model.con_to_name[cref.index]
270272
function JuMP.set_name(cref::MyConstraintRef, name::String)
271273
cref.model.con_to_name[cref.index] = name
274+
cref.model.name_to_con = nothing
275+
end
276+
function JuMP.constraint_by_name(model::MyModel, name::String)
277+
if model.name_to_con === nothing
278+
# Inspired from MOI/src/Utilities/model.jl
279+
model.name_to_con = Dict{String, ConstraintIndex}()
280+
for (con, con_name) in model.con_to_name
281+
if haskey(model.name_to_con, con_name)
282+
# -1 is a special value that means this string does not map to
283+
# a unique constraint name.
284+
model.name_to_con[con_name] = ConstraintIndex(-1)
285+
else
286+
model.name_to_con[con_name] = con
287+
end
288+
end
289+
end
290+
index = get(model.name_to_con, name, nothing)
291+
if index isa Nothing
292+
return nothing
293+
elseif index.value == -1
294+
error("Multiple constraints have the name $name.")
295+
else
296+
# We have no information on whether this is a vector constraint
297+
# or a scalar constraint
298+
return JuMP.ConstraintRef(model, index, JuMP.ScalarShape())
299+
end
272300
end
273301

274302
end

test/constraint.jl

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,34 @@
1-
function constraints_test(ModelType::Type{<:JuMP.AbstractModel})
1+
function test_constraint_name(constraint, name, F::Type, S::Type)
2+
@test JuMP.name(constraint) == name
3+
model = constraint.model
4+
@test constraint.index == JuMP.constraint_by_name(model, name).index
5+
if !(model isa JuMPExtension.MyModel)
6+
@test constraint.index == JuMP.constraint_by_name(model, name, F, S).index
7+
end
8+
end
9+
10+
function constraints_test(ModelType::Type{<:JuMP.AbstractModel},
11+
VariableRefType::Type{<:JuMP.AbstractVariableRef})
12+
AffExprType = JuMP.GenericAffExpr{Float64, VariableRefType}
13+
QuadExprType = JuMP.GenericQuadExpr{Float64, VariableRefType}
14+
215
@testset "SingleVariable constraints" begin
316
m = ModelType()
417
@variable(m, x)
518

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

1428
@variable(m, y[1:2])
1529
@constraint(m, cref2[i=1:2], y[i] in MOI.LessThan(float(i)))
16-
@test JuMP.name(cref2[1]) == "cref2[1]"
30+
test_constraint_name(cref2[1], "cref2[1]", JuMP.VariableRef,
31+
MOI.LessThan{Float64})
1732
c = JuMP.constraint_object(cref2[1])
1833
@test c.func == y[1]
1934
@test c.set == MOI.LessThan(1.0)
@@ -41,7 +56,7 @@ function constraints_test(ModelType::Type{<:JuMP.AbstractModel})
4156
cref = @constraint(m, 2x <= 10)
4257
@test JuMP.name(cref) == ""
4358
JuMP.set_name(cref, "c")
44-
@test JuMP.name(cref) == "c"
59+
test_constraint_name(cref, "c", JuMP.AffExpr, MOI.LessThan{Float64})
4560

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

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

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

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

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

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

268+
@testset "Constraint name" begin
269+
model = ModelType()
270+
@variable(model, x)
271+
@constraint(model, con, x^2 == 1)
272+
test_constraint_name(con, "con", QuadExprType, MOI.EqualTo{Float64})
273+
JuMP.set_name(con, "kon")
274+
@test JuMP.constraint_by_name(model, "con") isa Nothing
275+
test_constraint_name(con, "kon", QuadExprType, MOI.EqualTo{Float64})
276+
y = @constraint(model, kon, [x^2, x] in SecondOrderCone())
277+
err(name) = ErrorException("Multiple constraints have the name $name.")
278+
@test_throws err("kon") JuMP.constraint_by_name(model, "kon")
279+
JuMP.set_name(kon, "con")
280+
test_constraint_name(con, "kon", QuadExprType, MOI.EqualTo{Float64})
281+
test_constraint_name(kon, "con", Vector{QuadExprType},
282+
MOI.SecondOrderCone)
283+
JuMP.set_name(con, "con")
284+
@test_throws err("con") JuMP.constraint_by_name(model, "con")
285+
@test JuMP.constraint_by_name(model, "kon") isa Nothing
286+
JuMP.set_name(kon, "kon")
287+
test_constraint_name(con, "con", QuadExprType, MOI.EqualTo{Float64})
288+
test_constraint_name(kon, "kon", Vector{QuadExprType},
289+
MOI.SecondOrderCone)
290+
end
291+
250292
@testset "Useful PSD error message" begin
251293
model = ModelType()
252294
@variable(model, X[1:2, 1:2])
@@ -308,11 +350,11 @@ function constraints_test(ModelType::Type{<:JuMP.AbstractModel})
308350
end
309351

310352
@testset "Constraints for JuMP.Model" begin
311-
constraints_test(Model)
353+
constraints_test(Model, JuMP.VariableRef)
312354
end
313355

314356
@testset "Constraints for JuMPExtension.MyModel" begin
315-
constraints_test(JuMPExtension.MyModel)
357+
constraints_test(JuMPExtension.MyModel, JuMPExtension.MyVariableRef)
316358
end
317359

318360
@testset "Modifications" begin

0 commit comments

Comments
 (0)