Skip to content

Commit 7224166

Browse files
blegatodow
authored andcommitted
Test ConstraintDual for bridges
1 parent 4a2641b commit 7224166

File tree

5 files changed

+87
-7
lines changed

5 files changed

+87
-7
lines changed

docs/src/submodules/Bridges/implementation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ julia> MOI.Bridges.runtests(
7979
""",
8080
)
8181
Test Summary: | Pass Total Time
82-
Bridges.runtests | 29 29 0.0s
82+
Bridges.runtests | 30 30 0.0s
8383
```
8484

8585
There are a number of other useful keyword arguments.
@@ -123,5 +123,5 @@ Subject to:
123123
ScalarAffineFunction{Int64}-in-LessThan{Int64}
124124
(0) - (1) x <= (-1)
125125
Test Summary: | Pass Total Time
126-
Bridges.runtests | 29 29 0.0s
126+
Bridges.runtests | 30 30 0.0s
127127
```

src/Bridges/Bridges.jl

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ julia> MOI.Bridges.runtests(
283283
end,
284284
)
285285
Test Summary: | Pass Total Time
286-
Bridges.runtests | 32 32 0.8s
286+
Bridges.runtests | 33 33 0.8s
287287
```
288288
"""
289289
function runtests(args...; kwargs...)
@@ -293,12 +293,60 @@ 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+
eltype,
311+
model_eltype,
312+
)
313+
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{model_eltype}())
314+
mock = MOI.Utilities.MockOptimizer(inner)
315+
model = _bridged_model(Bridge{eltype}, mock)
316+
input_fn(model)
317+
final_touch(model)
318+
# Should be able to call final_touch multiple times.
319+
final_touch(model)
320+
# If the bridges does not support `ConstraintDualStart`, it probably won't
321+
# support `ConstraintDual` so we skip these tests
322+
list_of_constraints = MOI.get(model, MOI.ListOfConstraintTypesPresent())
323+
attr = MOI.ConstraintDual()
324+
for (F, S) in list_of_constraints
325+
if !MOI.supports(model, attr, MOI.ConstraintIndex{F,S})
326+
# We need all duals for `DualObjectiveValue` fallback
327+
# TODO except the ones with no constants, we could ignore them
328+
return
329+
end
330+
for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
331+
set = MOI.get(model, MOI.ConstraintSet(), ci)
332+
MOI.set(model, MOI.ConstraintDual(), ci, _fake_start(dual, set))
333+
end
334+
end
335+
model_dual =
336+
MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), eltype)
337+
mock_dual =
338+
MOI.Utilities.get_fallback(mock, MOI.DualObjectiveValue(), eltype)
339+
# Need `atol` in case one of them is zero and the other one almost zero
340+
Test.@test model_dual mock_dual atol = 1e-6
341+
end
342+
296343
function _runtests(
297344
Bridge::Type{<:AbstractBridge},
298345
input_fn::Function,
299346
output_fn::Function;
300347
variable_start = 1.2,
301348
constraint_start = 1.2,
349+
dual = constraint_start,
302350
eltype = Float64,
303351
model_eltype = eltype,
304352
print_inner_model::Bool = false,
@@ -403,6 +451,11 @@ function _runtests(
403451
Test.@testset "Test delete" begin # COV_EXCL_LINE
404452
_test_delete(Bridge, model, inner)
405453
end
454+
if !isnothing(dual)
455+
Test.@testset "Test ConstraintDual" begin
456+
_test_dual(Bridge, input_fn; dual, eltype, model_eltype)
457+
end
458+
end
406459
return
407460
end
408461

src/Bridges/Constraint/bridges/SplitHyperRectangleBridge.jl

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,15 +196,28 @@ end
196196

197197
function MOI.supports(
198198
model::MOI.ModelLike,
199-
attr::Union{MOI.ConstraintPrimalStart,MOI.ConstraintDualStart},
199+
attr::Union{
200+
MOI.ConstraintPrimalStart,
201+
MOI.ConstraintDualStart,
202+
MOI.ConstraintDual,
203+
},
200204
::Type{<:SplitHyperRectangleBridge{T,G}},
201205
) where {T,G}
202206
return MOI.supports(model, attr, MOI.ConstraintIndex{G,MOI.Nonnegatives})
203207
end
204208

205-
_get_free_start(bridge, ::MOI.ConstraintDualStart) = bridge.free_dual_start
209+
function _get_free_start(
210+
bridge,
211+
::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
212+
)
213+
return bridge.free_dual_start
214+
end
206215

207-
function _set_free_start(bridge, ::MOI.ConstraintDualStart, value)
216+
function _set_free_start(
217+
bridge,
218+
::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
219+
value,
220+
)
208221
bridge.free_dual_start = value
209222
return
210223
end
@@ -284,7 +297,7 @@ end
284297

285298
function MOI.set(
286299
model::MOI.ModelLike,
287-
attr::MOI.ConstraintDualStart,
300+
attr::Union{MOI.ConstraintDualStart,MOI.ConstraintDual},
288301
bridge::SplitHyperRectangleBridge{T},
289302
values::AbstractVector{T},
290303
) where {T}

src/Utilities/results.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,19 @@ function _constant_minus_bound(constant, lower, upper, dual)
108108
end
109109
end
110110

111+
function _dual_objective_value(
112+
model::MOI.ModelLike,
113+
ci::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction,MOI.ZeroOne},
114+
::Type{T},
115+
result_index::Integer,
116+
) where {T}
117+
constant = MOI.constant(MOI.get(model, MOI.ConstraintFunction(), ci), T)
118+
set = MOI.get(model, MOI.ConstraintSet(), ci)
119+
dual = MOI.get(model, MOI.ConstraintDual(result_index), ci)
120+
constant = _constant_minus_bound(constant, zero(T), one(T), dual)
121+
return set_dot(constant, dual, set)
122+
end
123+
111124
function _dual_objective_value(
112125
model::MOI.ModelLike,
113126
ci::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction,<:MOI.Interval},

test/Bridges/Constraint/ScalarFunctionizeBridge.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ function test_FunctionConversionBridge()
317317
variables: x, y
318318
ScalarNonlinearFunction(1.0 * x * x + 2.0 * x * y + 3.0 * y + 4.0) >= 1.0
319319
""",
320+
dual = nothing, # `get_fallback` ignores the constant `4.0` of the function
320321
)
321322
# VectorAffineFunction -> VectorQuadraticFunction
322323
MOI.Bridges.runtests(

0 commit comments

Comments
 (0)