Skip to content

Commit 2f583b9

Browse files
authored
Modify vector constraints for MatrixOfConstraints (#2977)
1 parent 15b62ad commit 2f583b9

File tree

3 files changed

+164
-14
lines changed

3 files changed

+164
-14
lines changed

src/Utilities/matrix_of_constraints.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,32 @@ function MOI.modify(
739739
return
740740
end
741741

742+
function MOI.modify(
743+
model::MatrixOfConstraints,
744+
ci::MOI.ConstraintIndex,
745+
change::MOI.MultirowChange,
746+
)
747+
r = rows(model, ci)
748+
for (output_index, new_coefficient) in change.new_coefficients
749+
if !modify_coefficients(
750+
model.coefficients,
751+
r[output_index],
752+
change.variable.value,
753+
new_coefficient,
754+
)
755+
throw(
756+
MOI.ModifyConstraintNotAllowed(
757+
ci,
758+
change,
759+
"cannot set a new non-zero coefficient because no entry " *
760+
"exists in the sparse matrix of `MatrixOfConstraints`",
761+
),
762+
)
763+
end
764+
end
765+
return
766+
end
767+
742768
# See https://github.com/jump-dev/MathOptInterface.jl/pull/2976
743769
# Ideally we would have made it so that `modify_constants` operated like
744770
# `modify_coefficients` and returned a `Bool` indicating success. But we didn't,

src/Utilities/sparse_matrix.jl

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,8 @@ function extract_function(
239239
continue
240240
end
241241
r = _shift(A.rowval[idx], _indexing(A), OneBasedIndexing())
242-
if r == row
242+
# `modify_coefficients` can create zeros
243+
if r == row && !iszero(A.nzval[idx])
243244
push!(
244245
func.terms,
245246
MOI.ScalarAffineTerm(A.nzval[idx], MOI.VariableIndex(col)),
@@ -266,10 +267,11 @@ function _extract_column_as_function(
266267
func = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], constant)
267268
for i in SparseArrays.nzrange(A, col)
268269
row = _shift(A.rowval[i], _indexing(A), OneBasedIndexing())
269-
push!(
270-
func.terms,
271-
MOI.ScalarAffineTerm(value_map(A.nzval[i]), MOI.VariableIndex(row)),
272-
)
270+
val = value_map(A.nzval[i])
271+
# `modify_coefficients` can create zeros
272+
if !iszero(val)
273+
push!(func.terms, MOI.ScalarAffineTerm(val, MOI.VariableIndex(row)))
274+
end
273275
end
274276
return func
275277
end
@@ -293,16 +295,19 @@ function extract_function(
293295
if row != rows[output_index]
294296
continue
295297
end
296-
push!(
297-
func.terms,
298-
MOI.VectorAffineTerm(
299-
output_index,
300-
MOI.ScalarAffineTerm(
301-
A.nzval[idx[col]],
302-
MOI.VariableIndex(col),
298+
# `modify_coefficients` can create zeros
299+
if !iszero(A.nzval[idx[col]])
300+
push!(
301+
func.terms,
302+
MOI.VectorAffineTerm(
303+
output_index,
304+
MOI.ScalarAffineTerm(
305+
A.nzval[idx[col]],
306+
MOI.VariableIndex(col),
307+
),
303308
),
304-
),
305-
)
309+
)
310+
end
306311
idx[col] += 1
307312
end
308313
end

test/Utilities/test_matrix_of_constraints.jl

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,7 @@ function test_modify_scalar_coefficient_change()
665665
@test f 5x + 3y
666666
MOI.modify(model, c, MOI.ScalarCoefficientChange(y, 0))
667667
f = MOI.get(model, MOI.ConstraintFunction(), c)
668+
@test MOI.Utilities.is_canonical(f)
668669
@test f 5x + 0y
669670
return
670671
end
@@ -826,6 +827,124 @@ function test_modify_set_constants()
826827
return
827828
end
828829

830+
function test_modify_multirow_change()
831+
model = _new_VectorSets()
832+
src = MOI.Utilities.Model{Int}()
833+
x = MOI.add_variables(src, 2)
834+
terms = [
835+
MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(2, x[1])),
836+
MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(3, x[2])),
837+
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(4, x[1])),
838+
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(5, x[2])),
839+
]
840+
func = MOI.VectorAffineFunction(terms, [0, 0])
841+
c = MOI.add_constraint(src, func, MOI.Nonnegatives(2))
842+
index_map = MOI.copy_to(model, src)
843+
ci = index_map[c]
844+
f = MOI.get(model, MOI.ConstraintFunction(), ci)
845+
@test length(f.terms) == 4
846+
x1 = index_map[x[1]]
847+
x2 = index_map[x[2]]
848+
MOI.modify(model, ci, MOI.MultirowChange(x1, [(1, 7), (2, 8)]))
849+
f = MOI.get(model, MOI.ConstraintFunction(), ci)
850+
coefs = Dict(
851+
(t.output_index, t.scalar_term.variable) =>
852+
t.scalar_term.coefficient for t in f.terms
853+
)
854+
@test coefs[(1, x1)] == 7
855+
@test coefs[(2, x1)] == 8
856+
@test coefs[(1, x2)] == 3
857+
@test coefs[(2, x2)] == 5
858+
return
859+
end
860+
861+
function test_modify_multirow_change_single_row()
862+
model = _new_VectorSets()
863+
src = MOI.Utilities.Model{Int}()
864+
x = MOI.add_variables(src, 2)
865+
terms = [
866+
MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(2, x[1])),
867+
MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(3, x[2])),
868+
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(4, x[1])),
869+
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(5, x[2])),
870+
]
871+
func = MOI.VectorAffineFunction(terms, [0, 0])
872+
c = MOI.add_constraint(src, func, MOI.Nonnegatives(2))
873+
index_map = MOI.copy_to(model, src)
874+
ci = index_map[c]
875+
x2 = index_map[x[2]]
876+
MOI.modify(model, ci, MOI.MultirowChange(x2, [(2, 9)]))
877+
f = MOI.get(model, MOI.ConstraintFunction(), ci)
878+
coefs = Dict(
879+
(t.output_index, t.scalar_term.variable) =>
880+
t.scalar_term.coefficient for t in f.terms
881+
)
882+
x1 = index_map[x[1]]
883+
@test coefs[(1, x1)] == 2
884+
@test coefs[(2, x1)] == 4
885+
@test coefs[(1, x2)] == 3
886+
@test coefs[(2, x2)] == 9
887+
return
888+
end
889+
890+
function test_modify_multirow_change_to_zero()
891+
model = _new_VectorSets()
892+
src = MOI.Utilities.Model{Int}()
893+
x = MOI.add_variables(src, 2)
894+
terms = [
895+
MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(2, x[1])),
896+
MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(3, x[2])),
897+
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(4, x[1])),
898+
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(5, x[2])),
899+
]
900+
func = MOI.VectorAffineFunction(terms, [0, 0])
901+
c = MOI.add_constraint(src, func, MOI.Nonnegatives(2))
902+
index_map = MOI.copy_to(model, src)
903+
ci = index_map[c]
904+
x1 = index_map[x[1]]
905+
x2 = index_map[x[2]]
906+
MOI.modify(model, ci, MOI.MultirowChange(x1, [(1, 0)]))
907+
f = MOI.get(model, MOI.ConstraintFunction(), ci)
908+
@test MOI.Utilities.is_canonical(f)
909+
@test length(f.terms) == 3
910+
coefs = Dict(
911+
(t.output_index, t.scalar_term.variable) =>
912+
t.scalar_term.coefficient for t in f.terms
913+
)
914+
@test !haskey(coefs, (1, x1))
915+
@test coefs[(2, x1)] == 4
916+
@test coefs[(1, x2)] == 3
917+
@test coefs[(2, x2)] == 5
918+
return
919+
end
920+
921+
function test_modify_multirow_change_no_entry()
922+
model = _new_VectorSets()
923+
src = MOI.Utilities.Model{Int}()
924+
x = MOI.add_variables(src, 3)
925+
terms = [
926+
MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(2, x[1])),
927+
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(4, x[1])),
928+
]
929+
func = MOI.VectorAffineFunction(terms, [0, 0])
930+
c = MOI.add_constraint(src, func, MOI.Nonnegatives(2))
931+
index_map = MOI.copy_to(model, src)
932+
ci = index_map[c]
933+
x3 = index_map[x[3]]
934+
MOI.modify(model, ci, MOI.MultirowChange(x3, [(1, 0)]))
935+
change = MOI.MultirowChange(x3, [(1, 5)])
936+
@test_throws(
937+
MOI.ModifyConstraintNotAllowed(
938+
ci,
939+
change,
940+
"cannot set a new non-zero coefficient because no entry " *
941+
"exists in the sparse matrix of `MatrixOfConstraints`",
942+
),
943+
MOI.modify(model, ci, change),
944+
)
945+
return
946+
end
947+
829948
function test_unsupported_constraint()
830949
model = _new_ScalarSets()
831950
x = MOI.VariableIndex(1)

0 commit comments

Comments
 (0)