From e2d10cbafc20a625b987937a72a96a39cb68d81e Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 27 Jan 2026 14:52:55 +1300 Subject: [PATCH 1/2] [Bridges] add SplitComplexIndicatorEqualToBridge --- src/Bridges/Constraint/Constraint.jl | 1 + .../SplitComplexIndicatorEqualToBridge.jl | 233 ++++++++++++++++++ .../SplitComplexIndicatorEqualToBridge.jl | 86 +++++++ 3 files changed, 320 insertions(+) create mode 100644 src/Bridges/Constraint/bridges/SplitComplexIndicatorEqualToBridge.jl create mode 100644 test/Bridges/Constraint/SplitComplexIndicatorEqualToBridge.jl diff --git a/src/Bridges/Constraint/Constraint.jl b/src/Bridges/Constraint/Constraint.jl index 3b195a47f0..f24f3b14b0 100644 --- a/src/Bridges/Constraint/Constraint.jl +++ b/src/Bridges/Constraint/Constraint.jl @@ -127,6 +127,7 @@ function add_all_bridges(model, ::Type{T}) where {T} MOI.Bridges.add_bridge(model, SOS1ToMILPBridge{T}) MOI.Bridges.add_bridge(model, SOS2ToMILPBridge{T}) MOI.Bridges.add_bridge(model, SplitComplexEqualToBridge{T}) + MOI.Bridges.add_bridge(model, SplitComplexIndicatorEqualToBridge{T}) MOI.Bridges.add_bridge(model, SplitComplexZerosBridge{T}) MOI.Bridges.add_bridge(model, SplitHyperRectangleBridge{T}) MOI.Bridges.add_bridge(model, SplitIntervalBridge{T}) diff --git a/src/Bridges/Constraint/bridges/SplitComplexIndicatorEqualToBridge.jl b/src/Bridges/Constraint/bridges/SplitComplexIndicatorEqualToBridge.jl new file mode 100644 index 0000000000..f2de11dcf2 --- /dev/null +++ b/src/Bridges/Constraint/bridges/SplitComplexIndicatorEqualToBridge.jl @@ -0,0 +1,233 @@ +# 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. + +""" + SplitComplexIndicatorEqualToBridge{T,F,G,A} <: Bridges.Constraint.AbstractBridge + +`SplitComplexIndicatorEqualToBridge` implements the following reformulation: + + * ``z \\implies f(x) + g(x) * im = a + b * im`` into ``z \\implies f(x) = a`` + and ``z \\implies g(x) = b`` + +## Source node + +`SplitComplexIndicatorEqualToBridge` supports: + + * `G` in [`MOI.Indicator{A,MOI.EqualTo{Complex{T}}`](@ref) + +where `G` is a function with `Complex` coefficients. + +## Target nodes + +`SplitComplexIndicatorEqualToBridge` creates: + + * `F` in [`MOI.Indicator{A,MOI.EqualTo{T}}`](@ref) + +where `F` is the type of the real/imaginary part of `G`. +""" +struct SplitComplexIndicatorEqualToBridge{ + T, + F<:MOI.Utilities.TypedVectorLike{T}, + G<:MOI.Utilities.TypedVectorLike{Complex{T}}, + A, +} <: AbstractBridge + real_constraint::Union{ + Nothing, + MOI.ConstraintIndex{F,MOI.Indicator{A,MOI.EqualTo{T}}}, + } + imag_constraint::Union{ + Nothing, + MOI.ConstraintIndex{F,MOI.Indicator{A,MOI.EqualTo{T}}}, + } +end + +const SplitComplexIndicatorEqualTo{T,OT<:MOI.ModelLike} = + SingleBridgeOptimizer{SplitComplexIndicatorEqualToBridge{T},OT} + +function _add_constraint_if_nonzero(model, A, f, x, rhs::T) where {T} + if iszero(f) && iszero(rhs) + return nothing + end + g = MOI.Utilities.operate(vcat, T, x, f) + return MOI.add_constraint(model, g, MOI.Indicator{A}(MOI.EqualTo(rhs))) +end + +function bridge_constraint( + ::Type{SplitComplexIndicatorEqualToBridge{T,F,G,A}}, + model::MOI.ModelLike, + func::G, + set::MOI.Indicator{A,MOI.EqualTo{Complex{T}}}, +) where {T,F,G,A} + @assert MOI.output_dimension(func) == 2 + scalars = MOI.Utilities.scalarize(func) + x, f = convert(MOI.VariableIndex, scalars[1]), scalars[2] + rhs = set.set.value + real_ci = _add_constraint_if_nonzero(model, A, real(f), x, real(rhs)) + imag_f = MOI.Utilities.operate(imag, T, f) + imag_ci = _add_constraint_if_nonzero(model, A, imag_f, x, imag(rhs)) + return SplitComplexIndicatorEqualToBridge{T,F,G,A}(real_ci, imag_ci) +end + +# We don't support `MOI.VariableIndex` as it would be a self-loop in the bridge +# graph. +function MOI.supports_constraint( + ::Type{<:SplitComplexIndicatorEqualToBridge{T}}, + ::Type{<:MOI.Utilities.TypedLike{Complex{T}}}, + ::Type{MOI.Indicator{A,MOI.EqualTo{Complex{T}}}}, +) where {T,A} + return true +end + +function MOI.Bridges.added_constrained_variable_types( + ::Type{<:SplitComplexIndicatorEqualToBridge}, +) + return Tuple{Type}[] +end + +function MOI.Bridges.added_constraint_types( + ::Type{SplitComplexIndicatorEqualToBridge{T,F,G,A}}, +) where {T,F,G,A} + return Tuple{Type,Type}[(F, MOI.Indicator{A,MOI.EqualTo{T}})] +end + +function concrete_bridge_type( + ::Type{<:SplitComplexIndicatorEqualToBridge{T}}, + G::Type{<:MOI.Utilities.TypedLike}, + ::Type{MOI.Indicator{A,MOI.EqualTo{Complex{T}}}}, +) where {T,A} + F = MA.promote_operation(imag, G) + return SplitComplexIndicatorEqualToBridge{T,F,G,A} +end + +function MOI.get( + model::MOI.ModelLike, + attr::MOI.ConstraintFunction, + bridge::SplitComplexIndicatorEqualToBridge{T,F,G,A}, +) where {T,F,G,A} + H = MOI.Utilities.scalar_type(G) + h = zero(H) + x = nothing + if bridge.real_constraint !== nothing + f = MOI.get(model, attr, bridge.real_constraint) + f_scalars = MOI.Utilities.scalarize(f) + x = convert(MOI.VariableIndex, f_scalars[1]) + MOI.Utilities.operate!(+, Complex{T}, h, convert(H, f_scalars[2])) + end + if bridge.imag_constraint !== nothing + f = MOI.get(model, attr, bridge.imag_constraint) + f_scalars = MOI.Utilities.scalarize(f) + x = convert(MOI.VariableIndex, f_scalars[1]) + MOI.Utilities.operate!(+, Complex{T}, h, im * f_scalars[2]) + end + return MOI.Utilities.operate(vcat, Complex{T}, x, h) +end + +function MOI.get( + model::MOI.ModelLike, + ::MOI.ConstraintSet, + bridge::SplitComplexIndicatorEqualToBridge{T,F,G,A}, +) where {T,F,G,A} + rhs = zero(T) + zero(T) * im + if bridge.real_constraint !== nothing + set = MOI.get(model, MOI.ConstraintSet(), bridge.real_constraint) + rhs += set.set.value + end + if bridge.imag_constraint !== nothing + set = MOI.get(model, MOI.ConstraintSet(), bridge.imag_constraint) + rhs += set.set.value * im + end + return MOI.Indicator{A}(MOI.EqualTo(rhs)) +end + +function MOI.get( + bridge::SplitComplexIndicatorEqualToBridge{T,F,G,A}, + ::MOI.NumberOfConstraints{F,MOI.Indicator{A,MOI.EqualTo{T}}}, +)::Int64 where {T,F,G,A} + return Int64(bridge.real_constraint !== nothing) + + Int64(bridge.imag_constraint !== nothing) +end + +function MOI.get( + bridge::SplitComplexIndicatorEqualToBridge{T,F,G,A}, + ::MOI.ListOfConstraintIndices{F,MOI.Indicator{A,MOI.EqualTo{T}}}, +) where {T,F,G,A} + list = MOI.ConstraintIndex{F,MOI.Indicator{A,MOI.EqualTo{T}}}[] + if bridge.real_constraint !== nothing + push!(list, bridge.real_constraint) + end + if bridge.imag_constraint !== nothing + push!(list, bridge.imag_constraint) + end + return list +end + +function MOI.delete( + model::MOI.ModelLike, + bridge::SplitComplexIndicatorEqualToBridge, +) + if bridge.real_constraint !== nothing + MOI.delete(model, bridge.real_constraint) + end + if bridge.imag_constraint !== nothing + MOI.delete(model, bridge.imag_constraint) + end + return +end + +function MOI.supports( + model::MOI.ModelLike, + attr::MOI.ConstraintPrimalStart, + ::Type{SplitComplexIndicatorEqualToBridge{T,F,G,A}}, +) where {T,F,G,A} + return MOI.supports( + model, + attr, + MOI.ConstraintIndex{F,MOI.Indicator{A,MOI.EqualTo{T}}}, + ) +end + +function MOI.get( + model::MOI.ModelLike, + attr::Union{MOI.ConstraintPrimal,MOI.ConstraintPrimalStart}, + bridge::SplitComplexIndicatorEqualToBridge{T}, +) where {T} + ret = zeros(Complex{T}, 2) + if bridge.real_constraint !== nothing + value = MOI.get(model, attr, bridge.real_constraint) + if value == nothing + return nothing + end + ret[1] = value[1] + ret[2] += value[2] + end + if bridge.imag_constraint !== nothing + value = MOI.get(model, attr, bridge.imag_constraint) + if value == nothing + return nothing + end + ret[1] = value[1] + ret[2] += value[2] * im + end + return ret +end + +_pass_nothing(f::Function, x::AbstractVector) = [x[1], f(x[2])] +_pass_nothing(::Any, ::Nothing) = nothing + +function MOI.set( + model::MOI.ModelLike, + attr::MOI.ConstraintPrimalStart, + bridge::SplitComplexIndicatorEqualToBridge{T}, + value, +) where {T} + if bridge.real_constraint !== nothing + MOI.set(model, attr, bridge.real_constraint, _pass_nothing(real, value)) + end + if bridge.imag_constraint !== nothing + MOI.set(model, attr, bridge.imag_constraint, _pass_nothing(imag, value)) + end + return +end diff --git a/test/Bridges/Constraint/SplitComplexIndicatorEqualToBridge.jl b/test/Bridges/Constraint/SplitComplexIndicatorEqualToBridge.jl new file mode 100644 index 0000000000..63c33d991a --- /dev/null +++ b/test/Bridges/Constraint/SplitComplexIndicatorEqualToBridge.jl @@ -0,0 +1,86 @@ +# 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 TestConstraintSplitComplexIndicatorEqualTo + +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 + +function test_runtests() + MOI.Bridges.runtests( + MOI.Bridges.Constraint.SplitComplexIndicatorEqualToBridge, + """ + variables: x, z + ::Complex{Float64}: [z, (1.0 + 2.0im) * x] in Indicator{ACTIVATE_ON_ONE}(EqualTo(3.0 + 4.0im)) + z in ZeroOne() + """, + """ + variables: x, z + ::Float64: [z, 1.0 * x] in Indicator{ACTIVATE_ON_ONE}(EqualTo(3.0)) + ::Float64: [z, 2.0 * x] in Indicator{ACTIVATE_ON_ONE}(EqualTo(4.0)) + z in ZeroOne() + """, + ) + MOI.Bridges.runtests( + MOI.Bridges.Constraint.SplitComplexIndicatorEqualToBridge, + """ + variables: x, z + ::Complex{Float64}: [z, (1.0 + 2.0im) * x] in Indicator{ACTIVATE_ON_ZERO}(EqualTo(3.0 + 4.0im)) + z in ZeroOne() + """, + """ + variables: x, z + ::Float64: [z, 1.0 * x] in Indicator{ACTIVATE_ON_ZERO}(EqualTo(3.0)) + ::Float64: [z, 2.0 * x] in Indicator{ACTIVATE_ON_ZERO}(EqualTo(4.0)) + z in ZeroOne() + """, + ) + MOI.Bridges.runtests( + MOI.Bridges.Constraint.SplitComplexIndicatorEqualToBridge, + """ + variables: x, z + ::Complex{Float64}: [z, (0.0 + 2.0im) * x] in Indicator{ACTIVATE_ON_ZERO}(EqualTo(0.0 + 4.0im)) + z in ZeroOne() + """, + """ + variables: x, z + ::Float64: [z, 2.0 * x] in Indicator{ACTIVATE_ON_ZERO}(EqualTo(4.0)) + z in ZeroOne() + """; + constraint_start = [1.0, 0.0 + 1.2im], + ) + MOI.Bridges.runtests( + MOI.Bridges.Constraint.SplitComplexIndicatorEqualToBridge, + """ + variables: x, z + ::Complex{Float64}: [z, (1.0 + 0.0im) * x] in Indicator{ACTIVATE_ON_ZERO}(EqualTo(3.0 + 0.0im)) + z in ZeroOne() + """, + """ + variables: x, z + ::Float64: [z, 1.0 * x] in Indicator{ACTIVATE_ON_ZERO}(EqualTo(3.0)) + z in ZeroOne() + """; + constraint_start = [1.0, 1.2 + 0.0im], + ) + return +end + +end # module + +TestConstraintSplitComplexIndicatorEqualTo.runtests() From c31baf7457807f351af9856fa29a210d29938525 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 27 Jan 2026 15:15:56 +1300 Subject: [PATCH 2/2] Update --- .../bridges/SplitComplexIndicatorEqualToBridge.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Bridges/Constraint/bridges/SplitComplexIndicatorEqualToBridge.jl b/src/Bridges/Constraint/bridges/SplitComplexIndicatorEqualToBridge.jl index f2de11dcf2..cef6ae8be2 100644 --- a/src/Bridges/Constraint/bridges/SplitComplexIndicatorEqualToBridge.jl +++ b/src/Bridges/Constraint/bridges/SplitComplexIndicatorEqualToBridge.jl @@ -5,7 +5,8 @@ # in the LICENSE.md file or at https://opensource.org/licenses/MIT. """ - SplitComplexIndicatorEqualToBridge{T,F,G,A} <: Bridges.Constraint.AbstractBridge + SplitComplexIndicatorEqualToBridge{T,F,G,A} <: + Bridges.Constraint.AbstractBridge `SplitComplexIndicatorEqualToBridge` implements the following reformulation: @@ -16,7 +17,7 @@ `SplitComplexIndicatorEqualToBridge` supports: - * `G` in [`MOI.Indicator{A,MOI.EqualTo{Complex{T}}`](@ref) + * `G` in `MOI.Indicator{A,MOI.EqualTo{Complex{T}}` where `G` is a function with `Complex` coefficients. @@ -24,7 +25,7 @@ where `G` is a function with `Complex` coefficients. `SplitComplexIndicatorEqualToBridge` creates: - * `F` in [`MOI.Indicator{A,MOI.EqualTo{T}}`](@ref) + * `F` in `MOI.Indicator{A,MOI.EqualTo{T}}` where `F` is the type of the real/imaginary part of `G`. """ @@ -71,11 +72,9 @@ function bridge_constraint( return SplitComplexIndicatorEqualToBridge{T,F,G,A}(real_ci, imag_ci) end -# We don't support `MOI.VariableIndex` as it would be a self-loop in the bridge -# graph. function MOI.supports_constraint( ::Type{<:SplitComplexIndicatorEqualToBridge{T}}, - ::Type{<:MOI.Utilities.TypedLike{Complex{T}}}, + ::Type{<:MOI.Utilities.TypedVectorLike{Complex{T}}}, ::Type{MOI.Indicator{A,MOI.EqualTo{Complex{T}}}}, ) where {T,A} return true