Skip to content
Open
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
29 changes: 22 additions & 7 deletions src/Utilities/matrix_of_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,24 @@ mutable struct MatrixOfConstraints{T,AT,BT,ST} <: MOI.ModelLike
caches::Vector{Any}
are_indices_mapped::Vector{BitSet}
final_touch::Bool
function MatrixOfConstraints{T,AT,BT,ST}() where {T,AT,BT,ST}
model = new{T,AT,BT,ST}(AT(), BT(), ST(), Any[], BitSet[], false)
MOI.empty!(model)
function MatrixOfConstraints{T}(coefficients, constants, sets) where {T}
model = new{T,typeof(coefficients),typeof(constants),typeof(sets)}(
coefficients,
constants,
sets,
Any[],
BitSet[],
false,
)
_reset_caches!(model)
return model
end
end

function MatrixOfConstraints{T,AT,BT,ST}() where {T,AT,BT,ST}
return MatrixOfConstraints{T}(AT(), BT(), ST())
end

###
### Interface for the .coefficients field
###
Expand Down Expand Up @@ -292,13 +303,17 @@ function rows end

MOI.is_empty(v::MatrixOfConstraints) = MOI.is_empty(v.sets)

function MOI.empty!(v::MatrixOfConstraints{T}) where {T}
function _reset_caches!(v::MatrixOfConstraints{T}) where {T}
v.caches =
[Tuple{_affine_function_type(T, S),S}[] for S in set_types(v.sets)]
return v.are_indices_mapped = [BitSet() for _ in eachindex(v.caches)]
end

function MOI.empty!(v::MatrixOfConstraints)
MOI.empty!(v.coefficients)
empty!(v.constants)
MOI.empty!(v.sets)
v.caches =
[Tuple{_affine_function_type(T, S),S}[] for S in set_types(v.sets)]
v.are_indices_mapped = [BitSet() for _ in eachindex(v.caches)]
_reset_caches!(v)
Copy link
Member Author

@blegat blegat Dec 20, 2025

Choose a reason for hiding this comment

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

These caches actually only make sense for MutableSparseMatrixCSC, it doesn't make sense for a SparseMatrixCSC nor for a MutableSparseMatrixCSR but let's not complicate this PR too much

