Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Bridges/Bridges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ include("SplitIntervalBridge.jl")
include("FlipSignBridge.jl")
include("NonlinearBridge.jl")
include("ToSOS1Bridge.jl")
include("ToZerosBridge.jl")

"""
add_all_bridges(model::MOI.ModelLike, ::Type{T} = Float64)
Expand All @@ -40,6 +41,7 @@ function add_all_bridges(model::MOI.ModelLike, ::Type{T} = Float64) where {T}
MOI.Bridges.add_bridge(model, SplitIntervalBridge{T})
MOI.Bridges.add_bridge(model, FlipSignBridge{T})
MOI.Bridges.add_bridge(model, ToSOS1Bridge{T})
MOI.Bridges.add_bridge(model, ToZerosBridge{T})
MOI.Bridges.add_bridge(model, VerticalBridge{T})
MOI.Bridges.add_bridge(model, NonlinearBridge{T})
return
Expand Down
105 changes: 105 additions & 0 deletions src/Bridges/ToZerosBridge.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors
#
# 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.

"""
ToZerosBridge{T} <: MOI.Bridges.Constraint.AbstractBridge

`ToZerosBridge` implements the following reformulation:

* `[x₁, x₂]` in `ComplementsWithSetType{Zeros}` into `[x₁]` in
[`MOI.Zeros`](@ref)

`ComplementsWithSetType{Zeros}` means that the activity `x₁` is complementary
to a variable `x₂` whose reference set is a single point (it is either free or
fixed). In both cases the complementarity condition forces the activity to be
zero, so the constraint reduces to `x₁ = 0`.

## Source node

`ToZerosBridge` supports:

* [`MOI.VectorOfVariables`](@ref) in
[`ComplementsWithSetType{MOI.Zeros}`](@ref)

## Target nodes

`ToZerosBridge` creates:

* [`MOI.VectorOfVariables`](@ref) in [`MOI.Zeros`](@ref)

"""
struct ToZerosBridge{T} <: MOI.Bridges.Constraint.AbstractBridge
zeros::MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Zeros}
func::MOI.VectorOfVariables
end

function MOI.Bridges.Constraint.bridge_constraint(
::Type{ToZerosBridge{T}},
model::MOI.ModelLike,
func::MOI.VectorOfVariables,
set::ComplementsWithSetType{MOI.Zeros},
) where {T}
@assert set.dimension == 2
x1 = func.variables[1]
ci = MOI.add_constraint(model, MOI.VectorOfVariables([x1]), MOI.Zeros(1))
return ToZerosBridge{T}(ci, func)
end

function MOI.supports_constraint(
::Type{<:ToZerosBridge},
::Type{MOI.VectorOfVariables},
::Type{ComplementsWithSetType{MOI.Zeros}},
)
return true
end

function MOI.Bridges.Constraint.concrete_bridge_type(
::Type{<:ToZerosBridge{T}},
::Type{MOI.VectorOfVariables},
::Type{ComplementsWithSetType{MOI.Zeros}},
) where {T}
return ToZerosBridge{T}
end

# Bridge metadata

function MOI.Bridges.added_constrained_variable_types(::Type{<:ToZerosBridge})
return Tuple{Type}[]
end

function MOI.Bridges.added_constraint_types(::Type{ToZerosBridge{T}}) where {T}
return Tuple{Type,Type}[(MOI.VectorOfVariables, MOI.Zeros)]
end

function MOI.get(
::ToZerosBridge,
::MOI.NumberOfConstraints{MOI.VectorOfVariables,MOI.Zeros},
)::Int64
return 1
end

function MOI.get(
bridge::ToZerosBridge,
::MOI.ListOfConstraintIndices{MOI.VectorOfVariables,MOI.Zeros},
)
return [bridge.zeros]
end

function MOI.get(
::MOI.ModelLike,
::MOI.ConstraintFunction,
bridge::ToZerosBridge,
)
return bridge.func
end

function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, ::ToZerosBridge)
return ComplementsWithSetType{MOI.Zeros}(2)
end

function MOI.delete(model::MOI.ModelLike, bridge::ToZerosBridge)
MOI.delete(model, bridge.zeros)
return
end
4 changes: 2 additions & 2 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,13 @@ function _sos1_bridge_type(
return Bridges.ToSOS1Bridge{T}
end

# VOV-in-Zeros → ToSOS1Bridge (trivial complementarity)
# VOV-in-Zeros → ToZerosBridge (trivial complementarity: activity is zero)
function _sos1_bridge_type(
::Type{T},
::Type{MOI.VectorOfVariables},
::Type{MOI.Zeros},
) where {T}
return Bridges.ToSOS1Bridge{T}
return Bridges.ToZerosBridge{T}
end

# Any non-VOV function → VerticalBridge (create slacks, then re-enter as VOV)
Expand Down
41 changes: 41 additions & 0 deletions test/Bridges/test_ToZerosBridge.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright (c) 2025 François Pacaud, Benoît Legat, and contributors
#
# 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 TestToZerosBridge

using Test

import MathOptComplements
import MathOptInterface as MOI

const M = "TestToZerosBridge.MathOptComplements"

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

function test_ToZerosBridge()
MOI.Bridges.runtests(
MathOptComplements.Bridges.ToZerosBridge,
"""
variables: x, y
[x, y] in $M.ComplementsWithSetType{MOI.Zeros}(2)
""",
"""
variables: x, y
[x] in Zeros(1)
""";
cannot_unbridge = true,
)
return
end

end # TestToZerosBridge

TestToZerosBridge.runtests()
6 changes: 4 additions & 2 deletions test/test_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,15 @@ function test_Optimizer_bridge_dispatch()
MOI.VectorAffineFunction{Float64},
S,
) == MathOptComplements.Bridges.NonlinearBridge{Float64,MOI.Nonnegatives}
# SOS1 path: VectorOfVariables in ComplementsWithSetType{Zeros} → ToSOS1Bridge
# SOS1 path: VectorOfVariables in ComplementsWithSetType{Zeros} →
# ToZerosBridge (the activity is complementary to a fixed/free variable, so
# it is forced to zero).
opt_sos1 = MathOptComplements.Optimizer(
MOI.Bridges.full_bridge_optimizer(HiGHS.Optimizer(), Float64),
)
S_zeros = MathOptComplements.ComplementsWithSetType{MOI.Zeros}
@test MOI.Bridges.bridge_type(opt_sos1, MOI.VectorOfVariables, S_zeros) ==
MathOptComplements.Bridges.ToSOS1Bridge{Float64}
MathOptComplements.Bridges.ToZerosBridge{Float64}
return
end

Expand Down
Loading