Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add complex EqualTo bridge #11

Merged
merged 2 commits into from
Feb 27, 2022
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
1 change: 1 addition & 0 deletions src/Bridges/Constraint/Constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const MOIB = MOI.Bridges
import ComplexOptInterface
const COI = ComplexOptInterface

include("split_equalto.jl")
include("split_zero.jl")

end
126 changes: 126 additions & 0 deletions src/Bridges/Constraint/split_equalto.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
struct SplitEqualToBridge{
T,
F<:MOI.Utilities.TypedScalarLike{T},
G<:MOI.Utilities.TypedScalarLike{Complex{T}},
} <: MOI.Bridges.Constraint.AbstractBridge
real_constraint::Union{Nothing,MOI.ConstraintIndex{F,MOI.EqualTo{T}}}
imag_constraint::Union{Nothing,MOI.ConstraintIndex{F,MOI.EqualTo{T}}}
end
function _add_constraint_if_nonzero(model, func, set)
if iszero(func) && iszero(MOI.constant(set))
return nothing
else
return MOI.add_constraint(model, func, set)
end
end
function MOI.Bridges.Constraint.bridge_constraint(
::Type{SplitEqualToBridge{T,F,G}},
model::MOI.ModelLike,
func::G,
set::MOI.EqualTo,
) where {T,F,G}
real_func = real(func)
imag_func = MOI.Utilities.operate(imag, T, func)
real_set = MOI.EqualTo(real(MOI.constant(set)))
imag_set = MOI.EqualTo(imag(MOI.constant(set)))
real_constraint = _add_constraint_if_nonzero(model, real_func, real_set)
imag_constraint = _add_constraint_if_nonzero(model, imag_func, imag_set)
return SplitEqualToBridge{T,F,G}(real_constraint, imag_constraint)
end

# We don't support `MOI.VariableIndex` as it would be a self-loop in the bridge graph
function MOI.supports_constraint(
::Type{SplitEqualToBridge{T}},
::Type{<:MOI.Utilities.TypedLike{Complex{T}}},
::Type{<:MOI.EqualTo},
) where {T}
return true
end
MOIB.added_constrained_variable_types(::Type{<:SplitEqualToBridge}) = Tuple{DataType}[]
function MOIB.added_constraint_types(::Type{SplitEqualToBridge{T,F,G}}) where {T,F,G}
return Tuple{DataType,DataType}[(F, MOI.EqualTo{T})]
end
function MOI.Bridges.Constraint.concrete_bridge_type(
::Type{<:SplitEqualToBridge{T}},
G::Type{<:MOI.Utilities.TypedLike},
::Type{<:MOI.EqualTo},
) where {T}
F = MA.promote_operation(imag, G)
return SplitEqualToBridge{T,F,G}
end

# Attributes, Bridge acting as a model
function MOI.get(
bridge::SplitEqualToBridge{T,F},
::MOI.NumberOfConstraints{F,MOI.EqualTo{T}},
) where {T,F}
return !isnothing(bridge.real_constraint) + !isnothing(bridge.imag_constraint)
end
function MOI.get(
bridge::SplitEqualToBridge{T,F},
::MOI.ListOfConstraintIndices{F,MOI.EqualTo{T}},
) where {T,F}
list = MOI.ConstraintIndex{F,MOI.EqualTo{T}}[]
if !isnothing(bridge.real_constraint)
push!(list, bridge.real_constraint)
end
if !isnothing(bridge.imag_constraint)
push!(list, bridge.imag_constraint)
end
return list
end

# Indices
function MOI.delete(model::MOI.ModelLike, bridge::SplitEqualToBridge)
if !isnothing(bridge.real_constraint)
MOI.delete(model, bridge.real_constraint)
end
if !isnothing(bridge.imag_constraint)
MOI.delete(model, bridge.imag_constraint)
end
end