v.final_touch = false
return
end
Expand Down
14 changes: 9 additions & 5 deletions src/Utilities/model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ mutable struct GenericModel{T,O,V,C} <: AbstractModelLike{T}
# A useful dictionary for extensions to store things. These are
# _not_ copied between models.
ext::Dict{Symbol,Any}
function GenericModel{T,O,V,C}() where {T,O,V,C}
return new{T,O,V,C}(
function GenericModel{T}(objective, variables, constraints) where {T}
return new{T,typeof(objective),typeof(variables),typeof(constraints)}(
"",
O(),
V(),
C(),
objective,
variables,
constraints,
Dict{MOI.VariableIndex,String}(),
nothing,
Dict{MOI.ConstraintIndex,String}(),
Expand All @@ -47,6 +47,10 @@ mutable struct GenericModel{T,O,V,C} <: AbstractModelLike{T}
end
end

function GenericModel{T,O,V,C}() where {T,O,V,C}
return GenericModel{T}(O(), V(), C())
end

abstract type AbstractOptimizer{T} <: MOI.AbstractOptimizer end

"""
Expand Down
22 changes: 12 additions & 10 deletions src/Utilities/sparse_matrix.jl
Original file line number Diff line number Diff line change
Expand Up @@ -183,19 +183,21 @@ function Base.convert(
)
end

function _first_in_column(
A::MutableSparseMatrixCSC{Tv,Ti},
row::Integer,
col::Integer,
) where {Tv,Ti}
_indexing(A::MutableSparseMatrixCSC) = A.indexing
_indexing(::SparseArrays.SparseMatrixCSC) = OneBasedIndexing()

const _SparseMatrixCSC{Tv,Ti} =
Union{MutableSparseMatrixCSC{Tv,Ti},SparseArrays.SparseMatrixCSC{Tv,Ti}}

function _first_in_column(A::_SparseMatrixCSC, row::Integer, col::Integer)
range = SparseArrays.nzrange(A, col)
row = _shift(row, OneBasedIndexing(), A.indexing)
row = _shift(row, OneBasedIndexing(), _indexing(A))
idx = searchsortedfirst(view(A.rowval, range), row)
return get(range, idx, last(range) + 1)
end

function extract_function(
A::MutableSparseMatrixCSC{T},
A::_SparseMatrixCSC{T},
Copy link
Member Author

Choose a reason for hiding this comment

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

We can either implement it for SparseMatrixCSC here or implement it in MatrixOptInterface.
The issue of the second option is that it's type piracy and it's just a copy-paste.
One might then ask for exctract_function for Base.Matrix or Transpose{SparseMatrixCSC{Tv,Ti}} etc... But let's keep this PR simple. I think, implementing Matrix, SparseMatrixCSC and their adjoint/transpose should be good enough for 99% of the cases of this use case which is already niche ^^

row::Integer,
constant::T,
) where {T}
Expand All @@ -205,7 +207,7 @@ function extract_function(
if idx > last(SparseArrays.nzrange(A, col))
continue
end
r = _shift(A.rowval[idx], A.indexing, OneBasedIndexing())
r = _shift(A.rowval[idx], _indexing(A), OneBasedIndexing())
if r == row
push!(
func.terms,
Expand All @@ -217,7 +219,7 @@ function extract_function(
end

function extract_function(
A::MutableSparseMatrixCSC{T},
A::_SparseMatrixCSC{T},
rows::UnitRange,
constants::Vector{T},
) where {T}
Expand All @@ -231,7 +233,7 @@ function extract_function(
if idx[col] > last(SparseArrays.nzrange(A, col))
continue
end
row = _shift(A.rowval[idx[col]], A.indexing, OneBasedIndexing())
row = _shift(A.rowval[idx[col]], _indexing(A), OneBasedIndexing())
if row != rows[output_index]
continue
end
Expand Down
72 changes: 72 additions & 0 deletions test/Utilities/matrix_of_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,78 @@ function test_unsupported_constraint()
return
end

MOI.Utilities.@product_of_sets(_EqualTos, MOI.EqualTo{T},)

function _equality_constraints(
A::AbstractMatrix{T},
b::AbstractVector{T},
) where {T}
sets = _EqualTos{T}()
for _ in eachindex(b)
MOI.Utilities.add_set(
sets,
MOI.Utilities.set_index(sets, MOI.EqualTo{T}),
)
end
MOI.Utilities.final_touch(sets)
constants = MOI.Utilities.Hyperrectangle(b, b)
model = MOI.Utilities.MatrixOfConstraints{T}(A, constants, sets)
model.final_touch = true
return model
end

# Inspired from MatrixOfConstraints
function test_lp_standard_form()
s = """
variables: x1, x2
cx1: x1 >= 0.0
cx2: x2 >= 0.0
c1: 1x1 == 5.0
c2: 3x1 + 4x2 == 6.0
minobjective: 7x1 + 8x2
"""
expected = MOI.Utilities.Model{Float64}()
MOI.Utilities.loadfromstring!(expected, s)

var_names = ["x1", "x2"]
con_names = ["c1", "c2"]

A = SparseArrays.sparse([
1.0 0.0
3.0 4.0
])
b = [5.0, 6.0]
form = MOI.Utilities.GenericModel{Float16}(
expected.objective,
expected.variables,
_equality_constraints(A, b),
)

model = MOI.Utilities.Model{Float64}()
MOI.copy_to(MOI.Bridges.Constraint.Scalarize{Float64}(model), form)
MOI.set(
model,
MOI.VariableName(),
MOI.VariableIndex.(eachindex(var_names)),
var_names,
)
MOI.set(
model,
MOI.ConstraintName(),
MOI.ConstraintIndex{
MOI.ScalarAffineFunction{Float64},
MOI.EqualTo{Float64},
}.(
eachindex(con_names),
),
con_names,
)

MOI.Test.util_test_models_equal(model, expected, var_names, con_names)

return
end

end

TestMatrixOfConstraints.runtests()
Loading