Skip to content

Commit 0068b8a

Browse files
authored
Implement scalar coefficient change for MatrixOfConstraints (#2975)
1 parent 5617783 commit 0068b8a

3 files changed

Lines changed: 173 additions & 0 deletions

File tree

src/Utilities/matrix_of_constraints.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,23 @@ and [`MOI.VectorConstantChange`](@ref) for [`MatrixOfConstraints`](@ref).
246246
"""
247247
function modify_constants end
248248

249+
"""
250+
modify_coefficients(
251+
coefficients,
252+
row::Integer,
253+
col::Integer,
254+
new_coefficient,
255+
)::Bool
256+
257+
Modify `coefficients` in-place to store `new_coefficient` at position
258+
`(row, col)`. Return `true` if the entry existed and was modified, and `false`
259+
if no entry exists at `(row, col)` in the sparse structure and `new_coefficient` is nonzero.
260+
261+
This function must be implemented to enable
262+
[`MOI.ScalarCoefficientChange`](@ref) for [`MatrixOfConstraints`](@ref).
263+
"""
264+
function modify_coefficients end
265+
249266
###
250267
### Interface for the .sets field
251268
###
@@ -698,6 +715,29 @@ function MOI.modify(
698715
return
699716
end
700717

718+
function MOI.modify(
719+
model::MatrixOfConstraints,
720+
ci::MOI.ConstraintIndex,
721+
change::MOI.ScalarCoefficientChange,
722+
)
723+
if !modify_coefficients(
724+
model.coefficients,
725+
rows(model, ci),
726+
change.variable.value,
727+
change.new_coefficient,
728+
)
729+
throw(
730+
MOI.ModifyConstraintNotAllowed(
731+
ci,
732+
change,
733+
"cannot set a new non-zero coefficient because no entry " *
734+
"exists in the sparse matrix of `MatrixOfConstraints`",
735+
),
736+
)
737+
end
738+
return
739+
end
740+
701741
function modify_constants(
702742
b::AbstractVector{T},
703743
row::Integer,

src/Utilities/sparse_matrix.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,22 @@ function load_terms(
161161
return
162162
end
163163

164+
function modify_coefficients(
165+
A::Union{MutableSparseMatrixCSC{Tv},SparseArrays.SparseMatrixCSC{Tv}},
166+
row::Integer,
167+
col::Integer,
168+
new_coefficient::Tv,
169+
) where {Tv}
170+
idx = _first_in_column(A, row, col)
171+
range = SparseArrays.nzrange(A, col)
172+
r = _shift(row, OneBasedIndexing(), _indexing(A))
173+
if idx <= last(range) && A.rowval[idx] == r
174+
A.nzval[idx] = new_coefficient
175+
return true
176+
end
177+
return iszero(new_coefficient)
178+
end
179+
164180
"""
165181
Base.convert(
166182
::Type{SparseMatrixCSC{Tv,Ti}},
@@ -189,6 +205,17 @@ _indexing(A::MutableSparseMatrixCSC) = A.indexing
189205

190206
_indexing(::SparseArrays.SparseMatrixCSC) = OneBasedIndexing()
191207

208+
"""
209+
_first_in_column(
210+
A::Union{MutableSparseMatrixCSC,SparseArrays.SparseMatrixCSC},
211+
row::Integer,
212+
col::Integer,
213+
)
214+
215+
Return the index of the first non-zero entry in the column `col` that has a row
216+
index greater than or equal to `row`.
217+
If no such entry exists, return `last(SparseArrays.nzrange(A, col)) + 1`.
218+
"""
192219
function _first_in_column(
193220
A::Union{MutableSparseMatrixCSC,SparseArrays.SparseMatrixCSC},
194221
row::Integer,

test/Utilities/test_matrix_of_constraints.jl

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,112 @@ function test_set_types_fallback()
636636
return
637637
end
638638

639+
function test_modify_scalar_coefficient_change()
640+
model = MOI.Utilities.GenericOptimizer{
641+
Int,
642+
MOI.Utilities.ObjectiveContainer{Int},
643+
MOI.Utilities.VariablesContainer{Int},
644+
MOI.Utilities.MatrixOfConstraints{
645+
Int,
646+
MOI.Utilities.MutableSparseMatrixCSC{
647+
Int,
648+
Int,
649+
MOI.Utilities.OneBasedIndexing,
650+
},
651+
MOI.Utilities.Hyperrectangle{Int},
652+
ScalarSets{Int},
653+
},
654+
}()
655+
x = MOI.add_variable(model)
656+
y = MOI.add_variable(model)
657+
func = 2x + 3y
658+
set = MOI.EqualTo(1)
659+
c = MOI.add_constraint(model, func, set)
660+
MOI.Utilities.final_touch(model, nothing)
661+
f = MOI.get(model, MOI.ConstraintFunction(), c)
662+
@test f 2x + 3y
663+
MOI.modify(model, c, MOI.ScalarCoefficientChange(x, 5))
664+
f = MOI.get(model, MOI.ConstraintFunction(), c)
665+
@test f 5x + 3y
666+
MOI.modify(model, c, MOI.ScalarCoefficientChange(y, 0))
667+
f = MOI.get(model, MOI.ConstraintFunction(), c)
668+
@test f 5x + 0y
669+
return
670+
end
671+
672+
function test_modify_scalar_coefficient_change_zero_based()
673+
model = MOI.Utilities.GenericOptimizer{
674+
Float64,
675+
MOI.Utilities.ObjectiveContainer{Float64},
676+
MOI.Utilities.VariablesContainer{Float64},
677+
MOI.Utilities.MatrixOfConstraints{
678+
Float64,
679+
MOI.Utilities.MutableSparseMatrixCSC{
680+
Float64,
681+
Int,
682+
MOI.Utilities.ZeroBasedIndexing,
683+
},
684+
MOI.Utilities.Hyperrectangle{Float64},
685+
ScalarSets{Float64},
686+
},
687+
}()
688+
src = MOI.Utilities.Model{Float64}()
689+
MOI.Utilities.loadfromstring!(
690+
src,
691+
"""
692+
variables: x, y
693+
minobjective: x + y
694+
c: x + 2.0 * y <= 3.0
695+
""",
696+
)
697+
index_map = MOI.copy_to(model, src)
698+
c = MOI.get(model, MOI.ConstraintIndex, "c")
699+
x = index_map[MOI.get(src, MOI.VariableIndex, "x")]
700+
y = index_map[MOI.get(src, MOI.VariableIndex, "y")]
701+
f = MOI.get(model, MOI.ConstraintFunction(), c)
702+
@test f 1.0x + 2.0y
703+
MOI.modify(model, c, MOI.ScalarCoefficientChange(x, 4.0))
704+
f = MOI.get(model, MOI.ConstraintFunction(), c)
705+
@test f 4.0x + 2.0y
706+
return
707+
end
708+
709+
function test_modify_scalar_coefficient_change_no_entry()
710+
model = MOI.Utilities.GenericOptimizer{
711+
Int,
712+
MOI.Utilities.ObjectiveContainer{Int},
713+
MOI.Utilities.VariablesContainer{Int},
714+
MOI.Utilities.MatrixOfConstraints{
715+
Int,
716+
MOI.Utilities.MutableSparseMatrixCSC{
717+
Int,
718+
Int,
719+
MOI.Utilities.OneBasedIndexing,
720+
},
721+
MOI.Utilities.Hyperrectangle{Int},
722+
ScalarSets{Int},
723+
},
724+
}()
725+
x = MOI.add_variable(model)
726+
y = MOI.add_variable(model)
727+
func = 2x
728+
set = MOI.EqualTo(1)
729+
c = MOI.add_constraint(model, func, set)
730+
MOI.Utilities.final_touch(model, nothing)
731+
MOI.modify(model, c, MOI.ScalarCoefficientChange(y, 0))
732+
change = MOI.ScalarCoefficientChange(y, 3)
733+
@test_throws(
734+
MOI.ModifyConstraintNotAllowed(
735+
c,
736+
change,
737+
"cannot set a new non-zero coefficient because no entry " *
738+
"exists in the sparse matrix of `MatrixOfConstraints`",
739+
),
740+
MOI.modify(model, c, change),
741+
)
742+
return
743+
end
744+
639745
function test_modify_vectorsets()
640746
model = _new_VectorSets()
641747
src = MOI.Utilities.Model{Int}()

0 commit comments

Comments
 (0)