diff --git a/src/Test/test_linear.jl b/src/Test/test_linear.jl index 4f2857b1f2..8ae45372d0 100644 --- a/src/Test/test_linear.jl +++ b/src/Test/test_linear.jl @@ -452,7 +452,11 @@ function setup_test( T[0, 0, 1], (MOI.ScalarAffineFunction{T}, MOI.LessThan{T}) => T[-2], ), - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, T[-1, 0, 2]), + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + T[-1, 0, 2], + (MOI.ScalarAffineFunction{T}, MOI.LessThan{T}) => T[-2], + ), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, T[1, 0, 0]), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, T[2, 0, 0]), (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, T[0, 2, 0]), diff --git a/src/Utilities/results.jl b/src/Utilities/results.jl index 65d3a7cfb9..4bc810f3d6 100644 --- a/src/Utilities/results.jl +++ b/src/Utilities/results.jl @@ -70,6 +70,10 @@ function get_fallback( ::Type{T}, )::T where {T} MOI.check_result_index_bounds(model, attr) + status = MOI.get(model, MOI.DualStatus(attr.result_index)) + if status == MOI.NO_SOLUTION + throw(MOI.GetAttributeNotAllowed(attr, "No dual solution is available")) + end value = zero(T) # sum will not work if there are zero constraints for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent()) value += _dual_objective_value(model, F, S, T, attr.result_index)::T @@ -77,7 +81,7 @@ function get_fallback( if MOI.get(model, MOI.ObjectiveSense()) != MOI.MAX_SENSE value = -value end - if !is_ray(MOI.get(model, MOI.DualStatus())) + if !is_ray(status) # The objective constant should not be present in rays F = MOI.get(model, MOI.ObjectiveFunctionType()) f = MOI.get(model, MOI.ObjectiveFunction{F}()) diff --git a/test/Utilities/test_mockoptimizer.jl b/test/Utilities/test_mockoptimizer.jl index c2550d3a12..1d297f72b6 100644 --- a/test/Utilities/test_mockoptimizer.jl +++ b/test/Utilities/test_mockoptimizer.jl @@ -92,11 +92,12 @@ function test_optimizer_solve_with_result() ), MOI.get(optimizer, MOI.ObjectiveValue(2)) ) + MOI.set(optimizer, MOI.DualStatus(1), MOI.FEASIBLE_POINT) @test_throws( ErrorException( "No mock dual is set for constraint `$c1` at result index `1`.", ), - MOI.get(optimizer, MOI.DualObjectiveValue()) + MOI.get(optimizer, MOI.DualObjectiveValue(1)) ) @test MOI.get(optimizer, MOI.DualObjectiveValue(2)) == 5.9 @test MOI.get(optimizer, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT diff --git a/test/Utilities/test_results.jl b/test/Utilities/test_results.jl index 12ca4c8793..43d0b3a476 100644 --- a/test/Utilities/test_results.jl +++ b/test/Utilities/test_results.jl @@ -43,6 +43,7 @@ function _test_hyperrectangle(T) ) MOI.set(model, MOI.ConstraintDual(), c1, T[4, -3]) MOI.set(model, MOI.ConstraintDual(), c2, T[-2, 5]) + MOI.set(model, MOI.DualStatus(), MOI.FEASIBLE_POINT) @test -53 == @inferred MOI.Utilities.get_fallback( model, MOI.DualObjectiveValue(), @@ -93,6 +94,7 @@ function test_dual_objective_value_open_interval_Interval_variable_index() ] MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) MOI.set.(model, MOI.ConstraintDual(), c, dual) + MOI.set(model, MOI.DualStatus(), MOI.FEASIBLE_POINT) d = MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), Float64) @test isapprox(d, obj) MOI.set.(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) @@ -142,6 +144,7 @@ function test_dual_objective_value_open_interval_Interval() ] MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) MOI.set.(model, MOI.ConstraintDual(), c, dual) + MOI.set(model, MOI.DualStatus(), MOI.FEASIBLE_POINT) d = MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), Float64) @test isapprox(d, obj) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) @@ -193,6 +196,7 @@ function test_dual_objective_value_open_interval_Hyperrectangle_variable_index() ] MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) MOI.set(model, MOI.ConstraintDual(), c, dual) + MOI.set(model, MOI.DualStatus(), MOI.FEASIBLE_POINT) d = MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), Float64) @test isapprox(d, obj) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) @@ -242,6 +246,7 @@ function test_dual_objective_value_open_interval_Hyperrectangle() ] MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) MOI.set(model, MOI.ConstraintDual(), c, dual) + MOI.set(model, MOI.DualStatus(), MOI.FEASIBLE_POINT) d = MOI.Utilities.get_fallback(model, MOI.DualObjectiveValue(), Float64) @test isapprox(d, obj) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) @@ -251,6 +256,41 @@ function test_dual_objective_value_open_interval_Hyperrectangle() return end +function test_get_fallback_DualObjectiveValue() + T = Float64 + 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)]), + ) + attr = MOI.DualObjectiveValue() + @test_throws( + MOI.GetAttributeNotAllowed(attr, "No dual solution is available"), + MOI.Utilities.get_fallback(model, attr, T), + ) + MOI.set(model, MOI.DualStatus(), MOI.FEASIBLE_POINT) + MOI.set(model, MOI.ConstraintDual(), c1, T[4, -3]) + MOI.set(model, MOI.ConstraintDual(), c2, T[-2, 5]) + @test MOI.Utilities.get_fallback(model, attr, T) == -53 + MOI.set(model, MOI.ResultCount(), 2) + attr = MOI.DualObjectiveValue(2) + @test_throws( + MOI.GetAttributeNotAllowed(attr, "No dual solution is available"), + MOI.Utilities.get_fallback(model, attr, T), + ) + return +end + end # module TestResults TestResults.runtests()