From 208de61020b750f12d0909e2eb695a2fed2f9bbe Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 9 Jul 2026 11:14:34 +1200 Subject: [PATCH 1/5] [Utilities] fix ProductOfSets to handle zero-dimensional rows --- src/Utilities/product_of_sets.jl | 155 ++++++++----------------- test/Utilities/test_product_of_sets.jl | 90 +++++++++++++- 2 files changed, 138 insertions(+), 107 deletions(-) diff --git a/src/Utilities/product_of_sets.jl b/src/Utilities/product_of_sets.jl index 0b951d201c..767cc458ac 100644 --- a/src/Utilities/product_of_sets.jl +++ b/src/Utilities/product_of_sets.jl @@ -170,17 +170,25 @@ macro product_of_sets(name, set_types...) mutable struct $(esc_name){$(T)} <: $MOI.Utilities.OrderedProductOfSets{$(T)} """ - During the copy, this counts the number of rows corresponding to - each set. At the end of copy, `final_touch` is called, which - converts this list into a cumulative ordering. + `dimension[i][j]` is the dimension of `ConstraintIndex().value` `j` + of set type `i`. """ - num_rows::Vector{Int} + dimension::Vector{Vector{Int}} """ - A dictionary which maps the `set_index` and `offset` of a set to the - dimension, that is, `dimension[(set_index,offset)] → dim`. + `offset[i][j]` is the 0-indexed row offset of constraint `j` of set + type `i`. The rows are therefore `offset[i][j] + 1:dimension[i][j]`. + + The `offset` vector gets created during `final_touch`. + """ + offset::Vector{Vector{Int}} + + """ + The total number of rows in the sets. + + This value gets set during `final_touch`. """ - dimension::Dict{Tuple{Int,Int},Int} + num_rows::Int """ A sanity bit to check that we don't call functions out-of-order. @@ -188,34 +196,26 @@ macro product_of_sets(name, set_types...) final_touch::Bool function $(esc_name){$(T)}() where {$(T)} - return new( - zeros(Int, $(length(set_types))), - Dict{Tuple{Int,Int},Int}(), - false, - ) + n = $(length(set_types)) + return new([Int[] for _ in 1:n], Vector{Int}[], 0, false) end end ) return _sets_code(esc_name, T, type_def, set_types...) end -MOI.is_empty(sets::OrderedProductOfSets) = all(iszero, sets.num_rows) +MOI.is_empty(sets::OrderedProductOfSets) = all(isempty, sets.dimension) function MOI.empty!(sets::OrderedProductOfSets) - fill!(sets.num_rows, 0) - empty!(sets.dimension) + map(empty!, sets.dimension) + empty!(sets.offset) sets.final_touch = false return end function MOI.dimension(sets::OrderedProductOfSets) @assert sets.final_touch - if isempty(sets.num_rows) - # There is no set type - return 0 - else - return sets.num_rows[end] - end + return sets.num_rows end function rows( @@ -224,7 +224,7 @@ function rows( ) where {T,S} @assert sets.final_touch i = set_index(sets, S)::Int - return (i == 1 ? 0 : sets.num_rows[i-1]) + ci.value + return sets.offset[i][ci.value] + 1 end function rows( @@ -233,28 +233,26 @@ function rows( ) where {T,S} @assert sets.final_touch i = set_index(sets, S)::Int - offset = i == 1 ? 0 : sets.num_rows[i-1] - return (offset + ci.value - 1) .+ (1:sets.dimension[(i, ci.value)]) -end - -function add_set(sets::OrderedProductOfSets, i) - @assert !sets.final_touch - sets.num_rows[i] += 1 - return sets.num_rows[i] + return sets.offset[i][ci.value] .+ (1:sets.dimension[i][ci.value]) end -function add_set(sets::OrderedProductOfSets, i, dim) +function add_set(sets::OrderedProductOfSets, i, dim = 1) @assert !sets.final_touch - ci = sets.num_rows[i] + 1 - sets.dimension[(i, ci)] = dim - sets.num_rows[i] += dim - return ci + push!(sets.dimension[i], dim) + return length(sets.dimension[i]) end function final_touch(sets::OrderedProductOfSets) @assert !sets.final_touch - for i in 2:length(sets.num_rows) - sets.num_rows[i] += sets.num_rows[i-1] + offset = 0 + for (i, dimension) in enumerate(sets.dimension) + offsets = Int[] + for d in dimension + push!(offsets, offset) + offset += d + sets.num_rows += d + end + push!(sets.offset, offsets) end sets.final_touch = true return @@ -268,10 +266,7 @@ the sum of the dimensions of the sets of type `S`. """ function num_rows(sets::OrderedProductOfSets, ::Type{S}) where {S} i = set_index(sets, S)::Int - if !sets.final_touch || i == 1 - return sets.num_rows[i] - end - return sets.num_rows[i] - sets.num_rows[i-1] + return sum(sets.dimension[i]) end function MOI.get( @@ -280,89 +275,39 @@ function MOI.get( ) where {T} return Tuple{Type,Type}[ (_affine_function_type(T, S), S) for - S in set_types(sets) if num_rows(sets, S) > 0 + (i, S) in enumerate(set_types(sets)) if !isempty(sets.dimension[i]) ] end -struct _UnevenIterator - i::Int - start::Int - stop::Int - dimension::Dict{Tuple{Int,Int},Int} -end - -Base.IteratorSize(::_UnevenIterator) = Base.SizeUnknown() - -function Base.iterate(it::_UnevenIterator, cur = it.start) - if cur > it.stop - return nothing - end - return (cur, cur + it.dimension[(it.i, cur)]) -end - -function Base.in(x::Int64, it::_UnevenIterator) - return it.start <= x <= it.stop && haskey(it.dimension, (it.i, x)) -end - -function _range_iterator( - ::OrderedProductOfSets{T}, - ::Int, - start::Int, - stop::Int, - ::Type{MOI.ScalarAffineFunction{T}}, -) where {T} - return start:stop -end - -function _range_iterator( - sets::OrderedProductOfSets{T}, - i::Int, - start::Int, - stop::Int, - ::Type{MOI.VectorAffineFunction{T}}, -) where {T} - return _UnevenIterator(i, start, stop, sets.dimension) -end - -function _range_iterator( - sets::OrderedProductOfSets{T}, - ::Type{F}, - ::Type{S}, -) where {T,F,S} - i = set_index(sets, S) - if i === nothing || F != _affine_function_type(T, S) - return - end - return _range_iterator(sets, i, 1, num_rows(sets, S), F) -end - -_length(::Nothing) = 0 -_length(r::UnitRange) = length(r) -_length(r::_UnevenIterator) = count(_ -> true, r) - function MOI.get( sets::OrderedProductOfSets, ::MOI.NumberOfConstraints{F,S}, ) where {F,S} - r = _range_iterator(sets, F, S) - return _length(r) + i = set_index(sets, S)::Union{Nothing,Int} + if i == nothing + return 0 + end + return length(sets.dimension[i]) end function MOI.get( sets::OrderedProductOfSets, ::MOI.ListOfConstraintIndices{F,S}, ) where {F,S} - rows = _range_iterator(sets, F, S) - if rows === nothing + i = set_index(sets, S)::Union{Nothing,Int} + if i == nothing return MOI.ConstraintIndex{F,S}[] end - return MOI.ConstraintIndex{F,S}.(rows) + return MOI.ConstraintIndex{F,S}.(1:length(sets.dimension[i])) end function MOI.is_valid( sets::OrderedProductOfSets, ci::MOI.ConstraintIndex{F,S}, ) where {F,S} - r = _range_iterator(sets, F, S) - return r !== nothing && ci.value in r + i = set_index(sets, S)::Union{Nothing,Int} + if i == nothing + return false + end + return 1 <= ci.value <= length(sets.dimension[i]) end diff --git a/test/Utilities/test_product_of_sets.jl b/test/Utilities/test_product_of_sets.jl index 7a913ecb47..d8718aed26 100644 --- a/test/Utilities/test_product_of_sets.jl +++ b/test/Utilities/test_product_of_sets.jl @@ -264,7 +264,7 @@ function test_vector_ListOfConstraintIndices() VAF = MOI.VectorAffineFunction{Float64} @test MOI.get(sets, MOI.ListOfConstraintIndices{VAF,MOI.Zeros}()) == MOI.ConstraintIndex{VAF,MOI.Zeros}[] - for (x, S) in zip([[1], [1, 3]], MOI.Utilities.set_types(sets)[1:2]) + for (x, S) in zip([[1], [1, 2]], MOI.Utilities.set_types(sets)[1:2]) ci = MOI.get(sets, MOI.ListOfConstraintIndices{VAF,S}()) @test ci == MOI.ConstraintIndex{VAF,S}.(x) end @@ -290,7 +290,93 @@ function test_vector_ListOfConstraintIndices2() S = MOI.Utilities.set_types(sets)[2] VAF = MOI.VectorAffineFunction{Float64} indices = MOI.get(sets, MOI.ListOfConstraintIndices{VAF,S}()) - @test indices == MOI.ConstraintIndex{VAF,S}.([1, 3, 6, 8]) + @test indices == MOI.ConstraintIndex{VAF,S}.([1, 2, 3, 4]) +end + +function test_zero_dimensional_function() + sets = _VectorSets{Int}() + i = MOI.Utilities.set_index(sets, MOI.Nonnegatives) + MOI.Utilities.add_set(sets, i, 0) + MOI.Utilities.add_set(sets, i, 2) + MOI.Utilities.add_set(sets, i, 0) + MOI.Utilities.add_set(sets, i, 1) + MOI.Utilities.add_set(sets, i, 0) + MOI.Utilities.final_touch(sets) + F, S = MOI.VectorAffineFunction{Int}, MOI.Nonnegatives + @test (F, S) in MOI.get(sets, MOI.ListOfConstraintTypesPresent()) + @test MOI.get(sets, MOI.NumberOfConstraints{F,S}()) == 5 + c = MOI.ConstraintIndex{F,S}.(1:5) + @test MOI.get(sets, MOI.ListOfConstraintIndices{F,S}()) == c + @test all(MOI.is_valid(sets, ci) for ci in c) + return +end + +function test_zero_dimensional_function_mix_of_sets() + sets = _VectorSets{Int}() + i1 = MOI.Utilities.set_index(sets, MOI.Nonpositives) + @test i1 == 1 # The tests below explicitly use this ordering. + i2 = MOI.Utilities.set_index(sets, MOI.Nonnegatives) + @test i2 == 2 # The tests below explicitly use this ordering. + i3 = MOI.Utilities.set_index(sets, MOI.EqualTo{Int}) + @test i3 == 3 # The tests below explicitly use this ordering. + MOI.Utilities.add_set(sets, i1, 0) + MOI.Utilities.add_set(sets, i1, 2) + MOI.Utilities.add_set(sets, i2, 0) + MOI.Utilities.add_set(sets, i2, 1) + MOI.Utilities.add_set(sets, i1, 0) + MOI.Utilities.add_set(sets, i1, 1) + MOI.Utilities.add_set(sets, i3) + MOI.Utilities.add_set(sets, i1, 0) + MOI.Utilities.final_touch(sets) + # MOI.Nonpositives + F, S = MOI.VectorAffineFunction{Int}, MOI.Nonpositives + @test (F, S) in MOI.get(sets, MOI.ListOfConstraintTypesPresent()) + @test MOI.get(sets, MOI.NumberOfConstraints{F,S}()) == 5 + c = MOI.ConstraintIndex{F,S}.(1:5) + @test MOI.get(sets, MOI.ListOfConstraintIndices{F,S}()) == c + @test all(MOI.is_valid(sets, ci) for ci in c) + @test MOI.Utilities.num_rows(sets, S) == 3 + @test MOI.Utilities.rows(sets, c[1]) == 1:0 + @test MOI.Utilities.rows(sets, c[2]) == 1:2 + @test MOI.Utilities.rows(sets, c[3]) == 3:2 + @test MOI.Utilities.rows(sets, c[4]) == 3:3 + @test MOI.Utilities.rows(sets, c[5]) == 4:3 + # MOI.Nonnegatives + F, S = MOI.VectorAffineFunction{Int}, MOI.Nonnegatives + @test (F, S) in MOI.get(sets, MOI.ListOfConstraintTypesPresent()) + @test MOI.get(sets, MOI.NumberOfConstraints{F,S}()) == 2 + c = MOI.ConstraintIndex{F,S}.(1:2) + @test MOI.get(sets, MOI.ListOfConstraintIndices{F,S}()) == c + @test all(MOI.is_valid(sets, ci) for ci in c) + @test MOI.Utilities.num_rows(sets, S) == 1 + @test MOI.Utilities.rows(sets, c[1]) == 4:3 + @test MOI.Utilities.rows(sets, c[2]) == 4:4 + # MOI.EqualTo + F, S = MOI.ScalarAffineFunction{Int}, MOI.EqualTo{Int} + @test (F, S) in MOI.get(sets, MOI.ListOfConstraintTypesPresent()) + @test MOI.get(sets, MOI.NumberOfConstraints{F,S}()) == 1 + c = MOI.ConstraintIndex{F,S}.(1:1) + @test MOI.get(sets, MOI.ListOfConstraintIndices{F,S}()) == c + @test all(MOI.is_valid(sets, ci) for ci in c) + @test MOI.Utilities.num_rows(sets, S) == 1 + @test MOI.Utilities.rows(sets, c[1]) == 5 + return +end + +function test_zero_dimensional_function_only() + sets = _VectorSets{Int}() + i = MOI.Utilities.set_index(sets, MOI.Nonnegatives) + MOI.Utilities.add_set(sets, i, 0) + MOI.Utilities.add_set(sets, i, 0) + MOI.Utilities.add_set(sets, i, 0) + MOI.Utilities.final_touch(sets) + F, S = MOI.VectorAffineFunction{Int}, MOI.Nonnegatives + @test (F, S) in MOI.get(sets, MOI.ListOfConstraintTypesPresent()) + @test MOI.get(sets, MOI.NumberOfConstraints{F,S}()) == 3 + c = MOI.ConstraintIndex{F,S}.(1:3) + @test MOI.get(sets, MOI.ListOfConstraintIndices{F,S}()) == c + @test all(MOI.is_valid(sets, ci) for ci in c) + return end end From 17013cf19cdffee6d8ce340543ea689f0586e720 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 9 Jul 2026 11:41:02 +1200 Subject: [PATCH 2/5] Simplify further --- src/Utilities/product_of_sets.jl | 86 ++++++++++++-------------- test/Utilities/test_product_of_sets.jl | 1 + 2 files changed, 39 insertions(+), 48 deletions(-) diff --git a/src/Utilities/product_of_sets.jl b/src/Utilities/product_of_sets.jl index 767cc458ac..3630369080 100644 --- a/src/Utilities/product_of_sets.jl +++ b/src/Utilities/product_of_sets.jl @@ -170,25 +170,14 @@ macro product_of_sets(name, set_types...) mutable struct $(esc_name){$(T)} <: $MOI.Utilities.OrderedProductOfSets{$(T)} """ - `dimension[i][j]` is the dimension of `ConstraintIndex().value` `j` - of set type `i`. - """ - dimension::Vector{Vector{Int}} - - """ - `offset[i][j]` is the 0-indexed row offset of constraint `j` of set - type `i`. The rows are therefore `offset[i][j] + 1:dimension[i][j]`. - - The `offset` vector gets created during `final_touch`. - """ - offset::Vector{Vector{Int}} - - """ - The total number of rows in the sets. + `rows[i][j]` corresponds to constraint `j` of set type `i`. - This value gets set during `final_touch`. + The value depends on `final_touch`: + * Before `final_touch`, these are `1:dimension` of the constraint + * After `final_touch`, these are the 1-indexed rows of the full + constraint matrix """ - num_rows::Int + rows::Vector{Vector{UnitRange{Int}}} """ A sanity bit to check that we don't call functions out-of-order. @@ -197,62 +186,58 @@ macro product_of_sets(name, set_types...) function $(esc_name){$(T)}() where {$(T)} n = $(length(set_types)) - return new([Int[] for _ in 1:n], Vector{Int}[], 0, false) + return new([UnitRange{Int}[] for _ in 1:n], false) end end ) return _sets_code(esc_name, T, type_def, set_types...) end -MOI.is_empty(sets::OrderedProductOfSets) = all(isempty, sets.dimension) +MOI.is_empty(sets::OrderedProductOfSets) = all(isempty, sets.rows) function MOI.empty!(sets::OrderedProductOfSets) - map(empty!, sets.dimension) - empty!(sets.offset) + map(empty!, sets.rows) sets.final_touch = false return end -function MOI.dimension(sets::OrderedProductOfSets) +function MOI.dimension(sets::OrderedProductOfSets)::Int @assert sets.final_touch - return sets.num_rows + return sum(num_rows(sets, S) for S in set_types(sets); init = 0) end function rows( sets::OrderedProductOfSets{T}, ci::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},S}, -) where {T,S} +)::Int where {T,S} @assert sets.final_touch i = set_index(sets, S)::Int - return sets.offset[i][ci.value] + 1 + return only(sets.rows[i][ci.value]) end function rows( sets::OrderedProductOfSets{T}, ci::MOI.ConstraintIndex{MOI.VectorAffineFunction{T},S}, -) where {T,S} +)::UnitRange{Int} where {T,S} @assert sets.final_touch i = set_index(sets, S)::Int - return sets.offset[i][ci.value] .+ (1:sets.dimension[i][ci.value]) + return sets.rows[i][ci.value] end -function add_set(sets::OrderedProductOfSets, i, dim = 1) +function add_set(sets::OrderedProductOfSets, i, dim = 1)::Int @assert !sets.final_touch - push!(sets.dimension[i], dim) - return length(sets.dimension[i]) + push!(sets.rows[i], 1:dim) + return length(sets.rows[i]) end -function final_touch(sets::OrderedProductOfSets) +function final_touch(sets::OrderedProductOfSets)::Nothing @assert !sets.final_touch offset = 0 - for (i, dimension) in enumerate(sets.dimension) - offsets = Int[] - for d in dimension - push!(offsets, offset) - offset += d - sets.num_rows += d + for (i, rows) in enumerate(sets.rows) + for (j, row) in enumerate(rows) + rows[j] = offset .+ row + offset += length(row) end - push!(sets.offset, offsets) end sets.final_touch = true return @@ -264,50 +249,55 @@ end Return the number of rows corresponding to a set of type `S`. That is, it is the sum of the dimensions of the sets of type `S`. """ -function num_rows(sets::OrderedProductOfSets, ::Type{S}) where {S} +function num_rows(sets::OrderedProductOfSets, ::Type{S})::Int where {S} + @assert sets.final_touch i = set_index(sets, S)::Int - return sum(sets.dimension[i]) + rows = sets.rows[i] + if isempty(rows) + return 0 + end + return max(0, last(rows[end]) - first(rows[1]) + 1) end function MOI.get( sets::OrderedProductOfSets{T}, ::MOI.ListOfConstraintTypesPresent, -) where {T} +)::Vector{Tuple{Type,Type}} where {T} return Tuple{Type,Type}[ (_affine_function_type(T, S), S) for - (i, S) in enumerate(set_types(sets)) if !isempty(sets.dimension[i]) + (i, S) in enumerate(set_types(sets)) if !isempty(sets.rows[i]) ] end function MOI.get( sets::OrderedProductOfSets, ::MOI.NumberOfConstraints{F,S}, -) where {F,S} +)::Int64 where {F,S} i = set_index(sets, S)::Union{Nothing,Int} if i == nothing return 0 end - return length(sets.dimension[i]) + return length(sets.rows[i]) end function MOI.get( sets::OrderedProductOfSets, ::MOI.ListOfConstraintIndices{F,S}, -) where {F,S} +)::Vector{MOI.ConstraintIndex{F,S}} where {F,S} i = set_index(sets, S)::Union{Nothing,Int} if i == nothing return MOI.ConstraintIndex{F,S}[] end - return MOI.ConstraintIndex{F,S}.(1:length(sets.dimension[i])) + return MOI.ConstraintIndex{F,S}.(1:length(sets.rows[i])) end function MOI.is_valid( sets::OrderedProductOfSets, ci::MOI.ConstraintIndex{F,S}, -) where {F,S} +)::Bool where {F,S} i = set_index(sets, S)::Union{Nothing,Int} if i == nothing return false end - return 1 <= ci.value <= length(sets.dimension[i]) + return 1 <= ci.value <= length(sets.rows[i]) end diff --git a/test/Utilities/test_product_of_sets.jl b/test/Utilities/test_product_of_sets.jl index d8718aed26..816c813db2 100644 --- a/test/Utilities/test_product_of_sets.jl +++ b/test/Utilities/test_product_of_sets.jl @@ -291,6 +291,7 @@ function test_vector_ListOfConstraintIndices2() VAF = MOI.VectorAffineFunction{Float64} indices = MOI.get(sets, MOI.ListOfConstraintIndices{VAF,S}()) @test indices == MOI.ConstraintIndex{VAF,S}.([1, 2, 3, 4]) + return end function test_zero_dimensional_function() From a5ce641525ceb76d2179123a6f0fd9ca16144a3f Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 9 Jul 2026 11:44:51 +1200 Subject: [PATCH 3/5] Update --- src/Utilities/matrix_of_constraints.jl | 6 +++--- src/Utilities/product_of_sets.jl | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Utilities/matrix_of_constraints.jl b/src/Utilities/matrix_of_constraints.jl index 69e6ec9683..074575a6c0 100644 --- a/src/Utilities/matrix_of_constraints.jl +++ b/src/Utilities/matrix_of_constraints.jl @@ -290,11 +290,11 @@ If `S` is not part of the list, return `nothing`. function set_index end """ - add_set(sets, i)::Int64 + add_set(sets, i::Int)::Int64 Add a scalar set of type index `i`. - add_set(sets, i, dim)::Int64 + add_set(sets, i::Int, dim::Int)::Int64 Add a vector set of type index `i` and dimension `dim`. @@ -387,7 +387,7 @@ function MOI.get( return MOI.get(v.sets, attr) end -_add_set(sets, i, ::MOI.AbstractScalarFunction) = add_set(sets, i) +_add_set(sets, i::Int, ::MOI.AbstractScalarFunction) = add_set(sets, i) function _add_set(sets, i, func::MOI.AbstractVectorFunction) return add_set(sets, i, MOI.output_dimension(func)) diff --git a/src/Utilities/product_of_sets.jl b/src/Utilities/product_of_sets.jl index 3630369080..e9eb1caf34 100644 --- a/src/Utilities/product_of_sets.jl +++ b/src/Utilities/product_of_sets.jl @@ -88,7 +88,7 @@ MOI.dimension(sets::MixOfScalarSets) = length(sets.set_ids) rows(::MixOfScalarSets, ci::MOI.ConstraintIndex) = ci.value -function add_set(sets::MixOfScalarSets, i) +function add_set(sets::MixOfScalarSets, i::Int)::Int64 push!(sets.set_ids, i) return length(sets.set_ids) end @@ -224,7 +224,7 @@ function rows( return sets.rows[i][ci.value] end -function add_set(sets::OrderedProductOfSets, i, dim = 1)::Int +function add_set(sets::OrderedProductOfSets, i::Int, dim::Int = 1)::Int64 @assert !sets.final_touch push!(sets.rows[i], 1:dim) return length(sets.rows[i]) From e57bf7ca13e8ed4e2e715b96d4dab451cd9978cc Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 9 Jul 2026 12:53:59 +1200 Subject: [PATCH 4/5] Update --- test/Utilities/test_product_of_sets.jl | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/Utilities/test_product_of_sets.jl b/test/Utilities/test_product_of_sets.jl index 816c813db2..2486c85482 100644 --- a/test/Utilities/test_product_of_sets.jl +++ b/test/Utilities/test_product_of_sets.jl @@ -380,6 +380,25 @@ function test_zero_dimensional_function_only() return end +function test_ordered_product_of_sets_is_valid() + sets = _VectorSets{Int}() + i = MOI.Utilities.set_index(sets, MOI.Nonnegatives) + MOI.Utilities.add_set(sets, i, 2) + MOI.Utilities.final_touch(sets) + F, S = MOI.VectorAffineFunction{Int}, MOI.Nonnegatives + @test MOI.is_valid(model, MOI.ConstraintIndex{F,S}(1)) + for ci in MOI.ConstraintIndex[ + MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}(1), + MOI.ConstraintIndex{F,S}(-1), + MOI.ConstraintIndex{F,S}(0), + MOI.ConstraintIndex{F,S}(2), + MOI.ConstraintIndex{F,S}(12345), + ] + @test !MOI.is_valid(model, ci) + end + return +end + end TestProductOfSets.runtests() From 2270f08033213ca2ffdceafd58f8284f639413fa Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 9 Jul 2026 13:48:34 +1200 Subject: [PATCH 5/5] Update --- test/Utilities/test_product_of_sets.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Utilities/test_product_of_sets.jl b/test/Utilities/test_product_of_sets.jl index 2486c85482..09f48c4c69 100644 --- a/test/Utilities/test_product_of_sets.jl +++ b/test/Utilities/test_product_of_sets.jl @@ -386,7 +386,7 @@ function test_ordered_product_of_sets_is_valid() MOI.Utilities.add_set(sets, i, 2) MOI.Utilities.final_touch(sets) F, S = MOI.VectorAffineFunction{Int}, MOI.Nonnegatives - @test MOI.is_valid(model, MOI.ConstraintIndex{F,S}(1)) + @test MOI.is_valid(sets, MOI.ConstraintIndex{F,S}(1)) for ci in MOI.ConstraintIndex[ MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}(1), MOI.ConstraintIndex{F,S}(-1), @@ -394,7 +394,7 @@ function test_ordered_product_of_sets_is_valid() MOI.ConstraintIndex{F,S}(2), MOI.ConstraintIndex{F,S}(12345), ] - @test !MOI.is_valid(model, ci) + @test !MOI.is_valid(sets, ci) end return end