Google OR-Tools v9.14
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 OR_TOOLS_SET_COVER_SET_COVER_INVARIANT_H_
15#define OR_TOOLS_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 // OR_TOOLS_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)
Recomputes the invariant to the given consistency level.
const SubsetBoolVector & is_redundant() const
BaseInt ComputeCardinality() const
Returns the cardinality of the solution in the invariant.
const SubsetToIntVector & num_free_elements() const
SetCoverSolutionResponse ExportSolutionAsProto() const
Returns the current solution as a proto.
const std::vector< SetCoverDecision > & trace() const
Returns the vector of the decisions which have led to the current solution.
bool CheckConsistency(ConsistencyLevel consistency) const
Checks the consistency of the invariant at the given consistency level.
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.
bool Deselect(SubsetIndex subset, ConsistencyLevel consistency)
const SubsetToIntVector & num_coverage_le_1_elements() const
const SubsetBoolVector & is_selected() const
Returns the subset assignment vector.
SetCoverModel * model() const
Returns the weighted set covering model to which the state applies.
Cost LowerBound() const
Returns the lower bound of the problem.
void ReportLowerBound(Cost lower_bound, bool is_cost_consistent)
const std::vector< SubsetIndex > & newly_removable_subsets() const
Returns the subsets that become removable after the last update.
bool Select(SubsetIndex subset, ConsistencyLevel consistency)
const ElementToIntVector & coverage() const
Returns vector containing number of subsets covering each element.
BaseInt ComputeNumFreeElements(SubsetIndex subset) const
Computes the number of free (uncovered) elements in the given subset.
void LoadTraceAndCoverage(const std::vector< SetCoverDecision > &trace, const ElementToIntVector &coverage)
Cost cost() const
Returns the cost of current solution.
ElementToIntVector ComputeCoverageInFocus(absl::Span< const SubsetIndex > focus) const
void Clear()
Clears the invariant. Also called by Initialize.
void ClearRemovabilityInformation()
Clear the removability information.
BaseInt num_uncovered_elements() const
Returns the number of uncovered elements.
const std::vector< SubsetIndex > & newly_non_removable_subsets() const
Returns the subsets that become non removable after the last update.
Main class for describing a weighted set-covering problem.
In SWIG mode, we don't want anything besides these top-level includes.
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
double Cost
Basic non-strict type for cost. The speed penalty for using double is ~2%.
Definition base_types.h:32