Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
set_cover_invariant.h
Go to the documentation of this file.
1// Copyright 2010-2025 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
14#ifndef ORTOOLS_SET_COVER_SET_COVER_INVARIANT_H_
15#define ORTOOLS_SET_COVER_SET_COVER_INVARIANT_H_
16
17#include <tuple>
18#include <vector>
19
20#include "absl/log/check.h"
21#include "absl/types/span.h"
25
26namespace operations_research {
27
28// A helper class used to store the decisions made during a search.
30 public:
31 SetCoverDecision() : decision_(0) {}
32
33 SetCoverDecision(SubsetIndex subset, bool value) {
34 static_assert(sizeof(subset) == sizeof(decision_));
35 DCHECK_GE(subset.value(), 0);
36 decision_ = value ? subset.value() : ~subset.value();
37 }
38
39 SubsetIndex subset() const {
40 return SubsetIndex(decision_ >= 0 ? decision_ : ~decision_);
41 }
42
43 bool decision() const { return decision_ >= 0; }
44
45 private:
46 BaseInt decision_;
47};
48
49// SetCoverInvariant does the bookkeeping for a solution to the
50// SetCoverModel passed as argument.
51// The state of a SetCoverInvariant instance is uniquely defined by a
52// SubsetBoolVector representing whether a subset is selected in the solution
53// or not.
54//
55// See https://cs.brown.edu/research/pubs/pdfs/1999/Michel-1999-LML.pdf
56// for an explanation of the terminology.
57//
58// A SetCoverInvariant is (relatively) small:
59// is_selected_: a partial solution, vector of Booleans of size #subsets.
60// From this, the following can be computed:
61// coverage_ : number of times an element is covered;
62// num_free_elements_: number of elements in a subset that are uncovered.
63// num_non_overcovered_elements_: the number of elements of a subset that
64// are covered 1 time or less (not overcovered) in the current solution;
65// is_redundant_, whether a subset can be removed from the solution.
66// is_redundant_[subset] == (num_non_overcovered_elements_[subet] == 0).
68 public:
69 // The consistency level of the invariant.
70 // The values denote the level of consistency of the invariant.
71 // There is an order between the levels, and the invariant is consistent at
72 // level k if it is consistent at all levels lower than k.
73 // The consistency level that is the most natural is to use kFreeAndUncovered,
74 // since it enables to implement most heuristics.
75 // kCostAndCoverage is used by LazyElementDegree, a fast greedy heuristic.
76 // kRedundancy is used for GuidedLocalSearch, because knowing whether a
77 // subset is redundant incrementally is faster than recomputing the
78 // information over and over again.
79 // Below, the quantities that are maintained at each level are listed.
80 enum class ConsistencyLevel {
81 kInconsistent = 0, // The invariant is not consistent.
82 kCostAndCoverage, // cost_ and coverage_.
83 kFreeAndUncovered, // num_free_elements_ and num_uncovered_elements_.
84 kRedundancy // is_redundant_ and num_non_overcovered_elements_.
85 };
86
87 // Constructs an empty weighted set covering solver state.
88 // The model may not change after the invariant was built.
89 explicit SetCoverInvariant(SetCoverModel* m) : model_(m) { Initialize(); }
90
91 // Initializes the solver once the data is set. The model cannot be changed
92 // afterwards.
93 void Initialize();
94
95 // Clears the invariant. Also called by Initialize.
96 void Clear();
97
98 // Returns the weighted set covering model to which the state applies.
99 SetCoverModel* model() const { return model_; }
100
101 const SetCoverModel* const_model() const { return model_; }
102
103 // Reports the lower bound of the problem.
104 // If is_cost_consistent is true, the lower bound is ignored and the cost is
105 // reported instead.
106 void ReportLowerBound(Cost lower_bound, bool is_cost_consistent) {
107 lower_bound_ = lower_bound;
108 is_cost_consistent_ = is_cost_consistent;
109 }
110
111 // Returns the cost of current solution.
112 Cost cost() const { return cost_; }
113
114 // Returns the cost of the current solution, or the lower bound if the
115 // solution is a relaxation.
117 // For the time being we assume that we either have a feasible solution or
118 // a relaxation.
119 return is_cost_consistent_ ? cost_ : lower_bound_;
120 }
121
122 // Returns true if the cost of the current solution is consistent with the
123 // solution.
124 bool is_cost_consistent() const { return is_cost_consistent_; }
125
126 // Returns the lower bound of the problem.
127 Cost LowerBound() const { return lower_bound_; }
128
129 // Returns the number of uncovered elements.
130 BaseInt num_uncovered_elements() const { return num_uncovered_elements_; }
131
132 // Returns the subset assignment vector.
133 const SubsetBoolVector& is_selected() const { return is_selected_; }
134
135 // Returns vector containing the number of elements in each subset that are
136 // not covered in the current solution.
138 return num_free_elements_;
139 }
140
141 // Returns the vector of numbers of free or exactly covered elements for
142 // each subset.
144 return num_non_overcovered_elements_;
145 }
146 // Returns vector containing number of subsets covering each element.
147 const ElementToIntVector& coverage() const { return coverage_; }
148
149 // Returns a vector containing the number of subsets within `focus` covering
150 // each element. Subsets that are without `focus` are not considered.
152 absl::Span<const SubsetIndex> focus) const;
153
154 // Returns vector of Booleans telling whether each subset can be removed from
155 // the solution.
156 const SubsetBoolVector& is_redundant() const { return is_redundant_; }
157
158 // Returns the vector of the decisions which have led to the current solution.
159 const std::vector<SetCoverDecision>& trace() const { return trace_; }
160
161 // Clears the trace.
162 void ClearTrace() { trace_.clear(); }
163
164 // Returns the cardinality of the solution in the invariant.
166 BaseInt cardinality = 0;
167 for (BaseInt i = 0; i < trace().size(); ++i) {
168 const SubsetIndex subset = trace()[i].subset();
169 DCHECK_EQ(trace()[i].decision(), is_selected_[subset]);
170 if (is_selected_[subset]) {
171 ++cardinality;
172 }
173 }
174 return cardinality;
175 }
176
177 // Clear the removability information.
179 newly_removable_subsets_.clear();
180 newly_non_removable_subsets_.clear();
181 }
182
183 // Returns the subsets that become removable after the last update.
184 const std::vector<SubsetIndex>& newly_removable_subsets() const {
185 return newly_removable_subsets_;
186 }
187
188 // Returns the subsets that become non removable after the last update.
189 const std::vector<SubsetIndex>& newly_non_removable_subsets() const {
190 return newly_non_removable_subsets_;
191 }
192
193 // Compresses the trace so that:
194 // - each subset appears only once,
195 // - there are only "positive" decisions.
196 // This trace is equivalent to the original trace in the sense that the cost
197 // and the covered elements are the same.
198 // This can be used to recover the solution by indices after local search.
199 void CompressTrace();
200
201 // Loads the solution and recomputes the data in the invariant.
203
204 // Loads the trace and the coverage. When both the trace and the coverage are
205 // loaded, the invariant is consistent at level kCostAndCoverage.
206 // It's also faster than to load a solution and recompute the cost and
207 // coverage. The trace and the coverage are expected to be consistent,
208 // otherwise the behavior is undefined (i.e. the invariant is not consistent,
209 // unless the code is run in DEBUG mode).
210 void LoadTraceAndCoverage(const std::vector<SetCoverDecision>& trace,
212
213 // Checks the consistency of the invariant at the given consistency level.
214 bool CheckConsistency(ConsistencyLevel consistency) const;
215
216 // Recomputes the invariant to the given consistency level.
217 void Recompute(ConsistencyLevel target_consistency);
218
219 // Returns true if the subset is redundant within the current solution, i.e.
220 // when all its elements are already covered twice. Note that the set need
221 // not be selected for this to happen.
222 // TODO(user): Implement this using AVX-512?
223 bool ComputeIsRedundant(SubsetIndex subset) const;
224
225 // Computes the number of free (uncovered) elements in the given subset.
226 BaseInt ComputeNumFreeElements(SubsetIndex subset) const;
227
228 // Includes subset in the solution by setting is_selected_[subset] to true
229 // and incrementally updating the invariant to the given consistency level.
230 // Returns false if the subset is already selected.
231 // This allows a programming style where Select is embedded in a DCHECK, or
232 // to write `if (Select(subset, consistency)) { ... }`, which is more readable
233 // than `if (!is_selected_[subset]) { Select(subset, consistency); ... }`
234 // The above is a good programming style for example to count how many
235 // elements were actually set.
236 bool Select(SubsetIndex subset, ConsistencyLevel consistency);
237
238 // Excludes subset from the solution by setting is_selected_[subset] to false
239 // and incrementally updating the invariant to the given consistency level.
240 // Returns false if the subset is already deselected.
241 bool Deselect(SubsetIndex subset, ConsistencyLevel consistency);
242
243 // Returns the current solution as a proto.
245
246 // Imports the solution from a proto.
248
249 private:
250 // Computes the cost and the coverage vector for the given choices.
251 // Temporarily uses |E| BaseInts.
252 std::tuple<Cost, ElementToIntVector> ComputeCostAndCoverage(
253 const SubsetBoolVector& choices) const;
254
255 // Computes the global number of uncovered elements and the
256 // vector containing the number of free elements for each subset from
257 // a coverage vector.
258 // Temporarily uses |S| BaseInts.
259 std::tuple<BaseInt, // Number of uncovered elements,
260 SubsetToIntVector> // Vector of number of free elements.
261 ComputeNumUncoveredAndFreeElements(const ElementToIntVector& cvrg) const;
262
263 // Computes the vector containing the number of non-overcovered elements per
264 // subset and the Boolean vector telling whether a subset is redundant w.r.t.
265 // the current solution.
266 // Temporarily uses |S| BaseInts.
267 std::tuple<SubsetToIntVector, // Number of non-overcovered elements,
268 SubsetBoolVector> // Redundancy for each of the subsets.
269 ComputeRedundancyInfo(const ElementToIntVector& cvrg) const;
270
271 // Returns true if the current consistency level consistency_ is lower than
272 // cheched_consistency and the desired consistency is higher than
273 // cheched_consistency.
274 bool NeedToRecompute(ConsistencyLevel cheched_consistency,
275 ConsistencyLevel target_consistency);
276
277 // The weighted set covering model on which the solver is run.
278 SetCoverModel* model_;
279
280 // Current cost.
281 Cost cost_;
282
283 // True it the cost is consistent with the solution. Used to decide whether to
284 // report the cost or the lower bound.
285 // This is initialized to true, and set to false when a lower bound is
286 // reported without a feasible solution.
287 bool is_cost_consistent_;
288
289 // The lower bound of the problem, when has_relaxation_ is true.
290 // By default it is 0.0.
291 Cost lower_bound_;
292
293 // The number of uncovered (or free) elements in the current solution.
295
296 // Current assignment.
297 // Takes |S| bits.
298 SubsetBoolVector is_selected_;
299
300 // A trace of the decisions, i.e. a list of decisions (subset, Boolean) that
301 // lead to the current solution.
302 // Takes at most |S| BaseInts.
303 std::vector<SetCoverDecision> trace_;
304
305 // The coverage of an element is the number of used subsets which contains
306 // the said element.
307 // Takes |E| BaseInts
308 ElementToIntVector coverage_;
309
310 // A vector that for each subset gives the number of free elements, i.e.
311 // elements whose coverage is 0.
312 // problem.
313 // Takes |S| BaseInts.
315
316 // Counts the number of free or exactly covered elements, i.e. whose coverage
317 // is 0 or 1.
318 // Takes at most |S| BaseInts. (More likely a few percent of that).
319 SubsetToIntVector num_non_overcovered_elements_;
320
321 // True if the subset is redundant, i.e. can be removed from the solution
322 // without making it infeasible.
323 // Takes |S| bits.
324 SubsetBoolVector is_redundant_;
325
326 // Subsets that became removable after the last update.
327 // Takes at most |S| BaseInts. (More likely a few percent of that).
328 std::vector<SubsetIndex> newly_removable_subsets_;
329
330 // Subsets that became non removable after the last update.
331 // Takes at most |S| BaseInts. (More likely a few percent of that).
332 std::vector<SubsetIndex> newly_non_removable_subsets_;
333
334 // Denotes the consistency level of the invariant.
335 // Some algorithms may need to recompute the invariant to a higher consistency
336 // level.
337 // TODO(user): think of making the enforcement of the consistency level
338 // automatic at the constructor level of the heuristic algorithms.
339 ConsistencyLevel consistency_level_;
340};
341
342} // namespace operations_research
343#endif // ORTOOLS_SET_COVER_SET_COVER_INVARIANT_H_
SetCoverDecision(SubsetIndex subset, bool value)
const SetCoverModel * const_model() const
bool ComputeIsRedundant(SubsetIndex subset) const
void Recompute(ConsistencyLevel target_consistency)
const SubsetBoolVector & is_redundant() const
const SubsetToIntVector & num_free_elements() const
SetCoverSolutionResponse ExportSolutionAsProto() const
const std::vector< SetCoverDecision > & trace() const
bool CheckConsistency(ConsistencyLevel consistency) const
void LoadSolution(const SubsetBoolVector &solution)
void ImportSolutionFromProto(const SetCoverSolutionResponse &message)
bool Deselect(SubsetIndex subset, ConsistencyLevel consistency)
const SubsetToIntVector & num_coverage_le_1_elements() const
const SubsetBoolVector & is_selected() const
void ReportLowerBound(Cost lower_bound, bool is_cost_consistent)
const std::vector< SubsetIndex > & newly_removable_subsets() const
bool Select(SubsetIndex subset, ConsistencyLevel consistency)
const ElementToIntVector & coverage() const
BaseInt ComputeNumFreeElements(SubsetIndex subset) const
void LoadTraceAndCoverage(const std::vector< SetCoverDecision > &trace, const ElementToIntVector &coverage)
ElementToIntVector ComputeCoverageInFocus(absl::Span< const SubsetIndex > focus) const
const std::vector< SubsetIndex > & newly_non_removable_subsets() const
OR-Tools root namespace.
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
util_intops::StrongVector< SubsetIndex, BaseInt > SubsetToIntVector
Definition base_types.h:65
util_intops::StrongVector< ElementIndex, BaseInt > ElementToIntVector
Definition base_types.h:64
util_intops::StrongVector< SubsetIndex, bool > SubsetBoolVector
Definition base_types.h:71