@@ -283,7 +283,7 @@ julia> MOI.Bridges.runtests(
283283 end,
284284 )
285285Test Summary: | Pass Total Time
286- Bridges.runtests | 32 32 0.8s
286+ Bridges.runtests | 33 33 0.8s
287287```
288288"""
289289function runtests (args... ; kwargs... )
@@ -293,12 +293,60 @@ function runtests(args...; kwargs...)
293293 return
294294end
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+
296343function _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
407460end
408461
0 commit comments