diff --git a/docs/src/submodules/Utilities/reference.md b/docs/src/submodules/Utilities/reference.md index 7b2beb0c8d..745ead9bc0 100644 --- a/docs/src/submodules/Utilities/reference.md +++ b/docs/src/submodules/Utilities/reference.md @@ -142,14 +142,13 @@ Utilities.add_set Utilities.rows Utilities.num_rows Utilities.set_with_dimension -``` - -```@docs Utilities.ProductOfSets Utilities.MixOfScalarSets Utilities.@mix_of_scalar_sets Utilities.OrderedProductOfSets Utilities.@product_of_sets +Utilities.RuntimeProductOfSets +Utilities.add_set_type ``` ## Fallbacks diff --git a/src/Utilities/product_of_sets.jl b/src/Utilities/product_of_sets.jl index 1be7dbf77c..320d119eb6 100644 --- a/src/Utilities/product_of_sets.jl +++ b/src/Utilities/product_of_sets.jl @@ -320,3 +320,82 @@ function MOI.is_valid( end return 1 <= ci.value <= length(sets.rows[i]) end + +""" + RuntimeProductOfSets{T}() + +An alternative to [`Utilities.@product_of_sets`](@ref), in which the set types +are decided at runtime using [`Utilities.add_set_type`](@ref). + +## Examples + +```jldoctest +julia> import MathOptInterface as MOI + +julia> sets = MOI.Utilities.RuntimeProductOfSets{Int}(); + +julia> MOI.Utilities.set_types(sets) +Type[] + +julia> MOI.Utilities.add_set_type(sets, MOI.Nonpositives) + +julia> MOI.Utilities.set_types(sets) +1-element Vector{Type}: + MathOptInterface.Nonpositives +``` +""" +mutable struct RuntimeProductOfSets{T} <: OrderedProductOfSets{T} + rows::Vector{Vector{UnitRange{Int}}} + final_touch::Bool + set_types::Vector{Type} + + function RuntimeProductOfSets{T}() where {T} + return new(Vector{UnitRange{Int}}[], false, Type[]) + end +end + +function set_index( + set::RuntimeProductOfSets, + ::Type{S}, +) where {S<:MOI.AbstractSet} + return findfirst(==(S), set.set_types) +end + +set_types(set::RuntimeProductOfSets) = set.set_types + +""" + add_set_type( + set::RuntimeProductOfSets, + ::Type{S}, + ) where {S<:MOI.AbstractSet} + +Declare that the [`Utilities.RuntimeProductOfSets`](@ref) `set` supports the +[`MOI.AbstractSet`](@ref) `S`. + +## Examples + +```jldoctest +julia> import MathOptInterface as MOI + +julia> sets = MOI.Utilities.RuntimeProductOfSets{Int}(); + +julia> MOI.Utilities.set_types(sets) +Type[] + +julia> MOI.Utilities.add_set_type(sets, MOI.Nonpositives) + +julia> MOI.Utilities.set_types(sets) +1-element Vector{Type}: + MathOptInterface.Nonpositives +``` +""" +function add_set_type( + set::RuntimeProductOfSets, + ::Type{S}, +) where {S<:MOI.AbstractSet} + if set_index(set, S) === nothing + push!(set.rows, Vector{UnitRange{Int}}[]) + push!(set.set_types, S) + end + return +end diff --git a/test/Utilities/test_product_of_sets.jl b/test/Utilities/test_product_of_sets.jl index c2a6c4daf3..c7304705d9 100644 --- a/test/Utilities/test_product_of_sets.jl +++ b/test/Utilities/test_product_of_sets.jl @@ -421,6 +421,71 @@ function test_property_num_sets() return end +function test_product_of_sets() + sets = MOI.Utilities.RuntimeProductOfSets{Int}() + @test MOI.Utilities.set_types(sets) == Type[] + MOI.Utilities.add_set_type(sets, MOI.Nonpositives) + @test MOI.Utilities.set_types(sets) == Type[MOI.Nonpositives] + MOI.Utilities.add_set_type(sets, MOI.Nonnegatives) + MOI.Utilities.add_set_type(sets, MOI.EqualTo{Int}) + @test MOI.Utilities.set_types(sets) == + Type[MOI.Nonpositives, MOI.Nonnegatives, MOI.EqualTo{Int}] + MOI.Utilities.add_set_type(sets, MOI.EqualTo{Int}) + @test MOI.Utilities.set_types(sets) == + Type[MOI.Nonpositives, MOI.Nonnegatives, MOI.EqualTo{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) + @test MOI.Utilities.num_rows(sets, MOI.Nonpositives) == 3 + @test MOI.Utilities.num_rows(sets, MOI.Nonnegatives) == 1 + @test MOI.Utilities.num_rows(sets, MOI.EqualTo{Int}) == 1 + 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 + end TestProductOfSets.runtests()