Skip to content

Fix missing MOI methods and update tests #153

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

Merged
merged 1 commit into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 12 additions & 14 deletions src/Interfaces/MOI/MOI_wrapper.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import MathOptInterface
const MOI = MathOptInterface
import MathOptInterface as MOI

# ==============================================================================
# HELPER FUNCTIONS
Expand Down Expand Up @@ -84,8 +83,8 @@ Wrapper for MOI.
mutable struct Optimizer{T} <: MOI.AbstractOptimizer
inner::Model{T}

is_feas::Bool # Model is feasibility problem if true
_obj_type::ObjType
objective_sense::Union{Nothing,MOI.OptimizationSense}
_obj_type::Union{Nothing,ObjType}

# Map MOI Variable/Constraint indices to internal indices
var_counter::Int # Should never be reset
Expand All @@ -98,10 +97,6 @@ mutable struct Optimizer{T} <: MOI.AbstractOptimizer
# Variable and constraint names
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)
bnd2name::Dict{MOI.ConstraintIndex, String}

# Keep track of bound constraints
var2bndtype::Dict{MOI.VariableIndex, Set{Type{<:MOI.AbstractScalarSet}}}
Expand All @@ -111,7 +106,9 @@ mutable struct Optimizer{T} <: MOI.AbstractOptimizer

function Optimizer{T}(;kwargs...) where{T}
m = new{T}(
Model{T}(), false, _SCALAR_AFFINE,
Model{T}(),
nothing, # objective_sense
nothing, # _obj_type
# Variable and constraint counters
0, 0,
# Index mapping
Expand All @@ -120,7 +117,6 @@ mutable struct Optimizer{T} <: MOI.AbstractOptimizer
# Name -> index mapping
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 @@ -138,6 +134,8 @@ Optimizer(;kwargs...) = Optimizer{Float64}(;kwargs...)
function MOI.empty!(m::Optimizer)
# Inner model
empty!(m.inner)
m.objective_sense = nothing
m._obj_type = nothing
# Reset index mappings
m.var_indices_moi = MOI.VariableIndex[]
m.con_indices_moi = MOI.ConstraintIndex[]
Expand All @@ -149,14 +147,15 @@ function MOI.empty!(m::Optimizer)
m.name2con = Dict{String, Set{MOI.ConstraintIndex}}()

# Reset bound tracking
m.bnd2name = Dict{MOI.ConstraintIndex, String}()
m.var2bndtype = Dict{MOI.VariableIndex, Set{MOI.ConstraintIndex}}()

m.solve_time = 0.0
return nothing
end

function MOI.is_empty(m::Optimizer)
m.objective_sense === nothing || return false
m._obj_type === nothing || return false
m.inner.pbdata.nvar == 0 || return false
m.inner.pbdata.ncon == 0 || return false

Expand All @@ -168,7 +167,6 @@ function MOI.is_empty(m::Optimizer)
length(m.name2var) == 0 || return false
length(m.name2con) == 0 || return false

length(m.bnd2name) == 0 || return false
length(m.var2bndtype) == 0 || return false

return true
Expand All @@ -182,8 +180,8 @@ end

MOI.supports_incremental_interface(::Optimizer) = true

function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike; kwargs...)
return MOI.Utilities.default_copy_to(dest, src; kwargs...)
function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
return MOI.Utilities.default_copy_to(dest, src)
end


Expand Down
42 changes: 27 additions & 15 deletions src/Interfaces/MOI/attributes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,25 @@ const SUPPORTED_MODEL_ATTR = Union{

MOI.supports(::Optimizer, ::SUPPORTED_MODEL_ATTR) = true

#
# ListOfModelAttributesSet
#
function MOI.get(m::Optimizer{T}, ::MOI.ListOfModelAttributesSet) where {T}
ret = MOI.AbstractModelAttribute[]
if !isempty(m.inner.pbdata.name)
push!(ret, MOI.Name())
end
if m.objective_sense !== nothing
push!(ret, MOI.ObjectiveSense())
end
if m._obj_type == _SINGLE_VARIABLE
push!(ret, MOI.ObjectiveFunction{MOI.VariableIndex}())
elseif m._obj_type == _SCALAR_AFFINE
push!(ret, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}())
end
return ret
end

#
# ListOfVariableIndices
#
Expand All @@ -125,36 +144,28 @@ MOI.get(m::Optimizer, ::MOI.NumberOfVariables) = m.inner.pbdata.nvar
#
# ObjectiveFunctionType
#
function MOI.get(
m::Optimizer{T}, ::MOI.ObjectiveFunctionType
) where{T}
function MOI.get(m::Optimizer{T}, ::MOI.ObjectiveFunctionType) where{T}
if m._obj_type == _SINGLE_VARIABLE
return MOI.VariableIndex
else
return MOI.ScalarAffineFunction{T}
end
return MOI.ScalarAffineFunction{T}
end

#
# ObjectiveSense
#
function MOI.get(m::Optimizer, ::MOI.ObjectiveSense)
m.is_feas && return MOI.FEASIBILITY_SENSE

return m.inner.pbdata.objsense ? MOI.MIN_SENSE : MOI.MAX_SENSE
return something(m.objective_sense, MOI.FEASIBILITY_SENSE)
end

function MOI.set(m::Optimizer, ::MOI.ObjectiveSense, s::MOI.OptimizationSense)
m.is_feas = (s == MOI.FEASIBILITY_SENSE)

