Skip to content

Commit c302e43

Browse files
authored
[Utilities] fix ProductOfSets to handle zero-dimensional rows (#3022)
1 parent 4e19ab8 commit c302e43

3 files changed

Lines changed: 202 additions & 120 deletions

File tree

src/Utilities/matrix_of_constraints.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,11 +290,11 @@ If `S` is not part of the list, return `nothing`.
290290
function set_index end
291291

292292
"""
293-
add_set(sets, i)::Int64
293+
add_set(sets, i::Int)::Int64
294294
295295
Add a scalar set of type index `i`.
296296
297-
add_set(sets, i, dim)::Int64
297+
add_set(sets, i::Int, dim::Int)::Int64
298298
299299
Add a vector set of type index `i` and dimension `dim`.
300300
@@ -387,7 +387,7 @@ function MOI.get(
387387
return MOI.get(v.sets, attr)
388388
end
389389

390-
_add_set(sets, i, ::MOI.AbstractScalarFunction) = add_set(sets, i)
390+
_add_set(sets, i::Int, ::MOI.AbstractScalarFunction) = add_set(sets, i)
391391

392392
function _add_set(sets, i, func::MOI.AbstractVectorFunction)
393393
return add_set(sets, i, MOI.output_dimension(func))

src/Utilities/product_of_sets.jl

Lines changed: 69 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ MOI.dimension(sets::MixOfScalarSets) = length(sets.set_ids)
8888

8989
rows(::MixOfScalarSets, ci::MOI.ConstraintIndex) = ci.value
9090

91-
function add_set(sets::MixOfScalarSets, i)
91+
function add_set(sets::MixOfScalarSets, i::Int)::Int64
9292
push!(sets.set_ids, i)
9393
return length(sets.set_ids)
9494
end
@@ -170,91 +170,91 @@ macro product_of_sets(name, set_types...)
170170
mutable struct $(esc_name){$(T)} <:
171171
$MOI.Utilities.OrderedProductOfSets{$(T)}
172172
"""
173-
During the copy, this counts the number of rows corresponding to
174-
each set. At the end of copy, `final_touch` is called, which
175-
converts this list into a cumulative ordering.
176-
"""
177-
num_rows::Vector{Int}
173+
`rows[i][j]` corresponds to constraint `j` of set type `i`.
178174
175+
The value depends on `final_touch`:
176+
* Before `final_touch`, these are `1:dimension` of the constraint
177+
* After `final_touch`, these are the 1-indexed rows of the full
178+
constraint matrix
179179
"""
180-
A dictionary which maps the `set_index` and `offset` of a set to the
181-
dimension, that is, `dimension[(set_index,offset)] → dim`.
182-
"""
183-
dimension::Dict{Tuple{Int,Int},Int}
180+
rows::Vector{Vector{UnitRange{Int}}}
184181

185182
"""
186183
A sanity bit to check that we don't call functions out-of-order.
187184
"""
188185
final_touch::Bool
189186

190187
function $(esc_name){$(T)}() where {$(T)}
191-
return new(
192-
zeros(Int, $(length(set_types))),
193-
Dict{Tuple{Int,Int},Int}(),
194-
false,
195-
)
188+
n = $(length(set_types))
189+
return new([UnitRange{Int}[] for _ in 1:n], false)
196190
end
197191
end
198192
)
199193
return _sets_code(esc_name, T, type_def, set_types...)
200194
end
201195

202-
MOI.is_empty(sets::OrderedProductOfSets) = all(iszero, sets.num_rows)
196+
MOI.is_empty(sets::OrderedProductOfSets) = all(isempty, sets.rows)
203197

204198
function MOI.empty!(sets::OrderedProductOfSets)
205-
fill!(sets.num_rows, 0)
206-
empty!(sets.dimension)
199+
map(empty!, sets.rows)
207200
sets.final_touch = false
208201
return
209202
end
210203

211-
function MOI.dimension(sets::OrderedProductOfSets)
204+
function MOI.dimension(sets::OrderedProductOfSets)::Int
212205
@assert sets.final_touch
213-
if isempty(sets.num_rows)
214-
# There is no set type
215-
return 0
216-
else
217-
return sets.num_rows[end]
206+
for i in reverse(eachindex(sets.rows))
207+
if !isempty(sets.rows[i])
208+
return last(sets.rows[i][end])
209+
end
218210
end
211+
return 0 # All rows were empty.
212+
end
213+
214+
# A backwards-compatible method to ensure callers of `sets.num_rows` still
215+
# works.
216+
function Base.getproperty(sets::OrderedProductOfSets, key::Symbol)
217+
if key == :num_rows
218+
if sets.final_touch
219+
return cumsum(num_rows(sets, S) for S in set_types(sets))
220+
end
221+
return Int[num_rows(sets, S) for S in set_types(sets)]
222+
end
223+
return getfield(sets, key)
219224
end
220225

221226
function rows(
222227
sets::OrderedProductOfSets{T},
223228
ci::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S},
224-
) where {T,S}
229+
)::Int where {T,S}
225230
@assert sets.final_touch
226231
i = set_index(sets, S)::Int
227-
return (i == 1 ? 0 : sets.num_rows[i-1]) + ci.value
232+
return only(sets.rows[i][ci.value])
228233
end
229234

230235
function rows(
231236
sets::OrderedProductOfSets{T},
232237
ci::MOI.ConstraintIndex{MOI.VectorAffineFunction{T},S},
233-
) where {T,S}
238+
)::UnitRange{Int} where {T,S}
234239
@assert sets.final_touch
235240
i = set_index(sets, S)::Int
236-
offset = i == 1 ? 0 : sets.num_rows[i-1]
237-
return (offset + ci.value - 1) .+ (1:sets.dimension[(i, ci.value)])
238-
end
239-
240-
function add_set(sets::OrderedProductOfSets, i)
241-
@assert !sets.final_touch
242-
sets.num_rows[i] += 1
243-
return sets.num_rows[i]
241+
return sets.rows[i][ci.value]
244242
end
245243

246-
function add_set(sets::OrderedProductOfSets, i, dim)
244+
function add_set(sets::OrderedProductOfSets, i::Int, dim::Int = 1)::Int64
247245
@assert !sets.final_touch
248-
ci = sets.num_rows[i] + 1
249-
sets.dimension[(i, ci)] = dim
250-
sets.num_rows[i] += dim
251-
return ci
246+
push!(sets.rows[i], 1:dim)
247+
return length(sets.rows[i])
252248
end
253249

254-
function final_touch(sets::OrderedProductOfSets)
250+
function final_touch(sets::OrderedProductOfSets)::Nothing
255251
@assert !sets.final_touch
256-
for i in 2:length(sets.num_rows)
257-
sets.num_rows[i] += sets.num_rows[i-1]
252+
offset = 0
253+
for (i, rows) in enumerate(sets.rows)
254+
for (j, row) in enumerate(rows)
255+
rows[j] = offset .+ row
256+
offset += length(row)
257+
end
258258
end
259259
sets.final_touch = true
260260
return
@@ -266,103 +266,57 @@ end
266266
Return the number of rows corresponding to a set of type `S`. That is, it is
267267
the sum of the dimensions of the sets of type `S`.
268268
"""
269-
function num_rows(sets::OrderedProductOfSets, ::Type{S}) where {S}
269+
function num_rows(sets::OrderedProductOfSets, ::Type{S})::Int where {S}
270270
i = set_index(sets, S)::Int
271-
if !sets.final_touch || i == 1
272-
return sets.num_rows[i]
271+
rows = sets.rows[i]
272+
if isempty(rows)
273+
return 0
274+
elseif sets.final_touch
275+
return max(0, last(rows[end]) - first(rows[1]) + 1)
276+
else
277+
return mapreduce(length, +, rows)
273278
end
274-
return sets.num_rows[i] - sets.num_rows[i-1]
275279
end
276280

277281
function MOI.get(
278282
sets::OrderedProductOfSets{T},
279283
::MOI.ListOfConstraintTypesPresent,
280-
) where {T}
284+
)::Vector{Tuple{Type,Type}} where {T}
281285
return Tuple{Type,Type}[
282286
(_affine_function_type(T, S), S) for
283-
S in set_types(sets) if num_rows(sets, S) > 0
287+
(i, S) in enumerate(set_types(sets)) if !isempty(sets.rows[i])
284288
]
285289
end
286290

