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