Skip to content

Commit

Permalink
Add set for low-rank constrained SDP (#2198)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
blegat authored Dec 24, 2024
1 parent 1be26a6 commit c3fad29
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 30 deletions.
17 changes: 9 additions & 8 deletions src/Bridges/Variable/set_map.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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
Expand All @@ -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,
)
Expand All @@ -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

Expand All @@ -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
4 changes: 4 additions & 0 deletions src/Bridges/set_map.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
10 changes: 8 additions & 2 deletions src/Utilities/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
100 changes: 80 additions & 20 deletions test/Bridges/set_map.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions test/Utilities/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand Down

0 comments on commit c3fad29

Please sign in to comment.