Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add constraint by name #1701

Merged
merged 11 commits into from Dec 22, 2018
16 changes: 16 additions & 0 deletions docs/src/constraints.md
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
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
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
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
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
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
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
@@ -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