Skip to content

Commit 1937e27

Browse files
authored
[Utilities] fix bug in function modification (#1217)
* [Utilities] fix bug in function modification * Fix on 32bit
1 parent 772b7d4 commit 1937e27

File tree

2 files changed

+118
-59
lines changed

2 files changed

+118
-59
lines changed

src/Utilities/functions.jl

Lines changed: 80 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -859,93 +859,114 @@ end
859859
function modify_function(f::VQF, change::MOI.VectorConstantChange)
860860
return VQF(f.affine_terms, f.quadratic_terms, change.new_constant)
861861
end
862+
862863
function _modifycoefficient(
863-
terms::Vector{<:MOI.ScalarAffineTerm},
864-
variable::VI,
865-
new_coefficient,
866-
)
864+
terms::Vector{MOI.ScalarAffineTerm{T}},
865+
variable::MOI.VariableIndex,
866+
new_coefficient::T,
867+
) where {T}
867868
terms = copy(terms)
868869
i = something(findfirst(t -> t.variable_index == variable, terms), 0)
869-
if iszero(i)
870-
# The variable was not already in the function
870+
if iszero(i) # The variable was not already in the function
871871
if !iszero(new_coefficient)
872872
push!(terms, MOI.ScalarAffineTerm(new_coefficient, variable))
873873
end
874-
else
875-
# The variable was already in the function
874+
else # The variable was already in the function
876875
if iszero(new_coefficient)
877876
deleteat!(terms, i)
878877
else
879878
terms[i] = MOI.ScalarAffineTerm(new_coefficient, variable)
880879
end
880+
# To account for duplicates, we need to delete any other instances of
881+
# `variable` in `terms`.
882+
for j = length(terms):-1:(i + 1)
883+
if terms[j].variable_index == variable
884+
deleteat!(terms, j)
885+
end
886+
end
881887
end
882888
return terms
883889
end
884-
function modify_function(f::SAF, change::MOI.ScalarCoefficientChange)
885-
lin = _modifycoefficient(f.terms, change.variable, change.new_coefficient)
886-
return SAF(lin, f.constant)
890+
891+
function modify_function(
892+
f::MOI.ScalarAffineFunction{T},
893+
change::MOI.ScalarCoefficientChange{T},
894+
) where {T}
895+
terms = _modifycoefficient(f.terms, change.variable, change.new_coefficient)
896+
return MOI.ScalarAffineFunction(terms, f.constant)
887897
end
888-
function modify_function(f::SQF, change::MOI.ScalarCoefficientChange)
889-
lin = _modifycoefficient(
898+
899+
function modify_function(
900+
f::MOI.ScalarQuadraticFunction{T},
901+
change::MOI.ScalarCoefficientChange{T},
902+
) where {T}
903+
terms = _modifycoefficient(
890904
f.affine_terms,
891905
change.variable,
892906
change.new_coefficient,
893907
)
894-
return SQF(lin, f.quadratic_terms, f.constant)
908+
return MOI.ScalarQuadraticFunction(terms, f.quadratic_terms, f.constant)
895909
end
910+
896911
function _modifycoefficients(
897-
n,
898-
terms::Vector{<:MOI.VectorAffineTerm},
899-
variable::VI,
900-
new_coefficients,
901-
)
912+
terms::Vector{MOI.VectorAffineTerm{T}},
913+
variable::MOI.VariableIndex,
914+
new_coefficients::Vector{Tuple{Int64,T}},
915+
) where {T}
902916
terms = copy(terms)
903-
# Maps between rows in the `MOI.VectorAffineTerm`s and indices in new_coefficients
904-
rowmap = Dict(c[1] => i for (i, c) in enumerate(new_coefficients))
905-
del = Int[]
906-
for i in 1:length(terms)
907-
if terms[i].scalar_term.variable_index == variable
908-
row = terms[i].output_index
909-
j = Base.get(rowmap, row, 0)
910-
if !iszero(j) # If it is zero, it means that the row should not be changed
911-
if iszero(new_coefficients[j][2])
912-
push!(del, i)
913-
else
914-
terms[i] = MOI.VectorAffineTerm(
915-
row,
916-
MOI.ScalarAffineTerm(new_coefficients[j][2], variable),
917-
)
918-
end
919-
rowmap[row] = 0 # We only change the first term of a row
920-
end
917+
coef_dict = Dict(k => v for (k, v) in new_coefficients)
918+
elements_to_delete = Int[]
919+
for (i, term) in enumerate(terms)
920+
if term.scalar_term.variable_index != variable
921+
continue
921922
end
922-
end
923-
deleteat!(terms, del)
924-
for (row, j) in rowmap
925-
if !iszero(j)
926-
push!(
927-
terms,
928-
MOI.VectorAffineTerm(
929-
row,
930-
MOI.ScalarAffineTerm(new_coefficients[j][2], variable),
931-
),
923+
new_coef = Base.get(coef_dict, term.output_index, nothing)
924+
if new_coef === nothing
925+
continue
926+
elseif iszero(new_coef)
927+
push!(elements_to_delete, i)
928+
else
929+
terms[i] = MOI.VectorAffineTerm(
930+
term.output_index,
931+
MOI.ScalarAffineTerm(new_coef, variable),
932932
)
933+
# Set the coefficient to 0.0 so we don't add duplicates.
934+
coef_dict[term.output_index] = zero(T)
933935
end
934936
end
937+
deleteat!(terms, elements_to_delete)
938+
# Add elements that were not previously in `terms`.
939+
for (k, v) in coef_dict
940+
if iszero(v)
941+
continue
942+
end
943+
push!(terms, MOI.VectorAffineTerm(k, MOI.ScalarAffineTerm(v, variable)))
944+
end
935945
return terms
936946
end
937-
function modify_function(f::VAF, change::MOI.MultirowChange)
938-
dim = MOI.output_dimension(f)
939-
coefficients = change.new_coefficients
940-
lin = _modifycoefficients(dim, f.terms, change.variable, coefficients)
941-
return VAF(lin, f.constants)
942-
end
943-
function modify_function(f::VQF, change::MOI.MultirowChange)
944-
dim = MOI.output_dimension(f)
945-
coefficients = change.new_coefficients
946-
lin =
947-
_modifycoefficients(dim, f.affine_terms, change.variable, coefficients)
948-
return VQF(lin, f.quadratic_terms, f.constants)
947+
948+
function modify_function(
949+
f::MOI.VectorAffineFunction{T},
950+
change::MOI.MultirowChange{T},
951+
) where {T}
952+
terms = _modifycoefficients(
953+
f.terms,
954+
change.variable,
955+
change.new_coefficients,
956+
)
957+
return MOI.VectorAffineFunction(terms, f.constants)
958+
end
959+
960+
function modify_function(
961+
f::MOI.VectorQuadraticFunction{T},
962+
change::MOI.MultirowChange{T},
963+
) where {T}
964+
terms = _modifycoefficients(
965+
f.affine_terms,
966+
change.variable,
967+
change.new_coefficients,
968+
)
969+
return MOI.VectorQuadraticFunction(terms, f.quadratic_terms, f.constants)
949970
end
950971

951972
# Arithmetic

test/Utilities/functions.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,3 +956,41 @@ end
956956
end
957957
end
958958
end
959+
960+
@testset "modifycoefficient_duplicates" begin
961+
model = MOI.Utilities.Model{Float64}()
962+
x = MOI.add_variable(model)
963+
f = MOI.ScalarAffineFunction(
964+
[MOI.ScalarAffineTerm(1.0, x), MOI.ScalarAffineTerm(1.0, x)],
965+
0.0,
966+
)
967+
new_f = MOI.Utilities.modify_function(
968+
f, MOI.ScalarCoefficientChange(x, 3.0)
969+
)
970+
@test new_f MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(3.0, x)], 0.0)
971+
end
972+
973+
@testset "modifycoefficients_duplicates" begin
974+
model = MOI.Utilities.Model{Float64}()
975+
x = MOI.add_variable(model)
976+
f = MOI.VectorAffineFunction(
977+
[
978+
MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x)),
979+
MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x)),
980+
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x)),
981+
MOI.VectorAffineTerm(3, MOI.ScalarAffineTerm(1.0, x)),
982+
],
983+
[0.0, 0.0, 0.0],
984+
)
985+
new_f = MOI.Utilities.modify_function(
986+
f, MOI.MultirowChange(x, [(1, 3.0), (2, 0.5)]),
987+
)
988+
@test new_f MOI.VectorAffineFunction(
989+
[
990+
MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(3.0, x)),
991+
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(0.5, x)),
992+
MOI.VectorAffineTerm(3, MOI.ScalarAffineTerm(1.0, x)),
993+
],
994+
[0.0, 0.0, 0.0],
995+
)
996+
end

0 commit comments

Comments
 (0)