From c3fad29fbf5f408231e03462ceb7046a3b6d8a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 24 Dec 2024 10:34:55 +0100 Subject: [PATCH] Add set for low-rank constrained SDP (#2198) * Add set for low-rank constrained SDP * SetWithDotProducts * Fix format * Update docs/src/manual/standard_form.md * Add low rank matrix * Add bridge * Add copy * Updates * Fix * Add test for bridge * Fix format * Add tests * Add bridge * Fix format * Rename * Add conversions * Remove what was moved to LowRankOpt * Remove what was moved to LowRankOpt * Add test * Fix format * Add tests --- src/Bridges/Variable/set_map.jl | 17 +++--- src/Bridges/set_map.jl | 4 ++ src/Utilities/functions.jl | 10 +++- test/Bridges/set_map.jl | 100 +++++++++++++++++++++++++------- test/Utilities/functions.jl | 4 ++ 5 files changed, 105 insertions(+), 30 deletions(-) diff --git a/src/Bridges/Variable/set_map.jl b/src/Bridges/Variable/set_map.jl index 6ebaf57538..d34685e661 100644 --- a/src/Bridges/Variable/set_map.jl +++ b/src/Bridges/Variable/set_map.jl @@ -129,7 +129,7 @@ function MOI.get( bridge::SetMapBridge, ) set = MOI.get(model, attr, bridge.constraint) - return MOI.Bridges.map_set(typeof(bridge), set) + return MOI.Bridges.map_set(bridge, set) end function MOI.set( @@ -149,7 +149,7 @@ function MOI.get( bridge::SetMapBridge, ) value = MOI.get(model, attr, bridge.constraint) - return MOI.Bridges.map_function(typeof(bridge), value) + return MOI.Bridges.map_function(bridge, value) end function MOI.get( @@ -171,7 +171,7 @@ function MOI.get( if any(isnothing, value) return nothing end - return MOI.Bridges.map_function(typeof(bridge), value, i) + return MOI.Bridges.map_function(bridge, value, i) end function MOI.supports( @@ -192,7 +192,7 @@ function MOI.set( if value === nothing MOI.set(model, attr, bridge.variables[i.value], nothing) else - bridged_value = MOI.Bridges.inverse_map_function(typeof(bridge), value) + bridged_value = MOI.Bridges.inverse_map_function(bridge, value) MOI.set(model, attr, bridge.variables[i.value], bridged_value) end return @@ -203,7 +203,7 @@ function MOI.Bridges.bridged_function( i::MOI.Bridges.IndexInVector, ) where {T} func = MOI.Bridges.map_function( - typeof(bridge), + bridge, MOI.VectorOfVariables(bridge.variables), i, ) @@ -212,7 +212,7 @@ end function unbridged_map(bridge::SetMapBridge{T}, vi::MOI.VariableIndex) where {T} F = MOI.ScalarAffineFunction{T} - mapped = MOI.Bridges.inverse_map_function(typeof(bridge), vi) + mapped = MOI.Bridges.inverse_map_function(bridge, vi) return Pair{MOI.VariableIndex,F}[bridge.variable=>mapped] end @@ -222,9 +222,10 @@ function unbridged_map( ) where {T} F = MOI.ScalarAffineFunction{T} func = MOI.VectorOfVariables(vis) - funcs = MOI.Bridges.inverse_map_function(typeof(bridge), func) + funcs = MOI.Bridges.inverse_map_function(bridge, func) scalars = MOI.Utilities.eachscalar(funcs) + # FIXME not correct for SetDotProducts, it won't recover the dot product variables return Pair{MOI.VariableIndex,F}[ - bridge.variables[i] => scalars[i] for i in eachindex(vis) + bridge.variables[i] => scalars[i] for i in eachindex(bridge.variables) ] end diff --git a/src/Bridges/set_map.jl b/src/Bridges/set_map.jl index c665c1dd00..11a51a9906 100644 --- a/src/Bridges/set_map.jl +++ b/src/Bridges/set_map.jl @@ -80,6 +80,10 @@ function map_function(::Type{BT}, func, i::IndexInVector) where {BT} return MOI.Utilities.eachscalar(map_function(BT, func))[i.value] end +function map_function(bridge::AbstractBridge, func, i::IndexInVector) + return map_function(typeof(bridge), func, i) +end + """ inverse_map_function(bridge::MOI.Bridges.AbstractBridge, func) inverse_map_function(::Type{BT}, func) where {BT} diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index 85cda9e0df..2d558f92e4 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -638,18 +638,24 @@ end A type that allows iterating over the scalar-functions that comprise an `AbstractVectorFunction`. """ -struct ScalarFunctionIterator{F<:MOI.AbstractVectorFunction,C} +struct ScalarFunctionIterator{F<:MOI.AbstractVectorFunction,C,S} <: + AbstractVector{S} f::F # Cache that can be used to store a precomputed datastructure that allows # an efficient implementation of `getindex`. cache::C + function ScalarFunctionIterator(f::MOI.AbstractVectorFunction, cache) + return new{typeof(f),typeof(cache),scalar_type(typeof(f))}(f, cache) + end end function ScalarFunctionIterator(func::MOI.AbstractVectorFunction) return ScalarFunctionIterator(func, scalar_iterator_cache(func)) end -scalar_iterator_cache(func::MOI.AbstractVectorFunction) = nothing +Base.size(s::ScalarFunctionIterator) = (MOI.output_dimension(s.f),) + +scalar_iterator_cache(::MOI.AbstractVectorFunction) = nothing function output_index_iterator(terms::AbstractVector, output_dimension) start = zeros(Int, output_dimension) diff --git a/test/Bridges/set_map.jl b/test/Bridges/set_map.jl index f4f600c60d..cc64cb18ed 100644 --- a/test/Bridges/set_map.jl +++ b/test/Bridges/set_map.jl @@ -22,7 +22,44 @@ end MOI.dimension(::SwapSet) = 2 -struct SwapBridge{T} <: MOI.Bridges.Constraint.SetMapBridge{ +struct VariableSwapBridge{T} <: + MOI.Bridges.Variable.SetMapBridge{T,MOI.Nonnegatives,SwapSet} + variables::MOI.Vector{MOI.VariableIndex} + constraint::MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Nonnegatives} + set::SwapSet +end + +function MOI.Bridges.Variable.bridge_constrained_variable( + ::Type{VariableSwapBridge{T}}, + model::MOI.ModelLike, + set::SwapSet, +) where {T} + variables, constraint = + MOI.add_constrained_variables(model, MOI.Nonnegatives(2)) + return VariableSwapBridge{T}(variables, constraint, set) +end + +MOI.Bridges.map_set(bridge::VariableSwapBridge, ::MOI.Nonnegatives) = bridge.set + +function MOI.Bridges.inverse_map_set(bridge::VariableSwapBridge, set::SwapSet) + if set.swap != bridge.set.swap + error("Cannot change swap set") + end + return MOI.Nonnegatives(2) +end + +function MOI.Bridges.map_function( + bridge::VariableSwapBridge, + func, + i::MOI.Bridges.IndexInVector, +) + return MOI.Bridges.map_function(bridge, func)[i.value] +end + +# Workaround until https://github.com/jump-dev/MathOptInterface.jl/issues/2117 is fixed +MOI.Bridges.inverse_map_function(::VariableSwapBridge, a::Float64) = a + +struct ConstraintSwapBridge{T} <: MOI.Bridges.Constraint.SetMapBridge{ T, MOI.Nonnegatives, SwapSet, @@ -34,7 +71,7 @@ struct SwapBridge{T} <: MOI.Bridges.Constraint.SetMapBridge{ end function MOI.Bridges.Constraint.bridge_constraint( - ::Type{SwapBridge{T}}, + ::Type{ConstraintSwapBridge{T}}, model::MOI.ModelLike, func::MOI.VectorOfVariables, set::SwapSet, @@ -44,17 +81,24 @@ function MOI.Bridges.Constraint.bridge_constraint( MOI.VectorOfVariables(swap(func.variables, set.swap)), MOI.Nonnegatives(2), ) - return SwapBridge{T}(ci, set) + return ConstraintSwapBridge{T}(ci, set) end -function MOI.Bridges.map_set(bridge::SwapBridge, set::SwapSet) +function MOI.Bridges.map_set(bridge::ConstraintSwapBridge, set::SwapSet) if set.swap != bridge.set.swap error("Cannot change swap set") end return MOI.Nonnegatives(2) end -MOI.Bridges.inverse_map_set(bridge::SwapBridge, ::MOI.Nonnegatives) = bridge.set +function MOI.Bridges.inverse_map_set( + bridge::ConstraintSwapBridge, + ::MOI.Nonnegatives, +) + return bridge.set +end + +const SwapBridge{T} = Union{VariableSwapBridge{T},ConstraintSwapBridge{T}} function MOI.Bridges.map_function(bridge::SwapBridge, func) return swap(func, bridge.set.swap) @@ -90,19 +134,10 @@ function swap(f::MOI.VectorOfVariables, do_swap::Bool) return MOI.VectorOfVariables(swap(f.variables, do_swap)) end -function runtests() - for name in names(@__MODULE__; all = true) - if startswith("$(name)", "test_") - @testset "$(name)" begin - getfield(@__MODULE__, name)() - end - end - end - return -end - function test_other_error() - model = MOI.Bridges.Constraint.SingleBridgeOptimizer{SwapBridge{Float64}}( + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ + ConstraintSwapBridge{Float64}, + }( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), ) x = MOI.add_variables(model, 2) @@ -123,8 +158,11 @@ function test_other_error() ) return end -function test_not_invertible() - model = MOI.Bridges.Constraint.SingleBridgeOptimizer{SwapBridge{Float64}}( + +function test_constraint_not_invertible() + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ + ConstraintSwapBridge{Float64}, + }( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), ) x = MOI.add_variables(model, 2) @@ -162,7 +200,7 @@ end function test_runtests() for do_swap in [false, true] MOI.Bridges.runtests( - SwapBridge, + ConstraintSwapBridge, model -> begin x = MOI.add_variables(model, 2) func = MOI.VectorOfVariables(x) @@ -176,6 +214,28 @@ function test_runtests() MOI.add_constraint(model, func, set) end, ) + MOI.Bridges.runtests( + VariableSwapBridge, + model -> begin + set = SwapSet(do_swap, NONE) + x = MOI.add_constrained_variables(model, set) + end, + model -> begin + set = MOI.Nonnegatives(2) + x = MOI.add_constrained_variables(model, set) + end, + ) + end + return +end + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end end return end diff --git a/test/Utilities/functions.jl b/test/Utilities/functions.jl index 78f63c90bb..6438436103 100644 --- a/test/Utilities/functions.jl +++ b/test/Utilities/functions.jl @@ -434,6 +434,8 @@ end function test_iteration_and_indexing_on_VectorOfVariables() f = MOI.VectorOfVariables([z, w, x, y]) it = MOI.Utilities.eachscalar(f) + @test it isa AbstractVector{MOI.VariableIndex} + @test size(it) == (4,) @test length(it) == 4 @test eltype(it) == MOI.VariableIndex @test collect(it) == [z, w, x, y] @@ -454,6 +456,8 @@ function test_indexing_on_VectorAffineFunction() [2, 7, 5], ) it = MOI.Utilities.eachscalar(f) + @test it isa AbstractVector{MOI.ScalarAffineFunction{Int}} + @test size(it) == (3,) @test length(it) == 3 @test eltype(it) == MOI.ScalarAffineFunction{Int} g = it[2]