more work on set covering
This commit is contained in:
committed by
Corentin Le Molgat
parent
36e0efd7e3
commit
040bb0ca5f
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user