Skip to content

Commit 7e27018

Browse files
authored
Fix SplitHyperRectangleBridge with all free rows (#2816)
1 parent b3e7bff commit 7e27018

File tree

2 files changed

+102
-7
lines changed

2 files changed

+102
-7
lines changed

src/Bridges/Constraint/bridges/SplitHyperRectangleBridge.jl

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,19 @@
2525
* `G` in [`MOI.Nonnegatives`](@ref)
2626
"""
2727
mutable struct SplitHyperRectangleBridge{T,G,F} <: AbstractBridge
28-
ci::MOI.ConstraintIndex{G,MOI.Nonnegatives}
28+
ci::Union{Nothing,MOI.ConstraintIndex{G,MOI.Nonnegatives}}
2929
set::MOI.HyperRectangle{T}
3030
free_rows::F
31+
free_primal_start::Union{Nothing,Vector{T}}
32+
free_dual_start::Union{Nothing,Vector{T}}
33+
34+
function SplitHyperRectangleBridge{T,G,F}(
35+
ci::Union{Nothing,MOI.ConstraintIndex{G,MOI.Nonnegatives}},
36+
set::MOI.HyperRectangle{T},
37+
free_rows::F,
38+
) where {T,G,F}
39+
return new{T,G,F}(ci, set, free_rows, nothing, nothing)
40+
end
3141
end
3242

3343
const SplitHyperRectangle{T,OT<:MOI.ModelLike} =
@@ -67,6 +77,9 @@ function bridge_constraint(
6777
push!(free_rows, i)
6878
end
6979
end
80+
if length(free_rows) == N
81+
return SplitHyperRectangleBridge{T,G,F}(nothing, s, f)
82+
end
7083
g = MOI.Utilities.vectorize(g_vec[rows_to_keep])
7184
ci = MOI.add_constraint(model, g, MOI.Nonnegatives(MOI.output_dimension(g)))
7285
return SplitHyperRectangleBridge{T,G,F}(ci, s, scalars[free_rows])
@@ -106,6 +119,9 @@ function MOI.get(
106119
::MOI.ConstraintFunction,
107120
bridge::SplitHyperRectangleBridge{T,G,F},
108121
) where {T,G,F}
122+
if bridge.ci === nothing
123+
return bridge.free_rows
124+
end
109125
f = MOI.get(model, MOI.ConstraintFunction(), bridge.ci)
110126
f_s = MOI.Utilities.eachscalar(f)
111127
func = Vector{eltype(f_s)}(undef, MOI.dimension(bridge.set))
@@ -154,22 +170,28 @@ function MOI.get(
154170
end
155171

156172
function MOI.delete(model::MOI.ModelLike, bridge::SplitHyperRectangleBridge)
157-
MOI.delete(model, bridge.ci)
173+
if bridge.ci !== nothing
174+
MOI.delete(model, bridge.ci)
175+
end
158176
return
159177
end
160178

161179
function MOI.get(
162-
::SplitHyperRectangleBridge{T,G},
180+
bridge::SplitHyperRectangleBridge{T,G},
163181
::MOI.NumberOfConstraints{G,MOI.Nonnegatives},
164182
)::Int64 where {T,G}
165-
return 1
183+
return ifelse(bridge.ci === nothing, 0, 1)
166184
end
167185

168186
function MOI.get(
169-
bridge::SplitHyperRectangleBridge{T,G},
187+
bridge::SplitHyperRectangleBridge{T,G,F},
170188
::MOI.ListOfConstraintIndices{G,MOI.Nonnegatives},
171-
) where {T,G}
172-
return [bridge.ci]
189+
) where {T,G,F}
190+
ret = MOI.ConstraintIndex{G,MOI.Nonnegatives}[]
191+
if bridge.ci !== nothing
192+
push!(ret, bridge.ci)
193+
end
194+
return ret
173195
end
174196

175197
function MOI.supports(
@@ -180,12 +202,49 @@ function MOI.supports(
180202
return MOI.supports(model, attr, MOI.ConstraintIndex{G,MOI.Nonnegatives})
181203
end
182204

205+
_get_free_start(bridge, ::MOI.ConstraintDualStart) = bridge.free_dual_start
206+
207+
function _set_free_start(bridge, ::MOI.ConstraintDualStart, value)
208+
bridge.free_dual_start = value
209+
return
210+
end
211+
212+
_get_free_start(bridge, ::MOI.ConstraintPrimalStart) = bridge.free_primal_start
213+
214+
function _set_free_start(bridge, ::MOI.ConstraintPrimalStart, value)
215+
bridge.free_primal_start = value
216+
return
217+
end
218+
219+
# This is a punned overload. We use Union{MOI.ConstraintDual,MOI.ConstraintDualStart}
220+
# in MOI.get, so this hits the ConstraintDual branch. Since no constraints are
221+
# ever added, we just assuem that the dual is `0.0` (this is feasible because)
222+
# the set is really `f(x) in Reals()`, so the dual set is `Zeros()`
223+
function _get_free_start(
224+
bridge::SplitHyperRectangleBridge{T},
225+
::MOI.ConstraintDual,
226+
) where {T}
227+
return zeros(T, MOI.dimension(bridge.set))
228+
end
229+
230+
# The same cannot be said for ConstraintPrimal because we have no mechanism for
231+
# evaluating the primal of the free rows. Throw an error instead.
232+
function _get_free_start(
233+
::SplitHyperRectangleBridge,
234+
attr::MOI.ConstraintPrimal,
235+
)
236+
return throw(MOI.GetAttributeNotAllowed(attr))
237+
end
238+
183239
function MOI.set(
184240
model::MOI.ModelLike,
185241
attr::MOI.ConstraintPrimalStart,
186242
bridge::SplitHyperRectangleBridge{T},
187243
value::AbstractVector{T},
188244
) where {T}
245+
if bridge.ci === nothing
246+
return _set_free_start(bridge, attr, value)
247+
end
189248
new_values = vcat(
190249
T[v - l for (v, l) in zip(value, bridge.set.lower) if isfinite(l)],
191250
T[u - v for (v, u) in zip(value, bridge.set.upper) if isfinite(u)],
@@ -199,6 +258,9 @@ function MOI.get(
199258
attr::Union{MOI.ConstraintPrimal,MOI.ConstraintPrimalStart},
200259
bridge::SplitHyperRectangleBridge{T},
201260
) where {T}
261+
if bridge.ci === nothing
262+
return _get_free_start(bridge, attr)
263+
end
202264
values = MOI.get(model, attr, bridge.ci)
203265
if values === nothing
204266
return nothing
@@ -226,6 +288,9 @@ function MOI.set(
226288
bridge::SplitHyperRectangleBridge{T},
227289
values::AbstractVector{T},
228290
) where {T}
291+
if bridge.ci === nothing
292+
return _set_free_start(bridge, attr, values)
293+
end
229294
set = bridge.set
230295
new_values = vcat(
231296
T[max(T(0), v) for (v, l) in zip(values, set.lower) if isfinite(l)],
@@ -240,6 +305,9 @@ function MOI.get(
240305
attr::Union{MOI.ConstraintDual,MOI.ConstraintDualStart},
241306
bridge::SplitHyperRectangleBridge{T},
242307
) where {T}
308+
if bridge.ci === nothing
309+
return _get_free_start(bridge, attr)
310+
end
243311
values = MOI.get(model, attr, bridge.ci)
244312
if values === nothing
245313
return nothing
@@ -267,6 +335,9 @@ function MOI.set(
267335
bridge::SplitHyperRectangleBridge{T},
268336
::Nothing,
269337
) where {T}
338+
if bridge.ci === nothing
339+
return _set_free_start(bridge, attr, nothing)
340+
end
270341
MOI.set(model, attr, bridge.ci, nothing)
271342
return
272343
end

test/Bridges/Constraint/SplitHyperRectangleBridge.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,30 @@ function test_runtests_free_row()
9999
return
100100
end
101101

102+
function test_runtests_all_free_rows()
103+
MOI.Bridges.runtests(
104+
MOI.Bridges.Constraint.SplitHyperRectangleBridge,
105+
"""
106+
variables: x
107+
[x] in HyperRectangle([-Inf], [Inf])
108+
""",
109+
"""
110+
variables: x
111+
""",
112+
)
113+
inner = MOI.Utilities.Model{Float64}()
114+
model = MOI.Bridges.Constraint.SplitHyperRectangle{Float64}(inner)
115+
x = MOI.add_variable(model)
116+
f = MOI.Utilities.operate(vcat, Float64, 1.0 * x)
117+
c = MOI.add_constraint(model, f, MOI.HyperRectangle([-Inf], [Inf]))
118+
@test MOI.get(model, MOI.ConstraintDual(), c) == [0.0]
119+
@test_throws(
120+
MOI.GetAttributeNotAllowed{MOI.ConstraintPrimal},
121+
MOI.get(model, MOI.ConstraintPrimal(), c)
122+
)
123+
return
124+
end
125+
102126
function test_basic_HyperRectangle()
103127
model = MOI.Bridges.Constraint.SplitHyperRectangle{Float64}(
104128
MOI.Utilities.Model{Float64}(),

0 commit comments

Comments
 (0)