OR-Tools  9.6
initial_basis.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <cmath>
18 #include <limits>
19 #include <queue>
20 #include <vector>
21 
22 #include "ortools/glop/markowitz.h"
24 
25 namespace operations_research {
26 namespace glop {
27 
29  const DenseRow& objective,
30  const DenseRow& lower_bound,
31  const DenseRow& upper_bound,
32  const VariableTypeRow& variable_type)
33  : max_scaled_abs_cost_(0.0),
34  bixby_column_comparator_(*this),
35  triangular_column_comparator_(*this),
36  compact_matrix_(compact_matrix),
37  objective_(objective),
38  lower_bound_(lower_bound),
39  upper_bound_(upper_bound),
40  variable_type_(variable_type) {}
41 
42 void InitialBasis::CompleteBixbyBasis(ColIndex num_cols,
43  RowToColMapping* basis) {
44  // Initialize can_be_replaced ('I' in Bixby's paper) and has_zero_coefficient
45  // ('r' in Bixby's paper).
46  const RowIndex num_rows = compact_matrix_.num_rows();
47  DenseBooleanColumn can_be_replaced(num_rows, false);
48  DenseBooleanColumn has_zero_coefficient(num_rows, false);
49  DCHECK_EQ(num_rows, basis->size());
50  basis->resize(num_rows, kInvalidCol);
51  for (RowIndex row(0); row < num_rows; ++row) {
52  if ((*basis)[row] == kInvalidCol) {
53  can_be_replaced[row] = true;
54  has_zero_coefficient[row] = true;
55  }
56  }
57 
58  // This is 'v' in Bixby's paper.
59  DenseColumn scaled_diagonal_abs(compact_matrix_.num_rows(), kInfinity);
60 
61  // Compute a list of candidate indices and sort them using the heuristic
62  // described in Bixby's paper.
63  std::vector<ColIndex> candidates;
64  ComputeCandidates(num_cols, &candidates);
65 
66  // Loop over the candidate columns, and add them to the basis if the
67  // heuristics are satisfied.
68  for (int i = 0; i < candidates.size(); ++i) {
69  bool enter_basis = false;
70  const ColIndex candidate_col_index = candidates[i];
71  const auto& candidate_col = compact_matrix_.column(candidate_col_index);
72 
73  // Bixby's heuristic only works with scaled columns. This should be the
74  // case by default since we only use this when the matrix is scaled, but
75  // it is not the case for our tests... The overhead for computing the
76  // infinity norm for each column should be minimal.
77  if (InfinityNorm(candidate_col) != 1.0) continue;
78 
79  RowIndex candidate_row;
80  Fractional candidate_coeff = RestrictedInfinityNorm(
81  candidate_col, has_zero_coefficient, &candidate_row);
82  const Fractional kBixbyHighThreshold = 0.99;
83  if (candidate_coeff > kBixbyHighThreshold) {
84  enter_basis = true;
85  } else if (IsDominated(candidate_col, scaled_diagonal_abs)) {
86  candidate_coeff = RestrictedInfinityNorm(candidate_col, can_be_replaced,
87  &candidate_row);
88  if (candidate_coeff != 0.0) {
89  enter_basis = true;
90  }
91  }
92 
93  if (enter_basis) {
94  can_be_replaced[candidate_row] = false;
95  SetSupportToFalse(candidate_col, &has_zero_coefficient);
96  const Fractional kBixbyLowThreshold = 0.01;
97  scaled_diagonal_abs[candidate_row] =
98  kBixbyLowThreshold * std::abs(candidate_coeff);
99  (*basis)[candidate_row] = candidate_col_index;
100  }
101  }
102 }
103 
104 void InitialBasis::GetPrimalMarosBasis(ColIndex num_cols,
105  RowToColMapping* basis) {
106  return GetMarosBasis<false>(num_cols, basis);
107 }
108 
109 void InitialBasis::GetDualMarosBasis(ColIndex num_cols,
110  RowToColMapping* basis) {
111  return GetMarosBasis<true>(num_cols, basis);
112 }
113 
115  RowToColMapping* basis) {
116  return CompleteTriangularBasis<false>(num_cols, basis);
117 }
118 
120  RowToColMapping* basis) {
121  return CompleteTriangularBasis<true>(num_cols, basis);
122 }
123 
124 template <bool only_allow_zero_cost_column>
125 void InitialBasis::CompleteTriangularBasis(ColIndex num_cols,
126  RowToColMapping* basis) {
127  // Initialize can_be_replaced.
128  const RowIndex num_rows = compact_matrix_.num_rows();
129  DenseBooleanColumn can_be_replaced(num_rows, false);
130  DCHECK_EQ(num_rows, basis->size());
131  basis->resize(num_rows, kInvalidCol);
132  for (RowIndex row(0); row < num_rows; ++row) {
133  if ((*basis)[row] == kInvalidCol) {
134  can_be_replaced[row] = true;
135  }
136  }
137 
138  // Initialize the residual non-zero pattern for the rows that can be replaced.
139  MatrixNonZeroPattern residual_pattern;
140  residual_pattern.Reset(num_rows, num_cols);
141  for (ColIndex col(0); col < num_cols; ++col) {
142  if (only_allow_zero_cost_column && objective_[col] != 0.0) continue;
143  for (const SparseColumn::Entry e : compact_matrix_.column(col)) {
144  if (can_be_replaced[e.row()]) {
145  residual_pattern.AddEntry(e.row(), col);
146  }
147  }
148  }
149 
150  // Initialize a priority queue of residual singleton columns.
151  // Also compute max_scaled_abs_cost_ for GetColumnPenalty().
152  std::vector<ColIndex> residual_singleton_column;
153  max_scaled_abs_cost_ = 0.0;
154  for (ColIndex col(0); col < num_cols; ++col) {
155  max_scaled_abs_cost_ =
156  std::max(max_scaled_abs_cost_, std::abs(objective_[col]));
157  if (residual_pattern.ColDegree(col) == 1) {
158  residual_singleton_column.push_back(col);
159  }
160  }
161  const Fractional kBixbyWeight = 1000.0;
162  max_scaled_abs_cost_ =
163  (max_scaled_abs_cost_ == 0.0) ? 1.0 : kBixbyWeight * max_scaled_abs_cost_;
164  std::priority_queue<ColIndex, std::vector<ColIndex>,
165  InitialBasis::TriangularColumnComparator>
166  queue(residual_singleton_column.begin(), residual_singleton_column.end(),
167  triangular_column_comparator_);
168 
169  // Process the residual singleton columns by priority and add them to the
170  // basis if their "diagonal" coefficient is not too small.
171  while (!queue.empty()) {
172  const ColIndex candidate = queue.top();
173  queue.pop();
174  if (residual_pattern.ColDegree(candidate) != 1) continue;
175 
176  // Find the position of the singleton and compute the infinity norm of
177  // the column (note that this is always 1.0 if the problem was scaled).
178  RowIndex row(kInvalidRow);
179  Fractional coeff = 0.0;
180  Fractional max_magnitude = 0.0;
181  for (const SparseColumn::Entry e : compact_matrix_.column(candidate)) {
182  max_magnitude = std::max(max_magnitude, std::abs(e.coefficient()));
183  if (can_be_replaced[e.row()]) {
184  row = e.row();
185  coeff = e.coefficient();
186  break;
187  }
188  }
189  const Fractional kStabilityThreshold = 0.01;
190  if (std::abs(coeff) < kStabilityThreshold * max_magnitude) continue;
191  DCHECK_NE(kInvalidRow, row);
192 
193  // Use this candidate column in the basis.
194  (*basis)[row] = candidate;
195  can_be_replaced[row] = false;
196  residual_pattern.DeleteRowAndColumn(row, candidate);
197  for (const ColIndex col : residual_pattern.RowNonZero(row)) {
198  if (col == candidate) continue;
199  residual_pattern.DecreaseColDegree(col);
200  if (residual_pattern.ColDegree(col) == 1) {
201  queue.push(col);
202  }
203  }
204  }
205 }
206 
207 int InitialBasis::GetMarosPriority(ColIndex col) const {
208  // Priority values for columns as defined in Maros's book.
209  switch (variable_type_[col]) {
211  return 3;
213  return 2;
215  return 2;
217  return 1;
219  return 0;
220  }
221 }
222 
223 int InitialBasis::GetMarosPriority(RowIndex row) const {
224  // Priority values for rows are equal to
225  // 3 - row priority values as defined in Maros's book
226  ColIndex slack_index(RowToColIndex(row) + compact_matrix_.num_cols() -
227  RowToColIndex(compact_matrix_.num_rows()));
228 
229  return GetMarosPriority(slack_index);
230 }
231 
232 template <bool only_allow_zero_cost_column>
233 void InitialBasis::GetMarosBasis(ColIndex num_cols, RowToColMapping* basis) {
234  VLOG(1) << "Starting Maros crash procedure.";
235 
236  // Initialize basis to the all-slack basis.
237  const RowIndex num_rows = compact_matrix_.num_rows();
238  const ColIndex first_slack = num_cols - RowToColIndex(num_rows);
239  DCHECK_EQ(num_rows, basis->size());
240  basis->resize(num_rows);
241  for (RowIndex row(0); row < num_rows; row++) {
242  (*basis)[row] = first_slack + RowToColIndex(row);
243  }
244 
245  // Initialize the set of available rows and columns.
246  DenseBooleanRow available(num_cols, true);
247  for (ColIndex col(0); col < first_slack; ++col) {
248  if (variable_type_[col] == VariableType::FIXED_VARIABLE ||
249  (only_allow_zero_cost_column && objective_[col] != 0.0)) {
250  available[col] = false;
251  }
252  }
253  for (ColIndex col = first_slack; col < num_cols; ++col) {
254  if (variable_type_[col] == VariableType::UNCONSTRAINED) {
255  available[col] = false;
256  }
257  }
258 
259  // Initialize the residual non-zero pattern for the active part of the matrix.
260  MatrixNonZeroPattern residual_pattern;
261  residual_pattern.Reset(num_rows, num_cols);
262  for (ColIndex col(0); col < first_slack; ++col) {
263  for (const SparseColumn::Entry e : compact_matrix_.column(col)) {
264  if (available[RowToColIndex(e.row())] && available[col]) {
265  residual_pattern.AddEntry(e.row(), col);
266  }
267  }
268  }
269 
270  // Go over residual pattern and mark rows as unavailable.
271  for (RowIndex row(0); row < num_rows; row++) {
272  if (residual_pattern.RowDegree(row) == 0) {
273  available[RowToColIndex(row) + first_slack] = false;
274  }
275  }
276 
277  for (;;) {
278  // Make row selection by the Row Priority Function (RPF) from Maros's
279  // book.
280  int max_row_priority_function = std::numeric_limits<int>::min();
281  RowIndex max_rpf_row = kInvalidRow;
282  for (RowIndex row(0); row < num_rows; row++) {
283  if (available[RowToColIndex(row) + first_slack]) {
284  const int rpf =
285  10 * (3 - GetMarosPriority(row)) - residual_pattern.RowDegree(row);
286  if (rpf > max_row_priority_function) {
287  max_row_priority_function = rpf;
288  max_rpf_row = row;
289  }
290  }
291  }
292  if (max_rpf_row == kInvalidRow) break;
293 
294  // Trace row for nonzero entries and pick one with best Column Priority
295  // Function (cpf).
296  const Fractional kStabilityThreshold = 1e-3;
297  ColIndex max_cpf_col(kInvalidCol);
298  int max_col_priority_function(std::numeric_limits<int>::min());
299  Fractional pivot_absolute_value = 0.0;
300  for (const ColIndex col : residual_pattern.RowNonZero(max_rpf_row)) {
301  if (!available[col]) continue;
302  const int cpf =
303  10 * GetMarosPriority(col) - residual_pattern.ColDegree(col);
304  if (cpf > max_col_priority_function) {
305  // Make sure that the pivotal entry is not too small in magnitude.
306  Fractional max_magnitude = 0;
307  pivot_absolute_value = 0.0;
308  const auto& column_values = compact_matrix_.column(col);
309  for (const SparseColumn::Entry e : column_values) {
310  const Fractional absolute_value = std::fabs(e.coefficient());
311  if (e.row() == max_rpf_row) pivot_absolute_value = absolute_value;
312  max_magnitude = std::max(max_magnitude, absolute_value);
313  }
314  if (pivot_absolute_value >= kStabilityThreshold * max_magnitude) {
315  max_col_priority_function = cpf;
316  max_cpf_col = col;
317  }
318  }
319  }
320 
321  if (max_cpf_col == kInvalidCol) {
322  available[RowToColIndex(max_rpf_row) + first_slack] = false;
323  continue;
324  }
325 
326  // Ensure that the row leaving the basis has a lower priority than the
327  // column entering the basis. If the best column is not good enough mark
328  // row as unavailable and choose another one.
329  const int row_priority = GetMarosPriority(max_rpf_row);
330  const int column_priority = GetMarosPriority(max_cpf_col);
331  if (row_priority >= column_priority) {
332  available[RowToColIndex(max_rpf_row) + first_slack] = false;
333  continue;
334  }
335 
336  // Use this candidate column in the basis. Update residual pattern and row
337  // counts list.
338  (*basis)[max_rpf_row] = max_cpf_col;
339 
340  VLOG(2) << "Slack variable " << max_rpf_row << " replaced by column "
341  << max_cpf_col
342  << ". Pivot coefficient magnitude: " << pivot_absolute_value << ".";
343 
344  available[max_cpf_col] = false;
345  available[first_slack + RowToColIndex(max_rpf_row)] = false;
346 
347  // Maintain the invariant that all the still available columns will have
348  // zeros on the rows we already replaced. This ensures the lower-triangular
349  // nature (after permutation) of the returned basis.
350  residual_pattern.DeleteRowAndColumn(max_rpf_row, max_cpf_col);
351  for (const ColIndex col : residual_pattern.RowNonZero(max_rpf_row)) {
352  available[col] = false;
353  }
354  }
355 }
356 
357 void InitialBasis::ComputeCandidates(ColIndex num_cols,
358  std::vector<ColIndex>* candidates) {
359  candidates->clear();
360  max_scaled_abs_cost_ = 0.0;
361  for (ColIndex col(0); col < num_cols; ++col) {
362  if (variable_type_[col] != VariableType::FIXED_VARIABLE &&
363  compact_matrix_.column(col).num_entries() > 0) {
364  candidates->push_back(col);
365  max_scaled_abs_cost_ =
366  std::max(max_scaled_abs_cost_, std::abs(objective_[col]));
367  }
368  }
369  const Fractional kBixbyWeight = 1000.0;
370  max_scaled_abs_cost_ =
371  (max_scaled_abs_cost_ == 0.0) ? 1.0 : kBixbyWeight * max_scaled_abs_cost_;
372  std::sort(candidates->begin(), candidates->end(), bixby_column_comparator_);
373 }
374 
375 int InitialBasis::GetColumnCategory(ColIndex col) const {
376  // Only the relative position of the returned number is important, so we use
377  // 2 for the category C2 in Bixby's paper and so on.
378  switch (variable_type_[col]) {
380  return 2;
382  return 3;
384  return 3;
386  return 4;
388  return 5;
389  }
390 }
391 
392 Fractional InitialBasis::GetColumnPenalty(ColIndex col) const {
393  const VariableType type = variable_type_[col];
394  Fractional penalty = 0.0;
395  if (type == VariableType::LOWER_BOUNDED) {
396  penalty = lower_bound_[col];
397  }
398  if (type == VariableType::UPPER_BOUNDED) {
399  penalty = -upper_bound_[col];
400  }
402  penalty = lower_bound_[col] - upper_bound_[col];
403  }
404  return penalty + std::abs(objective_[col]) / max_scaled_abs_cost_;
405 }
406 
407 bool InitialBasis::BixbyColumnComparator::operator()(ColIndex col_a,
408  ColIndex col_b) const {
409  if (col_a == col_b) return false;
410  const int category_a = initial_basis_.GetColumnCategory(col_a);
411  const int category_b = initial_basis_.GetColumnCategory(col_b);
412  if (category_a != category_b) {
413  return category_a < category_b;
414  } else {
415  return initial_basis_.GetColumnPenalty(col_a) <
416  initial_basis_.GetColumnPenalty(col_b);
417  }
418 }
419 
420 bool InitialBasis::TriangularColumnComparator::operator()(
421  ColIndex col_a, ColIndex col_b) const {
422  if (col_a == col_b) return false;
423  const int category_a = initial_basis_.GetColumnCategory(col_a);
424  const int category_b = initial_basis_.GetColumnCategory(col_b);
425  if (category_a != category_b) {
426  return category_a > category_b;
427  }
428 
429  // The nonzero is not in the original Bixby paper, but experiment shows it is
430  // important. It leads to sparser solves, but also sparser direction, which
431  // mean potentially less blocking variables on each pivot...
432  //
433  // TODO(user): Experiments more with this comparator or the
434  // BixbyColumnComparator.
435  if (initial_basis_.compact_matrix_.column(col_a).num_entries() !=
436  initial_basis_.compact_matrix_.column(col_b).num_entries()) {
437  return initial_basis_.compact_matrix_.column(col_a).num_entries() >
438  initial_basis_.compact_matrix_.column(col_b).num_entries();
439  }
440  return initial_basis_.GetColumnPenalty(col_a) >
441  initial_basis_.GetColumnPenalty(col_b);
442 }
443 
444 } // namespace glop
445 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
ColumnView column(ColIndex col) const
Definition: sparse.h:403
void CompleteTriangularPrimalBasis(ColIndex num_cols, RowToColMapping *basis)
void CompleteTriangularDualBasis(ColIndex num_cols, RowToColMapping *basis)
InitialBasis(const CompactSparseMatrix &compact_matrix, const DenseRow &objective, const DenseRow &lower_bound, const DenseRow &upper_bound, const VariableTypeRow &variable_type)
void CompleteBixbyBasis(ColIndex num_cols, RowToColMapping *basis)
void GetDualMarosBasis(ColIndex num_cols, RowToColMapping *basis)
void GetPrimalMarosBasis(ColIndex num_cols, RowToColMapping *basis)
void ComputeCandidates(ColIndex num_cols, std::vector< ColIndex > *candidates)
ColIndex col
Definition: markowitz.cc:186
RowIndex row
Definition: markowitz.cc:185
constexpr ColIndex kInvalidCol(-1)
Fractional InfinityNorm(const DenseColumn &v)
constexpr double kInfinity
Definition: lp_types.h:88
void SetSupportToFalse(const ColumnView &column, DenseBooleanColumn *b)
ColIndex RowToColIndex(RowIndex row)
Definition: lp_types.h:53
bool IsDominated(const ColumnView &column, const DenseColumn &radius)
constexpr RowIndex kInvalidRow(-1)
StrictITIVector< ColIndex, bool > DenseBooleanRow
Definition: lp_types.h:344
StrictITIVector< RowIndex, ColIndex > RowToColMapping
Definition: lp_types.h:384
Fractional RestrictedInfinityNorm(const ColumnView &column, const DenseBooleanColumn &rows_to_consider, RowIndex *row_index)
Collection of objects used to extend the Constraint Solver library.
IntVar * upper_bound
Definition: routing.cc:1087
IntVar * lower_bound
Definition: routing.cc:1086
IntVar *const objective_
Definition: search.cc:3068
#define VLOG(verboselevel)
Definition: vlog.h:39