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