Skip to content

Commit

Permalink
Fix MOI names interface (#113)
Browse files Browse the repository at this point in the history
* Fix ConstraintName interface

* Fix VariableName interface

* Bump version --> v0.9.2
  • Loading branch information
mtanneau authored Feb 7, 2022
1 parent 74180f3 commit 42e17e0
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 47 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Tulip"
uuid = "6dd1b50a-3aae-11e9-10b5-ef983d2400fa"
authors = ["Mathieu Tanneau <mathieu.tanneau@gmail.com>"]
version = "0.9.1"
version = "0.9.2"

[deps]
CodecBzip2 = "523fee87-0ab8-5b00-afb7-3ecf72e48cfd"
Expand Down
11 changes: 6 additions & 5 deletions src/Interfaces/MOI/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ mutable struct Optimizer{T} <: MOI.AbstractOptimizer
con_indices::Dict{MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, <:SCALAR_SETS{T}}, Int}

# Variable and constraint names
name2var::Dict{String, MOI.VariableIndex}
name2con::Dict{String, MOI.ConstraintIndex}
name2var::Dict{String, Set{MOI.VariableIndex}}
name2con::Dict{String, Set{MOI.ConstraintIndex}}
# MOIIndex -> name mapping for SingleVariable constraints
# Will be dropped with MOI 0.10
# => (https://github.com/jump-dev/MathOptInterface.jl/issues/832)
Expand All @@ -118,7 +118,8 @@ mutable struct Optimizer{T} <: MOI.AbstractOptimizer
MOI.VariableIndex[], Dict{MOI.VariableIndex, Int}(),
MOI.ConstraintIndex[], Dict{MOI.ConstraintIndex{MOI.ScalarAffineFunction, <:SCALAR_SETS{T}}, Int}(),
# Name -> index mapping
Dict{String, MOI.VariableIndex}(), Dict{String, MOI.ConstraintIndex}(),
Dict{String, Set{MOI.VariableIndex}}(),
Dict{String, Set{MOI.ConstraintIndex}}(),
Dict{MOI.ConstraintIndex, String}(), # Variable bounds tracking
Dict{MOI.VariableIndex, Set{Type{<:MOI.AbstractScalarSet}}}(),
0.0
Expand All @@ -144,8 +145,8 @@ function MOI.empty!(m::Optimizer)
m.con_indices = Dict{MOI.ConstraintIndex, Int}()

# Reset name mappings
m.name2var = Dict{String, MOI.VariableIndex}()
m.name2con = Dict{String, MOI.ConstraintIndex}()
m.name2var = Dict{String, Set{MOI.VariableIndex}}()
m.name2con = Dict{String, Set{MOI.ConstraintIndex}}()

