Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
set_cover_lagrangian.cc
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
15
16#include <algorithm>
17#include <cstdlib>
18#include <limits>
19#include <tuple>
20#include <vector>
21
22#include "absl/log/check.h"
23#include "absl/synchronization/blocking_counter.h"
28
29namespace operations_research {
30
31// Notes from a discussion with Luca Accorsi (accorsi@) and Francesco Cavaliere
32// regarding [1]:
33// - the 3-phase algorithm in the paper actually uses pricing (which would
34// better be called "partial" pricing),
35// - the columns that were used in the preceding solutions should be fixed,
36// because otherwise it diversifies too much and degrades the best solution
37// (under "queue" in the paper).
38// - the median algorithm is already in the STL (nth_element).
39
40// Denoted as u in [1], it is a dual vector: a column vector of nonnegative
41// (zero is included) multipliers for the different constraints.
42// A deterministic way to compute a feasible (non-optimal) u:
43// For all element indices i, u_i = min {j \in J_i} c_j / |I_j|, where
44// |I_j| denotes the number of elements covered by subset j.
45//
46// Concerning the fundamental ideas behind this approach, one may refer to [2].
48 ElementCostVector multipliers(model_.num_elements(),
49 std::numeric_limits<Cost>::infinity());
50 SubsetCostVector marginal_costs(model_.num_subsets());
51 // TODO(user): Parallelize this.
52 for (const SubsetIndex subset : model_.SubsetRange()) {
53 marginal_costs[subset] =
54 model_.subset_costs()[subset] / model_.columns()[subset].size();
55 }
56 // TODO(user): Parallelize this.
57 for (const ElementIndex element : model_.ElementRange()) {
58 // Minimum marginal cost to cover this element.
59 Cost min_marginal_cost = std::numeric_limits<Cost>::infinity();
60 const SparseRowView& rows = model_.rows();
61 // TODO(user): use std::min_element on rows[element] with a custom
62 // comparator that gets marginal_costs[subset]. Check performance.
63 for (const SubsetIndex subset : rows[element]) {
64 min_marginal_cost = std::min(min_marginal_cost, marginal_costs[subset]);
65 }
66 multipliers[element] = min_marginal_cost;
67 }
68 return multipliers;
69}
70
71namespace {
72// Computes the scalar product between a column and a vector of duals.
73// Profiling has shown that this is where most of the time is spent.
74// TODO(user): make this visible to other algorithms.
75// TODO(user): Investigate.
76Cost ScalarProduct(const SparseColumn& column, const ElementCostVector& dual) {
77 Cost result = 0.0;
78 for (const ColumnEntryIndex pos : column.index_range()) {
79 result += dual[column[pos]];
80 }
81 return result;
82}
83
84// Computes the reduced costs for a subset of subsets.
85// This is a helper function for ParallelComputeReducedCosts().
86// It is called on a slice of subsets, defined by slice_start and slice_end.
87// The reduced costs are computed using the multipliers vector.
88// The columns of the subsets are given by the columns view.
89// The result is stored in reduced_costs.
90void FillReducedCostsSlice(SubsetIndex slice_start, SubsetIndex slice_end,
91 const SubsetCostVector& costs,
92 const ElementCostVector& multipliers,
93 const SparseColumnView& columns,
94 SubsetCostVector* reduced_costs) {
95 for (SubsetIndex subset = slice_start; subset < slice_end; ++subset) {
96 (*reduced_costs)[subset] =
97 costs[subset] - ScalarProduct(columns[subset], multipliers);
98 }
99}
100
101BaseInt BlockSize(BaseInt size, int num_threads) {
102 return 1 + (size - 1) / num_threads;
103}
104} // namespace
105
106// Computes the reduced costs for all subsets in parallel using ThreadPool.
108 const SubsetCostVector& costs, const ElementCostVector& multipliers) const {
109 const SubsetIndex num_subsets(model_.num_subsets());
110 const SparseColumnView& columns = model_.columns();
111 SubsetCostVector reduced_costs(num_subsets);
112 // TODO(user): compute a close-to-optimal k-subset partitioning of the columns
113 // based on their sizes. [***]
114 const SubsetIndex block_size(BlockSize(num_subsets.value(), num_threads_));
115 absl::BlockingCounter num_threads_running(num_threads_);
116 SubsetIndex slice_start(0);
117 for (int thread_index = 0; thread_index < num_threads_; ++thread_index) {
118 const SubsetIndex slice_end =
119 std::min(slice_start + block_size, num_subsets);
120 thread_pool_->Schedule([&num_threads_running, slice_start, slice_end,
121 &costs, &multipliers, &columns, &reduced_costs]() {
122 FillReducedCostsSlice(slice_start, slice_end, costs, multipliers, columns,
123 &reduced_costs);
124 num_threads_running.DecrementCount();
125 });
126 slice_start = slice_end;
127 }
128 num_threads_running.Wait();
129 return reduced_costs;
130}
131
132// Reduced cost (row vector). Denoted as c_j(u) in [1], right after equation
133// (5). For a subset j, c_j(u) = c_j - sum_{i \in I_j}.u_i. I_j is the set of
134// indices for elements in subset j. For a general Integer Program A.x <= b,
135// this would be:
136// c_j(u) = c_j - sum_{i \in I_j} a_{ij}.u_i
138 const SubsetCostVector& costs, const ElementCostVector& multipliers) const {
139 const SparseColumnView& columns = model_.columns();
140 SubsetCostVector reduced_costs(costs.size());
141 FillReducedCostsSlice(SubsetIndex(0), SubsetIndex(reduced_costs.size()),
142 costs, multipliers, columns, &reduced_costs);
143 return reduced_costs;
144}
145
146namespace {
147// Helper function to compute the subgradient.
148// It fills a slice of the subgradient vector from indices slice_start to
149// slice_end. This is a helper function for ParallelComputeSubgradient(). The
150// subgradient is computed using the reduced costs vector.
151void FillSubgradientSlice(SubsetIndex slice_start, SubsetIndex slice_end,
152 const SparseColumnView& columns,
153 const SubsetCostVector& reduced_costs,
154 ElementCostVector* subgradient) {
155 for (SubsetIndex subset(slice_start); subset < slice_end; ++subset) {
156 if (reduced_costs[subset] < 0.0) {
157 for (const ElementIndex element : columns[subset]) {
158 (*subgradient)[element] -= 1.0;
159 }
160 }
161 }
162}
163} // namespace
164
165// Vector of primal slack variable. Denoted as s_i(u) in [1], equation (6).
166// For all element indices i, s_i(u) = 1 - sum_{j \in J_i} x_j(u),
167// where J_i denotes the set of indices of subsets j covering element i.
168// For a general Integer Program A x <= b, the subgradient cost vector is
169// defined as A x - b. See [2].
171 const SubsetCostVector& reduced_costs) const {
172 // NOTE(user): Should the initialization be done with coverage[element]?
173 ElementCostVector subgradient(model_.num_elements(), 1.0);
174 FillSubgradientSlice(SubsetIndex(0), SubsetIndex(reduced_costs.size()),
175 model_.columns(), reduced_costs, &subgradient);
176 return subgradient;
177}
178
180 const SubsetCostVector& reduced_costs) const {
181 const SubsetIndex num_subsets(model_.num_subsets());
182 const SparseColumnView& columns = model_.columns();
183 ElementCostVector subgradient(model_.num_elements(), 1.0);
184 // The subgradient has one component per element, each thread processes
185 // several subsets. Hence, have one vector per thread to avoid data races.
186 // TODO(user): it may be better to split the elements among the threads,
187 // although this might be less well-balanced.
188 std::vector<ElementCostVector> subgradients(
189 num_threads_, ElementCostVector(model_.num_elements()));
190 absl::BlockingCounter num_threads_running(num_threads_);
191 const SubsetIndex block_size(BlockSize(num_subsets.value(), num_threads_));
192 SubsetIndex slice_start(0);
193 for (int thread_index = 0; thread_index < num_threads_; ++thread_index) {
194 const SubsetIndex slice_end =
195 std::min(slice_start + block_size, num_subsets);
196 thread_pool_->Schedule([&num_threads_running, slice_start, slice_end,
197 &reduced_costs, &columns, &subgradients,
198 thread_index]() {
199 FillSubgradientSlice(slice_start, slice_end, columns, reduced_costs,
200 &subgradients[thread_index]);
201 num_threads_running.DecrementCount();
202 });
203 slice_start = slice_end;
204 }
205 num_threads_running.Wait();
206 for (int thread_index = 0; thread_index < num_threads_; ++thread_index) {
207 for (const ElementIndex element : model_.ElementRange()) {
208 subgradient[element] += subgradients[thread_index][element];
209 }
210 }
211 return subgradient;
212}
213
214namespace {
215// Helper function to compute the value of the Lagrangian.
216// This is a helper function for ParallelComputeLagrangianValue().
217// It is called on a slice of elements, defined by slice_start and slice_end.
218// The value of the Lagrangian is computed using the reduced costs vector and
219// the multipliers vector.
220// The result is stored in lagrangian_value.
221void FillLagrangianValueSlice(SubsetIndex slice_start, SubsetIndex slice_end,
222 const SubsetCostVector& reduced_costs,
223 Cost* lagrangian_value) {
224 // This is min \sum_{j \in N} c_j(u) x_j. This captures the remark above
225 // (**), taking into account the possible values for x_j, and using them to
226 // minimize the terms.
227 for (SubsetIndex subset(slice_start); subset < slice_end; ++subset) {
228 if (reduced_costs[subset] < 0.0) {
229 *lagrangian_value += reduced_costs[subset];
230 }
231 }
232}
233} // namespace
234
235// Compute the (scalar) value of the Lagrangian vector by fixing the value of
236// x_j based on the sign of c_j(u). In [1] equation (4), it is:
237// L(u) = min \sum_{j \in N} c_j(u) x_j + \sum{i \in M} u_i . This is obtained
238// - if c_j(u) < 0: x_j(u) = 1,
239// - if c_j(u) > 0: x_j(u) = 0, (**)
240// - if c_j(u) = 0: x_j(u) is unbound, in {0, 1}, we use 0.
241// For a general Integer Program A x <= b, the Lagrangian vector L(u) [2] is
242// L(u) = min \sum_{j \in N} c_j(u) x_j + \sum{i \in M} b_i.u_i.
244 const SubsetCostVector& reduced_costs,
245 const ElementCostVector& multipliers) const {
246 Cost lagrangian_value = 0.0;
247 // This is \sum{i \in M} u_i.
248 for (const Cost u : multipliers) {
249 lagrangian_value += u;
250 }
251 FillLagrangianValueSlice(SubsetIndex(0), SubsetIndex(reduced_costs.size()),
252 reduced_costs, &lagrangian_value);
253 return lagrangian_value;
254}
255
257 const SubsetCostVector& reduced_costs,
258 const ElementCostVector& multipliers) const {
259 Cost lagrangian_value = 0.0;
260 // This is \sum{i \in M} u_i.
261 for (const Cost u : multipliers) {
262 lagrangian_value += u;
263 }
264 std::vector<Cost> lagrangian_values(num_threads_, 0.0);
265 absl::BlockingCounter num_threads_running(num_threads_);
266 const SubsetIndex block_size(BlockSize(model_.num_subsets(), num_threads_));
267 const SubsetIndex num_subsets(model_.num_subsets());
268 SubsetIndex slice_start(0);
269 for (int thread_index = 0; thread_index < num_threads_; ++thread_index) {
270 const SubsetIndex slice_end =
271 std::min(slice_start + block_size, num_subsets);
272 thread_pool_->Schedule([&num_threads_running, slice_start, block_size,
273 num_subsets, thread_index, &reduced_costs,
274 &lagrangian_values]() {
275 const SubsetIndex slice_end =
276 std::min(slice_start + block_size, num_subsets);
277 FillLagrangianValueSlice(slice_start, slice_end, reduced_costs,
278 &lagrangian_values[thread_index]);
279 num_threads_running.DecrementCount();
280 });
281 slice_start = slice_end;
282 }
283 num_threads_running.Wait();
284 for (const Cost l : lagrangian_values) {
285 lagrangian_value += l;
286 }
287 return lagrangian_value;
288}
289
290// Perform a subgradient step.
291// In the general case, for an Integer Program A.x <=b, the Lagragian
292// multipliers vector at step k+1 is defined as: u^{k+1} = u^k + t_k (A x^k -
293// b) with term t_k = lambda_k * (UB - L(u^k)) / |A x^k - b|^2.
294// |.| is the 2-norm (i.e. Euclidean)
295// In our case, the problem A x <= b is in the form A x >= 1. We need to
296// replace A x - b by s_i(u) = 1 - sum_{j \in J_i} x_j(u).
297// |A x^k - b|^2 = |s(u)|^2, and t_k is of the form:
298// t_k = lambda_k * (UB - L(u^k)) / |s^k(u)|^2.
299// Now, the coordinates of the multipliers vectors u^k, u^k_i are nonnegative,
300// i.e. u^k_i >= 0. Negative values are simply cut off. Following [3], each of
301// the coordinates is defined as: u^{k+1}_i =
302// max(u^k_i + lambda_k * (UB - L(u^k)) / |s^k(u)|^2 * s^k_i(u), 0).
303// This is eq. (7) in [1].
305 double step_size, Cost lagrangian_value, Cost upper_bound,
306 const SubsetCostVector& reduced_costs,
307 ElementCostVector* multipliers) const {
308 // step_size is \lambda_k in [1].
309 DCHECK_GT(step_size, 0);
310 // Compute the square of the Euclidean norm of the subgradient vector.
311 const ElementCostVector subgradient = ComputeSubgradient(reduced_costs);
312 Cost subgradient_square_norm = 0.0;
313 for (const Cost x : subgradient) {
314 subgradient_square_norm += x * x;
315 }
316 // First compute lambda_k * (UB - L(u^k)).
317 const Cost factor =
318 step_size * (upper_bound - lagrangian_value) / subgradient_square_norm;
319 for (const ElementIndex element : model_.ElementRange()) {
320 // Avoid multipliers to go negative and to go over the roof. 1e6 chosen
321 // arbitrarily. [***]
322 (*multipliers)[element] = std::clamp(
323 (*multipliers)[element] + factor * subgradient[element], 0.0, 1e6);
324 }
325}
326
328 double step_size, Cost lagrangian_value, Cost upper_bound,
329 const SubsetCostVector& reduced_costs,
330 ElementCostVector* multipliers) const {
331 // step_size is \lambda_k in [1].
332 DCHECK_GT(step_size, 0);
333 // Compute the square of the Euclidean norm of the subgradient vector.
334 const ElementCostVector subgradient =
335 ParallelComputeSubgradient(reduced_costs);
336 Cost subgradient_square_norm = 0.0;
337 for (const Cost x : subgradient) {
338 subgradient_square_norm += x * x;
339 }
340 // First compute lambda_k * (UB - L(u^k)).
341 const Cost factor =
342 step_size * (upper_bound - lagrangian_value) / subgradient_square_norm;
343 for (const ElementIndex element : model_.ElementRange()) {
344 // Avoid multipliers to go negative and to go through the roof. 1e6 chosen
345 const Cost kRoof = 1e6; // Arbitrary value, from [1].
346 (*multipliers)[element] = std::clamp(
347 (*multipliers)[element] + factor * subgradient[element], 0.0, kRoof);
348 }
349}
350
352 const SubsetCostVector& reduced_costs, const SubsetBoolVector& solution,
353 const ElementCostVector& multipliers) const {
354 Cost gap = 0.0;
355 // TODO(user): Parallelize this, if need be.
356 for (const SubsetIndex subset : model_.SubsetRange()) {
357 if (solution[subset] && reduced_costs[subset] > 0.0) {
358 gap += reduced_costs[subset];
359 } else if (!solution[subset] && reduced_costs[subset] < 0.0) {
360 // gap += std::abs(reduced_costs[subset]); We know the sign of rhs.
361 gap -= reduced_costs[subset];
362 }
363 }
364 const ElementToIntVector& coverage = inv_->coverage();
365 for (const ElementIndex element : model_.ElementRange()) {
366 gap += (coverage[element] - 1) * multipliers[element];
367 }
368 return gap;
369}
370
371SubsetCostVector SetCoverLagrangian::ComputeDelta(
372 const SubsetCostVector& reduced_costs,
373 const ElementCostVector& multipliers) const {
374 SubsetCostVector delta(model_.num_subsets());
375 const ElementToIntVector& coverage = inv_->coverage();
376 // This is definition (9) in [1].
377 const SparseColumnView& columns = model_.columns();
378 // TODO(user): Parallelize this.
379 for (const SubsetIndex subset : model_.SubsetRange()) {
380 delta[subset] = std::max(reduced_costs[subset], 0.0);
381 for (const ElementIndex element : columns[subset]) {
382 const double size = coverage[element];
383 delta[subset] += multipliers[element] * (size - 1.0) / size;
384 }
385 }
386 return delta;
387}
388
389namespace {
390// Helper class to compute the step size for the multipliers.
391// The step size is updated every period iterations.
392// The step size is updated by multiplying it by a factor 0.5 or 1.5
393// on the relative change in the lower bound is greater than 0.01 or less
394// than 0.001, respectively. The lower bound is updated every period
395// iterations.
396class StepSizer {
397 public:
398 StepSizer(int period, double step_size)
399 : period_(period), iter_to_check_(period), step_size_(step_size) {
400 ResetBounds();
401 }
402 double UpdateStepSize(int iter, Cost lower_bound) {
403 min_lb_ = std::min(min_lb_, lower_bound);
404 max_lb_ = std::max(max_lb_, lower_bound);
405 if (iter == iter_to_check_) {
406 iter_to_check_ += period_;
407 // Bounds can be negative, so we need to take the absolute value.
408 // We also need to avoid division by zero. We decide to return 0.0 in
409 // that case, which is the same as not updating the step size.
410 const Cost lb_gap =
411 max_lb_ == 0.0 ? 0.0 : (max_lb_ - min_lb_) / std::abs(max_lb_);
412 DCHECK_GE(lb_gap, 0.0);
413 if (lb_gap > 0.01) {
414 step_size_ *= 0.5;
415 } else if (lb_gap <= 0.001) {
416 step_size_ *= 1.5;
417 }
418 step_size_ = std::clamp(step_size_, 1e-6, 10.0);
419 ResetBounds();
420 }
421 return step_size_;
422 }
423
424 private:
425 void ResetBounds() {
426 min_lb_ = std::numeric_limits<Cost>::infinity();
427 max_lb_ = -std::numeric_limits<Cost>::infinity();
428 }
429 int period_;
430 int iter_to_check_;
431 double step_size_;
432 Cost min_lb_;
433 Cost max_lb_;
434};
435
436// Helper class to decide whether to stop the algorithm.
437// The algorithm stops when the lower bound is not updated for a certain
438// number of iterations.
439class Stopper {
440 public:
441 explicit Stopper(int period)
442 : period_(period),
443 iter_to_check_(period),
444 lower_bound_(std::numeric_limits<Cost>::min()) {}
445 bool DecideWhetherToStop(int iter, Cost lb) {
446 DCHECK_GE(lb, lower_bound_);
447 if (iter == iter_to_check_) {
448 iter_to_check_ += period_;
449 const Cost delta = lb - lower_bound_;
450 const Cost relative_delta = delta / lb;
451 DCHECK_GE(delta, 0.0);
452 DCHECK_GE(relative_delta, 0.0);
453 lower_bound_ = lb;
454 return relative_delta < 0.001 && delta < 1;
455 }
456 return false;
457 }
458
459 private:
460 int period_;
461 int iter_to_check_;
462 Cost lower_bound_;
463};
464
465} // namespace
466
467namespace {
468// TODO(user): Add this to the file defining AdjustableKAryHeap.
469template <typename Priority, typename Index, bool IsMaxHeap>
470class TopKHeap {
471 public:
472 explicit TopKHeap(int max_size) : heap_(), max_size_(max_size) {}
473 void Clear() { heap_.Clear(); }
474 void Add(Priority priority, Index index) {
475 if (heap_.Size() < max_size_) {
476 heap_.Add(priority, index);
477 } else if (heap_.HasPriority(priority, heap_.TopPriority())) {
478 heap_.ReplaceTop(priority, index);
479 }
480 }
481
482 private:
483 AdjustableKAryHeap<Priority, Index, /*Arity=*/2, !IsMaxHeap> heap_;
484 int max_size_;
485};
486} // namespace
487
488// Computes a lower bound on the optimal cost.
489std::tuple<Cost, SubsetCostVector, ElementCostVector>
491 Cost upper_bound) {
492 Cost lower_bound = 0.0;
494 double step_size = 0.1; // [***] arbitrary, from [1].
495 StepSizer step_sizer(20, step_size); // [***] arbitrary, from [1].
496 Stopper stopper(100); // [***] arbitrary, from [1].
497 SubsetCostVector reduced_costs(costs);
498 // For the time being, 4 threads seems to be the fastest.
499 // Running linux perf of the process shows that up to 60% of the cycles are
500 // lost as idle cycles in the CPU backend, probably because the algorithm is
501 // memory bound.
502 for (int iter = 0; iter < 1000; ++iter) {
503 reduced_costs = ParallelComputeReducedCosts(costs, multipliers);
504 const Cost lagrangian_value =
505 ParallelComputeLagrangianValue(reduced_costs, multipliers);
506 ParallelUpdateMultipliers(step_size, lagrangian_value, upper_bound,
507 reduced_costs, &multipliers);
508 lower_bound = std::max(lower_bound, lagrangian_value);
509 // step_size should be updated like this. For the time besing, we keep the
510 // step size, because the implementation of the rest is not adequate yet
511 // step_size = step_sizer.UpdateStepSize(iter, lagrangian_value);
512 // if (stopper.DecideWhetherToStop(iter, lower_bound)) {
513 // break;
514 // }
515 }
516 return std::make_tuple(lower_bound, reduced_costs, multipliers);
517}
518
519} // namespace operations_research
const ElementToIntVector & coverage() const
Returns vector containing number of subsets covering each element.
void UpdateMultipliers(double step_size, Cost lagrangian_value, Cost upper_bound, const SubsetCostVector &reduced_costs, ElementCostVector *multipliers) const
Cost ComputeLagrangianValue(const SubsetCostVector &reduced_costs, const ElementCostVector &multipliers) const
ElementCostVector ComputeSubgradient(const SubsetCostVector &reduced_costs) const
void ParallelUpdateMultipliers(double step_size, Cost lagrangian_value, Cost upper_bound, const SubsetCostVector &reduced_costs, ElementCostVector *multipliers) const
ElementCostVector ParallelComputeSubgradient(const SubsetCostVector &reduced_costs) const
SubsetCostVector ParallelComputeReducedCosts(const SubsetCostVector &costs, const ElementCostVector &multipliers) const
Computes the reduced costs for all subsets in parallel using ThreadPool.
Cost ParallelComputeLagrangianValue(const SubsetCostVector &reduced_costs, const ElementCostVector &multipliers) const
ElementCostVector InitializeLagrangeMultipliers() const
Initializes the multipliers vector (u) based on the cost per subset.
std::tuple< Cost, SubsetCostVector, ElementCostVector > ComputeLowerBound(const SubsetCostVector &costs, Cost upper_bound)
Computes a lower bound on the optimal cost.
SubsetCostVector ComputeReducedCosts(const SubsetCostVector &costs, const ElementCostVector &multipliers) const
Cost ComputeGap(const SubsetCostVector &reduced_costs, const SubsetBoolVector &solution, const ElementCostVector &multipliers) const
util_intops::StrongIntRange< SubsetIndex > SubsetRange() const
Access to the ranges of subsets and elements.
const SparseColumnView & columns() const
Column view of the set covering problem.
StrongIntRange< IntType > index_range() const
Fractional ScalarProduct(const DenseRowOrColumn1 &u, const DenseRowOrColumn2 &v)
Definition lp_utils.h:52
In SWIG mode, we don't want anything besides these top-level includes.
util_intops::StrongVector< SubsetIndex, Cost > SubsetCostVector
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< ElementIndex, Cost > ElementCostVector
util_intops::StrongVector< ElementIndex, BaseInt > 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< ColumnEntryIndex, ElementIndex > SparseColumn
util_intops::StrongVector< ElementIndex, SparseRow > SparseRowView