Skip to content

Commit 344a671

Browse files
authored
Merge pull request #1005 from JuliaOpt/bl/splitzero
Add support for EqualTo and Zero in Split bridge
2 parents 68c2882 + 8b00bbf commit 344a671

File tree

5 files changed

+182
-80
lines changed

5 files changed

+182
-80
lines changed

src/Bridges/Constraint/interval.jl

Lines changed: 121 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,83 @@
1+
_lower_set(set::MOI.Interval) = MOI.GreaterThan(set.lower)
2+
_upper_set(set::MOI.Interval) = MOI.LessThan(set.upper)
3+
_lower_set(set::MOI.EqualTo) = MOI.GreaterThan(set.value)
4+
_upper_set(set::MOI.EqualTo) = MOI.LessThan(set.value)
5+
_lower_set(set::MOI.Zeros) = MOI.Nonnegatives(set.dimension)
6+
_upper_set(set::MOI.Zeros) = MOI.Nonpositives(set.dimension)
7+
18
"""
2-
SplitIntervalBridge{T}
9+
SplitIntervalBridge{T, F, S, LS, US}
10+
11+
The `SplitIntervalBridge` splits a `F`-in-`S` constraint into a `F`-in-`LS` and
12+
a `F`-in-`US` constraint where we have either:
13+
* `F = MOI.Interval{T}`, `LS = MOI.GreaterThan{T}` and `US = MOI.LessThan{T}`,
14+
* `F = MOI.EqualTo{T}`, `LS = MOI.GreaterThan{T}` and `US = MOI.LessThan{T}`, or
15+
* `F = MOI.Zeros`, `LS = MOI.Nonnegatives` and `US = MOI.Nonpositives`.
316
4-
The `SplitIntervalBridge` splits a constraint ``l ≤ ⟨a, x⟩ + α ≤ u`` into the constraints ``⟨a, x⟩ + α ≥ l`` and ``⟨a, x⟩ + α ≤ u``.
17+
For instance, if `F` is `MOI.ScalarAffineFunction` and `S` is `MOI.Interval`,
18+
it transforms the constraint ``l ≤ ⟨a, x⟩ + α ≤ u`` into the constraints
19+
``⟨a, x⟩ + α ≥ l`` and ``⟨a, x⟩ + α ≤ u``.
520
"""
6-
struct SplitIntervalBridge{T, F<:MOI.AbstractScalarFunction} <: AbstractBridge
7-
lower::CI{F, MOI.GreaterThan{T}}
8-
upper::CI{F, MOI.LessThan{T}}
9-
end
10-
function bridge_constraint(::Type{SplitIntervalBridge{T, F}}, model, f::F,
11-
s::MOI.Interval{T}) where {T, F}
12-
lower = MOI.add_constraint(model, f, MOI.GreaterThan(s.lower))
13-
upper = MOI.add_constraint(model, f, MOI.LessThan(s.upper))
14-
return SplitIntervalBridge{T, F}(lower, upper)
21+
struct SplitIntervalBridge{T, F<:MOI.AbstractFunction, S<:MOI.AbstractSet,
22+
LS<:MOI.AbstractSet, US<:MOI.AbstractSet} <: AbstractBridge
23+
lower::CI{F, LS}
24+
upper::CI{F, US}
25+
end
26+
function bridge_constraint(
27+
::Type{SplitIntervalBridge{T, F, S, LS, US}}, model::MOI.ModelLike, f::F,
28+
set::S) where {T, F, S, LS, US}
29+
lower = MOI.add_constraint(model, f, _lower_set(set))
30+
upper = MOI.add_constraint(model, f, _upper_set(set))
31+
return SplitIntervalBridge{T, F, S, LS, US}(lower, upper)
1532
end
1633

