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