m.objective_sense = s
if s == MOI.MIN_SENSE || s == MOI.FEASIBILITY_SENSE
m.inner.pbdata.objsense = true
elseif s == MOI.MAX_SENSE
m.inner.pbdata.objsense = false
else
error("Objetive sense not supported: $s")
@assert s == MOI.MAX_SENSE
m.inner.pbdata.objsense = false
end

return nothing
end

Expand All @@ -164,7 +175,8 @@ end
function MOI.get(m::Optimizer{T}, attr::MOI.ObjectiveValue) where{T}
MOI.check_result_index_bounds(m, attr)
raw_z = get_attribute(m.inner, ObjectiveValue())
return raw_z * !m.is_feas
is_feas = MOI.get(m, MOI.ObjectiveSense()) == MOI.FEASIBILITY_SENSE
return raw_z * !is_feas
end

#
Expand Down
46 changes: 28 additions & 18 deletions src/Interfaces/MOI/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,34 @@ const SUPPORTED_CONSTR_ATTR = Union{

MOI.supports(::Optimizer, ::A, ::Type{<:MOI.ConstraintIndex}) where{A<:SUPPORTED_CONSTR_ATTR} = true

function MOI.get(
m::Optimizer,
::MOI.ListOfConstraintAttributesSet{F,S},
) where {F,S}
ret = MOI.AbstractConstraintAttribute[]
for set in values(m.name2con)
if any(ci -> ci isa MOI.ConstraintIndex{F,S}, set)
push!(ret, MOI.ConstraintName())
break
end
end
return ret
end

_type_tuple(::MOI.ConstraintIndex{F,S}) where {F,S} = (F, S)

function MOI.get(m::Optimizer, ::MOI.ListOfConstraintTypesPresent)
ret = Tuple{Type,Type}[]
append!(ret, unique!(_type_tuple.(m.con_indices_moi)))
for set in values(m.var2bndtype)
for S in set
push!(ret, (MOI.VariableIndex, S))
end
end
unique!(ret)
return ret
end

# MOI boilerplate
function MOI.supports(::Optimizer, ::MOI.ConstraintName, ::Type{<:MOI.ConstraintIndex{<:MOI.VariableIndex}})
throw(MOI.VariableIndexConstraintNameError())
Expand Down Expand Up @@ -241,15 +269,6 @@ function MOI.delete(
set_attribute(m.inner, VariableUpperBound(), j, T(Inf))
end

# Update name tracking
old_name = get(m.bnd2name, c, "")
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
delete!(m.var2bndtype[v], S)

Expand Down Expand Up @@ -367,15 +386,6 @@ end
# ConstraintName
#

function MOI.get(
m::Optimizer{T}, ::MOI.ConstraintName,
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

function MOI.get(
m::Optimizer{T}, ::MOI.ConstraintName,
c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S}
Expand Down
12 changes: 7 additions & 5 deletions src/Interfaces/MOI/objective.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ end
# =============================================
function MOI.get(
m::Optimizer{T},
::MOI.ObjectiveFunction{MOI.VariableIndex}
) where{T}
::MOI.ObjectiveFunction{F}
) where{T,F}
obj = MOI.get(m, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}())
return convert(MOI.VariableIndex, obj)
return convert(F, obj)
end

function MOI.get(
Expand Down Expand Up @@ -77,7 +77,7 @@ end

# =============================================
# 3. Modify objective
# =============================================
# =============================================
function MOI.modify(
m::Optimizer{T},
c::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}},
Expand All @@ -90,6 +90,7 @@ function MOI.modify(
# Update inner model
j = m.var_indices[v]
m.inner.pbdata.obj[j] = chg.new_coefficient # TODO: use inner API
m._obj_type = _SCALAR_AFFINE
return nothing
end

Expand All @@ -100,5 +101,6 @@ function MOI.modify(
) where{T}
isfinite(chg.new_constant) || error("Objective constant term must be finite")
m.inner.pbdata.obj0 = chg.new_constant
m._obj_type = _SCALAR_AFFINE
return nothing
end
end
23 changes: 5 additions & 18 deletions src/Interfaces/MOI/variables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,6 @@ function MOI.add_variable(m::Optimizer{T}) where{T}
return x
end

# TODO: dispatch to inner model
function MOI.add_variables(m::Optimizer, N::Int)
N >= 0 || error("Cannot add negative number of variables")

N == 0 && return MOI.VariableIndex[]

vars = Vector{MOI.VariableIndex}(undef, N)
for j in 1:N
x = MOI.add_variable(m)
vars[j] = x
end

return vars
end


# =============================================
# 3. Delete variables
# =============================================
Expand Down Expand Up @@ -111,13 +95,16 @@ function MOI.set(m::Optimizer, ::MOI.VariableName, v::MOI.VariableIndex, name::S
# Check that variable does exist
MOI.throw_if_not_valid(m, v)

s = get!(m.name2var, name, Set{MOI.VariableIndex}())

# Update inner model
j = m.var_indices[v]
old_name = get_attribute(m.inner, VariableName(), j)
if name == old_name
return # It's the same name!
end
set_attribute(m.inner, VariableName(), j, name)

s = get!(m.name2var, name, Set{MOI.VariableIndex}())

# Update names mapping
push!(s, v)
# Delete old name
Expand Down
Loading
Loading