17-
MOI.supports_constraint(::Type{SplitIntervalBridge{T}}, ::Type{<:MOI.AbstractScalarFunction}, ::Type{MOI.Interval{T}}) where T = true
34+
function MOI.supports_constraint(
35+
::Type{SplitIntervalBridge{T}}, ::Type{<:MOI.AbstractScalarFunction},
36+
::Type{<:Union{MOI.Interval{T}, MOI.EqualTo{T}}}) where T
37+
return true
38+
end
39+
function MOI.supports_constraint(
40+
::Type{SplitIntervalBridge{T}}, ::Type{<:MOI.AbstractVectorFunction},
41+
::Type{MOI.Zeros}) where T
42+
return true
43+
end
1844
MOIB.added_constrained_variable_types(::Type{<:SplitIntervalBridge}) = Tuple{DataType}[]
19-
function MOIB.added_constraint_types(::Type{SplitIntervalBridge{T, F}}) where {T, F}
20-
return [(F, MOI.GreaterThan{T}), (F, MOI.LessThan{T})]
45+
function MOIB.added_constraint_types(::Type{SplitIntervalBridge{T, F, S, LS, US}}) where {T, F, S, LS, US}
46+
return [(F, LS), (F, US)]
47+
end
48+
function concrete_bridge_type(
49+
::Type{<:SplitIntervalBridge}, F::Type{<:MOI.AbstractScalarFunction},
50+
S::Type{<:Union{MOI.Interval{T}, MOI.EqualTo{T}}}) where T
51+
return SplitIntervalBridge{T, F, S, MOI.GreaterThan{T}, MOI.LessThan{T}}
2152
end
22-
function concrete_bridge_type(::Type{<:SplitIntervalBridge},
23-
F::Type{<:MOI.AbstractScalarFunction},
24-
::Type{MOI.Interval{T}}) where T
25-
return SplitIntervalBridge{T, F}
53+
function concrete_bridge_type(
54+
::Type{<:SplitIntervalBridge{T}}, F::Type{<:MOI.AbstractVectorFunction},
55+
::Type{MOI.Zeros}) where T
56+
return SplitIntervalBridge{T, F, MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives}
2657
end
2758

2859
# Attributes, Bridge acting as a model
29-
MOI.get(b::SplitIntervalBridge{T, F}, ::MOI.NumberOfConstraints{F, MOI.LessThan{T}}) where {T, F} = 1
30-
MOI.get(b::SplitIntervalBridge{T, F}, ::MOI.NumberOfConstraints{F, MOI.GreaterThan{T}}) where {T, F} = 1
31-
MOI.get(b::SplitIntervalBridge{T, F}, ::MOI.ListOfConstraintIndices{F, MOI.GreaterThan{T}}) where {T, F} = [b.lower]
32-
MOI.get(b::SplitIntervalBridge{T, F}, ::MOI.ListOfConstraintIndices{F, MOI.LessThan{T}}) where {T, F} = [b.upper]
60+
function MOI.get(::SplitIntervalBridge{T, F, S, LS},
61+
::MOI.NumberOfConstraints{F, LS}) where {T, F, S, LS}
62+
return 1
63+
end
64+
function MOI.get(::SplitIntervalBridge{T, F, S, LS, US},
65+
::MOI.NumberOfConstraints{F, US}) where {T, F, S, LS, US}
66+
return 1
67+
end
68+
function MOI.get(bridge::SplitIntervalBridge{T, F, S, LS},
69+
::MOI.ListOfConstraintIndices{F, LS}) where {T, F, S, LS}
70+
return [bridge.lower]
71+
end
72+
function MOI.get(bridge::SplitIntervalBridge{T, F, S, LS, US},
73+
::MOI.ListOfConstraintIndices{F, US}) where {T, F, S, LS, US}
74+
return [bridge.upper]
75+
end
3376

3477
# Indices
35-
function MOI.delete(model::MOI.ModelLike, c::SplitIntervalBridge)
36-
MOI.delete(model, c.lower)
37-
MOI.delete(model, c.upper)
78+
function MOI.delete(model::MOI.ModelLike, bridge::SplitIntervalBridge)
79+
MOI.delete(model, bridge.lower)
80+
MOI.delete(model, bridge.upper)
3881
end
3982