# Attributes, Bridge acting as a constraint
function MOI.supports(
::MOI.ModelLike,
::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart},
::Type{<:SplitEqualToBridge},
)
return true
end
function MOI.get(
model::MOI.ModelLike,
attr::Union{
MOI.ConstraintPrimal,
MOI.ConstraintPrimalStart,
MOI.ConstraintDual,
MOI.ConstraintDualStart,
},
bridge::SplitEqualToBridge{T},
) where {T}
if isnothing(bridge.real_constraint)
real_value = zero(T)
else
real_value = MOI.get(model, attr, bridge.real_constraint)
end
if isnothing(bridge.imag_constraint)
imag_value = zero(T)
else
imag_value = MOI.get(model, attr, bridge.imag_constraint)
end
return real_value + imag_value * im
end
function MOI.set(
model::MOI.ModelLike,
attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart},
bridge::SplitEqualToBridge{T},
value,
) where {T}
if !isnothing(bridge.real_constraint)
MOI.set(model, attr, bridge.real_constraint, real(value))
end
if !isnothing(bridge.imag_constraint)
MOI.set(model, attr, bridge.real_constraint, imag(value))
end
return
end
61 changes: 61 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,43 @@ function hermitian_psd_test(optimizer, config)
@test MOI.get(optimizer, MOI.ConstraintDual(), cx) ≈ zeros(9) atol = atol rtol = rtol
end

function equalto_1_test(optimizer, config)
atol = config.atol
rtol = config.rtol

MOI.empty!(optimizer)
x, cx = MOI.add_constrained_variables(optimizer, MOI.Nonnegatives(2))
func = (1.0 + 2.0im) * x[1] + (1.0 - 1.0im) * x[2]
c = MOI.add_constraint(optimizer, func, MOI.EqualTo(1.0 + 1.0im))
MOI.optimize!(optimizer)
@test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL
@test MOI.get(optimizer, MOI.VariablePrimal(), x) ≈ [2 / 3, 1 / 3] atol = atol rtol =
rtol
@test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) ≈ [2 / 3, 1 / 3] atol = atol rtol =
rtol
@test MOI.get(optimizer, MOI.ConstraintDual(), cx) ≈ zeros(2) atol = atol rtol = rtol
@test MOI.get(optimizer, MOI.ConstraintPrimal(), c) ≈ 1.0 + 1.0im atol = atol rtol =
rtol
@test MOI.get(optimizer, MOI.ConstraintDual(), c) ≈ 0.0 + 0.0im atol = atol rtol = rtol
end

function equalto_2_test(optimizer, config)
atol = config.atol
rtol = config.rtol

MOI.empty!(optimizer)
x, cx = MOI.add_constrained_variables(optimizer, MOI.Nonnegatives(1))
func = (1.0 + 0.0im) * x[1] + 1.0im * x[1] - (1.0 + 0.0im) * x[1]
c = MOI.add_constraint(optimizer, func, MOI.EqualTo(2.0im))
MOI.optimize!(optimizer)
@test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL
@test MOI.get(optimizer, MOI.VariablePrimal(), x) ≈ [2.0] atol = atol rtol = rtol
@test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) ≈ [2.0] atol = atol rtol = rtol
@test MOI.get(optimizer, MOI.ConstraintDual(), cx) ≈ zeros(1) atol = atol rtol = rtol
@test MOI.get(optimizer, MOI.ConstraintPrimal(), c) ≈ 2.0im atol = atol rtol = rtol
@test MOI.get(optimizer, MOI.ConstraintDual(), c) ≈ 0.0 + 0.0im atol = atol rtol = rtol
end

function zero_1_test(optimizer, config)
atol = config.atol
rtol = config.rtol
Expand Down Expand Up @@ -135,6 +172,30 @@ import CSDP
)
projection_test(bridged, config)
hermitian_psd_test(bridged, config)

bridged = MOI.Bridges.LazyBridgeOptimizer(
MOI.Utilities.CachingOptimizer(MOI.Utilities.Model{Float64}(), CSDP.Optimizer()),
)
MOI.Bridges.add_bridge(bridged, COI.Bridges.Constraint.SplitEqualToBridge{Float64})
equalto_1_test(bridged, config)
cis = MOI.get(
bridged.model,
MOI.ListOfConstraintIndices{
MOI.ScalarAffineFunction{Float64},
MOI.EqualTo{Float64},
}(),
)
@test length(cis) == 2
equalto_2_test(bridged, config)
cis = MOI.get(
bridged.model,
MOI.ListOfConstraintIndices{
MOI.ScalarAffineFunction{Float64},
MOI.EqualTo{Float64},
}(),
)
@test length(cis) == 1

bridged = MOI.Bridges.LazyBridgeOptimizer(
MOI.Utilities.CachingOptimizer(MOI.Utilities.Model{Float64}(), CSDP.Optimizer()),
)
Expand Down