Skip to content

Commit 39ec57e

Browse files
committed
Test ConstraintDual for bridges
1 parent 6746ec1 commit 39ec57e

File tree

3 files changed

+70
-8
lines changed

3 files changed

+70
-8
lines changed

src/Bridges/Bridges.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,12 +293,57 @@ function runtests(args...; kwargs...)
293293
return
294294
end
295295

296+
# A good way to check that the linear mapping implemented in the setter of
297+
# `ConstraintDual` is the inverse-adjoint of the mapping implemented in the
298+
# constraint transformation is to check `get_fallback` for `DualObjectiveValue`.
299+
# Indeed, it will check that the inner product between the constraint constants
300+
# and the dual is the same before and after the bridge transformations.
301+
# For this test to be enabled, the bridge should implement `supports`
302+
# for `ConstraintDual` and implement `MOI.set` for `ConstraintDual`.
303+
# Typically, this would be achieved using
304+
# `Union{ConstraintDual,ConstraintDualStart}` for `MOI.get`, `MOI.set` and
305+
# `MOI.supports`
306+
function _test_dual(
307+
Bridge::Type{<:AbstractBridge},
308+
input_fn::Function;
309+
dual,
310+
model_eltype,
311+
)
312+
inner = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{model_eltype}())
313+
model = _bridged_model(Bridge{model_eltype}, inner)
314+
input_fn(model)
315+
final_touch(model)
316+
# Should be able to call final_touch multiple times.
317+
final_touch(model)
318+
# If the bridges does not support `ConstraintDualStart`, it probably won't
319+
# support `ConstraintDual` so we skip these tests
320+
list_of_constraints = MOI.get(model, MOI.ListOfConstraintTypesPresent())
321+
attr = MOI.ConstraintDual()
322+
for (F, S) in list_of_constraints
323+
Test.@test MOI.supports(model, attr, MOI.ConstraintIndex{F,S})
324+
if !MOI.supports(model, attr, MOI.ConstraintIndex{F,S})
325+
# We need all duals for `DualObjectiveValue` fallback
326+
# TODO except the ones with no constants, we could ignore them
327+
return
328+
end
329+
for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
330+
set = MOI.get(model, MOI.ConstraintSet(), ci)
331+
MOI.set(model, MOI.ConstraintDual(), ci, _fake_start(dual, set))
332+
end
333+
end
334+
model_dual = MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), model_eltype)
335+
inner_dual = MOI.Utilities.get_fallback(inner, MOI.DualObjectiveValue(), model_eltype)
336+
# Need `atol` in case one of them is zero and the other one almost zero
337+
Test.@test model_dual inner_dual atol = 1e-6
338+
end
339+
296340
function _runtests(
297341
Bridge::Type{<:AbstractBridge},
298342
input_fn::Function,
299343
output_fn::Function;
300344
variable_start = 1.2,
301345
constraint_start = 1.2,
346+
dual = constraint_start,
302347
eltype = Float64,
303348
model_eltype = eltype,
304349
print_inner_model::Bool = false,
@@ -403,6 +448,11 @@ function _runtests(
403448
Test.@testset "Test delete" begin # COV_EXCL_LINE
404449
_test_delete(Bridge, model, inner)
405450
end
451+
if !isnothing(dual)
452+
Test.@testset "Test ConstraintDual" begin
453+
_test_dual(Bridge, input_fn; dual, model_eltype)
454+
end
455+
end
406456
return
407457
end
408458

src/Bridges/Constraint/bridges/SplitHyperRectangleBridge.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ end
174174

175175
function MOI.supports(
176176
model::MOI.ModelLike,
177-
attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart},
177+
attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart,MOI.ConstraintDual},
178178
::Type{<:SplitHyperRectangleBridge{T,G}},
179179
) where {T,G}
180180
return MOI.supports(model, attr, MOI.ConstraintIndex{G,MOI.Nonnegatives})
@@ -222,7 +222,7 @@ end
222222

223223
function MOI.set(
224224
model::MOI.ModelLike,
225-
attr::MOI.ConstraintDualStart,
225+
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
226226
bridge::SplitHyperRectangleBridge{T},
227227
values::AbstractVector{T},
228228
) where {T}

src/Utilities/results.jl

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,26 @@ function _dual_objective_value(
119119
set = MOI.get(model, MOI.ConstraintSet(), ci)
120120
dual = MOI.get(model, MOI.ConstraintDual(result_index), ci)
121121
constant = map(eachindex(func_constant)) do i
122-
return func_constant[i] - if dual[i] < zero(dual[i])
123-
# The dual is negative so it is in the dual of the MOI.LessThan cone
124-
# hence the upper bound of the Interval set is tight
125-
set.upper[i]
122+
constant = func_constant[i]
123+
if isfinite(set.upper[i])
124+
if isfinite(set.lower[i])
125+
if dual[i] < zero(dual[i])
126+
# The dual is negative so it is in the dual of the MOI.LessThan cone
127+
# hence the upper bound of the Interval set is tight
128+
constant -= set.upper[i]
129+
else
130+
# the lower bound is tight
131+
constant -= set.lower[i]
132+
end
133+
else
134+
constant -= set.upper[i]
135+
end
126136
else
127-
# the lower bound is tight
128-
set.lower[i]
137+
if isfinite(set.lower[i])
138+
constant -= set.lower[i]
139+
end
129140
end
141+
return constant
130142
end
131143
return set_dot(constant, dual, set)
132144
end

0 commit comments

Comments
 (0)