OR-Tools  9.6
update_row.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 <string>
17 
19 
20 namespace operations_research {
21 namespace glop {
22 
24  const CompactSparseMatrix& transposed_matrix,
25  const VariablesInfo& variables_info,
26  const RowToColMapping& basis,
27  const BasisFactorization& basis_factorization)
28  : matrix_(matrix),
29  transposed_matrix_(transposed_matrix),
30  variables_info_(variables_info),
31  basis_(basis),
32  basis_factorization_(basis_factorization),
33  unit_row_left_inverse_(),
34  non_zero_position_list_(),
35  non_zero_position_set_(),
36  coefficient_(),
37  num_operations_(0),
38  parameters_(),
39  stats_() {}
40 
42  SCOPED_TIME_STAT(&stats_);
43  left_inverse_computed_for_ = kInvalidRow;
44  update_row_computed_for_ = kInvalidRow;
45 }
46 
48  return unit_row_left_inverse_;
49 }
50 
52  RowIndex leaving_row) {
53  Invalidate();
54  basis_factorization_.TemporaryLeftSolveForUnitRow(RowToColIndex(leaving_row),
55  &unit_row_left_inverse_);
56  return unit_row_left_inverse_;
57 }
58 
59 void UpdateRow::ComputeUnitRowLeftInverse(RowIndex leaving_row) {
60  if (left_inverse_computed_for_ == leaving_row) return;
61  left_inverse_computed_for_ = leaving_row;
62  SCOPED_TIME_STAT(&stats_);
63 
64  basis_factorization_.LeftSolveForUnitRow(RowToColIndex(leaving_row),
65  &unit_row_left_inverse_);
66 
67  // TODO(user): Refactorize if the estimated accuracy is above a threshold.
68  IF_STATS_ENABLED(stats_.unit_row_left_inverse_accuracy.Add(
69  matrix_.ColumnScalarProduct(basis_[leaving_row],
70  unit_row_left_inverse_.values) -
71  1.0));
72  IF_STATS_ENABLED(stats_.unit_row_left_inverse_density.Add(
73  Density(unit_row_left_inverse_.values)));
74 }
75 
76 void UpdateRow::ComputeUpdateRow(RowIndex leaving_row) {
77  if (update_row_computed_for_ == leaving_row) return;
78  update_row_computed_for_ = leaving_row;
79  ComputeUnitRowLeftInverse(leaving_row);
80  SCOPED_TIME_STAT(&stats_);
81 
82  if (parameters_.use_transposed_matrix()) {
83  // Number of entries that ComputeUpdatesRowWise() will need to look at.
84  EntryIndex num_row_wise_entries(0);
85 
86  // Because we are about to do an expensive matrix-vector product, we make
87  // sure we drop small entries in the vector for the row-wise algorithm. We
88  // also computes its non-zeros to simplify the code below.
89  //
90  // TODO(user): So far we didn't generalize the use of drop tolerances
91  // everywhere in the solver, so we make sure to not modify
92  // unit_row_left_inverse_ that is also used elsewhere. However, because of
93  // that, we will not get the exact same result depending on the algortihm
94  // used below because the ComputeUpdatesColumnWise() will still use these
95  // small entries (no complexity changes).
96  const Fractional drop_tolerance = parameters_.drop_tolerance();
97  unit_row_left_inverse_filtered_non_zeros_.clear();
98  const auto view = transposed_matrix_.view();
99  if (unit_row_left_inverse_.non_zeros.empty()) {
100  const ColIndex size = unit_row_left_inverse_.values.size();
101  for (ColIndex col(0); col < size; ++col) {
102  if (std::abs(unit_row_left_inverse_.values[col]) > drop_tolerance) {
103  unit_row_left_inverse_filtered_non_zeros_.push_back(col);
104  num_row_wise_entries += view.ColumnNumEntries(col);
105  }
106  }
107  } else {
108  for (const auto e : unit_row_left_inverse_) {
109  if (std::abs(e.coefficient()) > drop_tolerance) {
110  unit_row_left_inverse_filtered_non_zeros_.push_back(e.column());
111  num_row_wise_entries += view.ColumnNumEntries(e.column());
112  }
113  }
114  }
115 
116  // The case of size 1 happens often enough to deserve special code.
117  //
118  // TODO(user): The impact is not as high as I hopped though, so not too
119  // important.
120  if (unit_row_left_inverse_filtered_non_zeros_.size() == 1) {
121  ComputeUpdatesForSingleRow(
122  unit_row_left_inverse_filtered_non_zeros_.front());
123  num_operations_ += num_row_wise_entries.value();
124  IF_STATS_ENABLED(stats_.update_row_density.Add(
125  static_cast<double>(non_zero_position_list_.size()) /
126  static_cast<double>(matrix_.num_cols().value())));
127  return;
128  }
129 
130  // Number of entries that ComputeUpdatesColumnWise() will need to look at.
131  const EntryIndex num_col_wise_entries =
132  variables_info_.GetNumEntriesInRelevantColumns();
133 
134  // Note that the thresholds were chosen (more or less) from the result of
135  // the microbenchmark tests of this file in September 2013.
136  // TODO(user): automate the computation of these constants at run-time?
137  const double row_wise = static_cast<double>(num_row_wise_entries.value());
138  if (row_wise < 0.5 * static_cast<double>(num_col_wise_entries.value())) {
139  if (row_wise < 1.1 * static_cast<double>(matrix_.num_cols().value())) {
140  ComputeUpdatesRowWiseHypersparse();
141 
142  // We use a multiplicative factor because these entries are often widely
143  // spread in memory. There is also some overhead to each fp operations.
144  num_operations_ +=
145  5 * num_row_wise_entries.value() + matrix_.num_cols().value() / 64;
146  } else {
147  ComputeUpdatesRowWise();
148  num_operations_ +=
149  num_row_wise_entries.value() + matrix_.num_rows().value();
150  }
151  } else {
152  ComputeUpdatesColumnWise();
153  num_operations_ +=
154  num_col_wise_entries.value() + matrix_.num_cols().value();
155  }
156  } else {
157  ComputeUpdatesColumnWise();
158  num_operations_ +=
159  variables_info_.GetNumEntriesInRelevantColumns().value() +
160  matrix_.num_cols().value();
161  }
162  IF_STATS_ENABLED(stats_.update_row_density.Add(
163  static_cast<double>(non_zero_position_list_.size()) /
164  static_cast<double>(matrix_.num_cols().value())));
165 }
166 
168  const std::string& algorithm) {
169  unit_row_left_inverse_.values = lhs;
170  ComputeNonZeros(lhs, &unit_row_left_inverse_filtered_non_zeros_);
171  if (algorithm == "column") {
172  ComputeUpdatesColumnWise();
173  } else if (algorithm == "row") {
174  ComputeUpdatesRowWise();
175  } else if (algorithm == "row_hypersparse") {
176  ComputeUpdatesRowWiseHypersparse();
177  } else {
178  LOG(DFATAL) << "Unknown algorithm in ComputeUpdateRowForBenchmark(): '"
179  << algorithm << "'";
180  }
181 }
182 
183 const DenseRow& UpdateRow::GetCoefficients() const { return coefficient_; }
184 
186  return non_zero_position_list_;
187 }
188 
189 void UpdateRow::SetParameters(const GlopParameters& parameters) {
190  parameters_ = parameters;
191 }
192 
193 // This is optimized for the case when the total number of entries is about
194 // the same as, or greater than, the number of columns.
195 void UpdateRow::ComputeUpdatesRowWise() {
196  SCOPED_TIME_STAT(&stats_);
197  coefficient_.AssignToZero(matrix_.num_cols());
198  const auto output_coeffs = coefficient_.view();
199  const auto view = transposed_matrix_.view();
200  for (ColIndex col : unit_row_left_inverse_filtered_non_zeros_) {
201  const Fractional multiplier = unit_row_left_inverse_[col];
202  for (const EntryIndex i : view.Column(col)) {
203  const ColIndex pos = RowToColIndex(view.EntryRow(i));
204  output_coeffs[pos] += multiplier * view.EntryCoefficient(i);
205  }
206  }
207 
208  non_zero_position_list_.clear();
209  const Fractional drop_tolerance = parameters_.drop_tolerance();
210  for (const ColIndex col : variables_info_.GetIsRelevantBitRow()) {
211  if (std::abs(output_coeffs[col]) > drop_tolerance) {
212  non_zero_position_list_.push_back(col);
213  }
214  }
215 }
216 
217 // This is optimized for the case when the total number of entries is smaller
218 // than the number of columns.
219 void UpdateRow::ComputeUpdatesRowWiseHypersparse() {
220  SCOPED_TIME_STAT(&stats_);
221  const ColIndex num_cols = matrix_.num_cols();
222  non_zero_position_set_.ClearAndResize(num_cols);
223  coefficient_.resize(num_cols, 0.0);
224 
225  const auto output_coeffs = coefficient_.view();
226  const auto view = transposed_matrix_.view();
227  for (ColIndex col : unit_row_left_inverse_filtered_non_zeros_) {
228  const Fractional multiplier = unit_row_left_inverse_[col];
229  for (const EntryIndex i : view.Column(col)) {
230  const ColIndex pos = RowToColIndex(view.EntryRow(i));
231  const Fractional v = multiplier * view.EntryCoefficient(i);
232  if (!non_zero_position_set_.IsSet(pos)) {
233  // Note that we could create the non_zero_position_list_ here, but we
234  // prefer to keep the non-zero positions sorted, so using the bitset is
235  // a good alernative. Of course if the solution is really really sparse,
236  // then sorting non_zero_position_list_ will be faster.
237  output_coeffs[pos] = v;
238  non_zero_position_set_.Set(pos);
239  } else {
240  output_coeffs[pos] += v;
241  }
242  }
243  }
244 
245  // Only keep in non_zero_position_set_ the relevant positions.
246  non_zero_position_set_.Intersection(variables_info_.GetIsRelevantBitRow());
247  non_zero_position_list_.clear();
248  const Fractional drop_tolerance = parameters_.drop_tolerance();
249  for (const ColIndex col : non_zero_position_set_) {
250  // TODO(user): Since the solution is really sparse, maybe storing the
251  // non-zero coefficients contiguously in a vector is better than keeping
252  // them as they are. Note however that we will iterate only twice on the
253  // update row coefficients during an iteration.
254  if (std::abs(output_coeffs[col]) > drop_tolerance) {
255  non_zero_position_list_.push_back(col);
256  }
257  }
258 }
259 
260 void UpdateRow::ComputeUpdatesForSingleRow(ColIndex row_as_col) {
261  coefficient_.resize(matrix_.num_cols(), 0.0);
262  non_zero_position_list_.clear();
263 
264  const DenseBitRow& is_relevant = variables_info_.GetIsRelevantBitRow();
265  const Fractional drop_tolerance = parameters_.drop_tolerance();
266  const Fractional multiplier = unit_row_left_inverse_[row_as_col];
267  const auto output_coeffs = coefficient_.view();
268  const auto view = transposed_matrix_.view();
269  for (const EntryIndex i : view.Column(row_as_col)) {
270  const ColIndex pos = RowToColIndex(view.EntryRow(i));
271  if (!is_relevant[pos]) continue;
272 
273  const Fractional v = multiplier * view.EntryCoefficient(i);
274  if (std::abs(v) > drop_tolerance) {
275  output_coeffs[pos] = v;
276  non_zero_position_list_.push_back(pos);
277  }
278  }
279 }
280 
281 void UpdateRow::ComputeUpdatesColumnWise() {
282  SCOPED_TIME_STAT(&stats_);
283 
284  coefficient_.resize(matrix_.num_cols(), 0.0);
285  non_zero_position_list_.clear();
286 
287  const Fractional drop_tolerance = parameters_.drop_tolerance();
288  const auto output_coeffs = coefficient_.view();
289  const auto view = matrix_.view();
290  const auto unit_row_left_inverse = unit_row_left_inverse_.values.const_view();
291  for (const ColIndex col : variables_info_.GetIsRelevantBitRow()) {
292  // Coefficient of the column right inverse on the 'leaving_row'.
293  const Fractional coeff =
294  view.ColumnScalarProduct(col, unit_row_left_inverse);
295 
296  // Nothing to do if 'coeff' is (almost) zero which does happen due to
297  // sparsity. Note that it shouldn't be too bad to use a non-zero drop
298  // tolerance here because even if we introduce some precision issues, the
299  // quantities updated by this update row will eventually be recomputed.
300  if (std::abs(coeff) > drop_tolerance) {
301  non_zero_position_list_.push_back(col);
302  output_coeffs[col] = coeff;
303  }
304  }
305 }
306 
307 // Note that we use the same algo as ComputeUpdatesColumnWise() here. The
308 // others version might be faster, but this is called at most once per solve, so
309 // it shouldn't be too bad.
310 void UpdateRow::ComputeFullUpdateRow(RowIndex leaving_row,
311  DenseRow* output) const {
312  CHECK_EQ(leaving_row, left_inverse_computed_for_);
313 
314  const ColIndex num_cols = matrix_.num_cols();
315  output->AssignToZero(num_cols);
316 
317  // Fills the only position at one in the basic columns.
318  (*output)[basis_[leaving_row]] = 1.0;
319 
320  // Fills the non-basic column.
321  const Fractional drop_tolerance = parameters_.drop_tolerance();
322  const auto view = matrix_.view();
323  const auto unit_row_left_inverse = unit_row_left_inverse_.values.const_view();
324  for (const ColIndex col : variables_info_.GetNotBasicBitRow()) {
325  const Fractional coeff =
326  view.ColumnScalarProduct(col, unit_row_left_inverse);
327  if (std::abs(coeff) > drop_tolerance) {
328  (*output)[col] = coeff;
329  }
330  }
331 }
332 
333 } // namespace glop
334 } // namespace operations_research
void ClearAndResize(IndexType size)
Definition: bitset.h:459
void Set(IndexType i)
Definition: bitset.h:514
void Intersection(const Bitset64< IndexType > &other)
Definition: bitset.h:562
bool IsSet(IndexType i) const
Definition: bitset.h:504
void LeftSolveForUnitRow(ColIndex j, ScatteredRow *y) const
void TemporaryLeftSolveForUnitRow(ColIndex j, ScatteredRow *y) const
Fractional ColumnScalarProduct(ColIndex col, const DenseRow &vector) const
Definition: sparse.h:421
const ScatteredRow & GetUnitRowLeftInverse() const
Definition: update_row.cc:47
const ScatteredRow & ComputeAndGetUnitRowLeftInverse(RowIndex leaving_row)
Definition: update_row.cc:51
const DenseRow & GetCoefficients() const
Definition: update_row.cc:183
void ComputeUpdateRowForBenchmark(const DenseRow &lhs, const std::string &algorithm)
Definition: update_row.cc:167
void ComputeUnitRowLeftInverse(RowIndex leaving_row)
Definition: update_row.cc:59
UpdateRow(const CompactSparseMatrix &matrix, const CompactSparseMatrix &transposed_matrix, const VariablesInfo &variables_info, const RowToColMapping &basis, const BasisFactorization &basis_factorization)
Definition: update_row.cc:23
void ComputeFullUpdateRow(RowIndex leaving_row, DenseRow *output) const
Definition: update_row.cc:310
void ComputeUpdateRow(RowIndex leaving_row)
Definition: update_row.cc:76
void SetParameters(const GlopParameters &parameters)
Definition: update_row.cc:189
const ColIndexVector & GetNonZeroPositions() const
Definition: update_row.cc:185
const DenseBitRow & GetNotBasicBitRow() const
const DenseBitRow & GetIsRelevantBitRow() const
SatParameters parameters
ColIndex col
Definition: markowitz.cc:186
std::vector< ColIndex > ColIndexVector
Definition: lp_types.h:350
void ComputeNonZeros(const StrictITIVector< IndexType, Fractional > &input, std::vector< IndexType > *non_zeros)
double Density(const DenseRow &row)
ColIndex RowToColIndex(RowIndex row)
Definition: lp_types.h:53
constexpr RowIndex kInvalidRow(-1)
Bitset64< ColIndex > DenseBitRow
Definition: lp_types.h:365
Collection of objects used to extend the Constraint Solver library.
#define IF_STATS_ENABLED(instructions)
Definition: stats.h:438
#define SCOPED_TIME_STAT(stats)
Definition: stats.h:439
StrictITIVector< Index, Fractional > values