OR-Tools  9.6
sharded_optimization_utils.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 #include <cmath>
18 #include <cstdint>
19 #include <limits>
20 #include <optional>
21 #include <random>
22 #include <utility>
23 #include <vector>
24 
25 #include "Eigen/Core"
26 #include "Eigen/SparseCore"
27 #include "absl/log/check.h"
28 #include "absl/random/distributions.h"
29 #include "ortools/base/logging.h"
30 #include "ortools/base/mathutil.h"
33 #include "ortools/pdlp/sharder.h"
34 
35 namespace operations_research::pdlp {
36 
37 constexpr double kInfinity = std::numeric_limits<double>::infinity();
38 using ::Eigen::ColMajor;
39 using ::Eigen::SparseMatrix;
40 using ::Eigen::VectorXd;
41 using ::Eigen::VectorXi;
42 
44  : sharder_(sharder) {
45  average_ = ZeroVector(*sharder_);
46 }
47 
48 // We considered the five averaging algorithms M_* listed on the first page of
49 // https://www.jstor.org/stable/2286154 and the Kahan summation algorithm
50 // (https://en.wikipedia.org/wiki/Kahan_summation_algorithm). Of these only M_14
51 // satisfies our desired property that a constant sequence is averaged without
52 // roundoff while requiring only a single vector be stored. We therefore use
53 // M_14 (actually a natural weighted generalization, see below).
54 void ShardedWeightedAverage::Add(const VectorXd& datapoint, double weight) {
55  CHECK_GE(weight, 0.0);
56  CHECK_EQ(datapoint.size(), average_.size());
57  // This `if` protects against NaN if `sum_weights_` also == 0.0.
58  if (weight > 0.0) {
59  const double weight_ratio = weight / (sum_weights_ + weight);
60  sharder_->ParallelForEachShard([&](const Sharder::Shard& shard) {
61  shard(average_) += weight_ratio * (shard(datapoint) - shard(average_));
62  });
63  sum_weights_ += weight;
64  }
65  ++num_terms_;
66 }
67 
69  SetZero(*sharder_, average_);
70  sum_weights_ = 0.0;
71  num_terms_ = 0;
72 }
73 
75  VectorXd result;
76  // TODO(user): consider returning a reference to avoid this copy.
77  AssignVector(average_, *sharder_, result);
78  return result;
79 }
80 
81 namespace {
82 
83 double CombineBounds(const double v1, const double v2,
84  const double infinite_bound_threshold) {
85  double max = 0.0;
86  if (std::abs(v1) < infinite_bound_threshold) {
87  max = std::abs(v1);
88  }
89  if (std::abs(v2) < infinite_bound_threshold) {
90  max = std::max(max, std::abs(v2));
91  }
92  return max;
93 }
94 
95 struct VectorInfo {
96  int64_t num_finite_nonzero = 0;
97  int64_t num_infinite = 0;
98  int64_t num_zero = 0;
99  // The largest absolute value of the finite non-zero values.
100  double largest = 0.0;
101  // The smallest absolute value of the finite non-zero values.
102  double smallest = 0.0;
103  // The average absolute value of the finite values.
104  double average = 0.0;
105  // The L2 norm of the finite values.
106  double l2_norm = 0.0;
107 };
108 
109 struct InfNormInfo {
110  VectorInfo row_norms;
111  VectorInfo col_norms;
112 };
113 
114 // `VectorInfoAccumulator` accumulates values for a `VectorInfo`.
115 // NOTE: In `VectorInfo`, the max and min of an empty set is 0.0 by convention.
116 // In `VectorInfoAccumulator`, it is -`kInfinity` and `kInfinity` to simplify
117 // adding additional values.
118 class VectorInfoAccumulator {
119  public:
120  VectorInfoAccumulator() {}
121  // Move-only even though move and copy are the same cost, to help catch
122  // unintentional moves/copies (which are probably performance bugs).
123  VectorInfoAccumulator(const VectorInfoAccumulator&) = delete;
124  VectorInfoAccumulator& operator=(const VectorInfoAccumulator&) = delete;
125  VectorInfoAccumulator(VectorInfoAccumulator&&) = default;
126  VectorInfoAccumulator& operator=(VectorInfoAccumulator&&) = default;
127  void Add(double value);
128  void Add(const VectorInfoAccumulator& other);
129  explicit operator VectorInfo() const;
130 
131  private:
132  int64_t num_infinite_ = 0;
133  int64_t num_zero_ = 0;
134  int64_t num_finite_nonzero_ = 0;
135  double max_ = -kInfinity;
136  double min_ = kInfinity;
137  double sum_ = 0.0;
138  double sum_squared_ = 0.0;
139 };
140 
141 void VectorInfoAccumulator::Add(const double value) {
142  if (std::isinf(value)) {
143  ++num_infinite_;
144  } else if (value == 0) {
145  ++num_zero_;
146  } else {
147  ++num_finite_nonzero_;
148  const double abs_value = std::abs(value);
149  max_ = std::max(max_, abs_value);
150  min_ = std::min(min_, abs_value);
151  sum_ += abs_value;
152  sum_squared_ += abs_value * abs_value;
153  }
154 }
155 
156 void VectorInfoAccumulator::Add(const VectorInfoAccumulator& other) {
157  num_infinite_ += other.num_infinite_;
158  num_zero_ += other.num_zero_;
159  num_finite_nonzero_ += other.num_finite_nonzero_;
160  max_ = std::max(max_, other.max_);
161  min_ = std::min(min_, other.min_);
162  sum_ += other.sum_;
163  sum_squared_ += other.sum_squared_;
164 }
165 
166 VectorInfoAccumulator::operator VectorInfo() const {
167  return VectorInfo{
168  .num_finite_nonzero = num_finite_nonzero_,
169  .num_infinite = num_infinite_,
170  .num_zero = num_zero_,
171  .largest = num_finite_nonzero_ > 0 ? max_ : 0.0,
172  .smallest = num_finite_nonzero_ > 0 ? min_ : 0.0,
173  .average = num_finite_nonzero_ + num_zero_ > 0
174  ? sum_ / (num_finite_nonzero_ + num_zero_)
175  : std::numeric_limits<double>::quiet_NaN(),
176  .l2_norm = std::sqrt(sum_squared_),
177  };
178 }
179 
180 VectorInfo CombineAccumulators(
181  const std::vector<VectorInfoAccumulator>& accumulators) {
182  VectorInfoAccumulator result;
183  for (const VectorInfoAccumulator& accumulator : accumulators) {
184  result.Add(accumulator);
185  }
186  return VectorInfo(result);
187 }
188 
189 // TODO(b/223148482): Switch `vec` to `const Eigen::Ref<const VectorXd>` if/when
190 // `Sharder` supports `Eigen::Ref`, to avoid a copy when called on
191 // `qp.Qp().objective_matrix->diagonal()`.
192 VectorInfo ComputeVectorInfo(const VectorXd& vec, const Sharder& sharder) {
193  std::vector<VectorInfoAccumulator> local_accumulator(sharder.NumShards());
194  sharder.ParallelForEachShard([&](const Sharder::Shard& shard) {
195  VectorInfoAccumulator shard_accumulator;
196  for (double element : shard(vec)) {
197  shard_accumulator.Add(element);
198  }
199  local_accumulator[shard.Index()] = std::move(shard_accumulator);
200  });
201  return CombineAccumulators(local_accumulator);
202 }
203 
204 VectorInfo VariableBoundGapInfo(const VectorXd& lower_bounds,
205  const VectorXd& upper_bounds,
206  const Sharder& sharder) {
207  std::vector<VectorInfoAccumulator> local_accumulator(sharder.NumShards());
208  sharder.ParallelForEachShard([&](const Sharder::Shard& shard) {
209  VectorInfoAccumulator shard_accumulator;
210  for (double element : shard(upper_bounds) - shard(lower_bounds)) {
211  shard_accumulator.Add(element);
212  }
213  local_accumulator[shard.Index()] = std::move(shard_accumulator);
214  });
215  return CombineAccumulators(local_accumulator);
216 }
217 
218 VectorInfo MatrixAbsElementInfo(
219  const SparseMatrix<double, ColMajor, int64_t>& matrix,
220  const Sharder& sharder) {
221  std::vector<VectorInfoAccumulator> local_accumulator(sharder.NumShards());
222  sharder.ParallelForEachShard([&](const Sharder::Shard& shard) {
223  VectorInfoAccumulator shard_accumulator;
224  const auto matrix_shard = shard(matrix);
225  for (int64_t col_idx = 0; col_idx < matrix_shard.outerSize(); ++col_idx) {
226  for (decltype(matrix_shard)::InnerIterator it(matrix_shard, col_idx); it;
227  ++it) {
228  shard_accumulator.Add(it.value());
229  }
230  }
231  local_accumulator[shard.Index()] = std::move(shard_accumulator);
232  });
233  return CombineAccumulators(local_accumulator);
234 }
235 
236 VectorInfo CombinedBoundsInfo(const VectorXd& rhs_upper_bounds,
237  const VectorXd& rhs_lower_bounds,
238  const Sharder& sharder,
239  const double infinite_bound_threshold =
240  std::numeric_limits<double>::infinity()) {
241  std::vector<VectorInfoAccumulator> local_accumulator(sharder.NumShards());
242  sharder.ParallelForEachShard([&](const Sharder::Shard& shard) {
243  VectorInfoAccumulator shard_accumulator;
244  const auto lb_shard = shard(rhs_lower_bounds);
245  const auto ub_shard = shard(rhs_upper_bounds);
246  for (int64_t i = 0; i < lb_shard.size(); ++i) {
247  shard_accumulator.Add(
248  CombineBounds(ub_shard[i], lb_shard[i], infinite_bound_threshold));
249  }
250  local_accumulator[shard.Index()] = std::move(shard_accumulator);
251  });
252  return CombineAccumulators(local_accumulator);
253 }
254 
255 InfNormInfo ConstraintMatrixRowColInfo(
256  const SparseMatrix<double, ColMajor, int64_t>& constraint_matrix,
257  const SparseMatrix<double, ColMajor, int64_t>& constraint_matrix_transpose,
258  const Sharder& matrix_sharder, const Sharder& matrix_transpose_sharder,
259  const Sharder& primal_sharder, const Sharder& dual_sharder) {
260  VectorXd row_norms = ScaledColLInfNorm(
261  constraint_matrix_transpose,
262  /*col_scaling_vec=*/OnesVector(primal_sharder),
263  /*row_scaling_vec=*/OnesVector(dual_sharder), matrix_transpose_sharder);
264  VectorXd col_norms = ScaledColLInfNorm(
265  constraint_matrix,
266  /*row_scaling_vec=*/OnesVector(dual_sharder),
267  /*col_scaling_vec=*/OnesVector(primal_sharder), matrix_sharder);
268  return InfNormInfo{.row_norms = ComputeVectorInfo(row_norms, dual_sharder),
269  .col_norms = ComputeVectorInfo(col_norms, primal_sharder)};
270 }
271 
272 } // namespace
273 
274 QuadraticProgramStats ComputeStats(
275  const ShardedQuadraticProgram& qp,
276  const double infinite_constraint_bound_threshold) {
277  // Caution: if the constraint matrix is empty, elementwise operations
278  // (like `.coeffs().maxCoeff()` or `.minCoeff()`) will fail.
279  InfNormInfo cons_matrix_norm_info = ConstraintMatrixRowColInfo(
282  qp.PrimalSharder(), qp.DualSharder());
283  VectorInfo cons_matrix_info = MatrixAbsElementInfo(
285  VectorInfo combined_bounds_info = CombinedBoundsInfo(
287  qp.DualSharder(), infinite_constraint_bound_threshold);
288  VectorInfo obj_vec_info =
289  ComputeVectorInfo(qp.Qp().objective_vector, qp.PrimalSharder());
290  VectorInfo gaps_info =
291  VariableBoundGapInfo(qp.Qp().variable_lower_bounds,
293  QuadraticProgramStats program_stats;
294  program_stats.set_num_variables(qp.PrimalSize());
295  program_stats.set_num_constraints(qp.DualSize());
296  program_stats.set_constraint_matrix_col_min_l_inf_norm(
297  cons_matrix_norm_info.col_norms.smallest);
298  program_stats.set_constraint_matrix_row_min_l_inf_norm(
299  cons_matrix_norm_info.row_norms.smallest);
300  program_stats.set_constraint_matrix_num_nonzeros(
301  cons_matrix_info.num_finite_nonzero);
302  program_stats.set_constraint_matrix_abs_max(cons_matrix_info.largest);
303  program_stats.set_constraint_matrix_abs_min(cons_matrix_info.smallest);
304  program_stats.set_constraint_matrix_abs_avg(cons_matrix_info.average);
305  program_stats.set_constraint_matrix_l2_norm(cons_matrix_info.l2_norm);
306  program_stats.set_combined_bounds_max(combined_bounds_info.largest);
307  program_stats.set_combined_bounds_min(combined_bounds_info.smallest);
308  program_stats.set_combined_bounds_avg(combined_bounds_info.average);
309  program_stats.set_combined_bounds_l2_norm(combined_bounds_info.l2_norm);
310  program_stats.set_variable_bound_gaps_num_finite(
311  gaps_info.num_finite_nonzero + gaps_info.num_zero);
312  program_stats.set_variable_bound_gaps_max(gaps_info.largest);
313  program_stats.set_variable_bound_gaps_min(gaps_info.smallest);
314  program_stats.set_variable_bound_gaps_avg(gaps_info.average);
315  program_stats.set_variable_bound_gaps_l2_norm(gaps_info.l2_norm);
316  program_stats.set_objective_vector_abs_max(obj_vec_info.largest);
317  program_stats.set_objective_vector_abs_min(obj_vec_info.smallest);
318  program_stats.set_objective_vector_abs_avg(obj_vec_info.average);
319  program_stats.set_objective_vector_l2_norm(obj_vec_info.l2_norm);
320  if (IsLinearProgram(qp.Qp())) {
321  program_stats.set_objective_matrix_num_nonzeros(0);
322  program_stats.set_objective_matrix_abs_max(0);
323  program_stats.set_objective_matrix_abs_min(0);
324  program_stats.set_objective_matrix_abs_avg(
325  std::numeric_limits<double>::quiet_NaN());
326  program_stats.set_objective_matrix_l2_norm(0);
327  } else {
328  VectorInfo obj_matrix_info = ComputeVectorInfo(
329  qp.Qp().objective_matrix->diagonal(), qp.PrimalSharder());
330  program_stats.set_objective_matrix_num_nonzeros(
331  obj_matrix_info.num_finite_nonzero);
332  program_stats.set_objective_matrix_abs_max(obj_matrix_info.largest);
333  program_stats.set_objective_matrix_abs_min(obj_matrix_info.smallest);
334  program_stats.set_objective_matrix_abs_avg(obj_matrix_info.average);
335  program_stats.set_objective_matrix_l2_norm(obj_matrix_info.l2_norm);
336  }
337  return program_stats;
338 }
339 
340 namespace {
341 
342 enum class ScalingNorm { kL2, kLInf };
343 
344 // Divides `vector` (componentwise) by the square root of `divisor`, updating
345 // `vector` in-place. If a component of `divisor` is equal to zero, leaves the
346 // component of `vector` unchanged. `sharder` should have the same size as
347 // `vector`. For best performance `sharder` should have been created with the
348 // `Sharder(int64_t, int, ThreadPool*)` constructor.
349 void DivideBySquareRootOfDivisor(const VectorXd& divisor,
350  const Sharder& sharder, VectorXd& vector) {
351  sharder.ParallelForEachShard([&](const Sharder::Shard& shard) {
352  auto vec_shard = shard(vector);
353  const auto divisor_shard = shard(divisor);
354  for (int64_t index = 0; index < vec_shard.size(); ++index) {
355  if (divisor_shard[index] != 0) {
356  vec_shard[index] /= std::sqrt(divisor_shard[index]);
357  }
358  }
359  });
360 }
361 
362 void ApplyScalingIterationsForNorm(const ShardedQuadraticProgram& sharded_qp,
363  const int num_iterations,
364  const ScalingNorm norm,
365  VectorXd& row_scaling_vec,
366  VectorXd& col_scaling_vec) {
367  const QuadraticProgram& qp = sharded_qp.Qp();
368  const int64_t num_col = qp.constraint_matrix.cols();
369  const int64_t num_row = qp.constraint_matrix.rows();
370  CHECK_EQ(num_col, col_scaling_vec.size());
371  CHECK_EQ(num_row, row_scaling_vec.size());
372  for (int i = 0; i < num_iterations; ++i) {
373  VectorXd col_norm;
374  VectorXd row_norm;
375  switch (norm) {
376  case ScalingNorm::kL2: {
377  col_norm = ScaledColL2Norm(qp.constraint_matrix, row_scaling_vec,
378  col_scaling_vec,
379  sharded_qp.ConstraintMatrixSharder());
380  row_norm = ScaledColL2Norm(
381  sharded_qp.TransposedConstraintMatrix(), col_scaling_vec,
382  row_scaling_vec, sharded_qp.TransposedConstraintMatrixSharder());
383  break;
384  }
385  case ScalingNorm::kLInf: {
386  col_norm = ScaledColLInfNorm(qp.constraint_matrix, row_scaling_vec,
387  col_scaling_vec,
388  sharded_qp.ConstraintMatrixSharder());
389  row_norm = ScaledColLInfNorm(
390  sharded_qp.TransposedConstraintMatrix(), col_scaling_vec,
391  row_scaling_vec, sharded_qp.TransposedConstraintMatrixSharder());
392  break;
393  }
394  }
395  DivideBySquareRootOfDivisor(col_norm, sharded_qp.PrimalSharder(),
396  col_scaling_vec);
397  DivideBySquareRootOfDivisor(row_norm, sharded_qp.DualSharder(),
398  row_scaling_vec);
399  }
400 }
401 
402 } // namespace
403 
405  const int num_iterations, VectorXd& row_scaling_vec,
406  VectorXd& col_scaling_vec) {
407  ApplyScalingIterationsForNorm(sharded_qp, num_iterations, ScalingNorm::kLInf,
408  row_scaling_vec, col_scaling_vec);
409 }
410 
412  VectorXd& row_scaling_vec, VectorXd& col_scaling_vec) {
413  ApplyScalingIterationsForNorm(sharded_qp, /*num_iterations=*/1,
414  ScalingNorm::kL2, row_scaling_vec,
415  col_scaling_vec);
416 }
417 
419  ShardedQuadraticProgram& sharded_qp) {
420  ScalingVectors scaling{
421  .row_scaling_vec = OnesVector(sharded_qp.DualSharder()),
422  .col_scaling_vec = OnesVector(sharded_qp.PrimalSharder())};
423  bool do_rescale = false;
424  if (rescaling_options.l_inf_ruiz_iterations > 0) {
425  do_rescale = true;
426  LInfRuizRescaling(sharded_qp, rescaling_options.l_inf_ruiz_iterations,
427  scaling.row_scaling_vec, scaling.col_scaling_vec);
428  }
429  if (rescaling_options.l2_norm_rescaling) {
430  do_rescale = true;
431  L2NormRescaling(sharded_qp, scaling.row_scaling_vec,
432  scaling.col_scaling_vec);
433  }
434  if (do_rescale) {
435  sharded_qp.RescaleQuadraticProgram(scaling.col_scaling_vec,
436  scaling.row_scaling_vec);
437  }
438  return scaling;
439 }
440 
442  const VectorXd& primal_solution,
443  const VectorXd& dual_product) {
444  LagrangianPart result{.gradient = VectorXd(sharded_qp.PrimalSize())};
445  const QuadraticProgram& qp = sharded_qp.Qp();
446  VectorXd value_parts(sharded_qp.PrimalSharder().NumShards());
447  sharded_qp.PrimalSharder().ParallelForEachShard(
448  [&](const Sharder::Shard& shard) {
449  if (IsLinearProgram(qp)) {
450  shard(result.gradient) =
451  shard(qp.objective_vector) - shard(dual_product);
452  value_parts[shard.Index()] =
453  shard(primal_solution).dot(shard(result.gradient));
454  } else {
455  // Note: using `auto` instead of `VectorXd` for the type of
456  // `objective_product` causes eigen to defer the matrix product until
457  // it is used (twice).
458  const VectorXd objective_product =
459  shard(*qp.objective_matrix) * shard(primal_solution);
460  shard(result.gradient) = shard(qp.objective_vector) +
461  objective_product - shard(dual_product);
462  value_parts[shard.Index()] =
463  shard(primal_solution)
464  .dot(shard(result.gradient) - 0.5 * objective_product);
465  }
466  });
467  result.value = value_parts.sum();
468  return result;
469 }
470 
471 double DualSubgradientCoefficient(const double constraint_lower_bound,
472  const double constraint_upper_bound,
473  const double dual,
474  const double primal_product) {
475  if (dual < 0.0) {
476  return constraint_upper_bound;
477  } else if (dual > 0.0) {
478  return constraint_lower_bound;
479  } else if (std::isfinite(constraint_lower_bound) &&
480  std::isfinite(constraint_upper_bound)) {
481  if (primal_product < constraint_lower_bound) {
482  return constraint_lower_bound;
483  } else if (primal_product > constraint_upper_bound) {
484  return constraint_upper_bound;
485  } else {
486  return primal_product;
487  }
488  } else if (std::isfinite(constraint_lower_bound)) {
489  return constraint_lower_bound;
490  } else if (std::isfinite(constraint_upper_bound)) {
491  return constraint_upper_bound;
492  } else {
493  return 0.0;
494  }
495 }
496 
498  const VectorXd& dual_solution,
499  const VectorXd& primal_product) {
500  LagrangianPart result{.gradient = VectorXd(sharded_qp.DualSize())};
501  const QuadraticProgram& qp = sharded_qp.Qp();
502  VectorXd value_parts(sharded_qp.DualSharder().NumShards());
503  sharded_qp.DualSharder().ParallelForEachShard(
504  [&](const Sharder::Shard& shard) {
505  const auto constraint_lower_bounds = shard(qp.constraint_lower_bounds);
506  const auto constraint_upper_bounds = shard(qp.constraint_upper_bounds);
507  const auto dual_solution_shard = shard(dual_solution);
508  auto dual_gradient_shard = shard(result.gradient);
509  const auto primal_product_shard = shard(primal_product);
510  double value_sum = 0.0;
511  for (int64_t i = 0; i < dual_gradient_shard.size(); ++i) {
512  dual_gradient_shard[i] = DualSubgradientCoefficient(
513  constraint_lower_bounds[i], constraint_upper_bounds[i],
514  dual_solution_shard[i], primal_product_shard[i]);
515  value_sum += dual_gradient_shard[i] * dual_solution_shard[i];
516  }
517  value_parts[shard.Index()] = value_sum;
518  dual_gradient_shard -= primal_product_shard;
519  });
520  result.value = value_parts.sum();
521  return result;
522 }
523 
524 namespace {
525 
526 using ::Eigen::ColMajor;
527 using ::Eigen::SparseMatrix;
528 
529 // Scales `vector` (in-place) to have norm 1, unless it has norm 0 (in which
530 // case it is left unscaled). Returns the original norm of `vector`.
531 double NormalizeVector(const Sharder& sharder, VectorXd& vector) {
532  const double norm = Norm(vector, sharder);
533  if (norm != 0.0) {
534  sharder.ParallelForEachShard(
535  [&](const Sharder::Shard& shard) { shard(vector) /= norm; });
536  }
537  return norm;
538 }
539 
540 // Estimates the probability that the power method, after k iterations, has
541 // relative error > `epsilon`. This is based on Theorem 4.1(a) (on page 13) from
542 // "Estimating the Largest Eigenvalue by the Power and Lanczos Algorithms with a
543 // Random Start"
544 // https://pdfs.semanticscholar.org/2b2e/a941e55e5fa2ee9d8f4ff393c14482051143.pdf
545 double PowerMethodFailureProbability(int64_t dimension, double epsilon, int k) {
546  if (k < 2 || epsilon <= 0.0) {
547  // The theorem requires `epsilon > 0` and `k >= 2`.
548  return 1.0;
549  }
550  return std::min(0.824, 0.354 / std::sqrt(epsilon * (k - 1))) *
551  std::sqrt(dimension) * std::pow(1.0 - epsilon, k - 0.5);
552 }
553 
554 SingularValueAndIterations EstimateMaximumSingularValue(
555  const SparseMatrix<double, ColMajor, int64_t>& matrix,
556  const SparseMatrix<double, ColMajor, int64_t>& matrix_transpose,
557  const std::optional<VectorXd>& active_set_indicator,
558  const std::optional<VectorXd>& transpose_active_set_indicator,
559  const Sharder& matrix_sharder, const Sharder& matrix_transpose_sharder,
560  const Sharder& primal_vector_sharder, const Sharder& dual_vector_sharder,
561  const double desired_relative_error, const double failure_probability,
562  std::mt19937& mt_generator) {
563  const int64_t dimension = matrix.cols();
564  VectorXd eigenvector(dimension);
565  // Even though it will be slower, we initialize `eigenvector` sequentially so
566  // that the result doesn't depend on the number of threads.
567  for (double& entry : eigenvector) {
568  entry = absl::Gaussian<double>(mt_generator);
569  }
570  if (active_set_indicator.has_value()) {
571  CoefficientWiseProductInPlace(*active_set_indicator, primal_vector_sharder,
572  eigenvector);
573  }
574  NormalizeVector(primal_vector_sharder, eigenvector);
575  double eigenvalue_estimate = 0.0;
576 
577  int num_iterations = 0;
578  // The maximum singular value of A is the square root of the maximum
579  // eigenvalue of A^T A. `epsilon` is the relative error needed for the maximum
580  // eigenvalue of A^T A that gives `desired_relative_error` for the maximum
581  // singular value of A.
582  const double epsilon = 1.0 - MathUtil::Square(1.0 - desired_relative_error);
583  while (PowerMethodFailureProbability(dimension, epsilon, num_iterations) >
584  failure_probability) {
585  VectorXd dual_eigenvector = TransposedMatrixVectorProduct(
586  matrix_transpose, eigenvector, matrix_transpose_sharder);
587  if (transpose_active_set_indicator.has_value()) {
588  CoefficientWiseProductInPlace(*transpose_active_set_indicator,
589  dual_vector_sharder, dual_eigenvector);
590  }
591  VectorXd next_eigenvector =
592  TransposedMatrixVectorProduct(matrix, dual_eigenvector, matrix_sharder);
593  if (active_set_indicator.has_value()) {
594  CoefficientWiseProductInPlace(*active_set_indicator,
595  primal_vector_sharder, next_eigenvector);
596  }
597  eigenvalue_estimate =
598  Dot(eigenvector, next_eigenvector, primal_vector_sharder);
599  eigenvector = std::move(next_eigenvector);
600  ++num_iterations;
601  const double primal_norm =
602  NormalizeVector(primal_vector_sharder, eigenvector);
603 
604  VLOG(1) << "Iteration " << num_iterations << " singular value estimate "
605  << std::sqrt(eigenvalue_estimate) << " primal norm " << primal_norm;
606  }
607  return SingularValueAndIterations{
608  .singular_value = std::sqrt(eigenvalue_estimate),
609  .num_iterations = num_iterations,
610  .estimated_relative_error = desired_relative_error};
611 }
612 
613 // Given `primal_solution`, compute a {0, 1}-valued vector that is nonzero in
614 // all the coordinates that are not saturating the primal variable bounds.
615 VectorXd ComputePrimalActiveSetIndicator(
616  const ShardedQuadraticProgram& sharded_qp,
617  const VectorXd& primal_solution) {
618  VectorXd indicator(sharded_qp.PrimalSize());
619  sharded_qp.PrimalSharder().ParallelForEachShard(
620  [&](const Sharder::Shard& shard) {
621  const auto lower_bound_shard =
622  shard(sharded_qp.Qp().variable_lower_bounds);
623  const auto upper_bound_shard =
624  shard(sharded_qp.Qp().variable_upper_bounds);
625  const auto primal_solution_shard = shard(primal_solution);
626  auto indicator_shard = shard(indicator);
627  const int64_t shard_size =
628  sharded_qp.PrimalSharder().ShardSize(shard.Index());
629  for (int64_t i = 0; i < shard_size; ++i) {
630  if ((primal_solution_shard[i] == lower_bound_shard[i]) ||
631  (primal_solution_shard[i] == upper_bound_shard[i])) {
632  indicator_shard[i] = 0.0;
633  } else {
634  indicator_shard[i] = 1.0;
635  }
636  }
637  });
638  return indicator;
639 }
640 
641 // Like `ComputePrimalActiveSetIndicator(sharded_qp, primal_solution)`, but this
642 // time using the implicit bounds on the dual variables.
643 VectorXd ComputeDualActiveSetIndicator(
644  const ShardedQuadraticProgram& sharded_qp, const VectorXd& dual_solution) {
645  VectorXd indicator(sharded_qp.DualSize());
646  sharded_qp.DualSharder().ParallelForEachShard(
647  [&](const Sharder::Shard& shard) {
648  const auto lower_bound_shard =
649  shard(sharded_qp.Qp().constraint_lower_bounds);
650  const auto upper_bound_shard =
651  shard(sharded_qp.Qp().constraint_upper_bounds);
652  const auto dual_solution_shard = shard(dual_solution);
653  auto indicator_shard = shard(indicator);
654  const int64_t shard_size =
655  sharded_qp.DualSharder().ShardSize(shard.Index());
656  for (int64_t i = 0; i < shard_size; ++i) {
657  if (dual_solution_shard[i] == 0.0 &&
658  (std::isinf(lower_bound_shard[i]) ||
659  std::isinf(upper_bound_shard[i]))) {
660  indicator_shard[i] = 0.0;
661  } else {
662  indicator_shard[i] = 1.0;
663  }
664  }
665  });
666  return indicator;
667 }
668 
669 } // namespace
670 
672  const ShardedQuadraticProgram& sharded_qp,
673  const std::optional<VectorXd>& primal_solution,
674  const std::optional<VectorXd>& dual_solution,
675  const double desired_relative_error, const double failure_probability,
676  std::mt19937& mt_generator) {
677  std::optional<VectorXd> primal_active_set_indicator;
678  std::optional<VectorXd> dual_active_set_indicator;
679  if (primal_solution.has_value()) {
680  primal_active_set_indicator =
681  ComputePrimalActiveSetIndicator(sharded_qp, *primal_solution);
682  }
683  if (dual_solution.has_value()) {
684  dual_active_set_indicator =
685  ComputeDualActiveSetIndicator(sharded_qp, *dual_solution);
686  }
687  return EstimateMaximumSingularValue(
688  sharded_qp.Qp().constraint_matrix,
689  sharded_qp.TransposedConstraintMatrix(), primal_active_set_indicator,
690  dual_active_set_indicator, sharded_qp.ConstraintMatrixSharder(),
692  sharded_qp.PrimalSharder(), sharded_qp.DualSharder(),
693  desired_relative_error, failure_probability, mt_generator);
694 }
695 
696 bool HasValidBounds(const ShardedQuadraticProgram& sharded_qp) {
697  const QuadraticProgram& qp = sharded_qp.Qp();
698  const bool constraint_bounds_valid =
700  [&](const Sharder::Shard& shard) {
701  return (shard(qp.constraint_lower_bounds).array() <=
702  shard(qp.constraint_upper_bounds).array())
703  .all();
704  });
705  const bool variable_bounds_valid =
707  [&](const Sharder::Shard& shard) {
708  return (shard(qp.variable_lower_bounds).array() <=
709  shard(qp.variable_upper_bounds).array())
710  .all();
711  });
712 
713  return constraint_bounds_valid && variable_bounds_valid;
714 }
715 
717  VectorXd& primal) {
718  sharded_qp.PrimalSharder().ParallelForEachShard(
719  [&](const Sharder::Shard& shard) {
720  const QuadraticProgram& qp = sharded_qp.Qp();
721  shard(primal) = shard(primal)
722  .cwiseMin(shard(qp.variable_upper_bounds))
723  .cwiseMax(shard(qp.variable_lower_bounds));
724  });
725 }
726 
728  VectorXd& dual) {
729  const QuadraticProgram& qp = sharded_qp.Qp();
730  sharded_qp.DualSharder().ParallelForEachShard(
731  [&](const Sharder::Shard& shard) {
732  const auto lower_bound_shard = shard(qp.constraint_lower_bounds);
733  const auto upper_bound_shard = shard(qp.constraint_upper_bounds);
734  auto dual_shard = shard(dual);
735 
736  for (int64_t i = 0; i < dual_shard.size(); ++i) {
737  if (!std::isfinite(upper_bound_shard[i])) {
738  dual_shard[i] = std::max(dual_shard[i], 0.0);
739  }
740  if (!std::isfinite(lower_bound_shard[i])) {
741  dual_shard[i] = std::min(dual_shard[i], 0.0);
742  }
743  }
744  });
745 }
746 
747 } // namespace operations_research::pdlp
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
static T Square(const T x)
Definition: mathutil.h:101
void RescaleQuadraticProgram(const Eigen::VectorXd &col_scaling_vec, const Eigen::VectorXd &row_scaling_vec)
const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > & TransposedConstraintMatrix() const
void Add(const Eigen::VectorXd &datapoint, double weight)
void ParallelForEachShard(const std::function< void(const Shard &)> &func) const
Definition: sharder.cc:104
bool ParallelTrueForAllShards(const std::function< bool(const Shard &)> &func) const
Definition: sharder.cc:147
int64_t value
int index
void SetZero(const Sharder &sharder, VectorXd &dest)
Definition: sharder.cc:173
LagrangianPart ComputeDualGradient(const ShardedQuadraticProgram &sharded_qp, const VectorXd &dual_solution, const VectorXd &primal_product)
double Dot(const VectorXd &v1, const VectorXd &v2, const Sharder &sharder)
Definition: sharder.cc:225
LagrangianPart ComputePrimalGradient(const ShardedQuadraticProgram &sharded_qp, const VectorXd &primal_solution, const VectorXd &dual_product)
VectorXd TransposedMatrixVectorProduct(const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &matrix, const VectorXd &vector, const Sharder &sharder)
Definition: sharder.cc:158
void LInfRuizRescaling(const ShardedQuadraticProgram &sharded_qp, const int num_iterations, VectorXd &row_scaling_vec, VectorXd &col_scaling_vec)
SingularValueAndIterations EstimateMaximumSingularValueOfConstraintMatrix(const ShardedQuadraticProgram &sharded_qp, const std::optional< VectorXd > &primal_solution, const std::optional< VectorXd > &dual_solution, const double desired_relative_error, const double failure_probability, std::mt19937 &mt_generator)
VectorXd ScaledColLInfNorm(const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &matrix, const VectorXd &row_scaling_vec, const VectorXd &col_scaling_vec, const Sharder &sharder)
Definition: sharder.cc:286
double DualSubgradientCoefficient(const double constraint_lower_bound, const double constraint_upper_bound, const double dual, const double primal_product)
bool HasValidBounds(const QuadraticProgram &qp)
bool IsLinearProgram(const QuadraticProgram &qp)
void ProjectToDualVariableBounds(const ShardedQuadraticProgram &sharded_qp, VectorXd &dual)
void CoefficientWiseProductInPlace(const VectorXd &scale, const Sharder &sharder, VectorXd &dest)
Definition: sharder.cc:211
void L2NormRescaling(const ShardedQuadraticProgram &sharded_qp, VectorXd &row_scaling_vec, VectorXd &col_scaling_vec)
VectorXd ScaledColL2Norm(const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &matrix, const VectorXd &row_scaling_vec, const VectorXd &col_scaling_vec, const Sharder &sharder)
Definition: sharder.cc:308
ScalingVectors ApplyRescaling(const RescalingOptions &rescaling_options, ShardedQuadraticProgram &sharded_qp)
QuadraticProgramStats ComputeStats(const ShardedQuadraticProgram &qp, const double infinite_constraint_bound_threshold)
void ProjectToPrimalVariableBounds(const ShardedQuadraticProgram &sharded_qp, VectorXd &primal)
double Norm(const VectorXd &vector, const Sharder &sharder)
Definition: sharder.cc:248
VectorXd ZeroVector(const Sharder &sharder)
Definition: sharder.cc:179
void AssignVector(const VectorXd &vec, const Sharder &sharder, VectorXd &dest)
Definition: sharder.cc:199
VectorXd OnesVector(const Sharder &sharder)
Definition: sharder.cc:185
int64_t weight
Definition: pack.cc:510
std::vector< double > lower_bounds
std::vector< double > upper_bounds
int64_t num_zero
int64_t num_finite_nonzero
VectorInfo row_norms
int64_t num_infinite
VectorInfo col_norms
Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > constraint_matrix
std::optional< Eigen::DiagonalMatrix< double, Eigen::Dynamic > > objective_matrix
#define VLOG(verboselevel)
Definition: vlog.h:39