Skip to content

Commit

Permalink
Add set conversion bridge (#2536)
Browse files Browse the repository at this point in the history
* Add set conversion bridge

* Remove debug

* Add doc and tests

* fix format

* Fixes

* Fixes

* Fix format

* Fix

* Update src/Bridges/Constraint/bridges/set_conversion.jl

* Add test

* Update src/Bridges/Constraint/bridges/set_conversion.jl

---------

Co-authored-by: Oscar Dowson <[email protected]>
  • Loading branch information
blegat and odow authored Oct 31, 2024
1 parent 95e16c4 commit 278fba5
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Bridges/Constraint/Constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ include("bridges/split_complex_zeros.jl")
include("bridges/split_hyperrectangle.jl")
include("bridges/hermitian.jl")
include("bridges/square.jl")
include("bridges/set_conversion.jl")
include("bridges/set_dot_scaling.jl")
include("bridges/table.jl")
include("bridges/vectorize.jl")
Expand Down
2 changes: 2 additions & 0 deletions src/Bridges/Constraint/bridges/functionize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ for these pairs of functions:
* [`MOI.ScalarQuadraticFunction`](@ref) to [`MOI.ScalarNonlinearFunction`](@ref)
* [`MOI.VectorAffineFunction`](@ref) to [`MOI.VectorQuadraticFunction`](@ref)
See also [`SetConversionBridge`](@ref).
## Source node
`FunctionConversionBridge` supports:
Expand Down
105 changes: 105 additions & 0 deletions src/Bridges/Constraint/bridges/set_conversion.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Copyright (c) 2017: Miles Lubin and contributors
# Copyright (c) 2017: Google Inc.
#
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

"""
SetConversionBridge{T,S2,S1,F} <:
MOI.Bridges.Constraint.SetMapBridge{T,S2,S1,F,F}
`SetConversionBridge` implements the following reformulations:
* ``f(x) \\in S1`` into ``f(x) \\in S2``
In order to add this bridge, you need to create a bridge specific
for a given type `T` and set `S2`:
```julia
MOI.Bridges.add_bridge(model, MOI.Bridges.Constraint.SetConversionBridge{T,S2})
```
In order to define a bridge with `S2` specified but `T` unspecified, for example
for `JuMP.add_bridge`, you can use
```julia
const MyBridge{T,S1,F} = MOI.Bridges.Constraint.SetConversionBridge{T,S2,S1,F}
```
See also [`FunctionConversionBridge`](@ref).
## Source node
`SetConversionBridge` supports:
* `F` in `S1`
## Target nodes
`SetConversionBridge` creates:
* `F` in `S2`
"""
struct SetConversionBridge{T,S2,S1,F} <:
MOI.Bridges.Constraint.SetMapBridge{T,S2,S1,F,F}
constraint::MOI.ConstraintIndex{F,S2}
end

function MOI.supports_constraint(
::Type{SetConversionBridge{T,S2}},
::Type{F},
::Type{S1},
) where {T,F<:MOI.AbstractFunction,S1<:MOI.AbstractSet,S2}
return isfinite(MOI.Bridges.Constraint.conversion_cost(S2, S1))
end

function MOI.Bridges.Constraint.concrete_bridge_type(
::Type{SetConversionBridge{T,S2}},
::Type{F},
::Type{S1},
) where {T,F<:MOI.AbstractFunction,S1<:MOI.AbstractSet,S2}
return SetConversionBridge{T,S2,S1,F}
end

function MOI.Bridges.Constraint.conversion_cost(
::Type{<:MOI.AbstractSet},
::Type{<:MOI.AbstractSet},
)
return Inf
end

function MOI.Bridges.bridging_cost(
::Type{<:SetConversionBridge{T,S2,S1}},
) where {T,S2,S1}
return MOI.Bridges.Constraint.conversion_cost(S2, S1)
end

function MOI.Bridges.map_set(
::Type{<:SetConversionBridge{T,S2,S1}},
set::S1,
) where {T,S2,S1}
return convert(S2, set)
end

function MOI.Bridges.inverse_map_set(
::Type{<:SetConversionBridge{T,S2,S1}},
set::S2,
) where {T,S2,S1}
return convert(S1, set)
end

function MOI.Bridges.map_function(::Type{<:SetConversionBridge}, func)
return func
end

function MOI.Bridges.inverse_map_function(::Type{<:SetConversionBridge}, func)
return func
end

function MOI.Bridges.adjoint_map_function(::Type{<:SetConversionBridge}, func)
return func
end

function MOI.Bridges.inverse_adjoint_map_function(
::Type{<:SetConversionBridge},
func,
)
return func
end
98 changes: 98 additions & 0 deletions test/Bridges/Constraint/set_conversion.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Copyright (c) 2017: Miles Lubin and contributors
# Copyright (c) 2017: Google Inc.
#
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

module TestConstraintSetConversion

using Test

import MathOptInterface as MOI

function runtests()
for name in names(@__MODULE__; all = true)
if startswith("$(name)", "test_")
@testset "$(name)" begin
getfield(@__MODULE__, name)()
end
end
end
return
end

struct Zero <: MOI.AbstractScalarSet end

function MOI.Bridges.Constraint.conversion_cost(
::Type{MOI.EqualTo{Float64}},
::Type{Zero},
)
return 1.0
end

Base.convert(::Type{MOI.EqualTo{Float64}}, ::Zero) = MOI.EqualTo(0.0)

function Base.convert(::Type{Zero}, s::MOI.EqualTo)
if !iszero(s.value)
throw(InexactError(convert, (Zero, s)))
end
return Zero()
end

# Does not make sense that this is convertible but it's
# just to test `conversion_cost`
function MOI.Bridges.Constraint.conversion_cost(
::Type{MOI.LessThan{Float64}},
::Type{Zero},
)
return 10.0
end

const EqualToBridge{T,S1,F} =
MOI.Bridges.Constraint.SetConversionBridge{T,MOI.EqualTo{T},S1,F}

function test_runtests()
MOI.Bridges.runtests(
EqualToBridge,
model -> begin
x = MOI.add_variable(model)
MOI.add_constraint(model, x, Zero())
end,
model -> begin
x = MOI.add_variable(model)
MOI.add_constraint(model, x, MOI.EqualTo(0.0))
end,
)
return
end

function test_conversion_cost(T = Float64)
model = MOI.Utilities.Model{T}()
bridged = MOI.Bridges.LazyBridgeOptimizer(model)
MOI.Bridges.add_bridge(
bridged,
MOI.Bridges.Constraint.SetConversionBridge{T,MOI.LessThan{T}},
)
@test MOI.Bridges.bridge_type(bridged, MOI.VariableIndex, Zero) ==
MOI.Bridges.Constraint.SetConversionBridge{
T,
MOI.LessThan{T},
Zero,
MOI.VariableIndex,
}
MOI.Bridges.add_bridge(
bridged,
MOI.Bridges.Constraint.SetConversionBridge{T,MOI.EqualTo{T}},
)
@test MOI.Bridges.bridge_type(bridged, MOI.VariableIndex, Zero) ==
MOI.Bridges.Constraint.SetConversionBridge{
T,
MOI.EqualTo{T},
Zero,
MOI.VariableIndex,
}
end

end # module

TestConstraintSetConversion.runtests()

0 comments on commit 278fba5

Please sign in to comment.