4083
# Attributes, Bridge acting as a constraint
@@ -50,33 +93,45 @@ function MOI.get(model::MOI.ModelLike, attr::Union{MOI.ConstraintPrimal, MOI.Con
5093
# lower and upper should give the same value
5194
return MOI.get(model, attr, bridge.lower)
5295
end
53-
function MOI.set(model::MOI.ModelLike, a::MOI.ConstraintPrimalStart,
96+
function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintPrimalStart,
5497
bridge::SplitIntervalBridge, value)
55-
MOI.set(model, a, bridge.lower, value)
56-
MOI.set(model, a, bridge.upper, value)
57-
end
98+
MOI.set(model, attr, bridge.lower, value)
99+
MOI.set(model, attr, bridge.upper, value)
100+
end
101+
# The map is:
102+
# x ∈ S <=> [1 1]' * x ∈ LS × US
103+
# So the adjoint map is
104+
# [1 1] * y ∈ S* <=> y ∈ (LS × US)*
105+
# where [1 1] * y = y[1] + y[2]
106+
# so we can just sum the dual values.
58107
function MOI.get(model::MOI.ModelLike, attr::Union{MOI.ConstraintDual, MOI.ConstraintDualStart},
59108
bridge::SplitIntervalBridge)
60-
# Should be nonnegative
61-
lower_dual = MOI.get(model, attr, bridge.lower)
62-
# Should be nonpositive
63-
upper_dual = MOI.get(model, attr, bridge.upper)
64-
return lower_dual > -upper_dual ? lower_dual : upper_dual
109+
return MOI.get(model, attr, bridge.lower) + MOI.get(model, attr, bridge.upper)
65110
end
66-
function MOI.set(model::MOI.ModelLike, a::MOI.ConstraintDualStart,
67-
bridge::SplitIntervalBridge, value)
111+
function _split_dual_start(value)
68112
if value < 0
69-
MOI.set(model, a, bridge.lower, 0.0)
70-
MOI.set(model, a, bridge.upper, value)
113+
return zero(value), value
71114
else
72-
MOI.set(model, a, bridge.lower, value)
73-
MOI.set(model, a, bridge.upper, 0.0)
115+
return value, zero(value)
116+
end
117+
end
118+
function _split_dual_start(value::Vector)
119+
lower = similar(value)
120+
upper = similar(value)
121+
for i in eachindex(value)
122+
lower[i], upper[i] = _split_dual_start(value[i])
74123
end
75124
end
125+
function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintDualStart,
126+
bridge::SplitIntervalBridge{T}, value) where T
127+
lower, upper = _split_dual_start(value)
128+
MOI.set(model, attr, bridge.lower, lower)
129+
MOI.set(model, attr, bridge.upper, upper)
130+
end
76131

77-
function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintBasisStatus, c::SplitIntervalBridge)
78-
lower_stat = MOI.get(model, MOI.ConstraintBasisStatus(), c.lower)
79-
upper_stat = MOI.get(model, MOI.ConstraintBasisStatus(), c.upper)
132+
function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintBasisStatus, bridge::SplitIntervalBridge)
133+
lower_stat = MOI.get(model, MOI.ConstraintBasisStatus(), bridge.lower)
134+
upper_stat = MOI.get(model, MOI.ConstraintBasisStatus(), bridge.upper)
80135
if lower_stat == MOI.NONBASIC_AT_LOWER
81136
@warn("GreaterThan constraints should not have basis status:" *
82137
" NONBASIC_AT_LOWER, instead use NONBASIC.")
@@ -99,28 +154,37 @@ function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintBasisStatus, c::SplitInte
99154
end
100155

101156
# Constraints
102-
function MOI.modify(model::MOI.ModelLike, c::SplitIntervalBridge, change::MOI.AbstractFunctionModification)
103-
MOI.modify(model, c.lower, change)
104-
MOI.modify(model, c.upper, change)
157+
function MOI.modify(model::MOI.ModelLike, bridge::SplitIntervalBridge, change::MOI.AbstractFunctionModification)
158+
MOI.modify(model, bridge.lower, change)
159+
MOI.modify(model, bridge.upper, change)
105160
end
106161

107162
function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintFunction,
108-
c::SplitIntervalBridge{T, F}, func::F) where {T, F}
109-
MOI.set(model, MOI.ConstraintFunction(), c.lower, func)
110-
MOI.set(model, MOI.ConstraintFunction(), c.upper, func)
163+
bridge::SplitIntervalBridge{T, F}, func::F) where {T, F}
164+
MOI.set(model, MOI.ConstraintFunction(), bridge.lower, func)
165+
MOI.set(model, MOI.ConstraintFunction(), bridge.upper, func)
111166
end
112167