# Reset bound tracking
m.bnd2name = Dict{MOI.ConstraintIndex, String}()
Expand Down
52 changes: 36 additions & 16 deletions src/Interfaces/MOI/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const SUPPORTED_CONSTR_ATTR = Union{
MOI.ConstraintPrimal,
MOI.ConstraintDual,
# MOI.ConstraintPrimalStart,
# MOI.ConstraintDualStart, # once dual warm-start is supported
# MOI.ConstraintDualStart, # once dual warm-start is supported
# MOI.ConstraintBasisStatus, # once cross-over is supported
MOI.ConstraintFunction,
MOI.ConstraintSet
Expand Down Expand Up @@ -86,7 +86,7 @@ function MOI.add_constraint(

# Update bound tracking
push!(m.var2bndtype[v], MOI.LessThan{T})

return MOI.ConstraintIndex{MOI.VariableIndex, MOI.LessThan{T}}(v.value)
end

Expand Down Expand Up @@ -243,7 +243,11 @@ function MOI.delete(

# Update name tracking
old_name = get(m.bnd2name, c, "")
old_name != "" && delete!(m.name2con, old_name) # delete old name
if old_name != "" && haskey(m.name2con, old_name)
s = m.name2con[old_name]
delete!(s, c)
length(s) == 0 && delete!(m.name2con, old_name)
end
delete!(m.bnd2name, c)

# Delete tracking of bounds
Expand Down Expand Up @@ -271,7 +275,11 @@ function MOI.delete(
delete!(m.con_indices, c)

# Update name tracking
old_name != "" && delete!(m.name2con, old_name)
if old_name != "" && haskey(m.name2con, old_name)
s = m.name2con[old_name]
delete!(s, c)
length(s) == 0 && delete!(m.name2con, old_name)
end

return nothing
end
Expand Down Expand Up @@ -351,7 +359,7 @@ function MOI.get(
for cidx in keys(m.con_indices)
ncon += isa(cidx, MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S})
end

return ncon
end

Expand All @@ -364,7 +372,7 @@ function MOI.get(
c::MOI.ConstraintIndex{MOI.VariableIndex, S}
) where {T, S<:SCALAR_SETS{T}}
MOI.throw_if_not_valid(m, c)

return get(m.bnd2name, c, "")
end

Expand All @@ -390,26 +398,38 @@ function MOI.set(
) where{T, S<:SCALAR_SETS{T}}
MOI.throw_if_not_valid(m, c)

# Check for dupplicate name
c_ = get(m.name2con, name, nothing)
c_ === nothing || c_ == c || error("Duplicate constraint name $name")
s = get!(m.name2con, name, Set{MOI.ConstraintIndex}())

# Update inner model
i = m.con_indices[c]
old_name = get_attribute(m.inner, ConstraintName(), i)
set_attribute(m.inner, ConstraintName(), i, name)

# Update constraint name tracking
delete!(m.name2con, old_name)
if name != ""
m.name2con[name] = c
push!(s, c)
# Delete old name
s_old = get(m.name2con, old_name, Set{MOI.ConstraintIndex}())
if length(s_old) == 0
# Constraint previously didn't have name --> ignore
elseif length(s_old) == 1
delete!(m.name2con, old_name)
else
delete!(s_old, c)
end
return nothing
end

function MOI.get(m::Optimizer, CIType::Type{<:MOI.ConstraintIndex}, name::String)
c = get(m.name2con, name, nothing)
return isa(c, CIType) ? c : nothing
s = get(m.name2con, name, Set{MOI.ConstraintIndex}())
if length(s) == 0
return nothing
elseif length(s) == 1
c = first(s)
return isa(c, CIType) ? c : nothing
else
error("Duplicate constraint name detected: $(name)")
end
return nothing
end

#
Expand Down Expand Up @@ -604,7 +624,7 @@ function MOI.set(
s::S
) where{T, S<:SCALAR_SETS{T}}
MOI.throw_if_not_valid(m, c)

# Update inner bounds
i = m.con_indices[c]
if S == MOI.LessThan{T}
Expand All @@ -626,7 +646,7 @@ end

#
# ConstraintPrimal
#
#
function MOI.get(
m::Optimizer{T}, attr::MOI.ConstraintPrimal,
c::MOI.ConstraintIndex{MOI.VariableIndex, S}
Expand Down
47 changes: 35 additions & 12 deletions src/Interfaces/MOI/variables.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# =============================================
# 1. Supported variable attributes
# =============================================

"""
SUPPORTED_VARIABLE_ATTR
Expand Down Expand Up @@ -29,7 +29,7 @@ function MOI.add_variable(m::Optimizer{T}) where{T}
m.var_counter += 1
x = MOI.VariableIndex(m.var_counter)
j = Tulip.add_variable!(m.inner.pbdata, Int[], T[], zero(T), T(-Inf), T(Inf))

# Update tracking of variables
m.var_indices[x] = j
m.var2bndtype[x] = Set{Type{<:MOI.AbstractScalarSet}}()
Expand Down Expand Up @@ -62,12 +62,18 @@ function MOI.delete(m::Optimizer, v::MOI.VariableIndex)

# Update inner model
j = m.var_indices[v]
old_name = get_attribute(m.inner, VariableName(), j)
delete_variable!(m.inner.pbdata, j)

# Remove bound tracking
delete!(m.var2bndtype, v)

# TODO: name update

# Name update
if old_name != ""
s = m.name2var[old_name]
delete!(s, v)
length(s) == 0 && delete!(m.name2var, old_name)
end

# Update indices correspondence
deleteat!(m.var_indices_moi, j)
Expand All @@ -82,7 +88,16 @@ end
# 4. Get/set variable attributes
# =============================================

MOI.get(m::Optimizer, ::Type{MOI.VariableIndex}, name::String) = get(m.name2var, name, nothing)
function MOI.get(m::Optimizer, ::Type{MOI.VariableIndex}, name::String)
s = get(m.name2var, name, Set{MOI.VariableIndex}())
if length(s) == 0
return nothing
elseif length(s) == 1
return first(s)
else
error("Duplicate variable name detected: $(name)")
end
end

function MOI.get(m::Optimizer, ::MOI.VariableName, v::MOI.VariableIndex)
MOI.throw_if_not_valid(m, v)
Expand All @@ -96,17 +111,25 @@ function MOI.set(m::Optimizer, ::MOI.VariableName, v::MOI.VariableIndex, name::S
# Check that variable does exist
MOI.throw_if_not_valid(m, v)

# Check that name is unique
v_ = get(m.name2var, name, nothing)
v_ === nothing || v_ == v || error("Duplicate variable name $name")
s = get!(m.name2var, name, Set{MOI.VariableIndex}())

# Update inner model
j = m.var_indices[v]
old_name = get_attribute(m.inner, VariableName(), j)
set_attribute(m.inner, VariableName(), j, name)

# Update names mapping
m.name2var[name] = v

push!(s, v)
# Delete old name
s_old = get(m.name2var, old_name, Set{MOI.ConstraintIndex}())
if length(s_old) == 0
# Variable didn't have name before
elseif length(s_old) == 1
# Delete this from mapping
delete!(m.name2var, old_name)
else
delete!(s_old, v)
end
return nothing
end

Expand All @@ -120,4 +143,4 @@ function MOI.get(m::Optimizer{T},
# Query inner solution
j = m.var_indices[x]
return m.inner.solution.x[j]
end
end
2 changes: 1 addition & 1 deletion src/Tulip.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ using SparseArrays

using TimerOutputs

version() = v"0.9.1"
version() = v"0.9.2"

include("utils.jl")

Expand Down
12 changes: 0 additions & 12 deletions test/Interfaces/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ const CONFIG = MOIT.Config(Float64, atol=1e-6, rtol=1e-6,
"test_model_ModelFilter_ListOfConstraintTypesPresent",
"test_model_Name",
"test_objective_set_via_modify",
# MOI expects to throw when getting duplicate cons / var names
"test_model_ScalarAffineFunction_ConstraintName",
"test_model_VariableName",
"test_model_duplicate_ScalarAffineFunction_ConstraintName",
"test_model_duplicate_VariableName",
"test_variable_VariableName",
# requires get quadratic objective
"test_objective_get_ObjectiveFunction_ScalarAffineFunction",
# Tulip not compliant with MOI convention for primal/dual infeasible models
Expand Down Expand Up @@ -67,12 +61,6 @@ end
"test_model_ModelFilter_ListOfConstraintTypesPresent",
"test_model_Name",
"test_objective_set_via_modify",
# MOI expects to throw when getting duplicate cons / var names
"test_model_ScalarAffineFunction_ConstraintName",
"test_model_VariableName",
"test_model_duplicate_ScalarAffineFunction_ConstraintName",
"test_model_duplicate_VariableName",
"test_variable_VariableName",
# requires get quadratic objective
"test_objective_get_ObjectiveFunction_ScalarAffineFunction",
# Tulip not compliant with MOI convention for primal/dual infeasible models
Expand Down

2 comments on commit 42e17e0

@mtanneau
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register()

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/54060

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.9.2 -m "<description of version>" 42e17e0afad4312f221725949fdcd0a1e35019a1
git push origin v0.9.2

Please sign in to comment.