From 195870f8c0c9118ae86d9c22df6f2b77bb7baf06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 10 May 2025 10:41:16 +0200 Subject: [PATCH 1/4] Fix get_fallback of DualObjectiveValue with HyperRectangle --- src/Utilities/results.jl | 39 +++++++++++++++++++++++--------- test/Utilities/results.jl | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 test/Utilities/results.jl diff --git a/src/Utilities/results.jl b/src/Utilities/results.jl index 981135ab02..219858b091 100644 --- a/src/Utilities/results.jl +++ b/src/Utilities/results.jl @@ -108,6 +108,28 @@ function _dual_objective_value( return set_dot(constant, dual, set) end +function _dual_objective_value( + model::MOI.ModelLike, + ci::MOI.ConstraintIndex{<:MOI.AbstractVectorFunction,<:MOI.HyperRectangle}, + ::Type{T}, + result_index::Integer, +) where {T} + func_constant = MOI.constant(MOI.get(model, MOI.ConstraintFunction(), ci), T) + set = MOI.get(model, MOI.ConstraintSet(), ci) + dual = MOI.get(model, MOI.ConstraintDual(result_index), ci) + constant = map(eachindex(func_constant)) do i + func_constant[i] - if dual[i] < zero(dual[i]) + # The dual is negative so it is in the dual of the MOI.LessThan cone + # hence the upper bound of the Interval set is tight + set.upper[i] + else + # the lower bound is tight + set.lower[i] + end + end + return set_dot(constant, dual, set) +end + function _dual_objective_value( model::MOI.ModelLike, ::Type{F}, @@ -116,23 +138,18 @@ function _dual_objective_value( result_index::Integer, ) where {T,F<:MOI.AbstractFunction,S<:MOI.AbstractSet} value = zero(T) + if F == variable_function_type(S) && !_has_constant(S) + return value # Shortcut + end for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) value += _dual_objective_value(model, ci, T, result_index) end return value end -function _dual_objective_value( - ::MOI.ModelLike, - ::Type{MOI.VectorOfVariables}, - ::Type{<:MOI.AbstractVectorSet}, - ::Type{T}, - ::Integer, -) where {T} - # No constant in the function nor set so no contribution to the dual - # objective value. - return zero(T) -end +_has_constant(::Type{<:MOI.AbstractScalarSet}) = true +_has_constant(::Type{<:MOI.AbstractVectorSet}) = false +_has_constant(::Type{<:MOI.HyperRectangle}) = true """ get_fallback( diff --git a/test/Utilities/results.jl b/test/Utilities/results.jl new file mode 100644 index 0000000000..91c3bf882a --- /dev/null +++ b/test/Utilities/results.jl @@ -0,0 +1,47 @@ +# Copyright (c) 2017: Miles Lubin and contributors +# Copyright (c) 2017: Google Inc. +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestResults + +using Test + +import MathOptInterface as MOI + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name) $T" for T in [Int, Float64] + getfield(@__MODULE__, name)(T) + end + end + end + return +end + +function test_hyperrectangle(T) + model = MOI.Utilities.MockOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}()), + T, + ) + x = MOI.add_variables(model, 2) + c1 = MOI.add_constraint( + model, + MOI.VectorOfVariables(x), + MOI.HyperRectangle(T[3, -7], T[5, -2]), + ) + c2 = MOI.add_constraint( + model, + MOI.Utilities.vectorize(x .+ T[11, 13]), + MOI.HyperRectangle(T[-T(6), -T(4)], [T(3), T(2)]), + ) + MOI.set(model, MOI.ConstraintDual(), c1, T[4, -3]) + MOI.set(model, MOI.ConstraintDual(), c2, T[-2, 5]) + @test -53 == @inferred MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), T) +end + +end + +TestResults.runtests() From 964d1af6bfae184015bcc73f2d5c24adf1395b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 10 May 2025 11:08:38 +0200 Subject: [PATCH 2/4] Fix format --- src/Utilities/results.jl | 5 +++-- test/Utilities/results.jl | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Utilities/results.jl b/src/Utilities/results.jl index 219858b091..151debdce4 100644 --- a/src/Utilities/results.jl +++ b/src/Utilities/results.jl @@ -114,11 +114,12 @@ function _dual_objective_value( ::Type{T}, result_index::Integer, ) where {T} - func_constant = MOI.constant(MOI.get(model, MOI.ConstraintFunction(), ci), T) + func_constant = + MOI.constant(MOI.get(model, MOI.ConstraintFunction(), ci), T) set = MOI.get(model, MOI.ConstraintSet(), ci) dual = MOI.get(model, MOI.ConstraintDual(result_index), ci) constant = map(eachindex(func_constant)) do i - func_constant[i] - if dual[i] < zero(dual[i]) + return func_constant[i] - if dual[i] < zero(dual[i]) # The dual is negative so it is in the dual of the MOI.LessThan cone # hence the upper bound of the Interval set is tight set.upper[i] diff --git a/test/Utilities/results.jl b/test/Utilities/results.jl index 91c3bf882a..5d659ec459 100644 --- a/test/Utilities/results.jl +++ b/test/Utilities/results.jl @@ -39,7 +39,11 @@ function test_hyperrectangle(T) ) MOI.set(model, MOI.ConstraintDual(), c1, T[4, -3]) MOI.set(model, MOI.ConstraintDual(), c2, T[-2, 5]) - @test -53 == @inferred MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), T) + @test -53 == @inferred MOI.Utilities.get_fallback( + model, + MOI.DualObjectiveValue(), + T, + ) end end From 0f831a79eae1d673ac0235710ab741937f8febd9 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Sun, 11 May 2025 09:49:45 +1200 Subject: [PATCH 3/4] Update results.jl --- test/Utilities/results.jl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/Utilities/results.jl b/test/Utilities/results.jl index 5d659ec459..94dd11f0db 100644 --- a/test/Utilities/results.jl +++ b/test/Utilities/results.jl @@ -13,15 +13,19 @@ import MathOptInterface as MOI function runtests() for name in names(@__MODULE__; all = true) if startswith("$(name)", "test_") - @testset "$(name) $T" for T in [Int, Float64] - getfield(@__MODULE__, name)(T) + @testset "$(name)" begin + getfield(@__MODULE__, name)() end end end return end -function test_hyperrectangle(T) +test_hyperrectangle_Int = _test_hyperrectangle(Int) + +test_hyperrectangle_Float64 = _test_hyperrectangle(Float64) + +function _test_hyperrectangle(T) model = MOI.Utilities.MockOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}()), T, @@ -44,8 +48,9 @@ function test_hyperrectangle(T) MOI.DualObjectiveValue(), T, ) + return end -end +end # module TestResults TestResults.runtests() From 7c20f52a3459be6e7b1b2c722bbea232002c9162 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Sun, 11 May 2025 09:50:03 +1200 Subject: [PATCH 4/4] Update results.jl --- test/Utilities/results.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Utilities/results.jl b/test/Utilities/results.jl index 94dd11f0db..5286604f7d 100644 --- a/test/Utilities/results.jl +++ b/test/Utilities/results.jl @@ -21,9 +21,9 @@ function runtests() return end -test_hyperrectangle_Int = _test_hyperrectangle(Int) +test_hyperrectangle_Int() = _test_hyperrectangle(Int) -test_hyperrectangle_Float64 = _test_hyperrectangle(Float64) +test_hyperrectangle_Float64() = _test_hyperrectangle(Float64) function _test_hyperrectangle(T) model = MOI.Utilities.MockOptimizer(