113-
function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet, c::SplitIntervalBridge, change::MOI.Interval)
114-
MOI.set(model, MOI.ConstraintSet(), c.lower, MOI.GreaterThan(change.lower))
115-
MOI.set(model, MOI.ConstraintSet(), c.upper, MOI.LessThan(change.upper))
168+
function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet,
169+
bridge::SplitIntervalBridge{T, F, S}, change::S) where {T, F, S}
170+
MOI.set(model, MOI.ConstraintSet(), bridge.lower, _lower_set(change))
171+
MOI.set(model, MOI.ConstraintSet(), bridge.upper, _upper_set(change))
116172
end
117173

118174
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction,
119-
b::SplitIntervalBridge)
120-
return MOI.get(model, attr, b.lower)
175+
bridge::SplitIntervalBridge)
176+
return MOI.get(model, attr, bridge.lower)
177+
end
178+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet,
179+
bridge::SplitIntervalBridge{T, F, MOI.Interval{T}}) where {T, F}
180+
return MOI.Interval(MOI.get(model, attr, bridge.lower).lower,
181+
MOI.get(model, attr, bridge.upper).upper)
182+
end
183+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet,
184+
bridge::SplitIntervalBridge{T, F, MOI.EqualTo{T}}) where {T, F}
185+
return MOI.EqualTo(MOI.get(model, attr, bridge.lower).lower)
121186
end
122187
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet,
123-
b::SplitIntervalBridge)
124-
return MOI.Interval(MOI.get(model, attr, b.lower).lower,
125-
MOI.get(model, attr, b.upper).upper)
188+
bridge::SplitIntervalBridge{T, F, MOI.Zeros}) where {T, F}
189+
return MOI.Zeros(MOI.get(model, attr, bridge.lower).dimension)
126190
end

src/Bridges/graph.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ end
5858
function Base.show(io::IO, graph::Graph)
5959
print(io, "Bridge graph with ")
6060
print(io, length(graph.variable_best), " variable nodes, ")
61-
print(io, length(graph.constraint_best), " variable nodes and ")
61+
print(io, length(graph.constraint_best), " constraint nodes and ")
6262
print(io, length(graph.objective_best), " objective nodes.")
6363
end
6464
function variable_nodes(graph::Graph)

test/Bridges/Constraint/interval.jl

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,24 @@ mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}()))
1212
config = MOIT.TestConfig()
1313
config_with_basis = MOIT.TestConfig(basis = true)
1414

15+
@testset "Split" begin
16+
T = Float64
17+
bridged_mock = MOIB.Constraint.SplitInterval{T}(mock)
18+
MOIT.basic_constraint_tests(bridged_mock, config, include=[
19+
(MOI.SingleVariable, MOI.Interval{T}),
20+
(MOI.ScalarAffineFunction{T}, MOI.Interval{T}),
21+
(MOI.ScalarQuadraticFunction{T}, MOI.Interval{T}),
22+
(MOI.SingleVariable, MOI.EqualTo{T}),
23+
(MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}),
24+
(MOI.ScalarQuadraticFunction{T}, MOI.EqualTo{T}),
25+
(MOI.VectorOfVariables, MOI.Zeros),
26+
(MOI.VectorAffineFunction{T}, MOI.Zeros),
27+
(MOI.VectorQuadraticFunction{T}, MOI.Zeros)
28+
])
29+
end
30+
1531
@testset "Interval" begin
1632
bridged_mock = MOIB.Constraint.SplitInterval{Float64}(mock)
17-
MOIT.basic_constraint_tests(bridged_mock, config,
18-
include=[(MOI.SingleVariable,
19-
MOI.Interval{Float64}),
20-
(MOI.ScalarAffineFunction{Float64},
21-
MOI.Interval{Float64}),
22-
(MOI.ScalarQuadraticFunction{Float64},
23-
MOI.Interval{Float64})])
2433
MOIU.set_mock_optimize!(mock,
2534
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [5.0, 5.0], con_basis =
2635
[(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [MOI.BASIC],
@@ -94,3 +103,31 @@ config_with_basis = MOIT.TestConfig(basis = true)
94103
test_delete_bridge(bridged_mock, ci, 2, ((MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, 0),
95104
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, 0)))
96105
end
106+
@testset "EqualTo{$T}" for T in [Float64, Int]
107+
bridged_mock = MOIB.Constraint.SplitInterval{T}(mock)
108+
MOIU.set_mock_optimize!(mock,
109+
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [one(T), one(T)],
110+
(MOI.ScalarAffineFunction{T}, MOI.GreaterThan{T}) => zeros(T, 2),
111+
(MOI.ScalarAffineFunction{T}, MOI.LessThan{T}) => zeros(T, 1)))
112+
MOIT.linear13test(bridged_mock, MOIT.TestConfig{T}())
113+
114+
ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}}()))
115+
116+
test_delete_bridge(bridged_mock, ci, 2, (
117+
(MOI.ScalarAffineFunction{T}, MOI.GreaterThan{T}, 1),
118+
(MOI.ScalarAffineFunction{T}, MOI.LessThan{T}, 0)))
119+
end
120+
@testset "Zeros" begin
121+
bridged_mock = MOIB.Constraint.SplitInterval{Float64}(mock)
122+
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock,
123+
[1.0, 0.0, 2.0],
124+
(MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => [[0.0, 0.0]],
125+
(MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => [[-3, -1]])
126+
MOIT.lin1vtest(bridged_mock, config)
127+
128+
ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Zeros}()))
129+
130+
test_delete_bridge(bridged_mock, ci, 3, (
131+
(MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives, 0),
132+
(MOI.VectorAffineFunction{Float64}, MOI.Nonpositives, 0)))
133+
end