287-
struct _UnevenIterator
288-
i::Int
289-
start::Int
290-
stop::Int
291-
dimension::Dict{Tuple{Int,Int},Int}
292-
end
293-
294-
Base.IteratorSize(::_UnevenIterator) = Base.SizeUnknown()
295-
296-
function Base.iterate(it::_UnevenIterator, cur = it.start)
297-
if cur > it.stop
298-
return nothing
299-
end
300-
return (cur, cur + it.dimension[(it.i, cur)])
301-
end
302-
303-
function Base.in(x::Int64, it::_UnevenIterator)
304-
return it.start <= x <= it.stop && haskey(it.dimension, (it.i, x))
305-
end
306-
307-
function _range_iterator(
308-
::OrderedProductOfSets{T},
309-
::Int,
310-
start::Int,
311-
stop::Int,
312-
::Type{MOI.ScalarAffineFunction{T}},
313-
) where {T}
314-
return start:stop
315-
end
316-
317-
function _range_iterator(
318-
sets::OrderedProductOfSets{T},
319-
i::Int,
320-
start::Int,
321-
stop::Int,
322-
::Type{MOI.VectorAffineFunction{T}},
323-
) where {T}
324-
return _UnevenIterator(i, start, stop, sets.dimension)
325-
end
326-
327-
function _range_iterator(
328-
sets::OrderedProductOfSets{T},
329-
::Type{F},
330-
::Type{S},
331-
) where {T,F,S}
332-
i = set_index(sets, S)
333-
if i === nothing || F != _affine_function_type(T, S)
334-
return
335-
end
336-
return _range_iterator(sets, i, 1, num_rows(sets, S), F)
337-
end
338-
339-
_length(::Nothing) = 0
340-
_length(r::UnitRange) = length(r)
341-
_length(r::_UnevenIterator) = count(_ -> true, r)
342-
343291
function MOI.get(
344292
sets::OrderedProductOfSets,
345293
::MOI.NumberOfConstraints{F,S},
346-
) where {F,S}
347-
r = _range_iterator(sets, F, S)
348-
return _length(r)
294+
)::Int64 where {F,S}
295+
i = set_index(sets, S)::Union{Nothing,Int}
296+
if i == nothing
297+
return 0
298+
end
299+
return length(sets.rows[i])
349300
end
350301

351302
function MOI.get(
352303
sets::OrderedProductOfSets,
353304
::MOI.ListOfConstraintIndices{F,S},
354-
) where {F,S}
355-
rows = _range_iterator(sets, F, S)
356-
if rows === nothing
305+
)::Vector{MOI.ConstraintIndex{F,S}} where {F,S}
306+
i = set_index(sets, S)::Union{Nothing,Int}
307+
if i == nothing
357308
return MOI.ConstraintIndex{F,S}[]
358309
end
359-
return MOI.ConstraintIndex{F,S}.(rows)
310+
return MOI.ConstraintIndex{F,S}.(1:length(sets.rows[i]))
360311
end
361312

362313
function MOI.is_valid(
363314
sets::OrderedProductOfSets,
364315
ci::MOI.ConstraintIndex{F,S},
365-
) where {F,S}
366-
r = _range_iterator(sets, F, S)
367-
return r !== nothing && ci.value in r
316+
)::Bool where {F,S}
317+
i = set_index(sets, S)::Union{Nothing,Int}
318+
if i == nothing
319+
return false
320+
end
321+
return 1 <= ci.value <= length(sets.rows[i])
368322
end

0 commit comments

Comments
 (0)