Skip to content

Commit 49b8f40

Browse files
authored
Merge pull request #983 from JuliaOpt/bl/delete_vector_variables
Faster deletion of vector of variables
2 parents 3f4c220 + a2df7e6 commit 49b8f40

File tree

4 files changed

+82
-30
lines changed

4 files changed

+82
-30
lines changed

docs/src/apireference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,7 @@ The following utilities are available for functions:
962962
Utilities.eval_variables
963963
Utilities.map_indices
964964
Utilities.substitute_variables
965+
Utilities.filter_variables
965966
Utilities.remove_variable
966967
Utilities.all_coefficients
967968
Utilities.unsafe_add

src/Utilities/functions.jl

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -618,38 +618,55 @@ function test_models_equal(model1::MOI.ModelLike, model2::MOI.ModelLike, variabl
618618
end
619619

620620

621-
_hasvar(v::VI, vi::Vector{VI}) = v in vi
622-
_hasvar(v::VI, vi::VI) = v == vi
623-
_hasvar(t::MOI.ScalarAffineTerm, vi) = _hasvar(t.variable_index, vi)
624-
_hasvar(t::MOI.ScalarQuadraticTerm, vi) = _hasvar(t.variable_index_1, vi) || _hasvar(t.variable_index_2, vi)
625-
_hasvar(t::Union{MOI.VectorAffineTerm, MOI.VectorQuadraticTerm}, vi) = _hasvar(t.scalar_term, vi)
621+
_keep_all(keep::Function, v::MOI.VariableIndex) = keep(v)
622+
_keep_all(keep::Function, t::MOI.ScalarAffineTerm) = keep(t.variable_index)
623+
_keep_all(keep::Function, t::MOI.ScalarQuadraticTerm) = keep(t.variable_index_1) && keep(t.variable_index_2)
624+
_keep_all(keep::Function, t::Union{MOI.VectorAffineTerm, MOI.VectorQuadraticTerm}) = _keep_all(keep, t.scalar_term)
626625
# Removes terms or variables in `vis_or_terms` that contains the variable of index `vi`
627-
function _rmvar(vis_or_terms::Vector, vi)
628-
return vis_or_terms[.!_hasvar.(vis_or_terms, Ref(vi))]
626+
function _filter_variables(keep::Function, variables_or_terms::Vector)
627+
return filter(el -> _keep_all(keep, el), variables_or_terms)
629628
end
630629

631630
"""
632-
remove_variable(f::AbstractFunction, vi::VariableIndex)
631+
filter_variables(keep::Function, f::AbstractFunction)
633632
634-
Return a new function `f` with the variable vi removed.
633+
Return a new function `f` with the variable `vi` such that `!keep(vi)` removed.
635634
"""
636-
function remove_variable end
637-
function remove_variable(f::MOI.SingleVariable, vi::MOI.VariableIndex)
638-
if f.variable == vi
635+
function filter_variables end
636+
function filter_variables(keep::Function, f::MOI.SingleVariable)
637+
if !keep(f.variable)
639638
error("Cannot remove variable from a `SingleVariable` function of the",
640639
" same variable.")
641640
end
642641
return f
643642
end
644-
function remove_variable(f::VVF, vi)
645-
VVF(_rmvar(f.variables, vi))
643+
function filter_variables(keep::Function, f::MOI.VectorOfVariables)
644+
return MOI.VectorOfVariables(_filter_variables(keep, f.variables))
645+
end
646+
function filter_variables(
647+
keep::Function, f::Union{MOI.ScalarAffineFunction, MOI.VectorAffineFunction})
648+
return typeof(f)(_filter_variables(keep, f.terms), MOI.constant(f))
646649
end
647-
function remove_variable(f::Union{SAF, VAF}, vi)
648-
typeof(f)(_rmvar(f.terms, vi), MOI.constant(f))
650+
function filter_variables(
651+
keep, f::Union{MOI.ScalarQuadraticFunction, MOI.VectorQuadraticFunction})
652+
return typeof(f)(
653+
_filter_variables(keep, f.affine_terms),
654+
_filter_variables(keep, f.quadratic_terms),
655+
MOI.constant(f))
656+
end
657+
658+
"""
659+
remove_variable(f::AbstractFunction, vi::VariableIndex)
660+
661+
Return a new function `f` with the variable vi removed.
662+
"""
663+
function remove_variable(f::MOI.AbstractFunction, vi::MOI.VariableIndex)
664+
return filter_variables(v -> v != vi, f)
649665
end
650-
function remove_variable(f::Union{SQF, VQF}, vi)
651-
terms = _rmvar.((f.affine_terms, f.quadratic_terms), Ref(vi))
652-
typeof(f)(terms..., MOI.constant(f))
666+
function remove_variable(f::MOI.AbstractFunction, vis::Vector{MOI.VariableIndex})
667+
# Create a `Set` to test membership in `vis` in O(1).
668+
set = Set(vis)
669+
return filter_variables(vi -> !(vi in set), f)
653670
end
654671

655672
"""
@@ -671,7 +688,7 @@ function modify_function(f::VQF, change::MOI.VectorConstantChange)
671688
end
672689
function _modifycoefficient(terms::Vector{<:MOI.ScalarAffineTerm}, variable::VI, new_coefficient)
673690
terms = copy(terms)
674-
i = something(findfirst(t -> _hasvar(t, variable), terms), 0)
691+
i = something(findfirst(t -> t.variable_index == variable, terms), 0)
675692
if iszero(i)
676693
# The variable was not already in the function
677694
if !iszero(new_coefficient)
@@ -701,7 +718,7 @@ function _modifycoefficients(n, terms::Vector{<:MOI.VectorAffineTerm}, variable:
701718
rowmap = Dict(c[1]=>i for (i,c) in enumerate(new_coefficients))
702719
del = Int[]
703720
for i in 1:length(terms)
704-
if _hasvar(terms[i], variable)
721+
if terms[i].scalar_term.variable_index == variable
705722
row = terms[i].output_index
706723
j = Base.get(rowmap, row, 0)
707724
if !iszero(j) # If it is zero, it means that the row should not be changed

src/Utilities/model.jl

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,27 @@ function remove_variable(f::MOI.VectorOfVariables, s, vi::VI)
127127
end
128128
return g, t
129129
end
130-
function _remove_variable(constrs::Vector, vi::VI)
130+
function _remove_variable(constrs::Vector{<:ConstraintEntry}, vi::VI)
131131
for i in eachindex(constrs)
132132
ci, f, s = constrs[i]
133133
constrs[i] = (ci, remove_variable(f, s, vi)...)
134134
end
135-
return CI{MOI.SingleVariable}[]
135+
end
136+
filter_variables(keep::Function, f, s) = filter_variables(keep, f), s
137+
function filter_variables(keep::Function, f::MOI.VectorOfVariables, s)
138+
g = filter_variables(keep, f)
139+
if length(g.variables) != length(f.variables)
140+
t = MOI.update_dimension(s, length(g.variables))
141+
else
142+
t = s
143+
end
144+
return g, t
145+
end
146+
function _filter_variables(keep::Function, constrs::Vector{<:ConstraintEntry})
147+
for i in eachindex(constrs)
148+
ci, f, s = constrs[i]
149+
constrs[i] = (ci, filter_variables(keep, f, s)...)
150+
end
136151
end
137152
function _vector_of_variables_with(::Vector, ::Union{VI, MOI.Vector{VI}})
138153
return CI{MOI.VectorOfVariables}[]
@@ -172,19 +187,15 @@ function _vector_of_variables_with(
172187
end
173188
return rm
174189
end
175-
function MOI.delete(model::AbstractModel{T}, vi::VI) where T
190+
function _delete_variable(model::AbstractModel{T}, vi::MOI.VariableIndex) where T
176191
MOI.throw_if_not_valid(model, vi)
177-
model.objective = remove_variable(model.objective, vi)
178192
# If a variable is removed, the `VectorOfVariables` constraints using this
179193
# variable only need to be removed too. `vov_to_remove` is the list of
180194
# indices of the `VectorOfVariables` constraints of `vi`.
181195
vov_to_remove = broadcastvcat(constrs -> _vector_of_variables_with(constrs, vi), model)
182196
for ci in vov_to_remove
183197
MOI.delete(model, ci)
184198
end
185-
# `VectorOfVariables` constraints with sets not supporting dimension update
186-
# were either deleted or an error was thrown. The rest is modified now.
187-
broadcastcall(constrs -> _remove_variable(constrs, vi), model)
188199
model.single_variable_mask[vi.value] = 0x0
189200
if model.variable_indices === nothing
190201
model.variable_indices = Set(MOI.get(model,
@@ -203,16 +214,33 @@ function MOI.delete(model::AbstractModel{T}, vi::VI) where T
203214
delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semicontinuous{T}}(vi.value))
204215
delete!(model.con_to_name, MOI.ConstraintIndex{MOI.SingleVariable, MOI.Semiinteger{T}}(vi.value))
205216
end
206-
function MOI.delete(model::AbstractModel, vis::Vector{VI})
217+
function MOI.delete(model::AbstractModel, vi::MOI.VariableIndex)
218+
_delete_variable(model, vi)
219+
# `VectorOfVariables` constraints with sets not supporting dimension update
220+
# were either deleted or an error was thrown. The rest is modified now.
221+
broadcastcall(constrs -> _remove_variable(constrs, vi), model)
222+
model.objective = remove_variable(model.objective, vi)
223+
end
224+
function MOI.delete(model::AbstractModel, vis::Vector{MOI.VariableIndex})
225+
if isempty(vis)
226+
# In `keep`, we assume that `model.variable_indices !== nothing` so
227+
# at least one variable need to be deleted.
228+
return
229+
end
207230
# Delete `VectorOfVariables(vis)` constraints as otherwise, it will error
208231
# when removing variables one by one.
209232
vov_to_remove = broadcastvcat(constrs -> _vector_of_variables_with(constrs, vis), model)
210233
for ci in vov_to_remove
211234
MOI.delete(model, ci)
212235
end
213236
for vi in vis
214-
MOI.delete(model, vi)
237+
_delete_variable(model, vi)
215238
end
239+
# `VectorOfVariables` constraints with sets not supporting dimension update
240+
# were either deleted or an error was thrown. The rest is modified now.
241+
keep(vi::MOI.VariableIndex) = vi in model.variable_indices
242+
model.objective = filter_variables(keep, model.objective)
243+
broadcastcall(constrs -> _filter_variables(keep, constrs), model)
216244
end
217245

218246
function MOI.is_valid(model::AbstractModel,

test/Utilities/model.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,17 +200,23 @@ end
200200
@test 1 == @inferred MOI.get(model, MOI.NumberOfConstraints{MOI.VectorAffineFunction{Int},MOI.SecondOrderCone}())
201201
@test MOI.get(model, MOI.ConstraintFunction(), c6).constants == f6.constants
202202

203+
fx = MOI.SingleVariable(x)
204+
fy = MOI.SingleVariable(y)
205+
obj = 1fx + 2fy
206+
MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj)
203207
message = string("Cannot delete variable as it is constrained with other",
204208
" variables in a `MOI.VectorOfVariables`.")
205209
err = MOI.DeleteNotAllowed(y, message)
206210
@test_throws err MOI.delete(model, y)
211+
@test MOI.get(model, MOI.ObjectiveFunction{typeof(obj)}()) 1fx + 2fy
207212

208213
@test MOI.is_valid(model, c8)
209214
MOI.delete(model, c8)
210215
@test !MOI.is_valid(model, c8)
211216
@test 0 == @inferred MOI.get(model, MOI.NumberOfConstraints{MOI.VectorOfVariables,MOI.SecondOrderCone}())
212217

213218
MOI.delete(model, y)
219+
@test MOI.get(model, MOI.ObjectiveFunction{typeof(obj)}()) 1fx
214220

215221
f = MOI.get(model, MOI.ConstraintFunction(), c2)
216222
@test f.affine_terms == MOI.VectorAffineTerm.([1, 2], MOI.ScalarAffineTerm.([3, 1], [x, x]))

0 commit comments

Comments
 (0)