Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
set_cover_submodel.h
Go to the documentation of this file.
1// Copyright 2025 Francesco Cavaliere
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_SUBMODEL_H
15#define OR_TOOLS_SET_COVER_SET_COVER_SUBMODEL_H
16
18
20
21// TODO(anyone): since we are working within the scp namespace, the "SetCover*"
22// prefix became redundant and can be removed. For now, only redefine it
23// locally.
25
26// Forward declarations, see below for the definition of the classes.
27struct PrimalDualState;
28struct Solution;
29struct DualState;
30
31// The CFT algorithm generates sub-models in two distinct ways:
32//
33// 1. It fixes specific columns (incrementally) into any generated solution.
34// Once a column is fixed, it is excluded from future decisions, as it is
35// already part of the solution. Additionally, rows that are covered by the
36// fixed columns are removed from consideration as well, along with any
37// columns that exclusively cover those rows, as they become redundant. The
38// fixing process starts with the entire model and progressively fixes more
39// columns until it becomes empty. A "view-based" sub-model is well-suited
40// for this part.
41//
42// 2. The CFT mostly works on a "core" sub-model by focusing on a subset of
43// columns. The core model is derived from the original model but is
44// significantly smaller, as it typically includes only a limited number of
45// columns per row (on average, around six columns per row). Unlike the
46// incremental nature of column fixing, core models are constructed from
47// scratch during each update. This type of small model can take advantage of
48// a Model object which stores the sub-model explicitly in memory, avoiding
49// looping over "inactive" columns and rows. Both SubModelView and CoreModel
50// can be used as a core model.
51//
52// Two types of "core-model" representations are implemented, both of which can
53// be used interchangeably:
54//
55// 1. SubModelView: A lightweight view of the original model. It dynamically
56// filters and exposes only the active rows and columns from the original
57// data structures, skipping "inactive" items.
58//
59// 2. CoreModel: A fully compacted and explicit representation of a sub-model.
60// It stores the filtered data explicitly, making it more suitable
61// for scenarios where compact storage and faster access are required.
62//
63// While CoreModel stores an explicit representation of the sub-model,
64// SubModelView maintains vectors sized according to the original model's
65// dimensions. As a result, depending on the dimensions of the original model,
66// CoreModel can actually be more memory-efficient.
67class SubModelView;
68class CoreModel;
70
71// `SubModelView` provides a mechanism to interact with a subset of the rows and
72// columns of a SetCoverModel, effectively creating a filtered view of the
73// model. This abstraction allows operations to be performed on a restricted
74// portion of the model without modifying the original data structure. The
75// filtering is achieved using index lists and sizes vectors, which define the
76// active rows and columns. This approach ensures flexibility and avoids
77// unnecessary duplication of data. Columns/rows sizes are uses to both keep
78// track of the number of elements in them and also provide the "activation"
79// status: (item size == 0) <==> inactive
80// SubModelView inherits from IndexListSubModelView, which provides the "view"
81// machinery.
83 using base_view = IndexListModelView;
84
85 public:
86 // Empty initialization to facilitate delayed construction
87 SubModelView() = default;
88
89 // Identity sub-model: all items are considered
90 SubModelView(const Model* model);
91
92 // Focus construction: create a sub-model with only the required items
93 SubModelView(const Model* model,
94 const std::vector<FullSubsetIndex>& columns_focus);
95
96 virtual ~SubModelView() = default;
97
99
100 // Current fixed cost: sum of the cost of the fixed columns
101 Cost fixed_cost() const { return fixed_cost_; }
102
103 // List of fixed columns.
104 const std::vector<FullSubsetIndex>& fixed_columns() const {
105 return fixed_columns_;
106 }
107
108 // Redefine the active items. The new sub-model will ignore all columns not in
109 // focus and (optionally) the rows for which row_flags is not true. It does
110 // not overwrite the current fixing.
111 void SetFocus(const std::vector<FullSubsetIndex>& columns_focus);
112
113 // Fix the provided columns, removing them for the submodel. Rows now covered
114 // by fixed columns are also removed from the submodel along with non-fixed
115 // columns that only cover those rows.
116 virtual Cost FixMoreColumns(const std::vector<SubsetIndex>& columns_to_fix);
117
118 virtual void ResetColumnFixing(
119 const std::vector<FullSubsetIndex>& columns_to_fix,
120 const DualState& state);
121
122 // Hook function for specializations. This function can be used to define a
123 // "small" core model considering a subset of the full model through the use
124 // of column-generation or by only selecting columns with good reduced cost in
125 // the full model.
126 virtual bool UpdateCore(Cost best_lower_bound,
127 const ElementCostVector& best_multipliers,
128 const Solution& best_solution, bool force) {
129 return false;
130 }
131
133 return StrongModelView(full_model_);
134 }
135
136 private:
137 void ResetToIdentitySubModel();
138
139 // Pointer to the original model
140 const Model* full_model_;
141
142 // Columns/rows sizes after filtering (size==0 <==> inactive)
143 SubsetToIntVector cols_sizes_;
144 ElementToIntVector rows_sizes_;
145
146 // List of columns/rows currectly active
147 std::vector<SubsetIndex> cols_focus_;
148 std::vector<ElementIndex> rows_focus_;
149
150 // Fixing data
151 std::vector<FullSubsetIndex> fixed_columns_;
152 Cost fixed_cost_ = .0;
153};
154
155// CoreModel stores a subset of the filtered columns and rows in an explicit
156// Model object.
157// The indices are compacted and mapped to the range [0, <sub-model-size>],
158// effectively creating a smaller set-covering model. Similar to SubModelView,
159// the core model supports column fixing and focusing on a subset of the
160// original model. Mappings are maintained to translate indices back to the
161// original model space.
162class CoreModel : private Model {
163 public:
164 // Empty initialization to facilitate delayed construction
165 CoreModel() = default;
166
167 // Identity sub-model: all items are considered
168 CoreModel(const Model* model);
169
170 // Focus construction: create a sub-model with only the required items
171 CoreModel(const Model* model,
172 const std::vector<FullSubsetIndex>& columns_focus);
173
174 virtual ~CoreModel() = default;
175
177 BaseInt num_subsets() const { return full_model_.num_subsets(); }
178 BaseInt num_elements() const { return full_model_.num_elements(); }
181 BaseInt column_size(SubsetIndex j) const {
182 DCHECK(SubsetIndex() <= j && j < SubsetIndex(num_subsets()));
183 return columns()[j].size();
184 }
185 BaseInt row_size(ElementIndex i) const {
186 DCHECK(ElementIndex() <= i && i < ElementIndex(num_elements()));
187 return rows()[i].size();
188 }
189
190 FullElementIndex MapCoreToFullElementIndex(ElementIndex core_i) const {
191 DCHECK(ElementIndex() <= core_i && core_i < ElementIndex(num_elements()));
192 return core2full_row_map_[core_i];
193 }
194 ElementIndex MapFullToCoreElementIndex(FullElementIndex full_i) const {
195 DCHECK(FullElementIndex() <= full_i &&
196 full_i < FullElementIndex(num_elements()));
197 return full2core_row_map_[full_i];
198 }
199 FullSubsetIndex MapCoreToFullSubsetIndex(SubsetIndex core_j) const {
200 DCHECK(SubsetIndex() <= core_j && core_j < SubsetIndex(num_subsets()));
201 return core2full_col_map_[core_j];
202 }
203 // Member function relevant for the CFT inherited from Model
204 using Model::columns;
206 using Model::rows;
208 using Model::SubsetRange;
209
211
212 // Current fixed cost: sum of the cost of the fixed columns
213 Cost fixed_cost() const { return fixed_cost_; }
214 // List of fixed columns.
215 const std::vector<FullSubsetIndex>& fixed_columns() const {
216 return fixed_columns_;
217 }
218
219 // Redefine the active items. The new sub-model will ignore all columns not in
220 // focus and (optionally) the rows for which row_flags is not true. It does
221 // not overwrite the current fixing.
222 void SetFocus(const std::vector<FullSubsetIndex>& columns_focus);
223
224 // Fix the provided columns, removing them for the submodel. Rows now covered
225 // by fixed columns are also removed from the submodel along with non-fixed
226 // columns that only cover those rows.
227 virtual Cost FixMoreColumns(const std::vector<SubsetIndex>& columns_to_fix);
228
229 virtual void ResetColumnFixing(
230 const std::vector<FullSubsetIndex>& columns_to_fix,
231 const DualState& state);
232
233 // Hook function for specializations. This function can be used to define a
234 // "small" core model considering a subset of the full model through the use
235 // of column-generation or by only selecting columns with good reduced cost in
236 // the full model.
237 virtual bool UpdateCore(Cost best_lower_bound,
238 const ElementCostVector& best_multipliers,
239 const Solution& best_solution, bool force) {
240 return false;
241 }
242
243 StrongModelView StrongTypedFullModelView() const { return full_model_; }
244
245 private:
246 void MarkNewFixingInMaps(const std::vector<SubsetIndex>& columns_to_fix);
247 CoreToFullElementMapVector MakeOrFillBothRowMaps();
248 Model MakeNewCoreModel(const CoreToFullElementMapVector& new_c2f_col_map);
249 void ResetToIdentitySubModel();
250
251 // Pointer to the original model
252 StrongModelView full_model_;
253
254 FullToCoreElementMapVector full2core_row_map_;
255 CoreToFullElementMapVector core2full_row_map_;
256 CoreToFullSubsetMapVector core2full_col_map_;
257
258 // Fixing data
259 Cost fixed_cost_ = .0;
260 std::vector<FullSubsetIndex> fixed_columns_;
261
262 static constexpr SubsetIndex null_subset_index =
263 std::numeric_limits<SubsetIndex>::max();
264 static constexpr ElementIndex null_element_index =
265 std::numeric_limits<ElementIndex>::max();
266 static constexpr FullSubsetIndex null_full_subset_index =
267 std::numeric_limits<FullSubsetIndex>::max();
268 static constexpr FullElementIndex null_full_element_index =
269 std::numeric_limits<FullElementIndex>::max();
270};
271
272template <typename SubModelT>
273bool ValidateSubModel(const SubModelT& model) {
274 if (model.num_elements() <= 0) {
275 std::cerr << "Sub-Model has no elements.\n";
276 return false;
277 }
278 if (model.num_subsets() <= 0) {
279 std::cerr << "Sub-Model has no subsets.\n";
280 return false;
281 }
282
283 for (SubsetIndex j : model.SubsetRange()) {
284 const auto& column = model.columns()[j];
285 if (model.column_size(j) == 0) {
286 std::cerr << "Column " << j << " is empty.\n";
287 return false;
288 }
289 BaseInt j_size = std::distance(column.begin(), column.end());
290 if (j_size != model.column_size(j)) {
291 std::cerr << "Sub-Model size mismatch on column " << j << ", " << j_size
292 << " != " << model.column_size(j) << "\n";
293 return false;
294 }
295 }
296
297 for (ElementIndex i : model.ElementRange()) {
298 const auto& row = model.rows()[i];
299 if (model.row_size(i) == 0) {
300 std::cerr << "Row " << i << " is empty.\n";
301 return false;
302 }
303 BaseInt i_size = std::distance(row.begin(), row.end());
304 if (i_size != model.row_size(i)) {
305 std::cerr << "Sub-Model size mismatch on row " << i << ", " << i_size
306 << " != " << model.row_size(i) << "\n";
307 return false;
308 }
309 }
310 return true;
311}
312
313} // namespace operations_research::scp
314#endif /* OR_TOOLS_SET_COVER_SET_COVER_SUBMODEL_H */
Main class for describing a weighted set-covering problem.
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.
const SparseColumnView & columns() const
Column view of the set covering problem.
virtual void ResetColumnFixing(const std::vector< FullSubsetIndex > &columns_to_fix, const DualState &state)
void SetFocus(const std::vector< FullSubsetIndex > &columns_focus)
const std::vector< FullSubsetIndex > & fixed_columns() const
List of fixed columns.
const SparseRowView & rows() const
Row view of the set covering problem.
CoreModel()=default
Empty initialization to facilitate delayed construction.
FullElementIndex MapCoreToFullElementIndex(ElementIndex core_i) const
StrongModelView StrongTypedFullModelView() const
ElementIndex MapFullToCoreElementIndex(FullElementIndex full_i) const
FullSubsetIndex MapCoreToFullSubsetIndex(SubsetIndex core_j) const
BaseInt column_size(SubsetIndex j) const
virtual bool UpdateCore(Cost best_lower_bound, const ElementCostVector &best_multipliers, const Solution &best_solution, bool force)
Cost fixed_cost() const
Current fixed cost: sum of the cost of the fixed columns.
BaseInt row_size(ElementIndex i) const
const SparseColumnView & columns() const
Member function relevant for the CFT inherited from Model.
virtual Cost FixMoreColumns(const std::vector< SubsetIndex > &columns_to_fix)
Cost fixed_cost() const
Current fixed cost: sum of the cost of the fixed columns.
virtual bool UpdateCore(Cost best_lower_bound, const ElementCostVector &best_multipliers, const Solution &best_solution, bool force)
StrongModelView StrongTypedFullModelView() const
void SetFocus(const std::vector< FullSubsetIndex > &columns_focus)
const std::vector< FullSubsetIndex > & fixed_columns() const
List of fixed columns.
SubModelView()=default
Empty initialization to facilitate delayed construction.
virtual void ResetColumnFixing(const std::vector< FullSubsetIndex > &columns_to_fix, const DualState &state)
virtual Cost FixMoreColumns(const std::vector< SubsetIndex > &columns_to_fix)
bool ValidateSubModel(const SubModelT &model)
util_intops::StrongVector< SubsetIndex, FullSubsetIndex > CoreToFullSubsetMapVector
util_intops::StrongVector< FullElementIndex, ElementIndex > FullToCoreElementMapVector
util_intops::StrongVector< ElementIndex, FullElementIndex > CoreToFullElementMapVector
util_intops::StrongVector< SubsetIndex, BaseInt > SubsetToIntVector
Definition base_types.h:65
util_intops::StrongVector< ElementIndex, Cost > ElementCostVector
Definition base_types.h:59
util_intops::StrongVector< ElementIndex, BaseInt > ElementToIntVector
Definition base_types.h:64
double Cost
Basic non-strict type for cost. The speed penalty for using double is ~2%.
Definition base_types.h:32
Utility aggregate to store and pass around both primal and dual states.