Skip to content

Commit 260d6e1

Browse files
authored
[Utilities] add get_fallback support in CachingOptimizer (#1893)
1 parent d374e69 commit 260d6e1

File tree

2 files changed

+105
-25
lines changed

2 files changed

+105
-25
lines changed

src/Utilities/cachingoptimizer.jl

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -823,22 +823,57 @@ function MOI.supports(
823823
(m.state == NO_OPTIMIZER || MOI.supports(m.optimizer, attr)::Bool)
824824
end
825825

826-
function MOI.get(model::CachingOptimizer, attr::MOI.AbstractModelAttribute)
827-
if MOI.is_set_by_optimize(attr)
828-
if state(model) == NO_OPTIMIZER
829-
error(
830-
"Cannot query $(attr) from caching optimizer because no " *
831-
"optimizer is attached.",
832-
)
826+
function _get_model_attribute(model::CachingOptimizer, attr::MOI.ObjectiveValue)
827+
try
828+
return MOI.get(model.optimizer, attr)
829+
catch err
830+
if !(err isa MOI.GetAttributeNotAllowed)
831+
rethrow(err)
833832
end
834-
return map_indices(
835-
model.optimizer_to_model_map,
836-
attr,
837-
MOI.get(model.optimizer, attr)::MOI.attribute_value_type(attr),
838-
)
839-
else
833+
return get_fallback(model, attr)
834+
end
835+
end
836+
837+
function _get_model_attribute(
838+
model::CachingOptimizer,
839+
attr::MOI.DualObjectiveValue,
840+
)
841+
try
842+
return MOI.get(model.optimizer, attr)
843+
catch err
844+
if !(err isa MOI.GetAttributeNotAllowed)
845+
rethrow(err)
846+
end
847+
MOI.check_result_index_bounds(model, attr)
848+
# We don't know what coefficient type to use, so just use whatever the
849+
# objective value type is. This is slightly inefficient, but it
850+
# shouldn't be a bottleneck.
851+
obj = _get_model_attribute(model, MOI.ObjectiveValue(attr.result_index))
852+
return get_fallback(model, attr, typeof(obj))
853+
end
854+
end
855+
856+
function _get_model_attribute(
857+
model::CachingOptimizer,
858+
attr::MOI.AbstractModelAttribute,
859+
)
860+
return map_indices(
861+
model.optimizer_to_model_map,
862+
attr,
863+
MOI.get(model.optimizer, attr)::MOI.attribute_value_type(attr),
864+
)
865+
end
866+
867+
function MOI.get(model::CachingOptimizer, attr::MOI.AbstractModelAttribute)
868+
if !MOI.is_set_by_optimize(attr)
840869
return MOI.get(model.model_cache, attr)
870+
elseif state(model) == NO_OPTIMIZER
871+
error(
872+
"Cannot query $(attr) from caching optimizer because no " *
873+
"optimizer is attached.",
874+
)
841875
end
876+
return _get_model_attribute(model, attr)
842877
end
843878

844879
function MOI.get(

test/Utilities/cachingoptimizer.jl

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -805,24 +805,25 @@ function test_copy_to_and_optimize!()
805805
end
806806

807807
###
808-
### Test ConstraintPrimal fallback in CachingOptimizer.
808+
### Test get_fallback in CachingOptimizer.
809809
###
810-
### It's a bit complicated because we need an optimizer that errors on
811-
### ConstraintPrimal. This is the minimal set of methods needed to test the
812-
### fallback.
810+
### It's a bit complicated because we need an optimizer with the minimal set of
811+
### methods needed to test the fallbacks.
813812
###
814813

815-
struct _ConstraintPrimal1310 <: MOI.AbstractOptimizer end
814+
struct _GetFallbackModel1310 <: MOI.AbstractOptimizer end
816815

817-
MOI.is_empty(::_ConstraintPrimal1310) = true
816+
MOI.is_empty(::_GetFallbackModel1310) = true
818817

819-
MOI.get(::_ConstraintPrimal1310, ::MOI.ListOfModelAttributesSet) = []
818+
MOI.get(::_GetFallbackModel1310, ::MOI.ListOfModelAttributesSet) = []
820819

821-
MOI.get(::_ConstraintPrimal1310, ::MOI.PrimalStatus) = MOI.FEASIBLE_POINT
820+
MOI.get(::_GetFallbackModel1310, ::MOI.PrimalStatus) = MOI.FEASIBLE_POINT
822821

823-
MOI.get(::_ConstraintPrimal1310, ::MOI.ResultCount) = 1
822+
MOI.get(::_GetFallbackModel1310, ::MOI.DualStatus) = MOI.FEASIBLE_POINT
824823

825-
function MOI.optimize!(::_ConstraintPrimal1310, model::MOI.ModelLike)
824+
MOI.get(::_GetFallbackModel1310, ::MOI.ResultCount) = 1
825+
826+
function MOI.optimize!(::_GetFallbackModel1310, model::MOI.ModelLike)
826827
index_map = MOI.IndexMap()
827828
for x in MOI.get(model, MOI.ListOfVariableIndices())
828829
index_map[x] = x
@@ -836,17 +837,25 @@ function MOI.optimize!(::_ConstraintPrimal1310, model::MOI.ModelLike)
836837
end
837838

838839
function MOI.get(
839-
::_ConstraintPrimal1310,
840+
::_GetFallbackModel1310,
840841
::MOI.VariablePrimal,
841842
::MOI.VariableIndex,
842843
)
843844
return 1.2
844845
end
845846

847+
function MOI.get(
848+
::_GetFallbackModel1310,
849+
::MOI.ConstraintDual,
850+
::MOI.ConstraintIndex,
851+
)
852+
return 1.2
853+
end
854+
846855
function test_ConstraintPrimal_fallback()
847856
model = MOI.Utilities.CachingOptimizer(
848857
MOI.Utilities.Model{Float64}(),
849-
_ConstraintPrimal1310(),
858+
_GetFallbackModel1310(),
850859
)
851860
x = MOI.add_variable(model)
852861
c = MOI.add_constraint(model, x, MOI.GreaterThan(1.0))
@@ -876,6 +885,42 @@ function test_ConstraintPrimal_fallback_error()
876885
return
877886
end
878887

888+
function test_ObjectiveValue_fallback()
889+
model = MOI.Utilities.CachingOptimizer(
890+
MOI.Utilities.Model{Float64}(),
891+
_GetFallbackModel1310(),
892+
)
893+
x = MOI.add_variable(model)
894+
MOI.add_constraint(model, x, MOI.GreaterThan(1.0))
895+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
896+
MOI.set(model, MOI.ObjectiveFunction{typeof(x)}(), x)
897+
MOI.optimize!(model)
898+
@test MOI.get(model, MOI.ObjectiveValue()) == 1.2
899+
@test_throws(
900+
MOI.ResultIndexBoundsError(MOI.ObjectiveValue(2), 1),
901+
MOI.get(model, MOI.ObjectiveValue(2)),
902+
)
903+
return
904+
end
905+
906+
function test_DualObjectiveValue_fallback()
907+
model = MOI.Utilities.CachingOptimizer(
908+
MOI.Utilities.Model{Float64}(),
909+
_GetFallbackModel1310(),
910+
)
911+
x = MOI.add_variable(model)
912+
MOI.add_constraint(model, x, MOI.GreaterThan(1.0))
913+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
914+
MOI.set(model, MOI.ObjectiveFunction{typeof(x)}(), x)
915+
MOI.optimize!(model)
916+
@test MOI.get(model, MOI.DualObjectiveValue()) == 1.2
917+
@test_throws(
918+
MOI.ResultIndexBoundsError(MOI.DualObjectiveValue(2), 1),
919+
MOI.get(model, MOI.DualObjectiveValue(2)),
920+
)
921+
return
922+
end
923+
879924
struct _OptimizerAttributeValue1670 end
880925

881926
function test_map_indices_issue_1670()

0 commit comments

Comments
 (0)