OR-Tools  9.2
primal_edge_norms.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 
16 #include "ortools/base/timer.h"
18 
19 namespace operations_research {
20 namespace glop {
21 
23  const VariablesInfo& variables_info,
24  const BasisFactorization& basis_factorization)
25  : compact_matrix_(compact_matrix),
26  variables_info_(variables_info),
27  basis_factorization_(basis_factorization),
28  stats_(),
29  recompute_edge_squared_norms_(true),
30  reset_devex_weights_(true),
31  edge_squared_norms_(),
32  matrix_column_norms_(),
33  devex_weights_(),
34  direction_left_inverse_(),
35  num_operations_(0) {}
36 
38  SCOPED_TIME_STAT(&stats_);
39  recompute_edge_squared_norms_ = true;
40  reset_devex_weights_ = true;
41  for (bool* watcher : watchers_) *watcher = true;
42 }
43 
45  if (pricing_rule_ != GlopParameters ::STEEPEST_EDGE) return false;
46  return recompute_edge_squared_norms_;
47 }
48 
50  switch (pricing_rule_) {
52  return GetMatrixColumnNorms();
54  return GetEdgeSquaredNorms();
56  return GetDevexWeights();
57  }
58 }
59 
61  if (recompute_edge_squared_norms_) ComputeEdgeSquaredNorms();
62  return edge_squared_norms_;
63 }
64 
66  if (reset_devex_weights_) ResetDevexWeights();
67  return devex_weights_;
68 }
69 
71  if (matrix_column_norms_.empty()) ComputeMatrixColumnNorms();
72  return matrix_column_norms_;
73 }
74 
76  ColIndex entering_col, const ScatteredColumn& direction) {
77  if (!recompute_edge_squared_norms_) {
78  SCOPED_TIME_STAT(&stats_);
79  // Recompute the squared norm of the edge used during this
80  // iteration, i.e. the entering edge. Note the PreciseSquaredNorm()
81  // since it is a small price to pay for an increased precision.
82  const Fractional old_squared_norm = edge_squared_norms_[entering_col];
83  const Fractional precise_squared_norm = 1.0 + PreciseSquaredNorm(direction);
84  edge_squared_norms_[entering_col] = precise_squared_norm;
85 
86  const Fractional precise_norm = sqrt(precise_squared_norm);
87  const Fractional estimated_edges_norm_accuracy =
88  (precise_norm - sqrt(old_squared_norm)) / precise_norm;
89  stats_.edges_norm_accuracy.Add(estimated_edges_norm_accuracy);
90  if (std::abs(estimated_edges_norm_accuracy) >
91  parameters_.recompute_edges_norm_threshold()) {
92  VLOG(1) << "Recomputing edge norms: " << sqrt(precise_squared_norm)
93  << " vs " << sqrt(old_squared_norm);
94  recompute_edge_squared_norms_ = true;
95  for (bool* watcher : watchers_) *watcher = true;
96  }
97  }
98 }
99 
100 void PrimalEdgeNorms::UpdateBeforeBasisPivot(ColIndex entering_col,
101  ColIndex leaving_col,
102  RowIndex leaving_row,
103  const ScatteredColumn& direction,
104  UpdateRow* update_row) {
105  SCOPED_TIME_STAT(&stats_);
106  DCHECK_NE(entering_col, leaving_col);
107  if (!recompute_edge_squared_norms_) {
108  update_row->ComputeUpdateRow(leaving_row);
109  ComputeDirectionLeftInverse(entering_col, direction);
110  UpdateEdgeSquaredNorms(entering_col, leaving_col, leaving_row,
111  direction.values, *update_row);
112  }
113  if (!reset_devex_weights_) {
114  // Resets devex weights once in a while. If so, no need to update them
115  // before.
116  ++num_devex_updates_since_reset_;
117  if (num_devex_updates_since_reset_ >
118  parameters_.devex_weights_reset_period()) {
119  reset_devex_weights_ = true;
120  } else {
121  update_row->ComputeUpdateRow(leaving_row);
122  UpdateDevexWeights(entering_col, leaving_col, leaving_row,
123  direction.values, *update_row);
124  }
125  }
126 }
127 
128 void PrimalEdgeNorms::ComputeMatrixColumnNorms() {
129  SCOPED_TIME_STAT(&stats_);
130  matrix_column_norms_.resize(compact_matrix_.num_cols(), 0.0);
131  for (ColIndex col(0); col < compact_matrix_.num_cols(); ++col) {
132  matrix_column_norms_[col] = SquaredNorm(compact_matrix_.column(col));
133  num_operations_ += compact_matrix_.column(col).num_entries().value();
134  }
135 }
136 
137 void PrimalEdgeNorms::ComputeEdgeSquaredNorms() {
138  SCOPED_TIME_STAT(&stats_);
139 
140  // Since we will do a lot of inversions, it is better to be as efficient and
141  // precise as possible by refactorizing the basis.
142  DCHECK(basis_factorization_.IsRefactorized());
143  edge_squared_norms_.resize(compact_matrix_.num_cols(), 0.0);
144  for (const ColIndex col : variables_info_.GetIsRelevantBitRow()) {
145  // Note the +1.0 in the squared norm for the component of the edge on the
146  // 'entering_col'.
147  edge_squared_norms_[col] = 1.0 + basis_factorization_.RightSolveSquaredNorm(
148  compact_matrix_.column(col));
149  }
150  recompute_edge_squared_norms_ = false;
151 }
152 
153 // TODO(user): It should be possible to reorganize the code and call this when
154 // the value of direction is no longer needed. This will simplify the code and
155 // avoid a copy here.
156 void PrimalEdgeNorms::ComputeDirectionLeftInverse(
157  ColIndex entering_col, const ScatteredColumn& direction) {
158  SCOPED_TIME_STAT(&stats_);
159 
160  // Initialize direction_left_inverse_ to direction. Note the special case when
161  // the non-zero vector is empty which means we don't know and need to use the
162  // dense version.
163  const ColIndex size = RowToColIndex(direction.values.size());
164  const double kThreshold = 0.05 * size.value();
165  if (!direction_left_inverse_.non_zeros.empty() &&
166  (direction_left_inverse_.non_zeros.size() + direction.non_zeros.size() <
167  2 * kThreshold)) {
168  ClearAndResizeVectorWithNonZeros(size, &direction_left_inverse_);
169  for (const auto e : direction) {
170  direction_left_inverse_[RowToColIndex(e.row())] = e.coefficient();
171  }
172  } else {
173  direction_left_inverse_.values = Transpose(direction.values);
174  direction_left_inverse_.non_zeros.clear();
175  }
176 
177  if (direction.non_zeros.size() < kThreshold) {
178  direction_left_inverse_.non_zeros = TransposedView(direction).non_zeros;
179  }
180  basis_factorization_.LeftSolve(&direction_left_inverse_);
181 
182  // TODO(user): Refactorize if estimated accuracy above a threshold.
183  IF_STATS_ENABLED(stats_.direction_left_inverse_accuracy.Add(
184  compact_matrix_.ColumnScalarProduct(entering_col,
185  direction_left_inverse_.values) -
186  SquaredNorm(direction.values)));
187  IF_STATS_ENABLED(stats_.direction_left_inverse_density.Add(
188  Density(direction_left_inverse_.values)));
189 }
190 
191 // Let new_edge denote the edge of 'col' in the new basis. We want:
192 // reduced_costs_[col] = ScalarProduct(new_edge, basic_objective_);
193 // edge_squared_norms_[col] = SquaredNorm(new_edge);
194 //
195 // In order to compute this, we use the formulas:
196 // new_leaving_edge = old_entering_edge / divisor.
197 // new_edge = old_edge + update_coeff * new_leaving_edge.
198 void PrimalEdgeNorms::UpdateEdgeSquaredNorms(ColIndex entering_col,
199  ColIndex leaving_col,
200  RowIndex leaving_row,
201  const DenseColumn& direction,
202  const UpdateRow& update_row) {
203  SCOPED_TIME_STAT(&stats_);
204 
205  // 'pivot' is the value of the entering_edge at 'leaving_row'.
206  // The edge of the 'leaving_col' in the new basis is equal to
207  // entering_edge / 'pivot'.
208  const Fractional pivot = -direction[leaving_row];
209  DCHECK_NE(pivot, 0.0);
210 
211  // Note that this should be precise because of the call to
212  // TestEnteringEdgeNormPrecision().
213  const Fractional entering_squared_norm = edge_squared_norms_[entering_col];
214  const Fractional leaving_squared_norm =
215  std::max(1.0, entering_squared_norm / Square(pivot));
216 
217  int stat_lower_bounded_norms = 0;
218  const Fractional factor = 2.0 / pivot;
219  for (const ColIndex col : update_row.GetNonZeroPositions()) {
220  const Fractional coeff = update_row.GetCoefficient(col);
221  const Fractional scalar_product = compact_matrix_.ColumnScalarProduct(
222  col, direction_left_inverse_.values);
223  num_operations_ += compact_matrix_.column(col).num_entries().value();
224 
225  // Update the edge squared norm of this column. Note that the update
226  // formula used is important to maximize the precision. See an explanation
227  // in the dual context in Koberstein's PhD thesis, section 8.2.2.1.
228  edge_squared_norms_[col] +=
229  coeff * (coeff * leaving_squared_norm + factor * scalar_product);
230 
231  // Make sure it doesn't go under a known lower bound (TODO(user): ref?).
232  // This way norms are always >= 1.0 .
233  // TODO(user): precompute 1 / Square(pivot) or 1 / pivot? it will be
234  // slightly faster, but may introduce numerical issues. More generally,
235  // this test is only needed in a few cases, so is it worth it?
236  const Fractional lower_bound = 1.0 + Square(coeff / pivot);
237  if (edge_squared_norms_[col] < lower_bound) {
238  edge_squared_norms_[col] = lower_bound;
239  ++stat_lower_bounded_norms;
240  }
241  }
242  edge_squared_norms_[leaving_col] = leaving_squared_norm;
243  stats_.lower_bounded_norms.Add(stat_lower_bounded_norms);
244 }
245 
246 void PrimalEdgeNorms::UpdateDevexWeights(
247  ColIndex entering_col /* index q in the paper */,
248  ColIndex leaving_col /* index p in the paper */, RowIndex leaving_row,
249  const DenseColumn& direction, const UpdateRow& update_row) {
250  SCOPED_TIME_STAT(&stats_);
251 
252  // Compared to steepest edge update, the DEVEX weight uses the largest of the
253  // norms of two vectors to approximate the norm of the sum.
254  const Fractional entering_norm = sqrt(PreciseSquaredNorm(direction));
255  const Fractional pivot_magnitude = std::abs(direction[leaving_row]);
256  const Fractional leaving_norm =
257  std::max(1.0, entering_norm / pivot_magnitude);
258  for (const ColIndex col : update_row.GetNonZeroPositions()) {
259  const Fractional coeff = update_row.GetCoefficient(col);
260  const Fractional update_vector_norm = std::abs(coeff) * leaving_norm;
261  devex_weights_[col] =
262  std::max(devex_weights_[col], Square(update_vector_norm));
263  }
264  devex_weights_[leaving_col] = Square(leaving_norm);
265 }
266 
267 void PrimalEdgeNorms::ResetDevexWeights() {
268  SCOPED_TIME_STAT(&stats_);
269  if (parameters_.initialize_devex_with_column_norms()) {
270  devex_weights_ = GetMatrixColumnNorms();
271  } else {
272  devex_weights_.assign(compact_matrix_.num_cols(), 1.0);
273  }
274  num_devex_updates_since_reset_ = 0;
275  reset_devex_weights_ = false;
276 }
277 
278 } // namespace glop
279 } // namespace operations_research
const DenseRow & Transpose(const DenseColumn &col)
ColIndex RowToColIndex(RowIndex row)
Definition: lp_types.h:49
#define VLOG(verboselevel)
Definition: base/logging.h:983
void TestEnteringEdgeNormPrecision(ColIndex entering_col, const ScatteredColumn &direction)
Fractional RightSolveSquaredNorm(const ColumnView &a) const
ColIndex col
Definition: markowitz.cc:183
#define SCOPED_TIME_STAT(stats)
Definition: stats.h:438
void UpdateBeforeBasisPivot(ColIndex entering_col, ColIndex leaving_col, RowIndex leaving_row, const ScatteredColumn &direction, UpdateRow *update_row)
void ClearAndResizeVectorWithNonZeros(IndexType size, ScatteredRowOrCol *v)
void assign(IntType size, const T &v)
Definition: lp_types.h:278
ColumnView column(ColIndex col) const
Definition: sparse.h:369
void ComputeUpdateRow(RowIndex leaving_row)
Definition: update_row.cc:71
Fractional PreciseSquaredNorm(const SparseColumn &v)
int64_t max
Definition: alldiff_cst.cc:140
StrictITIVector< RowIndex, Fractional > DenseColumn
Definition: lp_types.h:332
bool empty() const
static constexpr PricingRule STEEPEST_EDGE
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:891
StrictITIVector< Index, Fractional > values
double lower_bound
static constexpr PricingRule DEVEX
PrimalEdgeNorms(const CompactSparseMatrix &compact_matrix, const VariablesInfo &variables_info, const BasisFactorization &basis_factorization)
Fractional ColumnScalarProduct(ColIndex col, const DenseRow &vector) const
Definition: sparse.h:387
double Density(const DenseRow &row)
#define DCHECK(condition)
Definition: base/logging.h:889
Fractional Square(Fractional f)
static constexpr PricingRule DANTZIG
const ScatteredRow & TransposedView(const ScatteredColumn &c)
Collection of objects used to extend the Constraint Solver library.
Fractional SquaredNorm(const SparseColumn &v)
const DenseBitRow & GetIsRelevantBitRow() const
#define IF_STATS_ENABLED(instructions)
Definition: stats.h:437