test/Bridges/bridge_optimizer.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ bridged_mock = MOIB.Constraint.LessToGreater{Float64}(MOIB.Constraint.SplitInter
143143
@testset "Unsupported constraint attribute" begin
144144
attr = MOIT.UnknownConstraintAttribute()
145145
err = ArgumentError(
146-
"Bridge of type `MathOptInterface.Bridges.Constraint.SplitIntervalBridge{Float64,MathOptInterface.SingleVariable}` " *
146+
"Bridge of type `$MOI.Bridges.Constraint.SplitIntervalBridge{Float64,$MOI.SingleVariable,$MOI.Interval{Float64},$MOI.GreaterThan{Float64},$MOI.LessThan{Float64}}` " *
147147
"does not support accessing the attribute `$attr`.")
148148
x = MOI.add_variable(bridged_mock)
149149
ci = MOI.add_constraint(bridged_mock, MOI.SingleVariable(x),
@@ -228,9 +228,9 @@ end
228228

229229
@testset "Show" begin
230230
@test sprint(show, bridged_mock) == raw"""
231-
MOIB.Constraint.SingleBridgeOptimizer{MOIB.Constraint.LessToGreaterBridge{Float64,F,G} where G<:MOI.AbstractScalarFunction where F<:MOI.AbstractScalarFunction,MOIB.Constraint.SingleBridgeOptimizer{MOIB.Constraint.SplitIntervalBridge{Float64,F} where F<:MOI.AbstractScalarFunction,MOIU.MockOptimizer{NoIntervalModel{Float64}}}}
231+
MOIB.Constraint.SingleBridgeOptimizer{MOIB.Constraint.LessToGreaterBridge{Float64,F,G} where G<:MOI.AbstractScalarFunction where F<:MOI.AbstractScalarFunction,MOIB.Constraint.SingleBridgeOptimizer{MOIB.Constraint.SplitIntervalBridge{Float64,F,S,LS,US} where US<:MOI.AbstractSet where LS<:MOI.AbstractSet where S<:MOI.AbstractSet where F<:MOI.AbstractFunction,MOIU.MockOptimizer{NoIntervalModel{Float64}}}}
232232
with 1 constraint bridge
233-
with inner model MOIB.Constraint.SingleBridgeOptimizer{MOIB.Constraint.SplitIntervalBridge{Float64,F} where F<:MOI.AbstractScalarFunction,MOIU.MockOptimizer{NoIntervalModel{Float64}}}
233+
with inner model MOIB.Constraint.SingleBridgeOptimizer{MOIB.Constraint.SplitIntervalBridge{Float64,F,S,LS,US} where US<:MOI.AbstractSet where LS<:MOI.AbstractSet where S<:MOI.AbstractSet where F<:MOI.AbstractFunction,MOIU.MockOptimizer{NoIntervalModel{Float64}}}
234234
with 0 constraint bridges
235235
with inner model MOIU.MockOptimizer{NoIntervalModel{Float64}}"""
236236
end

0 commit comments

Comments
 (0)