Skip to content

Commit f82d87b

Browse files
authored
Add complex EqualTo bridge (#11)
* Add complex EqualTo bridge * Fix format
1 parent b3b600d commit f82d87b

File tree

3 files changed

+188
-0
lines changed

3 files changed

+188
-0
lines changed

src/Bridges/Constraint/Constraint.jl

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const MOIB = MOI.Bridges
1111
import ComplexOptInterface
1212
const COI = ComplexOptInterface
1313

14+
include("split_equalto.jl")
1415
include("split_zero.jl")
1516

1617
end
+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
struct SplitEqualToBridge{
2+
T,
3+
F<:MOI.Utilities.TypedScalarLike{T},
4+
G<:MOI.Utilities.TypedScalarLike{Complex{T}},
5+
} <: MOI.Bridges.Constraint.AbstractBridge
6+
real_constraint::Union{Nothing,MOI.ConstraintIndex{F,MOI.EqualTo{T}}}
7+
imag_constraint::Union{Nothing,MOI.ConstraintIndex{F,MOI.EqualTo{T}}}
8+
end
9+
function _add_constraint_if_nonzero(model, func, set)
10+
if iszero(func) && iszero(MOI.constant(set))
11+
return nothing
12+
else
13+
return MOI.add_constraint(model, func, set)
14+
end
15+
end
16+
function MOI.Bridges.Constraint.bridge_constraint(
17+
::Type{SplitEqualToBridge{T,F,G}},
18+
model::MOI.ModelLike,
19+
func::G,
20+
set::MOI.EqualTo,
21+
) where {T,F,G}
22+
real_func = real(func)
23+
imag_func = MOI.Utilities.operate(imag, T, func)
24+
real_set = MOI.EqualTo(real(MOI.constant(set)))
25+
imag_set = MOI.EqualTo(imag(MOI.constant(set)))
26+
real_constraint = _add_constraint_if_nonzero(model, real_func, real_set)
27+
imag_constraint = _add_constraint_if_nonzero(model, imag_func, imag_set)
28+
return SplitEqualToBridge{T,F,G}(real_constraint, imag_constraint)
29+
end
30+
31+
# We don't support `MOI.VariableIndex` as it would be a self-loop in the bridge graph
32+
function MOI.supports_constraint(
33+
::Type{SplitEqualToBridge{T}},
34+
::Type{<:MOI.Utilities.TypedLike{Complex{T}}},
35+
::Type{<:MOI.EqualTo},
36+
) where {T}
37+
return true
38+
end
39+
MOIB.added_constrained_variable_types(::Type{<:SplitEqualToBridge}) = Tuple{DataType}[]
40+
function MOIB.added_constraint_types(::Type{SplitEqualToBridge{T,F,G}}) where {T,F,G}
41+
return Tuple{DataType,DataType}[(F, MOI.EqualTo{T})]
42+
end
43+
function MOI.Bridges.Constraint.concrete_bridge_type(
44+
::Type{<:SplitEqualToBridge{T}},
45+
G::Type{<:MOI.Utilities.TypedLike},
46+
::Type{<:MOI.EqualTo},
47+
) where {T}
48+
F = MA.promote_operation(imag, G)
49+
return SplitEqualToBridge{T,F,G}
50+
end
51+
52+
# Attributes, Bridge acting as a model
53+
function MOI.get(
54+
bridge::SplitEqualToBridge{T,F},
55+
::MOI.NumberOfConstraints{F,MOI.EqualTo{T}},
56+
) where {T,F}
57+
return !isnothing(bridge.real_constraint) + !isnothing(bridge.imag_constraint)
58+
end
59+
function MOI.get(
60+
bridge::SplitEqualToBridge{T,F},
61+
::MOI.ListOfConstraintIndices{F,MOI.EqualTo{T}},
62+
) where {T,F}
63+
list = MOI.ConstraintIndex{F,MOI.EqualTo{T}}[]
64+
if !isnothing(bridge.real_constraint)
65+
push!(list, bridge.real_constraint)
66+
end
67+
if !isnothing(bridge.imag_constraint)
68+
push!(list, bridge.imag_constraint)
69+
end
70+
return list
71+
end
72+
73+
# Indices
74+
function MOI.delete(model::MOI.ModelLike, bridge::SplitEqualToBridge)
75+
if !isnothing(bridge.real_constraint)
76+
MOI.delete(model, bridge.real_constraint)
77+
end
78+
if !isnothing(bridge.imag_constraint)
79+
MOI.delete(model, bridge.imag_constraint)
80+
end
81+
end
82+
83+
# Attributes, Bridge acting as a constraint
84+
function MOI.supports(
85+
::MOI.ModelLike,
86+
::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart},
87+
::Type{<:SplitEqualToBridge},
88+
)
89+
return true
90+
end
91+
function MOI.get(
92+
model::MOI.ModelLike,
93+
attr::Union{
94+
MOI.ConstraintPrimal,
95+
MOI.ConstraintPrimalStart,
96+
MOI.ConstraintDual,
97+
MOI.ConstraintDualStart,
98+
},
99+
bridge::SplitEqualToBridge{T},
100+
) where {T}
101+
if isnothing(bridge.real_constraint)
102+
real_value = zero(T)
103+
else
104+
real_value = MOI.get(model, attr, bridge.real_constraint)
105+
end
106+
if isnothing(bridge.imag_constraint)
107+
imag_value = zero(T)
108+
else
109+
imag_value = MOI.get(model, attr, bridge.imag_constraint)
110+
end
111+
return real_value + imag_value * im
112+
end
113+
function MOI.set(
114+
model::MOI.ModelLike,
115+
attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart},
116+
bridge::SplitEqualToBridge{T},
117+
value,
118+
) where {T}
119+
if !isnothing(bridge.real_constraint)
120+
MOI.set(model, attr, bridge.real_constraint, real(value))
121+
end
122+
if !isnothing(bridge.imag_constraint)
123+
MOI.set(model, attr, bridge.real_constraint, imag(value))
124+
end
125+
return
126+
end

