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