diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index 05678a4f8b..0606fe6444 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,58 @@ 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. + +function MOI.get( + model::CachingOptimizer, + attr::MOI.ConstraintPrimal, + index::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[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..2243cf8e61 100644 --- a/test/Utilities/cachingoptimizer.jl +++ b/test/Utilities/cachingoptimizer.jl @@ -797,6 +797,68 @@ 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] + 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 TestCachingOptimizer.runtests()