fix set cover bug

This commit is contained in:
Laurent Perron
2024-11-08 13:52:22 -08:00
parent f2f03f2eaf
commit 5f663bdfb9
3 changed files with 32 additions and 6 deletions

View File

@@ -367,6 +367,7 @@ cc_test(
":set_cover_invariant",
":set_cover_mip",
":set_cover_model",
"//ortools/base:parse_text_proto",
"//ortools/base:gmock_main",
"@com_google_absl//absl/log",
"@com_google_absl//absl/log:check",

View File

@@ -659,7 +659,8 @@ bool GuidedLocalSearch::NextSolution(absl::Span<const SubsetIndex> focus,
}
}
for (int iteration = 0; iteration < num_iterations; ++iteration) {
for (int iteration = 0;
!priority_heap_.IsEmpty() && iteration < num_iterations; ++iteration) {
// Improve current solution respective to the current penalties.
const SubsetIndex best_subset(priority_heap_.TopIndex());
if (inv_->is_selected()[best_subset]) {
@@ -671,8 +672,10 @@ bool GuidedLocalSearch::NextSolution(absl::Span<const SubsetIndex> focus,
best_subset.value()});
}
inv_->Flip(best_subset, CL::kRedundancy); // Flip the best subset.
DCHECK(!utility_heap_.IsEmpty());
// Getting the subset with highest utility.
// Getting the subset with highest utility. utility_heap_ is not empty,
// because we just inserted a pair.
const SubsetIndex penalized_subset(utility_heap_.TopIndex());
utility_heap_.Pop();
++penalties_[penalized_subset];
@@ -680,6 +683,7 @@ bool GuidedLocalSearch::NextSolution(absl::Span<const SubsetIndex> focus,
{static_cast<float>(inv_->model()->subset_costs()[penalized_subset] /
(1 + penalties_[penalized_subset])),
penalized_subset.value()});
DCHECK(!utility_heap_.IsEmpty());
// Get removable subsets (Add them to the heap).
for (const SubsetIndex subset : inv_->newly_removable_subsets()) {
@@ -687,6 +691,7 @@ bool GuidedLocalSearch::NextSolution(absl::Span<const SubsetIndex> focus,
inv_->model()->subset_costs()[subset]);
priority_heap_.Insert({delta_selected, subset.value()});
}
DCHECK(!priority_heap_.IsEmpty());
for (const SubsetIndex subset : {penalized_subset, best_subset}) {
const float delta = ComputeDelta(subset);
@@ -694,9 +699,11 @@ bool GuidedLocalSearch::NextSolution(absl::Span<const SubsetIndex> focus,
priority_heap_.Insert({delta, subset.value()});
}
}
DCHECK(!priority_heap_.IsEmpty());
// Get new non removable subsets.
// (Delete them from the heap)
// Get new non removable subsets and remove them from the heap.
// This is when the priority_heap_ can become empty and end the outer loop
// early.
for (const SubsetIndex subset : inv_->newly_non_removable_subsets()) {
priority_heap_.Remove(subset.value());
}

View File

@@ -25,12 +25,30 @@
#include "ortools/algorithms/set_cover_model.h"
#include "ortools/base/gmock.h"
#include "ortools/base/logging.h"
#include "ortools/base/parse_text_proto.h"
namespace operations_research {
namespace {
using google::protobuf::contrib::parse_proto::ParseTextProtoOrDie;
using CL = SetCoverInvariant::ConsistencyLevel;
TEST(SetCoverTest, GuidedLocalSearchVerySmall) {
SetCoverProto proto = ParseTextProtoOrDie(R"pb(
subset { cost: 1 element: 1 element: 2 }
subset { cost: 1 element: 0 })pb");
SetCoverModel model;
model.ImportModelFromProto(proto);
CHECK(model.ComputeFeasibility());
SetCoverInvariant inv(&model);
GreedySolutionGenerator greedy_search(&inv);
CHECK(greedy_search.NextSolution());
CHECK(inv.CheckConsistency(CL::kFreeAndUncovered));
GuidedLocalSearch search(&inv);
CHECK(search.NextSolution(100));
CHECK(inv.CheckConsistency(CL::kRedundancy));
}
SetCoverModel CreateKnightsCoverModel(int num_rows, int num_cols) {
SetCoverModel model;
constexpr int knight_row_move[] = {2, 1, -1, -2, -2, -1, 1, 2};
@@ -335,7 +353,7 @@ TEST(SetCoverTest, KnightsCoverElementDegreeRandomClear) {
SetCoverInvariant inv(&model);
Cost best_cost = std::numeric_limits<Cost>::max();
SubsetBoolVector best_choices;
for (int i = 0; i < 1000; ++i) {
for (int i = 0; i < 10000; ++i) {
LazyElementDegreeSolutionGenerator degree(&inv);
CHECK(degree.NextSolution());
CHECK(inv.CheckConsistency(CL::kCostAndCoverage));