test/runtests.jl

+61
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,43 @@ function hermitian_psd_test(optimizer, config)
7777
@test MOI.get(optimizer, MOI.ConstraintDual(), cx) zeros(9) atol = atol rtol = rtol
7878
end
7979

80+
function equalto_1_test(optimizer, config)
81+
atol = config.atol
82+
rtol = config.rtol
83+
84+
MOI.empty!(optimizer)
85+
x, cx = MOI.add_constrained_variables(optimizer, MOI.Nonnegatives(2))
86+
func = (1.0 + 2.0im) * x[1] + (1.0 - 1.0im) * x[2]
87+
c = MOI.add_constraint(optimizer, func, MOI.EqualTo(1.0 + 1.0im))
88+
MOI.optimize!(optimizer)
89+
@test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL
90+
@test MOI.get(optimizer, MOI.VariablePrimal(), x) [2 / 3, 1 / 3] atol = atol rtol =
91+
rtol
92+
@test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) [2 / 3, 1 / 3] atol = atol rtol =
93+
rtol
94+
@test MOI.get(optimizer, MOI.ConstraintDual(), cx) zeros(2) atol = atol rtol = rtol
95+
@test MOI.get(optimizer, MOI.ConstraintPrimal(), c) 1.0 + 1.0im atol = atol rtol =
96+
rtol
97+
@test MOI.get(optimizer, MOI.ConstraintDual(), c) 0.0 + 0.0im atol = atol rtol = rtol
98+
end
99+
100+
function equalto_2_test(optimizer, config)
101+
atol = config.atol
102+
rtol = config.rtol
103+
104+
MOI.empty!(optimizer)
105+
x, cx = MOI.add_constrained_variables(optimizer, MOI.Nonnegatives(1))
106+
func = (1.0 + 0.0im) * x[1] + 1.0im * x[1] - (1.0 + 0.0im) * x[1]
107+
c = MOI.add_constraint(optimizer, func, MOI.EqualTo(2.0im))
108+
MOI.optimize!(optimizer)
109+
@test MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL
110+
@test MOI.get(optimizer, MOI.VariablePrimal(), x) [2.0] atol = atol rtol = rtol
111+
@test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) [2.0] atol = atol rtol = rtol
112+
@test MOI.get(optimizer, MOI.ConstraintDual(), cx) zeros(1) atol = atol rtol = rtol
113+
@test MOI.get(optimizer, MOI.ConstraintPrimal(), c) 2.0im atol = atol rtol = rtol
114+
@test MOI.get(optimizer, MOI.ConstraintDual(), c) 0.0 + 0.0im atol = atol rtol = rtol
115+
end
116+
80117
function zero_1_test(optimizer, config)
81118
atol = config.atol
82119
rtol = config.rtol
@@ -135,6 +172,30 @@ import CSDP
135172
)
136173
projection_test(bridged, config)
137174
hermitian_psd_test(bridged, config)
175+
176+
bridged = MOI.Bridges.LazyBridgeOptimizer(
177+
MOI.Utilities.CachingOptimizer(MOI.Utilities.Model{Float64}(), CSDP.Optimizer()),
178+
)
179+
MOI.Bridges.add_bridge(bridged, COI.Bridges.Constraint.SplitEqualToBridge{Float64})
180+
equalto_1_test(bridged, config)
181+
cis = MOI.get(
182+
bridged.model,
183+
MOI.ListOfConstraintIndices{
184+
MOI.ScalarAffineFunction{Float64},
185+
MOI.EqualTo{Float64},
186+
}(),
187+
)
188+
@test length(cis) == 2
189+
equalto_2_test(bridged, config)
190+
cis = MOI.get(
191+
bridged.model,
192+
MOI.ListOfConstraintIndices{
193+
MOI.ScalarAffineFunction{Float64},
194+
MOI.EqualTo{Float64},
195+
}(),
196+
)
197+
@test length(cis) == 1
198+
138199
bridged = MOI.Bridges.LazyBridgeOptimizer(
139200
MOI.Utilities.CachingOptimizer(MOI.Utilities.Model{Float64}(), CSDP.Optimizer()),
140201
)

0 commit comments

Comments
 (0)