From d60bfc27b8ed31827a2f5e1609d868094d9f5b06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 21 Dec 2023 13:34:06 +0100 Subject: [PATCH 1/5] Enable fallback for ConstraintDual of variable indices --- src/Utilities/cachingoptimizer.jl | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index 672900c906..f8e68de644 100644 --- a/src/Utilities/cachingoptimizer.jl +++ b/src/Utilities/cachingoptimizer.jl @@ -999,6 +999,49 @@ function MOI.get( end end +function MOI.get( + model::CachingOptimizer, + attr::MOI.ConstraintDual, + index::MOI.ConstraintIndex{<:Union{MOI.VariableIndex,MOI.VectorOfVariables}}, +) + _throw_if_get_attribute_not_allowed(model, attr; needs_optimizer_map = true) + try + return MOI.get( + model.optimizer, + attr, + model.model_to_optimizer_map[index], + ) + catch err + # Thrown if .optimizer doesn't support attr + if !(err isa MOI.GetAttributeNotAllowed) + rethrow(err) + end + return get_fallback(model, attr, index) + end +end + +function MOI.get( + model::CachingOptimizer, + attr::MOI.ConstraintDual, + indices::Vector{<:MOI.ConstraintIndex{<:Union{MOI.VariableIndex,MOI.VectorOfVariables}}}, +) + _throw_if_get_attribute_not_allowed(model, attr; needs_optimizer_map = true) + try + return MOI.get( + model.optimizer, + attr, + [model.model_to_optimizer_map[i] for i in indices], + ) + catch err + # Thrown if .optimizer doesn't support attr + if !(err isa MOI.GetAttributeNotAllowed) + rethrow(err) + end + return [get_fallback(model, attr, i) for i in indices] + end +end + + ##### ##### Names ##### From 8a84e31df2c97d5995883fb81cc4aa4a8b3e9f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 21 Dec 2023 13:40:23 +0100 Subject: [PATCH 2/5] Refactor for less copy-paste --- src/Utilities/cachingoptimizer.jl | 105 ++++++++++++------------------ 1 file changed, 40 insertions(+), 65 deletions(-) diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index f8e68de644..70c74b9cc4 100644 --- a/src/Utilities/cachingoptimizer.jl +++ b/src/Utilities/cachingoptimizer.jl @@ -915,6 +915,8 @@ function MOI.get( return MOI.get(model.optimizer, attr) end +_has_fallback(::MOI.AnyAttribute, ::Type{<:MOI.Index}) = false + function MOI.get( model::CachingOptimizer, attr::Union{MOI.AbstractVariableAttribute,MOI.AbstractConstraintAttribute}, @@ -924,87 +926,62 @@ function MOI.get( return MOI.get(model.model_cache, attr, index) end _throw_if_get_attribute_not_allowed(model, attr; needs_optimizer_map = true) - value = MOI.get( - model.optimizer, - attr, - model.model_to_optimizer_map[index], - )::MOI.attribute_value_type(attr) - return map_indices(model.optimizer_to_model_map, attr, value) + if _has_fallback(attr, typeof(index)) + return _caching_get_fallback(model, attr, index) + else + value = MOI.get( + model.optimizer, + attr, + model.model_to_optimizer_map[index], + )::MOI.attribute_value_type(attr) + return map_indices(model.optimizer_to_model_map, attr, value) + end end function MOI.get( model::CachingOptimizer, attr::Union{MOI.AbstractVariableAttribute,MOI.AbstractConstraintAttribute}, - indices::Vector{<:MOI.Index}, -) + indices::Vector{I}, +) where {I<:MOI.Index} if !MOI.is_set_by_optimize(attr) return MOI.get(model.model_cache, attr, indices) end _throw_if_get_attribute_not_allowed(model, attr; needs_optimizer_map = true) - value = MOI.get( - model.optimizer, - attr, - map(Base.Fix1(getindex, model.model_to_optimizer_map), indices), - )::Vector{<:MOI.attribute_value_type(attr)} - return map_indices(model.optimizer_to_model_map, attr, value) + if _has_fallback(attr, I) + return _caching_get_fallback(model, attr, indices) + else + value = MOI.get( + model.optimizer, + attr, + map(Base.Fix1(getindex, model.model_to_optimizer_map), indices), + )::Vector{<:MOI.attribute_value_type(attr)} + return map_indices(model.optimizer_to_model_map, attr, value) + end end ### -### MOI.ConstraintPrimal +### MOI.ConstraintPrimal and MOI.ConstraintDual ### -# ConstraintPrimal is slightly unique for CachingOptimizer because if the solver +# `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. +# The `ConstraintDual` of a `VariableIndex` or `VectorOfVariables` can +# also be computed from the `ConstraintDual` of the other constraints. -function MOI.get( - model::CachingOptimizer, - attr::MOI.ConstraintPrimal, - index::MOI.ConstraintIndex, -) - _throw_if_get_attribute_not_allowed(model, attr; needs_optimizer_map = true) - try - return MOI.get( - model.optimizer, - attr, - model.model_to_optimizer_map[index], - ) - catch err - # Thrown if .optimizer doesn't support attr - if !(err isa MOI.GetAttributeNotAllowed) - rethrow(err) - end - return get_fallback(model, attr, index) - end +_has_fallback(::MOI.ConstraintPrimal, ::Type{<:MOI.ConstraintIndex}) = true +function _has_fallback( + ::MOI.ConstraintDual, + ::Type{<:MOI.ConstraintIndex{F}}, +) where {F<:Union{MOI.VariableIndex,MOI.VectorOfVariables}} + return true end -function MOI.get( +function _caching_get_fallback( model::CachingOptimizer, - attr::MOI.ConstraintPrimal, - indices::Vector{<:MOI.ConstraintIndex}, -) - _throw_if_get_attribute_not_allowed(model, attr; needs_optimizer_map = true) - try - return MOI.get( - model.optimizer, - attr, - [model.model_to_optimizer_map[i] for i in indices], - ) - catch err - # Thrown if .optimizer doesn't support attr - if !(err isa MOI.GetAttributeNotAllowed) - rethrow(err) - end - return [get_fallback(model, attr, i) for i in indices] - end -end - -function MOI.get( - model::CachingOptimizer, - attr::MOI.ConstraintDual, - index::MOI.ConstraintIndex{<:Union{MOI.VariableIndex,MOI.VectorOfVariables}}, + attr::MOI.AbstractConstraintAttribute, + index::MOI.ConstraintIndex, ) - _throw_if_get_attribute_not_allowed(model, attr; needs_optimizer_map = true) try return MOI.get( model.optimizer, @@ -1020,12 +997,11 @@ function MOI.get( end end -function MOI.get( +function _caching_get_fallback( model::CachingOptimizer, - attr::MOI.ConstraintDual, - indices::Vector{<:MOI.ConstraintIndex{<:Union{MOI.VariableIndex,MOI.VectorOfVariables}}}, + attr::MOI.AbstractConstraintAttribute, + indices::Vector{<:MOI.ConstraintIndex}, ) - _throw_if_get_attribute_not_allowed(model, attr; needs_optimizer_map = true) try return MOI.get( model.optimizer, @@ -1041,7 +1017,6 @@ function MOI.get( end end - ##### ##### Names ##### From 2dd2a052cab6e0adfc750ffb37d64c867739475b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 21 Dec 2023 13:56:16 +0100 Subject: [PATCH 3/5] Add tests --- test/Utilities/cachingoptimizer.jl | 53 +++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/test/Utilities/cachingoptimizer.jl b/test/Utilities/cachingoptimizer.jl index 534168cfb5..3173819a54 100644 --- a/test/Utilities/cachingoptimizer.jl +++ b/test/Utilities/cachingoptimizer.jl @@ -851,13 +851,13 @@ function MOI.get( return 1.2 end -function MOI.get( - ::_GetFallbackModel1310, - ::MOI.ConstraintDual, - ::MOI.ConstraintIndex, -) - return 1.2 -end +#function MOI.get( +# ::_GetFallbackModel1310, +# ::MOI.ConstraintDual, +# ::MOI.ConstraintIndex{MOI.ScalarAffineFunction}, +#) +# return 1.2 +#end function test_ConstraintPrimal_fallback() model = MOI.Utilities.CachingOptimizer( @@ -880,6 +880,43 @@ function test_ConstraintPrimal_fallback() return end +function test_ConstraintDual_variable_fallback() + model = MOI.Utilities.CachingOptimizer( + MOI.Utilities.Model{Float64}(), + _GetFallbackModel1310(), + ) + x = MOI.add_variable(model) + cx = MOI.add_constraint(model, x, MOI.GreaterThan(1.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), x) + MOI.optimize!(model) + @test MOI.get(model, MOI.ConstraintDual(), cx) == 1.0 + @test_throws( + MOI.ResultIndexBoundsError(MOI.ConstraintDual(2), 1), + MOI.get(model, MOI.ConstraintDual(2), cx), + ) + @test_throws( + MOI.ResultIndexBoundsError(MOI.ConstraintDual(2), 1), + MOI.get(model, MOI.ConstraintDual(2), [cx]), + ) + return +end + +function test_ConstraintDual_nonvariable_nofallback() + model = MOI.Utilities.CachingOptimizer( + MOI.Utilities.Model{Float64}(), + _GetFallbackModel1310(), + ) + x = MOI.add_variable(model) + cx = MOI.add_constraint(model, x + 1.0, MOI.GreaterThan(1.0)) + MOI.optimize!(model) + @test_throws( + MOI.GetAttributeNotAllowed, + MOI.get(model, MOI.ConstraintDual(), cx), + ) + return +end + function test_ConstraintPrimal_fallback_error() model = MOI.Utilities.CachingOptimizer( MOI.Utilities.Model{Float64}(), @@ -926,7 +963,7 @@ function test_DualObjectiveValue_fallback() MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) MOI.set(model, MOI.ObjectiveFunction{typeof(x)}(), x) MOI.optimize!(model) - @test MOI.get(model, MOI.DualObjectiveValue()) == 1.2 + @test MOI.get(model, MOI.DualObjectiveValue()) == 1.0 @test_throws( MOI.ResultIndexBoundsError(MOI.DualObjectiveValue(2), 1), MOI.get(model, MOI.DualObjectiveValue(2)), From b32ea6094b7438d3b42977c73ebe5b7872f0491b Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Fri, 22 Dec 2023 08:58:29 +1300 Subject: [PATCH 4/5] Update cachingoptimizer.jl --- test/Utilities/cachingoptimizer.jl | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/Utilities/cachingoptimizer.jl b/test/Utilities/cachingoptimizer.jl index 3173819a54..feb42eb079 100644 --- a/test/Utilities/cachingoptimizer.jl +++ b/test/Utilities/cachingoptimizer.jl @@ -851,14 +851,6 @@ function MOI.get( return 1.2 end -#function MOI.get( -# ::_GetFallbackModel1310, -# ::MOI.ConstraintDual, -# ::MOI.ConstraintIndex{MOI.ScalarAffineFunction}, -#) -# return 1.2 -#end - function test_ConstraintPrimal_fallback() model = MOI.Utilities.CachingOptimizer( MOI.Utilities.Model{Float64}(), From 22593506e6862c9824f03c0cde5c6c45294f89a7 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Fri, 22 Dec 2023 09:05:13 +1300 Subject: [PATCH 5/5] Update cachingoptimizer.jl --- src/Utilities/cachingoptimizer.jl | 35 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index 70c74b9cc4..fba56bbbb2 100644 --- a/src/Utilities/cachingoptimizer.jl +++ b/src/Utilities/cachingoptimizer.jl @@ -927,15 +927,14 @@ function MOI.get( end _throw_if_get_attribute_not_allowed(model, attr; needs_optimizer_map = true) if _has_fallback(attr, typeof(index)) - return _caching_get_fallback(model, attr, index) - else - value = MOI.get( - model.optimizer, - attr, - model.model_to_optimizer_map[index], - )::MOI.attribute_value_type(attr) - return map_indices(model.optimizer_to_model_map, attr, value) + return _get_fallback(model, attr, index) end + value = MOI.get( + model.optimizer, + attr, + model.model_to_optimizer_map[index], + )::MOI.attribute_value_type(attr) + return map_indices(model.optimizer_to_model_map, attr, value) end function MOI.get( @@ -948,15 +947,14 @@ function MOI.get( end _throw_if_get_attribute_not_allowed(model, attr; needs_optimizer_map = true) if _has_fallback(attr, I) - return _caching_get_fallback(model, attr, indices) - else - value = MOI.get( - model.optimizer, - attr, - map(Base.Fix1(getindex, model.model_to_optimizer_map), indices), - )::Vector{<:MOI.attribute_value_type(attr)} - return map_indices(model.optimizer_to_model_map, attr, value) + return _get_fallback(model, attr, indices) end + value = MOI.get( + model.optimizer, + attr, + map(Base.Fix1(getindex, model.model_to_optimizer_map), indices), + )::Vector{<:MOI.attribute_value_type(attr)} + return map_indices(model.optimizer_to_model_map, attr, value) end ### @@ -970,6 +968,7 @@ end # also be computed from the `ConstraintDual` of the other constraints. _has_fallback(::MOI.ConstraintPrimal, ::Type{<:MOI.ConstraintIndex}) = true + function _has_fallback( ::MOI.ConstraintDual, ::Type{<:MOI.ConstraintIndex{F}}, @@ -977,7 +976,7 @@ function _has_fallback( return true end -function _caching_get_fallback( +function _get_fallback( model::CachingOptimizer, attr::MOI.AbstractConstraintAttribute, index::MOI.ConstraintIndex, @@ -997,7 +996,7 @@ function _caching_get_fallback( end end -function _caching_get_fallback( +function _get_fallback( model::CachingOptimizer, attr::MOI.AbstractConstraintAttribute, indices::Vector{<:MOI.ConstraintIndex},