@@ -293,12 +293,57 @@ 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+ 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+
296340function _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
407457end
408458
0 commit comments