Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
set_cover_invariant.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <limits>
18#include <tuple>
19#include <vector>
20
21#include "absl/log/check.h"
22#include "absl/types/span.h"
25
26namespace operations_research {
27
28namespace {
29bool SupportsAvx512() { return false; }
30} // namespace
31
32// Note: in many of the member functions, variables have "crypterse" names
33// to avoid confusing them with member data. For example mrgnl_impcts is used
34// to avoid confusion with num_free_elements_.
36 DCHECK(model_->ComputeFeasibility());
37 model_->CreateSparseRowView();
38
39 cost_ = 0.0;
40
41 const BaseInt num_subsets = model_->num_subsets();
42 const BaseInt num_elements = model_->num_elements();
43
44 is_selected_.assign(num_subsets, false);
45 num_free_elements_.assign(num_subsets, 0);
46 num_non_overcovered_elements_.assign(num_subsets, 0);
47 is_redundant_.assign(num_subsets, false);
48
49 const SparseColumnView& columns = model_->columns();
50 for (const SubsetIndex subset : model_->SubsetRange()) {
51 num_free_elements_[subset] = columns[subset].size();
52 num_non_overcovered_elements_[subset] = columns[subset].size();
53 }
54
55 coverage_.assign(num_elements, 0);
56
57 // No need to reserve for trace_ and other vectors as extending with
58 // push_back is fast enough.
59
60 num_uncovered_elements_ = num_elements;
61 supports_avx512_ = SupportsAvx512();
62 is_fully_updated_ = true;
63}
64
66 auto [cst, cvrg] = ComputeCostAndCoverage(is_selected_);
67 CHECK_EQ(cost_, cst);
68 for (const ElementIndex element : model_->ElementRange()) {
69 CHECK_EQ(cvrg[element], coverage_[element]);
70 }
71 auto [num_uncvrd_elts, num_free_elts] =
72 ComputeNumUncoveredAndFreeElements(cvrg);
73 auto [num_non_ovrcvrd_elts, is_rdndnt] =
74 ComputeNumNonOvercoveredElementsAndIsRedundant(cvrg);
75 for (const SubsetIndex subset : model_->SubsetRange()) {
76 CHECK_EQ(num_free_elts[subset], num_free_elements_[subset]);
77 if (is_fully_updated_) {
78 CHECK_EQ(is_rdndnt[subset], is_redundant_[subset]);
79 CHECK_EQ(is_rdndnt[subset], num_non_ovrcvrd_elts[subset] == 0);
80 }
81 }
82 return true;
83}
84
89
91 std::tie(cost_, coverage_) = ComputeCostAndCoverage(is_selected_);
92 std::tie(num_uncovered_elements_, num_free_elements_) =
93 ComputeNumUncoveredAndFreeElements(coverage_);
94 std::tie(num_non_overcovered_elements_, is_redundant_) =
95 ComputeNumNonOvercoveredElementsAndIsRedundant(coverage_);
96 is_fully_updated_ = true;
97}
98
100 std::tie(num_non_overcovered_elements_, is_redundant_) =
101 ComputeNumNonOvercoveredElementsAndIsRedundant(coverage_);
102 is_fully_updated_ = true;
103}
104
105std::tuple<Cost, ElementToIntVector> SetCoverInvariant::ComputeCostAndCoverage(
106 const SubsetBoolVector& choices) const {
107 Cost cst = 0.0;
108 ElementToIntVector cvrg(model_->num_elements(), 0);
109 const SparseColumnView& columns = model_->columns();
110 // Initialize coverage, update cost, and compute the coverage for
111 // all the elements covered by the selected subsets.
112 const SubsetCostVector& subset_costs = model_->subset_costs();
113 for (SubsetIndex subset(0); bool b : choices) {
114 if (b) {
115 cst += subset_costs[subset];
116 for (const ElementIndex element : columns[subset]) {
117 ++cvrg[element];
118 }
119 }
120 ++subset;
121 }
122 return {cst, cvrg};
123}
124
126 const absl::Span<const SubsetIndex> focus) const {
127 ElementToIntVector coverage(coverage_.size());
128 for (const SubsetIndex subset : focus) {
129 if (is_selected_[subset]) {
130 for (const ElementIndex element : model_->columns()[subset]) {
131 ++coverage[element];
132 }
133 }
134 }
135 return coverage;
136}
137
138std::tuple<BaseInt, SubsetToIntVector>
139SetCoverInvariant::ComputeNumUncoveredAndFreeElements(
140 const ElementToIntVector& cvrg) const {
141 BaseInt num_uncvrd_elts = model_->num_elements();
142
143 const BaseInt num_subsets(model_->num_subsets());
144 SubsetToIntVector num_free_elts(num_subsets, 0);
145
146 const SparseColumnView& columns = model_->columns();
147 // Initialize number of free elements and number of elements covered 0 or 1.
148 for (const SubsetIndex subset : model_->SubsetRange()) {
149 num_free_elts[subset] = columns[subset].size();
150 }
151
152 const SparseRowView& rows = model_->rows();
153 for (const ElementIndex element : model_->ElementRange()) {
154 if (cvrg[element] >= 1) {
155 --num_uncvrd_elts;
156 for (const SubsetIndex subset : rows[element]) {
157 --num_free_elts[subset];
158 }
159 }
160 }
161 return {num_uncvrd_elts, num_free_elts};
162}
163
164std::tuple<SubsetToIntVector, SubsetBoolVector>
165SetCoverInvariant::ComputeNumNonOvercoveredElementsAndIsRedundant(
166 const ElementToIntVector& cvrg) const {
167 const BaseInt num_subsets(model_->num_subsets());
168 SubsetToIntVector num_cvrg_le_1_elts(num_subsets, 0);
169 SubsetBoolVector is_rdndnt(num_subsets, false);
170
171 const SparseColumnView& columns = model_->columns();
172 // Initialize number of free elements and number of elements covered 0 or 1.
173 for (const SubsetIndex subset : model_->SubsetRange()) {
174 num_cvrg_le_1_elts[subset] = columns[subset].size();
175 }
176
177 const SparseRowView& rows = model_->rows();
178 for (const ElementIndex element : model_->ElementRange()) {
179 if (cvrg[element] >= 2) {
180 for (const SubsetIndex subset : rows[element]) {
181 --num_cvrg_le_1_elts[subset];
182 if (num_cvrg_le_1_elts[subset] == 0) {
183 is_rdndnt[subset] = true;
184 }
185 }
186 }
187 }
188 return {num_cvrg_le_1_elts, is_rdndnt};
189}
190
192 // As of 2024-05-14, this is as fast as "smarter" alternatives that try to
193 // avoid some memory writes by keeping track of already visited subsets.
194 // We also tried to use is_selected_ as a helper, which slowed down
195 // everything.
196 const SubsetIndex num_subsets(model_->num_subsets());
197 SubsetBoolVector last_value_seen(num_subsets, false);
198 for (BaseInt i = 0; i < trace_.size(); ++i) {
199 const SubsetIndex subset(trace_[i].subset());
200 last_value_seen[subset] = trace_[i].decision();
201 }
202 BaseInt w = 0; // Write index.
203 for (BaseInt i = 0; i < trace_.size(); ++i) {
204 const SubsetIndex subset(trace_[i].subset());
205 if (last_value_seen[subset]) {
206 last_value_seen[subset] = false;
207 trace_[w] = SetCoverDecision(subset, true);
208 ++w;
209 }
210 }
211 trace_.resize(w);
212}
213
214bool SetCoverInvariant::ComputeIsRedundant(SubsetIndex subset) const {
215 if (is_fully_updated_) {
216 return is_redundant_[subset];
217 }
218 if (is_selected_[subset]) {
219 for (const ElementIndex element : model_->columns()[subset]) {
220 if (coverage_[element] <= 1) { // If deselected, it will be <= 0...
221 return false;
222 }
223 }
224 } else {
225 for (const ElementIndex element : model_->columns()[subset]) {
226 if (coverage_[element] == 0) { // Cannot be removed from the problem.
227 return false;
228 }
229 }
230 }
231 return true;
232}
233
234void SetCoverInvariant::Flip(SubsetIndex subset, bool incremental_full_update) {
235 if (!is_selected_[subset]) {
236 Select(subset, incremental_full_update);
237 } else {
238 Deselect(subset, incremental_full_update);
239 }
240}
241
242void SetCoverInvariant::Select(SubsetIndex subset,
243 bool incremental_full_update) {
244 if (incremental_full_update) {
246 } else {
247 is_fully_updated_ = false;
248 }
249 DVLOG(1) << "Selecting subset " << subset;
250 DCHECK(!is_selected_[subset]);
251 DCHECK(CheckConsistency());
252 trace_.push_back(SetCoverDecision(subset, true));
253 is_selected_[subset] = true;
254 const SubsetCostVector& subset_costs = model_->subset_costs();
255 cost_ += subset_costs[subset];
256 if (supports_avx512_) {
257 SelectAvx512(subset);
258 return;
259 }
260 const SparseColumnView& columns = model_->columns();
261 const SparseRowView& rows = model_->rows();
262 for (const ElementIndex element : columns[subset]) {
263 if (coverage_[element] == 0) {
264 // `element` will be newly covered.
265 --num_uncovered_elements_;
266 for (const SubsetIndex impacted_subset : rows[element]) {
267 --num_free_elements_[impacted_subset];
268 }
269 } else if (incremental_full_update && coverage_[element] == 1) {
270 // `element` will be newly overcovered.
271 for (const SubsetIndex impacted_subset : rows[element]) {
272 --num_non_overcovered_elements_[impacted_subset];
273 if (num_non_overcovered_elements_[impacted_subset] == 0) {
274 // All the elements in impacted_subset are now overcovered, so it
275 // is removable. Note that this happens only when the last element
276 // of impacted_subset becomes overcovered.
277 DCHECK(!is_redundant_[impacted_subset]);
278 if (is_selected_[impacted_subset]) {
279 new_removable_subsets_.push_back(impacted_subset);
280 }
281 is_redundant_[impacted_subset] = true;
282 }
283 }
284 }
285 // Update coverage. Notice the asymmetry with Deselect where coverage is
286 // **decremented** before being tested. This allows to have more symmetrical
287 // code for conditions.
288 ++coverage_[element];
289 }
290 if (incremental_full_update) {
291 if (is_redundant_[subset]) {
292 new_removable_subsets_.push_back(subset);
293 } else {
294 new_non_removable_subsets_.push_back(subset);
295 }
296 }
297 DCHECK(CheckConsistency());
298}
299
300void SetCoverInvariant::Deselect(SubsetIndex subset,
301 bool incremental_full_update) {
302 if (incremental_full_update) {
304 } else {
305 is_fully_updated_ = false;
306 }
307 DVLOG(1) << "Deselecting subset " << subset;
308 // If already selected, then num_free_elements == 0.
309 DCHECK(is_selected_[subset]);
310 DCHECK_EQ(num_free_elements_[subset], 0);
311 DCHECK(CheckConsistency());
312 trace_.push_back(SetCoverDecision(subset, false));
313 is_selected_[subset] = false;
314 const SubsetCostVector& subset_costs = model_->subset_costs();
315 cost_ -= subset_costs[subset];
316 if (supports_avx512_) {
317 DeselectAvx512(subset);
318 return;
319 }
320 const SparseColumnView& columns = model_->columns();
321 const SparseRowView& rows = model_->rows();
322 for (const ElementIndex element : columns[subset]) {
323 // Update coverage. Notice the asymmetry with Select where coverage is
324 // incremented after being tested.
325 --coverage_[element];
326 if (coverage_[element] == 0) {
327 // `element` is no longer covered.
328 ++num_uncovered_elements_;
329 for (const SubsetIndex impacted_subset : rows[element]) {
330 ++num_free_elements_[impacted_subset];
331 }
332 } else if (incremental_full_update && coverage_[element] == 1) {
333 // `element` will be no longer overcovered.
334 for (const SubsetIndex impacted_subset : rows[element]) {
335 if (num_non_overcovered_elements_[impacted_subset] == 0) {
336 // There is one element of impacted_subset which is not overcovered.
337 // impacted_subset has just become non-removable.
338 DCHECK(is_redundant_[impacted_subset]);
339 if (is_selected_[impacted_subset]) {
340 new_non_removable_subsets_.push_back(impacted_subset);
341 }
342 is_redundant_[impacted_subset] = false;
343 }
344 ++num_non_overcovered_elements_[impacted_subset];
345 }
346 }
347 }
348 // Since subset is now deselected, there is no need
349 // nor meaning in adding it a list of removable or non-removable subsets.
350 // This is a dissymmetry with Select.
351 DCHECK(CheckConsistency());
352}
353
354void SetCoverInvariant::SelectAvx512(SubsetIndex) {
355 LOG(FATAL) << "SelectAvx512 is not implemented";
356}
357
358void SetCoverInvariant::DeselectAvx512(SubsetIndex) {
359 LOG(FATAL) << "DeselectAvx512 is not implemented";
360}
361
362SetCoverSolutionResponse SetCoverInvariant::ExportSolutionAsProto() const {
363 SetCoverSolutionResponse message;
364 message.set_num_subsets(is_selected_.size());
365 Cost lower_bound = std::numeric_limits<Cost>::max();
366 for (const SubsetIndex subset : model_->SubsetRange()) {
367 if (is_selected_[subset]) {
368 message.add_subset(subset.value());
369 }
370 lower_bound = std::min(model_->subset_costs()[subset], lower_bound);
371 }
372 message.set_cost(cost_);
373 message.set_cost_lower_bound(lower_bound);
374 return message;
375}
376
378 const SetCoverSolutionResponse& message) {
379 is_selected_.resize(SubsetIndex(message.num_subsets()), false);
380 for (auto s : message.subset()) {
381 is_selected_[SubsetIndex(s)] = true;
382 }
384 Cost cost = message.cost();
385 CHECK_EQ(cost, cost_);
386}
387
388} // namespace operations_research
A helper class used to store the decisions made during a search.
bool ComputeIsRedundant(SubsetIndex subset) const
SetCoverSolutionResponse ExportSolutionAsProto() const
Returns the current solution as a proto.
void RecomputeInvariant()
Recomputes all the invariants for the current solution.
void LoadSolution(const SubsetBoolVector &solution)
Loads the solution and recomputes the data in the invariant.
void ImportSolutionFromProto(const SetCoverSolutionResponse &message)
Imports the solution from a proto.
const ElementToIntVector & coverage() const
Returns vector containing number of subsets covering each element.
Cost cost() const
Returns the cost of current solution.
ElementToIntVector ComputeCoverageInFocus(absl::Span< const SubsetIndex > focus) const
void ClearRemovabilityInformation()
Clear the removability information.
util_intops::StrongIntRange< ElementIndex > ElementRange() const
const SparseRowView & rows() const
Row view of the set covering problem.
util_intops::StrongIntRange< SubsetIndex > SubsetRange() const
Access to the ranges of subsets and elements.
const SubsetCostVector & subset_costs() const
Vector of costs for each subset.
void CreateSparseRowView()
Creates the sparse ("dual") representation of the problem.
const SparseColumnView & columns() const
Column view of the set covering problem.
void assign(size_type n, const value_type &val)
void resize(size_type new_size)
int64_t b
Definition table.cc:45
double lower_bound
double solution
In SWIG mode, we don't want anything besides these top-level includes.
util_intops::StrongVector< SubsetIndex, BaseInt, IntAllocator > SubsetToIntVector
util_intops::StrongVector< ElementIndex, BaseInt, IntAllocator > ElementToIntVector
util_intops::StrongVector< SubsetIndex, bool > SubsetBoolVector
util_intops::StrongVector< SubsetIndex, SparseColumn > SparseColumnView
double Cost
Basic non-strict type for cost. The speed penalty for using double is ~2%.
util_intops::StrongVector< SubsetIndex, Cost, CostAllocator > SubsetCostVector
util_intops::StrongVector< ElementIndex, SparseRow > SparseRowView
trees with all degrees equal w the current value of w
std::string message
Definition trace.cc:397