From 7f19224d249aa8b297a947e7210e1129f8132e6d Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 22 Nov 2021 12:16:16 +1300 Subject: [PATCH 1/5] [Utilities] use ConstraintPrimal fallback in CachingOptimizer --- src/Utilities/cachingoptimizer.jl | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index 05678a4f8b..f5ab75163a 100644 --- a/src/Utilities/cachingoptimizer.jl +++ b/src/Utilities/cachingoptimizer.jl @@ -853,6 +853,7 @@ function MOI.get( return MOI.get(model.model_cache, attr, index) end end + function MOI.get( model::CachingOptimizer, attr::Union{MOI.AbstractVariableAttribute,MOI.AbstractConstraintAttribute}, @@ -878,6 +879,39 @@ function MOI.get( end end +### +### MOI.ConstraintPrimal +### + +# ConstraintPrimal is slightly unique for CachingOptimizer because if the solver +# doesn't support the attribute directly, we can use the fallback to query the +# function from the cache and the variable value from the optimizer. + +_mapped_indices(map, index::MOI.ConstraintIndex) = map[index] +_mapped_indices(map, indices::Vector) = _mapped_indices(Ref(map), indices) + +function MOI.get( + model::CachingOptimizer, + attr::MOI.ConstraintPrimal, + index::Union{Vector{<:MOI.ConstraintIndex},MOI.ConstraintIndex}, +) + if state(model) == NO_OPTIMIZER + error( + "Cannot query $(attr) from caching optimizer because no " * + "optimizer is attached.", + ) + end + try + return MOI.get( + model.optimizer, + attr, + _mapped_indices(model.model_to_optimizer_map, index), + ) + catch + return get_fallback(model, attr, index) + end +end + ##### ##### Names ##### From 3c0c9919fb52994b1ac137f05cd354b3604eecb1 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 22 Nov 2021 13:18:30 +1300 Subject: [PATCH 2/5] Add tests --- src/Utilities/cachingoptimizer.jl | 29 +++++++++++++++--- test/Utilities/cachingoptimizer.jl | 49 ++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index f5ab75163a..e002d590da 100644 --- a/src/Utilities/cachingoptimizer.jl +++ b/src/Utilities/cachingoptimizer.jl @@ -887,13 +887,10 @@ end # doesn't support the attribute directly, we can use the fallback to query the # function from the cache and the variable value from the optimizer. -_mapped_indices(map, index::MOI.ConstraintIndex) = map[index] -_mapped_indices(map, indices::Vector) = _mapped_indices(Ref(map), indices) - function MOI.get( model::CachingOptimizer, attr::MOI.ConstraintPrimal, - index::Union{Vector{<:MOI.ConstraintIndex},MOI.ConstraintIndex}, + index::MOI.ConstraintIndex, ) if state(model) == NO_OPTIMIZER error( @@ -905,13 +902,35 @@ function MOI.get( return MOI.get( model.optimizer, attr, - _mapped_indices(model.model_to_optimizer_map, index), + model.model_to_optimizer_map[index], ) catch return get_fallback(model, attr, index) end end +function MOI.get( + model::CachingOptimizer, + attr::MOI.ConstraintPrimal, + indices::Vector{<:MOI.ConstraintIndex} +) + if state(model) == NO_OPTIMIZER + error( + "Cannot query $(attr) from caching optimizer because no " * + "optimizer is attached.", + ) + end + try + return MOI.get( + model.optimizer, + attr, + [model.model_to_optimizer_map[i] for i in indices], + ) + catch + return [get_fallback(model, attr, i) for i in indices] + end +end + ##### ##### Names ##### diff --git a/test/Utilities/cachingoptimizer.jl b/test/Utilities/cachingoptimizer.jl index 20c5787b2b..e6f3cf9226 100644 --- a/test/Utilities/cachingoptimizer.jl +++ b/test/Utilities/cachingoptimizer.jl @@ -797,6 +797,55 @@ function test_copy_to_and_optimize!() return end +### +### Test ConstraintPrimal fallback in CachingOptimizer. +### +### It's a bit complicated because we need an optimizer that errors on +### ConstraintPrimal. This is the minimal set of methods needed to test the +### fallback. +### + +struct _ConstraintPrimal1310 <: MOI.AbstractOptimizer end + +MOI.is_empty(::_ConstraintPrimal1310) = true + +MOI.get(::_ConstraintPrimal1310, ::MOI.ListOfModelAttributesSet) = [] + +MOI.get(::_ConstraintPrimal1310, ::MOI.PrimalStatus) = MOI.FEASIBLE_POINT + +function MOI.optimize!(::_ConstraintPrimal1310, model::MOI.ModelLike) + index_map = MOI.IndexMap() + for x in MOI.get(model, MOI.ListOfVariableIndices()) + index_map[x] = x + end + for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent()) + for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) + index_map[ci] = ci + end + end + return index_map, false +end + +function MOI.get( + ::_ConstraintPrimal1310, + ::MOI.VariablePrimal, + ::MOI.VariableIndex, +) + return 1.2 +end + +function test_ConstraintPrimal_fallback() + model = MOI.Utilities.CachingOptimizer( + MOI.Utilities.Model{Float64}(), + _ConstraintPrimal1310(), + ) + x = MOI.add_variable(model) + c = MOI.add_constraint(model, x, MOI.GreaterThan(1.0)) + MOI.optimize!(model) + @test MOI.get(model, MOI.ConstraintPrimal(), c) == 1.2 + @test MOI.get(model, MOI.ConstraintPrimal(), [c]) == [1.2] +end + end # module TestCachingOptimizer.runtests() From 3d523230fffc070e5dfa7aa439618dbede2c76aa Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 22 Nov 2021 14:55:20 +1300 Subject: [PATCH 3/5] Update src/Utilities/cachingoptimizer.jl --- src/Utilities/cachingoptimizer.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index e002d590da..0606fe6444 100644 --- a/src/Utilities/cachingoptimizer.jl +++ b/src/Utilities/cachingoptimizer.jl @@ -912,7 +912,7 @@ end function MOI.get( model::CachingOptimizer, attr::MOI.ConstraintPrimal, - indices::Vector{<:MOI.ConstraintIndex} + indices::Vector{<:MOI.ConstraintIndex}, ) if state(model) == NO_OPTIMIZER error( From cf07c5bc43f53d7a8e518ec43457a00e771ee7cc Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 22 Nov 2021 20:07:00 +1300 Subject: [PATCH 4/5] Update cachingoptimizer.jl --- test/Utilities/cachingoptimizer.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/Utilities/cachingoptimizer.jl b/test/Utilities/cachingoptimizer.jl index e6f3cf9226..62d2db8648 100644 --- a/test/Utilities/cachingoptimizer.jl +++ b/test/Utilities/cachingoptimizer.jl @@ -841,6 +841,14 @@ function test_ConstraintPrimal_fallback() ) x = MOI.add_variable(model) c = MOI.add_constraint(model, x, MOI.GreaterThan(1.0)) + @test_throws( + ErrorException, + MOI.get(model, MOI.ConstraintPrimal(), c), + ) + @test_throws( + ErrorException, + MOI.get(model, MOI.ConstraintPrimal(), [c]), + ) MOI.optimize!(model) @test MOI.get(model, MOI.ConstraintPrimal(), c) == 1.2 @test MOI.get(model, MOI.ConstraintPrimal(), [c]) == [1.2] From 5654b991fc3dfd797150b713797398697e00bf36 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 23 Nov 2021 08:44:46 +1300 Subject: [PATCH 5/5] Update cachingoptimizer.jl --- test/Utilities/cachingoptimizer.jl | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/test/Utilities/cachingoptimizer.jl b/test/Utilities/cachingoptimizer.jl index 62d2db8648..2243cf8e61 100644 --- a/test/Utilities/cachingoptimizer.jl +++ b/test/Utilities/cachingoptimizer.jl @@ -841,17 +841,22 @@ function test_ConstraintPrimal_fallback() ) x = MOI.add_variable(model) c = MOI.add_constraint(model, x, MOI.GreaterThan(1.0)) - @test_throws( - ErrorException, - MOI.get(model, MOI.ConstraintPrimal(), c), - ) - @test_throws( - ErrorException, - MOI.get(model, MOI.ConstraintPrimal(), [c]), - ) MOI.optimize!(model) @test MOI.get(model, MOI.ConstraintPrimal(), c) == 1.2 @test MOI.get(model, MOI.ConstraintPrimal(), [c]) == [1.2] + return +end + +function test_ConstraintPrimal_fallback_error() + model = MOI.Utilities.CachingOptimizer( + MOI.Utilities.Model{Float64}(), + MOI.Utilities.AUTOMATIC, + ) + x = MOI.add_variable(model) + c = MOI.add_constraint(model, x, MOI.GreaterThan(1.0)) + @test_throws(ErrorException, MOI.get(model, MOI.ConstraintPrimal(), c)) + @test_throws(ErrorException, MOI.get(model, MOI.ConstraintPrimal(), [c])) + return end end # module