more work on set covering

This commit is contained in:
Laurent Perron
2024-04-24 14:34:27 +02:00
committed by Corentin Le Molgat
parent 36e0efd7e3
commit 040bb0ca5f
3 changed files with 240 additions and 214 deletions

View File

@@ -83,14 +83,37 @@ bool RandomSolutionGenerator::NextSolution(
std::shuffle(shuffled.begin(), shuffled.end(), absl::BitGen());
for (const SubsetIndex subset : shuffled) {
if (inv_->is_selected()[subset]) continue;
if (inv_->marginal_impacts()[subset] != 0) {
inv_->Toggle(subset, true);
if (inv_->num_free_elements()[subset] != 0) {
inv_->UnsafeUse(subset);
}
}
DCHECK(inv_->CheckConsistency());
return true;
}
namespace {
bool LogAndCheck(const SetCoverInvariant* inv,
const std::vector<SubsetIndex>& focus) {
for (const SubsetIndex subset : focus) {
DVLOG(1) << "subset: " << subset.value()
<< " is_selected: " << inv->is_selected()[subset]
<< " is_removable: " << inv->is_removable()[subset]
<< " cardinality: " << inv->model()->columns()[subset].size()
<< " marginal_impact: " << inv->num_free_elements()[subset]
<< " num overcovered elements: "
<< inv->model()->columns()[subset].size().value() -
inv->num_coverage_le_1_elements()[subset].value();
}
DCHECK(inv->CheckConsistency());
for (const SubsetIndex subset : focus) {
DCHECK_EQ(inv->is_removable()[subset],
inv->num_coverage_le_1_elements()[subset] == 0);
}
DCHECK_EQ(inv->num_uncovered_elements(), 0);
return true;
}
} // anonymous namespace
// GreedySolutionGenerator.
bool GreedySolutionGenerator::NextSolution() {
@@ -114,10 +137,11 @@ bool GreedySolutionGenerator::NextSolution(
DVLOG(1) << "focus.size(): " << focus.size();
subset_priorities.reserve(focus.size());
for (const SubsetIndex subset : focus) {
if (!inv_->is_selected()[subset] && inv_->marginal_impacts()[subset] != 0) {
if (!inv_->is_selected()[subset] &&
inv_->num_free_elements()[subset] != 0) {
// NOMUTANTS -- reason, for C++
const float priority =
elements_per_cost[subset] * inv_->marginal_impacts()[subset].value();
elements_per_cost[subset] * inv_->num_free_elements()[subset].value();
subset_priorities.push_back({priority, subset.value()});
}
}
@@ -126,15 +150,14 @@ bool GreedySolutionGenerator::NextSolution(
// TODO(user): research more about the best value for Arity.
AdjustableKAryHeap<SubsetIndexWithPriority, 16, true> pq(
subset_priorities, inv_->model()->num_subsets().value());
const ElementIndex num_elements(inv_->model()->num_elements());
ElementIndex num_elements_covered(inv_->num_elements_covered());
while (num_elements_covered < num_elements && !pq.IsEmpty()) {
while (!pq.IsEmpty()) {
const SubsetIndex best_subset(pq.Pop().index());
const std::vector<SubsetIndex> impacted_subsets =
inv_->Toggle(best_subset, true);
inv_->UnsafeUse(best_subset);
// NOMUTANTS -- reason, for C++
if (inv_->num_uncovered_elements() == 0) break;
for (const SubsetIndex subset : impacted_subsets) {
if (subset == best_subset) continue;
const ElementIndex marginal_impact(inv_->marginal_impacts()[subset]);
const ElementIndex marginal_impact(inv_->num_free_elements()[subset]);
if (marginal_impact > 0) {
const float priority =
marginal_impact.value() * elements_per_cost[subset];
@@ -145,45 +168,26 @@ bool GreedySolutionGenerator::NextSolution(
}
for (const SubsetIndex subset : focus) {
DCHECK_EQ(inv_->is_removable()[subset],
inv_->num_overcovered_elements()[subset].value() ==
inv_->model()->columns()[subset].size().value());
inv_->num_coverage_le_1_elements()[subset] == 0);
}
num_elements_covered = inv_->num_elements_covered();
DVLOG(1) << "Cost = " << inv_->cost() << " num_uncovered_elements = "
<< num_elements - num_elements_covered;
DVLOG(1) << "Cost = " << inv_->cost()
<< " num_uncovered_elements = " << inv_->num_uncovered_elements();
}
DCHECK(pq.IsEmpty());
DCHECK(num_elements_covered == num_elements);
DCHECK(inv_->CheckConsistency());
for (const SubsetIndex subset : focus) {
DVLOG(1) << "subset: " << subset.value()
<< " is_selected: " << inv_->is_selected()[subset]
<< " is_removable: " << inv_->is_removable()[subset]
<< " cardinality: " << inv_->model()->columns()[subset].size()
<< " marginal_impact: " << inv_->marginal_impacts()[subset]
<< " num overcovered elements: "
<< inv_->num_overcovered_elements()[subset];
}
for (const SubsetIndex subset : focus) {
DCHECK_EQ(inv_->is_removable()[subset],
inv_->num_overcovered_elements()[subset].value() ==
inv_->model()->columns()[subset].size().value());
}
// Don't expect the queue to be empty, because of the break in the while loop.
DCHECK(LogAndCheck(inv_, focus));
return true;
}
class SubsetIndexWithElementDegree {
class ElementIndexWithDegree {
public:
using Index = int;
using Priority = int;
SubsetIndexWithElementDegree() = default;
SubsetIndexWithElementDegree(Priority priority, Index index)
ElementIndexWithDegree() = default;
ElementIndexWithDegree(Priority priority, Index index)
: index_(index), priority_(priority) {}
Priority priority() const { return priority_; }
Index index() const { return index_; }
inline bool operator<(const SubsetIndexWithElementDegree other) const {
inline bool operator<(const ElementIndexWithDegree other) const {
if (other.priority() != priority()) {
return priority() < other.priority();
}
@@ -210,7 +214,7 @@ bool ElementDegreeSolutionGenerator::NextSolution(
DVLOG(1) << "Entering ElementDegreeSolutionGenerator::NextSolution";
inv_->RecomputeInvariant();
const ElementIndex num_elements(inv_->model()->num_elements());
std::vector<SubsetIndexWithElementDegree> element_degrees;
std::vector<ElementIndexWithDegree> element_degrees;
element_degrees.reserve(num_elements.value());
const SparseRowView& rows = inv_->model()->rows();
for (ElementIndex element(0); element < num_elements; ++element) {
@@ -222,54 +226,39 @@ bool ElementDegreeSolutionGenerator::NextSolution(
// The priority queue maintains the non-covered element with the smallest
// degree.
AdjustableKAryHeap<SubsetIndexWithElementDegree, 16, false> pq(
AdjustableKAryHeap<ElementIndexWithDegree, 16, false> pq(
element_degrees, num_elements.value());
ElementIndex num_elements_covered(inv_->num_elements_covered());
const SparseColumnView& columns = inv_->model()->columns();
while (num_elements_covered < num_elements && !pq.IsEmpty()) {
while (!pq.IsEmpty()) {
const ElementIndex best_element(pq.Pop().index());
DCHECK_EQ(inv_->coverage()[best_element], 0)
<< "Element " << best_element.value()
<< " is already covered. num_elements_covered: " << num_elements_covered
<< " is already covered. num_uncovered_elements: "
<< inv_->num_uncovered_elements()
<< " / num_elements: " << num_elements;
Cost min_ratio = std::numeric_limits<Cost>::max();
SubsetIndex best_subset(-1);
for (const SubsetIndex subset : rows[best_element]) {
const Cost ratio =
costs[subset] / inv_->marginal_impacts()[subset].value();
costs[subset] / inv_->num_free_elements()[subset].value();
if (ratio < min_ratio) {
min_ratio = ratio;
best_subset = subset;
}
}
DCHECK_NE(best_subset, -1);
inv_->Toggle(best_subset, true);
inv_->UnsafeUse(best_subset);
if (inv_->num_uncovered_elements() == 0) break;
for (const ElementIndex element : columns[best_subset]) {
if (element != best_element) {
pq.Remove(element.value());
}
}
num_elements_covered = inv_->num_elements_covered();
DVLOG(1) << "Cost = " << inv_->cost() << " num_uncovered_elements = "
<< num_elements - num_elements_covered;
}
DCHECK(pq.IsEmpty());
DCHECK(num_elements_covered == num_elements);
DCHECK(inv_->CheckConsistency());
for (const SubsetIndex subset : focus) {
DVLOG(1) << "subset: " << subset.value()
<< " is_selected: " << inv_->is_selected()[subset]
<< " is_removable: " << inv_->is_removable()[subset]
<< " cardinality: " << inv_->model()->columns()[subset].size()
<< " marginal_impact: " << inv_->marginal_impacts()[subset]
<< " num overcovered elements: "
<< inv_->num_overcovered_elements()[subset];
}
for (const SubsetIndex subset : focus) {
DCHECK_EQ(inv_->is_removable()[subset],
inv_->num_overcovered_elements()[subset].value() ==
inv_->model()->columns()[subset].size().value());
DVLOG(1) << "Cost = " << inv_->cost()
<< " num_uncovered_elements = " << inv_->num_uncovered_elements();
}
// Don't expect the queue to be empty, because of the break in the while loop.
DCHECK(LogAndCheck(inv_, focus));
return true;
}
@@ -293,7 +282,7 @@ bool SteepestSearch::NextSolution(absl::Span<const SubsetIndex> focus,
DVLOG(1) << "Entering SteepestSearch::NextSolution, num_iterations = "
<< num_iterations;
// Return false if inv_ contains no solution.
if (inv_->num_elements_covered() != inv_->model()->num_elements()) {
if (inv_->num_uncovered_elements() != 0) {
return false;
}
@@ -316,7 +305,7 @@ bool SteepestSearch::NextSolution(absl::Span<const SubsetIndex> focus,
if (!inv_->is_removable()[best_subset]) continue;
DCHECK_GT(costs[best_subset], 0.0);
const std::vector<SubsetIndex> impacted_subsets =
inv_->Toggle(best_subset, false);
inv_->UnsafeRemove(best_subset);
for (const SubsetIndex subset : impacted_subsets) {
if (!inv_->is_removable()[subset]) {
pq.Remove(subset.value());
@@ -324,7 +313,7 @@ bool SteepestSearch::NextSolution(absl::Span<const SubsetIndex> focus,
}
DVLOG(1) << "Cost = " << inv_->cost();
}
DCHECK(inv_->num_elements_covered() == inv_->model()->num_elements());
DCHECK_EQ(inv_->num_uncovered_elements(), 0);
DCHECK(inv_->CheckConsistency());
return true;
}
@@ -447,7 +436,8 @@ bool GuidedTabuSearch::NextSolution(const std::vector<SubsetIndex>& focus,
}
}
inv_->LoadSolution(best_choices);
DCHECK(inv_->num_elements_covered() == inv_->model()->num_elements());
DCHECK_EQ(inv_->num_uncovered_elements(), 0);
DCHECK(inv_->CheckConsistency());
return true;
}
@@ -478,8 +468,8 @@ std::vector<SubsetIndex> ClearRandomSubsets(absl::Span<const SubsetIndex> focus,
}
SampleSubsets(&chosen_indices, num_subsets);
for (const SubsetIndex subset : chosen_indices) {
// Use UnsafeToggle because we allow non-solutions.
inv->UnsafeToggle(subset, false);
// We can use UnsafeRemove because we allow non-solutions.
inv->UnsafeRemove(subset);
}
return chosen_indices;
}
@@ -530,8 +520,8 @@ std::vector<SubsetIndex> ClearMostCoveredElements(
// Testing has shown that sorting sampled_subsets is not necessary.
// Now, un-select the subset in sampled_subsets.
for (const SubsetIndex subset : sampled_subsets) {
// Use UnsafeToggle because we allow non-solutions.
inv->UnsafeToggle(subset, false);
// Use UnsafeRemove because we allow non-solutions.
inv->UnsafeRemove(subset);
}
return sampled_subsets;
}

View File

@@ -25,26 +25,29 @@
namespace operations_research {
// Note: in many of the member functions, variables have "crypterse" names
// to avoid confusing them with member data. For example mrgnl_impcts is used
// to avoid confusion with marginal_impacts_.
// to avoid confusion with num_free_elements_.
void SetCoverInvariant::Initialize() {
DCHECK(model_->ComputeFeasibility());
model_->CreateSparseRowView();
cost_ = 0.0;
const SubsetIndex num_subsets(model_->num_subsets());
is_selected_.assign(num_subsets, false);
is_removable_.assign(num_subsets, false);
marginal_impacts_.assign(num_subsets, ElementIndex(0));
num_overcovered_elements_.assign(num_subsets, ElementIndex(0));
num_free_elements_.assign(num_subsets, ElementIndex(0));
num_coverage_le_1_elements_.assign(num_subsets, ElementIndex(0));
const SparseColumnView& columns = model_->columns();
for (SubsetIndex subset(0); subset < num_subsets; ++subset) {
marginal_impacts_[subset] = columns[subset].size().value();
num_free_elements_[subset] = columns[subset].size().value();
num_coverage_le_1_elements_[subset] = columns[subset].size().value();
}
const ElementIndex num_elements(model_->num_elements());
coverage_.assign(num_elements, SubsetIndex(0));
cost_ = 0.0;
num_elements_covered_ = ElementIndex(0);
num_uncovered_elements_ = num_elements;
}
bool SetCoverInvariant::CheckConsistency() const {
@@ -53,16 +56,13 @@ bool SetCoverInvariant::CheckConsistency() const {
for (ElementIndex element(0); element < model_->num_elements(); ++element) {
CHECK_EQ(cvrg[element], coverage_[element]);
}
auto [num_elts_cvrd, mrgnl_impcts, num_ovrcvrd_elts, is_rmvble] =
auto [num_uncvrd_elts, mrgnl_impcts, num_01_elts, is_rdndnt] =
ComputeImpliedData(cvrg);
CHECK_EQ(num_elts_cvrd, num_elements_covered_);
const SparseColumnView& columns = model_->columns();
for (SubsetIndex subset(0); subset < columns.size(); ++subset) {
CHECK_EQ(mrgnl_impcts[subset], marginal_impacts_[subset]);
CHECK_EQ(num_ovrcvrd_elts[subset], num_overcovered_elements_[subset]);
CHECK_EQ(is_rmvble[subset], is_removable_[subset]);
CHECK_EQ(is_rmvble[subset],
num_ovrcvrd_elts[subset] == columns[subset].size().value());
CHECK_EQ(mrgnl_impcts[subset], num_free_elements_[subset]);
CHECK_EQ(is_rdndnt[subset], is_removable_[subset]);
CHECK_EQ(is_rdndnt[subset], num_01_elts[subset] == 0);
}
return true;
}
@@ -74,9 +74,10 @@ void SetCoverInvariant::LoadSolution(const SubsetBoolVector& c) {
void SetCoverInvariant::RecomputeInvariant() {
std::tie(cost_, coverage_) = ComputeCostAndCoverage(is_selected_);
std::tie(num_elements_covered_, marginal_impacts_, num_overcovered_elements_,
is_removable_) = ComputeImpliedData(coverage_);
}
std::tie(num_uncovered_elements_, num_free_elements_,
num_coverage_le_1_elements_, is_removable_) =
ComputeImpliedData(coverage_);
}
std::tuple<Cost, ElementToSubsetVector>
SetCoverInvariant::ComputeCostAndCoverage(
@@ -84,7 +85,7 @@ SetCoverInvariant::ComputeCostAndCoverage(
Cost cst = 0.0;
ElementToSubsetVector cvrg(model_->num_elements(), SubsetIndex(0));
const SparseColumnView& columns = model_->columns();
// Initialize marginal_impacts_, update cost_, and compute the coverage for
// Initialize coverage, update cost, and compute the coverage for
// all the elements covered by the selected subsets.
const SubsetCostVector& subset_costs = model_->subset_costs();
for (SubsetIndex subset(0); bool b : choices) {
@@ -99,71 +100,91 @@ SetCoverInvariant::ComputeCostAndCoverage(
return {cst, cvrg};
}
std::tuple<ElementIndex, SubsetToElementVector, SubsetToElementVector,
SubsetBoolVector>
std::tuple<ElementIndex, // Number of uncovered elements
SubsetToElementVector, // Number of free elements for each subset
SubsetToElementVector, // Number of elem covered 0 or 1 times.
SubsetBoolVector> // Removability for each subset
SetCoverInvariant::ComputeImpliedData(const ElementToSubsetVector& cvrg) const {
ElementIndex num_elts_cvrd(0);
SubsetIndex num_subsets = model_->num_subsets();
SubsetToElementVector mrgnl_impcts(num_subsets, ElementIndex(0));
const ElementIndex num_elements(model_->num_elements());
ElementIndex num_uncvrd_elts(num_elements);
const SubsetIndex num_subsets(model_->num_subsets());
SubsetToElementVector num_ovrcvrd_elts(num_subsets, ElementIndex(0));
SubsetBoolVector is_rmvble(num_subsets, false);
SubsetToElementVector num_free_elts(num_subsets, ElementIndex(0));
SubsetToElementVector num_cvrg_le_1_elts(num_subsets, ElementIndex(0));
SubsetBoolVector is_rdndnt(num_subsets, false);
const SparseColumnView& columns = model_->columns();
// Initialize marginal_impacts_.
// Initialize number of free elements and number of elements covered 0 or 1.
for (SubsetIndex subset(0); subset < num_subsets; ++subset) {
mrgnl_impcts[subset] = columns[subset].size().value();
}
num_free_elts[subset] = columns[subset].size().value();
num_cvrg_le_1_elts[subset] = columns[subset].size().value();
}
// Update num_ovrcvrd_elts[subset] and num_elts_cvrd.
// When all elements in subset are overcovered, is_removable_[subset] is true.
const SparseRowView& rows = model_->rows();
const ElementIndex num_elements(model_->num_elements());
for (ElementIndex element(0); element < num_elements; ++element) {
if (cvrg[element] == 1) {
++num_elts_cvrd; // Just need to update this ...
for (SubsetIndex subset : rows[element]) {
--mrgnl_impcts[subset]; // ... and marginal_impacts_.
--num_uncvrd_elts; // Just need to update this ...
for (SubsetIndex subset : rows[element]) {
--num_free_elts[subset];
}
} else {
if (cvrg[element] >= 2) {
++num_elts_cvrd; // Same as when cvrg[element] == 1
--num_uncvrd_elts; // Same as when cvrg[element] == 1
for (SubsetIndex subset : rows[element]) {
--mrgnl_impcts[subset]; // Same as when cvrg[element] >= 2
++num_ovrcvrd_elts[subset];
if (num_ovrcvrd_elts[subset] == columns[subset].size().value()) {
is_rmvble[subset] = true;
--num_free_elts[subset]; // Same as when cvrg[element] == 1
--num_cvrg_le_1_elts[subset];
if (num_cvrg_le_1_elts[subset] == 0) {
is_rdndnt[subset] = true;
}
}
}
}
}
}
}
}
return {num_elts_cvrd, mrgnl_impcts, num_ovrcvrd_elts, is_rmvble};
return {num_uncvrd_elts, num_free_elts, num_cvrg_le_1_elts, is_rdndnt};
}
std::vector<SubsetIndex> SetCoverInvariant::Toggle(SubsetIndex subset,
bool value) {
// Note: "if p then q" is also "not(p) or q", or p <= q (p LE q).
// If selected, then is_removable, to make sure we still have a solution.
DCHECK(is_selected_[subset] <= is_removable_[subset])
<< "is_selected_ = " << is_selected_[subset]
<< " is_removable_ = " << is_removable_[subset] << " subset = " << subset;
// If value, then marginal_impact > 0, to not increase the cost.
DCHECK((value <= (marginal_impacts_[subset] > 0)));
return UnsafeToggle(subset, value);
if (value) {
DCHECK(!is_removable_[subset]);
DCHECK_GT(num_free_elements_[subset], 0);
return UnsafeUse(subset);
} else {
DCHECK(is_removable_[subset]);
return UnsafeRemove(subset);
}
}
std::vector<SubsetIndex> SetCoverInvariant::UnsafeToggle(SubsetIndex subset,
bool value) {
DCHECK_NE(is_selected_[subset], value);
// If already selected, then marginal_impact == 0.
DCHECK(is_selected_[subset] <= (marginal_impacts_[subset] == 0));
DVLOG(1) << (value ? "S" : "Des") << "electing subset " << subset;
is_selected_[subset] = value;
if (value) {
return UnsafeUse(subset);
} else {
return UnsafeRemove(subset);
}
}
namespace {
void CollectSubsets(SubsetIndex s, std::vector<SubsetIndex>* subsets,
SubsetBoolVector* subset_seen) {
if (!(*subset_seen)[s]) {
(*subset_seen)[s] = true;
subsets->push_back(s);
}
}
} // namespace
std::vector<SubsetIndex> SetCoverInvariant::UnsafeUse(SubsetIndex subset) {
DCHECK(!is_selected_[subset]);
DVLOG(1) << "Selecting subset " << subset;
is_selected_[subset] = true;
const SubsetCostVector& subset_costs = model_->subset_costs();
cost_ += value ? subset_costs[subset] : -subset_costs[subset];
const int delta = value ? 1 : -1;
cost_ += subset_costs[subset];
const SparseColumnView& columns = model_->columns();
@@ -172,66 +193,77 @@ std::vector<SubsetIndex> SetCoverInvariant::UnsafeToggle(SubsetIndex subset,
impacted_subsets.reserve(columns.size().value());
// Initialize subset_seen so that `subset` not be in impacted_subsets.
subset_seen[subset] = true;
auto collect_impacted_subsets = [&impacted_subsets,
&subset_seen](SubsetIndex s) {
if (!subset_seen[s]) {
subset_seen[s] = true;
impacted_subsets.push_back(s);
}
};
subset_seen[subset] = true;
const SparseRowView& rows = model_->rows();
for (const ElementIndex element : columns[subset]) {
// Update coverage.
coverage_[element] += delta;
// There are four different types of events that matter when updating
// the invariant. Two for each case when value is true or false.
const SparseRowView& rows = model_->rows();
if (value) {
if (coverage_[element] == 1) {
// `element` is newly covered.
++num_elements_covered_;
for (const SubsetIndex impacted_subset : rows[element]) {
collect_impacted_subsets(impacted_subset);
--marginal_impacts_[impacted_subset];
++coverage_[element];
if (coverage_[element] == 1) {
// `element` is newly covered.
--num_uncovered_elements_;
for (const SubsetIndex impacted_subset : rows[element]) {
CollectSubsets(impacted_subset, &impacted_subsets, &subset_seen);
--num_free_elements_[impacted_subset];
}
} else if (coverage_[element] == 2) {
// `element` is newly overcovered.
for (const SubsetIndex impacted_subset : rows[element]) {
CollectSubsets(impacted_subset, &impacted_subsets, &subset_seen);
--num_coverage_le_1_elements_[impacted_subset];
if (num_coverage_le_1_elements_[impacted_subset] == 0) {
// All the elements in impacted_subset are now overcovered, so it
// is removable. Note that this happens only when the last element
// of impacted_subset becomes overcovered.
is_removable_[impacted_subset] = true;
}
}
} else if (coverage_[element] == 2) {
// `element` is newly overcovered.
for (const SubsetIndex impacted_subset : rows[element]) {
collect_impacted_subsets(impacted_subset);
++num_overcovered_elements_[impacted_subset];
if (num_overcovered_elements_[impacted_subset] ==
columns[impacted_subset].size().value()) {
// All the elements in impacted_subset are now overcovered, so it
// is removable. Note that this happens only when the last element
// of impacted_subset becomes overcovered.
is_removable_[impacted_subset] = true;
}
}
}
} else { // value is false
if (coverage_[element] == 0) {
// `element` is no longer covered.
--num_elements_covered_;
for (const SubsetIndex impacted_subset : rows[element]) {
collect_impacted_subsets(impacted_subset);
++marginal_impacts_[impacted_subset];
}
} else if (coverage_[element] == 1) {
// `element` is no longer overcovered.
for (const SubsetIndex impacted_subset : rows[element]) {
collect_impacted_subsets(impacted_subset);
--num_overcovered_elements_[impacted_subset];
if (num_overcovered_elements_[impacted_subset] ==
columns[impacted_subset].size().value() - 1) {
// There is one element of impacted_subset which is not overcovered.
// impacted_subset has just become non-removable.
is_removable_[impacted_subset] = false;
}
}
DCHECK(CheckConsistency());
return impacted_subsets;
}
std::vector<SubsetIndex> SetCoverInvariant::UnsafeRemove(SubsetIndex subset) {
DCHECK(is_selected_[subset]);
// If already selected, then num_free_elements == 0.
DCHECK_EQ(num_free_elements_[subset], 0);
DVLOG(1) << "Deselecting subset " << subset;
is_selected_[subset] = false;
const SubsetCostVector& subset_costs = model_->subset_costs();
cost_ -= subset_costs[subset];
const SparseColumnView& columns = model_->columns();
SubsetBoolVector subset_seen(columns.size(), false);
std::vector<SubsetIndex> impacted_subsets;
impacted_subsets.reserve(columns.size().value());
// Initialize subset_seen so that `subset` not be in impacted_subsets.
subset_seen[subset] = true;
const SparseRowView& rows = model_->rows();
for (const ElementIndex element : columns[subset]) {
// Update coverage.
--coverage_[element];
if (coverage_[element] == 0) {
// `element` is no longer covered.
++num_uncovered_elements_;
for (const SubsetIndex impacted_subset : rows[element]) {
CollectSubsets(impacted_subset, &impacted_subsets, &subset_seen);
++num_free_elements_[impacted_subset];
}
} else if (coverage_[element] == 1) {
// `element` is no longer overcovered.
for (const SubsetIndex impacted_subset : rows[element]) {
CollectSubsets(impacted_subset, &impacted_subsets, &subset_seen);
++num_coverage_le_1_elements_[impacted_subset];
if (num_coverage_le_1_elements_[impacted_subset] == 1) {
// There is one element of impacted_subset which is not overcovered.
// impacted_subset has just become non-removable.
is_removable_[impacted_subset] = false;
}
}
}
}
DCHECK(CheckConsistency());

View File

@@ -37,16 +37,14 @@ using SubsetBoolVector = glop::StrictITIVector<SubsetIndex, bool>;
// for an explanation of the terminology.
//
// A SetCoverInvariant is (relatively) small:
// is_selected_, a partial solution, vector of Booleans of size #subsets.
// is_selected_: a partial solution, vector of Booleans of size #subsets.
// From this, the following can be computed:
// coverage_, the number of times an elememt is covered;
// marginal_impacts_, the number of elements of a subset still uncovered;
// num_overcovered_elements_, the number of elements of a subset that
// are covered two times or more in the current solution;
// coverage_: the number of times an elememt is covered;
// num_free_elements_: the number of elements of a subset still uncovered;
// num_coverage_le_1_elements_: the number of elements of a subset that
// are covered 1 time or less (not overcovered) in the current solution;
// is_removable_, whether a subset can be removed from the solution.
// is_removable_[subset] == (num_over_cover_elements_[subet] == card(subset))
// where card(subset) is the size() of the column corresponding to subset in
// the model.
// is_removable_[subset] == (num_coverage_le_1_elements_[subet] == 0).
class SetCoverInvariant {
public:
// Constructs an empty weighted set covering solver state.
@@ -71,19 +69,24 @@ class SetCoverInvariant {
// Returns the cost of current solution.
Cost cost() const { return cost_; }
// Returns the number of uncovered elements.
ElementIndex num_uncovered_elements() const {
return num_uncovered_elements_;
}
// Returns the subset assignment vector.
const SubsetBoolVector& is_selected() const { return is_selected_; }
// Returns vector containing the number of elements in each subset that are
// not covered in the current solution.
const SubsetToElementVector& marginal_impacts() const {
return marginal_impacts_;
const SubsetToElementVector& num_free_elements() const {
return num_free_elements_;
}
// Returns vector containing the number of elements in each subset that are
// covered two times or more in the current solution.
const SubsetToElementVector& num_overcovered_elements() const {
return num_overcovered_elements_;
// Returns the vector of numbers of free or exactly covered elements for
// each subset.
const SubsetToElementVector& num_coverage_le_1_elements() const {
return num_coverage_le_1_elements_;
}
// Returns vector containing number of subsets covering each element.
@@ -93,9 +96,6 @@ class SetCoverInvariant {
// the solution.
const SubsetBoolVector& is_removable() const { return is_removable_; }
// Returns the number of elements covered.
ElementIndex num_elements_covered() const { return num_elements_covered_; }
// Stores the solution and recomputes the data in the invariant.
void LoadSolution(const SubsetBoolVector& c);
@@ -117,6 +117,10 @@ class SetCoverInvariant {
// Only checks that value is different from is_selected_[subset].
std::vector<SubsetIndex> UnsafeToggle(SubsetIndex subset, bool value);
std::vector<SubsetIndex> UnsafeUse(SubsetIndex subset);
std::vector<SubsetIndex> UnsafeRemove(SubsetIndex subset);
// Returns the current solution as a proto.
SetCoverSolutionResponse ExportSolutionAsProto() const;
@@ -129,13 +133,12 @@ class SetCoverInvariant {
const SubsetBoolVector& choices) const;
// Computes the implied data for the given coverage cvrg:
// The implied consists of:
// - the number of elements covered,
// - the vector of marginal impacts for each subset,
// - the vector of overcovered elements for each subset,
// - the vector of removability for each subset.
std::tuple<ElementIndex, SubsetToElementVector, SubsetToElementVector,
SubsetBoolVector>
// The implied data consists of:
std::tuple<ElementIndex, // Number of uncovered elements.
SubsetToElementVector, // Number of free elements / subset.
SubsetToElementVector, // Number of elem covered 0 or 1 times /
// subset.
SubsetBoolVector> // Removability for each subset.
ComputeImpliedData(const ElementToSubsetVector& cvrg) const;
// Internal UnsafeToggle where value is a constant for the template.
@@ -148,26 +151,27 @@ class SetCoverInvariant {
// Current cost.
Cost cost_;
// The number of elements covered in the current solution.
ElementIndex num_elements_covered_;
// The number of uncovered (or free) elements in the current solution.
ElementIndex num_uncovered_elements_;
// Current assignment.
SubsetBoolVector is_selected_;
// The marginal impact of a subset is the number of elements in that subset
// that are not covered in the current solution.
SubsetToElementVector marginal_impacts_;
// A vector that for each subset gives the number of free elements, i.e.
// elements whose coverage is 0.
// problem.
SubsetToElementVector num_free_elements_;
// Counts the number of "overcovered" elements for a given subset. Overcovered
// elements are the ones whose coverage is 2 and above.
SubsetToElementVector num_overcovered_elements_;
// Counts the number of free or exactly covered elements, i.e. whose coverage
// is 0 or 1.
SubsetToElementVector num_coverage_le_1_elements_;
// The coverage of an element is the number of used subsets which contains
// the said element.
ElementToSubsetVector coverage_;
// True if the subset can be removed from the solution without making it
// infeasible.
// True if the subset is redundant, i.e. can be removed from the solution
// without making it infeasible.
SubsetBoolVector is_removable_;
};