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