OR-Tools  9.6
iteration_stats.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 <optional>
20 #include <random>
21 #include <utility>
22 #include <vector>
23 
24 #include "Eigen/Core"
25 #include "Eigen/SparseCore"
26 #include "absl/log/check.h"
27 #include "absl/random/distributions.h"
28 #include "ortools/base/mathutil.h"
31 #include "ortools/pdlp/sharder.h"
32 #include "ortools/pdlp/solve_log.pb.h"
33 #include "ortools/pdlp/solvers.pb.h"
34 
36 namespace {
37 
38 using ::Eigen::VectorXd;
39 
40 // `ResidualNorms` contains measures of the infeasibility of a primal or dual
41 // solution. `objective_correction` is the (additive) adjustment to the
42 // objective function from the reduced costs. `objective_full_correction` is the
43 // (additive) adjustment to the objective function if all dual residuals were
44 // set to zero, while `l_inf_residual`, `l_2_residual`, and
45 // `l_inf_componentwise_residual` are the L_infinity, L_2, and L_infinity
46 // (componentwise) norms of the residuals (portions of the primal gradient not
47 // included in the reduced costs).
48 struct ResidualNorms {
52  double l_2_residual;
54 };
55 
56 // Computes norms of the primal residual infeasibilities (b - A x) of the
57 // unscaled problem. Note the primal residuals of the unscaled problem are equal
58 // to those of the scaled problem divided by `row_scaling_vec`. Does not perform
59 // any corrections (so the returned `.objective_correction == 0` and
60 // `.objective_full_correction == 0`). `sharded_qp` is assumed to be the scaled
61 // problem. If `use_homogeneous_constraint_bounds` is set to true the residuals
62 // are computed with upper and lower bounds zeroed out (note that we only zero
63 // out the bounds that are finite in the original problem).
64 // NOTE: `componentwise_residual_offset` only affects the value of
65 // `l_inf_componentwise_residual` in the returned `ResidualNorms`.
66 ResidualNorms PrimalResidualNorms(
67  const ShardedQuadraticProgram& sharded_qp, const VectorXd& row_scaling_vec,
68  const VectorXd& scaled_primal_solution,
69  const double componentwise_residual_offset,
70  bool use_homogeneous_constraint_bounds = false) {
71  const QuadraticProgram& qp = sharded_qp.Qp();
72  CHECK_EQ(row_scaling_vec.size(), sharded_qp.DualSize());
73  CHECK_EQ(scaled_primal_solution.size(), sharded_qp.PrimalSize());
74 
75  VectorXd primal_product = TransposedMatrixVectorProduct(
76  sharded_qp.TransposedConstraintMatrix(), scaled_primal_solution,
77  sharded_qp.TransposedConstraintMatrixSharder());
78  VectorXd local_l_inf_residual(sharded_qp.DualSharder().NumShards());
79  VectorXd local_sumsq_residual(sharded_qp.DualSharder().NumShards());
80  VectorXd local_l_inf_componentwise_residual(
81  sharded_qp.DualSharder().NumShards());
82  sharded_qp.DualSharder().ParallelForEachShard(
83  [&](const Sharder::Shard& shard) {
84  const auto lower_bound_shard = shard(qp.constraint_lower_bounds);
85  const auto upper_bound_shard = shard(qp.constraint_upper_bounds);
86  const auto row_scaling_shard = shard(row_scaling_vec);
87  const auto primal_product_shard = shard(primal_product);
88  double l_inf_residual = 0.0;
89  double sumsq_residual = 0.0;
90  double l_inf_componentwise_residual = 0.0;
91  for (int64_t i = 0; i < primal_product_shard.size(); ++i) {
92  const double upper_bound = (use_homogeneous_constraint_bounds &&
93  std::isfinite(upper_bound_shard[i]))
94  ? 0.0
95  : upper_bound_shard[i];
96  const double lower_bound = (use_homogeneous_constraint_bounds &&
97  std::isfinite(lower_bound_shard[i]))
98  ? 0.0
99  : lower_bound_shard[i];
100  double scaled_residual = 0.0;
101  double residual_bound = 0.0;
102  if (primal_product_shard[i] > upper_bound) {
103  scaled_residual = primal_product_shard[i] - upper_bound;
104  residual_bound = upper_bound;
105  } else if (primal_product_shard[i] < lower_bound) {
106  scaled_residual = lower_bound - primal_product_shard[i];
107  residual_bound = lower_bound;
108  }
109  const double residual = scaled_residual / row_scaling_shard[i];
111  sumsq_residual += residual * residual;
112  // Special case: ignore `residual` if == 0, to avoid NaN if offset and
113  // bound are both zero.
114  if (residual > 0.0) {
117  residual / (componentwise_residual_offset +
118  std::abs(residual_bound / row_scaling_shard[i])));
119  }
120  }
121  local_l_inf_residual[shard.Index()] = l_inf_residual;
122  local_sumsq_residual[shard.Index()] = sumsq_residual;
123  local_l_inf_componentwise_residual[shard.Index()] =
125  });
126  return ResidualNorms{
127  .objective_correction = 0.0,
128  .objective_full_correction = 0.0,
129  .l_inf_residual = local_l_inf_residual.lpNorm<Eigen::Infinity>(),
130  .l_2_residual = std::sqrt(local_sumsq_residual.sum()),
132  local_l_inf_componentwise_residual.lpNorm<Eigen::Infinity>(),
133  };
134 }
135 
136 // Decides whether a primal gradient term should be handled as a reduced cost or
137 // as a dual residual. See the documentation for
138 // `PrimalDualHybridGradientParams::
139 // handle_some_primal_gradients_on_finite_bounds_as_residuals`.
140 bool HandlePrimalGradientTermAsReducedCost(
141  const PrimalDualHybridGradientParams& params, double primal_gradient,
142  double primal_value, double lower_bound, double upper_bound) {
143  if (primal_gradient == 0.0) return true;
144  const double active_bound = primal_gradient > 0.0 ? lower_bound : upper_bound;
145  if (params.handle_some_primal_gradients_on_finite_bounds_as_residuals()) {
146  // Note that this test is always false if `active_bound` is infinite.
147  return std::abs(primal_value - active_bound) <= std::abs(primal_value);
148  } else {
149  return std::isfinite(active_bound);
150  }
151 }
152 
153 // Computes norms of the dual residuals and reduced costs of the unscaled
154 // problem. Note the primal gradient of the unscaled problem is equal to
155 // `scaled_primal_gradient` divided by `col_scaling_vec`. `sharded_qp` is
156 // assumed to be the scaled problem. See
157 // https://developers.google.com/optimization/lp/pdlp_math and the documentation
158 // for `PrimalDualHybridGradientParams::
159 // handle_some_primal_gradients_on_finite_bounds_as_residuals` for details and
160 // notation.
161 // NOTE: `componentwise_residual_offset` only affects the value of
162 // `l_inf_componentwise_residual` in the returned `ResidualNorms`.
163 ResidualNorms DualResidualNorms(const PrimalDualHybridGradientParams& params,
164  const ShardedQuadraticProgram& sharded_qp,
165  const VectorXd& col_scaling_vec,
166  const VectorXd& scaled_primal_solution,
167  const VectorXd& scaled_primal_gradient,
168  const double componentwise_residual_offset) {
169  const QuadraticProgram& qp = sharded_qp.Qp();
170  CHECK_EQ(col_scaling_vec.size(), sharded_qp.PrimalSize());
171  CHECK_EQ(scaled_primal_gradient.size(), sharded_qp.PrimalSize());
172  VectorXd local_dual_correction(sharded_qp.PrimalSharder().NumShards());
173  VectorXd local_dual_full_correction(sharded_qp.PrimalSharder().NumShards());
174  VectorXd local_l_inf_residual(sharded_qp.PrimalSharder().NumShards());
175  VectorXd local_sumsq_residual(sharded_qp.PrimalSharder().NumShards());
176  VectorXd local_l_inf_componentwise_residual(
177  sharded_qp.PrimalSharder().NumShards());
178  sharded_qp.PrimalSharder().ParallelForEachShard(
179  [&](const Sharder::Shard& shard) {
180  const auto lower_bound_shard = shard(qp.variable_lower_bounds);
181  const auto upper_bound_shard = shard(qp.variable_upper_bounds);
182  const auto primal_gradient_shard = shard(scaled_primal_gradient);
183  const auto col_scaling_shard = shard(col_scaling_vec);
184  const auto primal_solution_shard = shard(scaled_primal_solution);
185  const auto objective_shard = shard(qp.objective_vector);
186  double dual_correction = 0.0;
187  double dual_full_correction = 0.0;
188  double l_inf_residual = 0.0;
189  double sumsq_residual = 0.0;
190  double l_inf_componentwise_residual = 0.0;
191  for (int64_t i = 0; i < primal_gradient_shard.size(); ++i) {
192  // The corrections use the scaled values because
193  // unscaled_lower_bound = lower_bound * scale and
194  // unscaled_primal_gradient = primal_gradient / scale, so the scales
195  // cancel out.
196  if (primal_gradient_shard[i] == 0.0) continue;
197  const double bound_for_rc = primal_gradient_shard[i] > 0.0
198  ? lower_bound_shard[i]
199  : upper_bound_shard[i];
200  dual_full_correction += bound_for_rc * primal_gradient_shard[i];
201  if (HandlePrimalGradientTermAsReducedCost(
202  params, primal_gradient_shard[i], primal_solution_shard[i],
203  lower_bound_shard[i], upper_bound_shard[i])) {
204  dual_correction += bound_for_rc * primal_gradient_shard[i];
205  } else {
206  const double scaled_residual = std::abs(primal_gradient_shard[i]);
207  const double residual = scaled_residual / col_scaling_shard[i];
209  sumsq_residual += residual * residual;
210  // Special case: ignore `residual` if == 0, to avoid NaN if offset
211  // and objective are both zero.
212  if (residual > 0.0) {
215  residual /
216  (componentwise_residual_offset +
217  std::abs(objective_shard[i] / col_scaling_shard[i])));
218  }
219  }
220  }
221  local_dual_correction[shard.Index()] = dual_correction;
222  local_dual_full_correction[shard.Index()] = dual_full_correction;
223  local_l_inf_residual[shard.Index()] = l_inf_residual;
224  local_sumsq_residual[shard.Index()] = sumsq_residual;
225  local_l_inf_componentwise_residual[shard.Index()] =
227  });
228  return ResidualNorms{
229  .objective_correction = local_dual_correction.sum(),
230  .objective_full_correction = local_dual_full_correction.sum(),
231  .l_inf_residual = local_l_inf_residual.lpNorm<Eigen::Infinity>(),
232  .l_2_residual = std::sqrt(local_sumsq_residual.sum()),
234  local_l_inf_componentwise_residual.lpNorm<Eigen::Infinity>(),
235  };
236 }
237 
238 // Returns Qx.
239 VectorXd ObjectiveProduct(const ShardedQuadraticProgram& sharded_qp,
240  const VectorXd& primal_solution) {
241  CHECK_EQ(primal_solution.size(), sharded_qp.PrimalSize());
242  VectorXd result(primal_solution.size());
243  if (IsLinearProgram(sharded_qp.Qp())) {
244  SetZero(sharded_qp.PrimalSharder(), result);
245  } else {
246  sharded_qp.PrimalSharder().ParallelForEachShard(
247  [&](const Sharder::Shard& shard) {
248  shard(result) =
249  shard(*sharded_qp.Qp().objective_matrix) * shard(primal_solution);
250  });
251  }
252  return result;
253 }
254 
255 // Returns 1/2 x^T Q x (the quadratic term in the objective).
256 double QuadraticObjective(const ShardedQuadraticProgram& sharded_qp,
257  const VectorXd& primal_solution,
258  const VectorXd& objective_product) {
259  CHECK_EQ(primal_solution.size(), sharded_qp.PrimalSize());
260  CHECK_EQ(objective_product.size(), sharded_qp.PrimalSize());
261  return 0.5 *
262  Dot(objective_product, primal_solution, sharded_qp.PrimalSharder());
263 }
264 
265 // Returns `objective_product` + c − A^T y when `use_zero_primal_objective` is
266 // false, and returns − A^T y when `use_zero_primal_objective` is true.
267 // `objective_product` is passed by value, and modified in place.
268 VectorXd PrimalGradientFromObjectiveProduct(
269  const ShardedQuadraticProgram& sharded_qp, const VectorXd& dual_solution,
270  VectorXd objective_product, bool use_zero_primal_objective = false) {
271  const QuadraticProgram& qp = sharded_qp.Qp();
272  CHECK_EQ(dual_solution.size(), sharded_qp.DualSize());
273  CHECK_EQ(objective_product.size(), sharded_qp.PrimalSize());
274 
275  // Note that this modifies `objective_product`, replacing its entries with
276  // the primal gradient.
277  sharded_qp.ConstraintMatrixSharder().ParallelForEachShard(
278  [&](const Sharder::Shard& shard) {
279  if (use_zero_primal_objective) {
280  shard(objective_product) =
281  -shard(qp.constraint_matrix).transpose() * dual_solution;
282  } else {
283  shard(objective_product) +=
284  shard(qp.objective_vector) -
285  shard(qp.constraint_matrix).transpose() * dual_solution;
286  }
287  });
288  return objective_product;
289 }
290 
291 // Returns the value of y term in the objective of the dual problem, that is,
292 // (l^c)^T[y]_+ − (u^c)^T[y]_− in the dual objective from
293 // https://developers.google.com/optimization/lp/pdlp_math.
294 double DualObjectiveBoundsTerm(const ShardedQuadraticProgram& sharded_qp,
295  const VectorXd& dual_solution) {
296  const QuadraticProgram& qp = sharded_qp.Qp();
297  return sharded_qp.DualSharder().ParallelSumOverShards(
298  [&](const Sharder::Shard& shard) {
299  // This assumes that the dual variables are feasible, that is, that
300  // the term corresponding to the "y" variables in the dual objective
301  // in https://developers.google.com/optimization/lp/pdlp_math is finite.
302  const auto lower_bound_shard = shard(qp.constraint_lower_bounds);
303  const auto upper_bound_shard = shard(qp.constraint_upper_bounds);
304  const auto dual_shard = shard(dual_solution);
305  // Can't use `.dot(.cwiseMin(...))` because that gives 0 * inf = NaN.
306  double sum = 0.0;
307  for (int64_t i = 0; i < dual_shard.size(); ++i) {
308  if (dual_shard[i] > 0.0) {
309  sum += lower_bound_shard[i] * dual_shard[i];
310  } else if (dual_shard[i] < 0.0) {
311  sum += upper_bound_shard[i] * dual_shard[i];
312  }
313  }
314  return sum;
315  });
316 }
317 
318 // Computes the projection of `vector` onto a pseudo-random vector determined
319 // by `seed_generator`. `seed_generator` is used as the source of a random seed
320 // for each shard's portion of the vector.
321 double RandomProjection(const VectorXd& vector, const Sharder& sharder,
322  std::mt19937& seed_generator) {
323  std::vector<std::mt19937> shard_seeds;
324  shard_seeds.reserve(sharder.NumShards());
325  for (int shard = 0; shard < sharder.NumShards(); ++shard) {
326  shard_seeds.emplace_back((seed_generator)());
327  }
328  // Computes `vector` * gaussian_random_vector and ||gaussian_random_vector||^2
329  // to normalize by afterwards.
330  VectorXd dot_product(sharder.NumShards());
331  VectorXd gaussian_norm_squared(sharder.NumShards());
332  sharder.ParallelForEachShard([&](const Sharder::Shard& shard) {
333  const auto vector_shard = shard(vector);
334  double shard_dot_product = 0.0;
335  double shard_norm_squared = 0.0;
336  std::mt19937 random{shard_seeds[shard.Index()]};
337  for (int64_t i = 0; i < vector_shard.size(); ++i) {
338  const double projection_element = absl::Gaussian(random, 0.0, 1.0);
339  shard_dot_product += projection_element * vector_shard[i];
340  shard_norm_squared += MathUtil::Square(projection_element);
341  }
342  dot_product[shard.Index()] = shard_dot_product;
343  gaussian_norm_squared[shard.Index()] = shard_norm_squared;
344  });
345  return dot_product.sum() / std::sqrt(gaussian_norm_squared.sum());
346 }
347 } // namespace
348 
349 ConvergenceInformation ComputeConvergenceInformation(
350  const PrimalDualHybridGradientParams& params,
351  const ShardedQuadraticProgram& scaled_sharded_qp,
352  const Eigen::VectorXd& col_scaling_vec,
353  const Eigen::VectorXd& row_scaling_vec,
354  const Eigen::VectorXd& scaled_primal_solution,
355  const Eigen::VectorXd& scaled_dual_solution,
356  const double componentwise_primal_residual_offset,
357  const double componentwise_dual_residual_offset, PointType candidate_type) {
358  const QuadraticProgram& qp = scaled_sharded_qp.Qp();
359  CHECK_EQ(col_scaling_vec.size(), scaled_sharded_qp.PrimalSize());
360  CHECK_EQ(row_scaling_vec.size(), scaled_sharded_qp.DualSize());
361  CHECK_EQ(scaled_primal_solution.size(), scaled_sharded_qp.PrimalSize());
362  CHECK_EQ(scaled_dual_solution.size(), scaled_sharded_qp.DualSize());
363 
364  // See https://developers.google.com/optimization/lp/pdlp_math#rescaling for
365  // notes describing the connection between the scaled and unscaled problem.
366 
367  ConvergenceInformation result;
368  ResidualNorms primal_residuals = PrimalResidualNorms(
369  scaled_sharded_qp, row_scaling_vec, scaled_primal_solution,
370  componentwise_primal_residual_offset);
371  result.set_l_inf_primal_residual(primal_residuals.l_inf_residual);
372  result.set_l2_primal_residual(primal_residuals.l_2_residual);
373  result.set_l_inf_componentwise_primal_residual(
374  primal_residuals.l_inf_componentwise_residual);
375 
376  result.set_l_inf_primal_variable(
377  ScaledLInfNorm(scaled_primal_solution, col_scaling_vec,
378  scaled_sharded_qp.PrimalSharder()));
379  result.set_l2_primal_variable(ScaledNorm(scaled_primal_solution,
380  col_scaling_vec,
381  scaled_sharded_qp.PrimalSharder()));
382  result.set_l_inf_dual_variable(ScaledLInfNorm(
383  scaled_dual_solution, row_scaling_vec, scaled_sharded_qp.DualSharder()));
384  result.set_l2_dual_variable(ScaledNorm(scaled_dual_solution, row_scaling_vec,
385  scaled_sharded_qp.DualSharder()));
386 
387  VectorXd scaled_objective_product =
388  ObjectiveProduct(scaled_sharded_qp, scaled_primal_solution);
389  const double quadratic_objective = QuadraticObjective(
390  scaled_sharded_qp, scaled_primal_solution, scaled_objective_product);
391  VectorXd scaled_primal_gradient = PrimalGradientFromObjectiveProduct(
392  scaled_sharded_qp, scaled_dual_solution,
393  std::move(scaled_objective_product));
394  result.set_primal_objective(qp.ApplyObjectiveScalingAndOffset(
395  quadratic_objective + Dot(qp.objective_vector, scaled_primal_solution,
396  scaled_sharded_qp.PrimalSharder())));
397 
398  // This is the dual objective from
399  // https://developers.google.com/optimization/lp/pdlp_math minus the last term
400  // (involving r). All scaling terms cancel out.
401  const double dual_objective_piece =
402  -quadratic_objective +
403  DualObjectiveBoundsTerm(scaled_sharded_qp, scaled_dual_solution);
404 
405  ResidualNorms dual_residuals = DualResidualNorms(
406  params, scaled_sharded_qp, col_scaling_vec, scaled_primal_solution,
407  scaled_primal_gradient, componentwise_dual_residual_offset);
408  result.set_dual_objective(qp.ApplyObjectiveScalingAndOffset(
409  dual_objective_piece + dual_residuals.objective_correction));
410  result.set_corrected_dual_objective(qp.ApplyObjectiveScalingAndOffset(
411  dual_objective_piece + dual_residuals.objective_full_correction));
412  result.set_l_inf_dual_residual(dual_residuals.l_inf_residual);
413  result.set_l2_dual_residual(dual_residuals.l_2_residual);
414  result.set_l_inf_componentwise_dual_residual(
415  dual_residuals.l_inf_componentwise_residual);
416 
417  result.set_candidate_type(candidate_type);
418  return result;
419 }
420 
421 InfeasibilityInformation ComputeInfeasibilityInformation(
422  const PrimalDualHybridGradientParams& params,
423  const ShardedQuadraticProgram& scaled_sharded_qp,
424  const Eigen::VectorXd& col_scaling_vec,
425  const Eigen::VectorXd& row_scaling_vec,
426  const Eigen::VectorXd& scaled_primal_ray,
427  const Eigen::VectorXd& scaled_dual_ray, PointType candidate_type) {
428  const QuadraticProgram& qp = scaled_sharded_qp.Qp();
429  CHECK_EQ(col_scaling_vec.size(), scaled_sharded_qp.PrimalSize());
430  CHECK_EQ(row_scaling_vec.size(), scaled_sharded_qp.DualSize());
431  CHECK_EQ(scaled_primal_ray.size(), scaled_sharded_qp.PrimalSize());
432  CHECK_EQ(scaled_dual_ray.size(), scaled_sharded_qp.DualSize());
433 
434  double l_inf_primal = ScaledLInfNorm(scaled_primal_ray, col_scaling_vec,
435  scaled_sharded_qp.PrimalSharder());
436  double l_inf_dual = ScaledLInfNorm(scaled_dual_ray, row_scaling_vec,
437  scaled_sharded_qp.DualSharder());
438  InfeasibilityInformation result;
439  // Compute primal infeasibility information.
440  VectorXd scaled_primal_gradient = PrimalGradientFromObjectiveProduct(
441  scaled_sharded_qp, scaled_dual_ray,
442  ZeroVector(scaled_sharded_qp.PrimalSharder()),
443  /*use_zero_primal_objective=*/true);
444  // We don't use `dual_residuals.l_inf_componentwise_residual`, so don't need
445  // to set `componentwise_residual_offset` to a meaningful value.
446  ResidualNorms dual_residuals = DualResidualNorms(
447  params, scaled_sharded_qp, col_scaling_vec, scaled_primal_ray,
448  scaled_primal_gradient, /*componentwise_residual_offset=*/0.0);
449 
450  double dual_ray_objective =
451  DualObjectiveBoundsTerm(scaled_sharded_qp, scaled_dual_ray) +
452  dual_residuals.objective_correction;
453  if (l_inf_dual > 0) {
454  result.set_dual_ray_objective(dual_ray_objective / l_inf_dual);
455  result.set_max_dual_ray_infeasibility(dual_residuals.l_inf_residual /
456  l_inf_dual);
457  } else {
458  result.set_dual_ray_objective(0.0);
459  result.set_max_dual_ray_infeasibility(0.0);
460  }
461 
462  // Compute dual infeasibility information. We don't use
463  // `primal_residuals.l_inf_componentwise_residual`, so don't need to set
464  // `componentwise_residual_offset` to a meaningful value.
465  ResidualNorms primal_residuals =
466  PrimalResidualNorms(scaled_sharded_qp, row_scaling_vec, scaled_primal_ray,
467  /*componentwise_residual_offset=*/0.0,
468  /*use_homogeneous_constraint_bounds=*/true);
469  // `primal_residuals` contains the violations of the linear constraints. The
470  // signs of the components are also constrained by the presence or absence
471  // of variable bounds.
472  VectorXd primal_ray_local_sign_max_violation(
473  scaled_sharded_qp.PrimalSharder().NumShards());
474  scaled_sharded_qp.PrimalSharder().ParallelForEachShard(
475  [&](const Sharder::Shard& shard) {
476  const auto lower_bound_shard =
477  shard(scaled_sharded_qp.Qp().variable_lower_bounds);
478  const auto upper_bound_shard =
479  shard(scaled_sharded_qp.Qp().variable_upper_bounds);
480  const auto ray_shard = shard(scaled_primal_ray);
481  const auto scale_shard = shard(col_scaling_vec);
482  double local_max = 0.0;
483  for (int64_t i = 0; i < ray_shard.size(); ++i) {
484  if (std::isfinite(lower_bound_shard[i])) {
485  local_max = std::max(local_max, -ray_shard[i] * scale_shard[i]);
486  }
487  if (std::isfinite(upper_bound_shard[i])) {
488  local_max = std::max(local_max, ray_shard[i] * scale_shard[i]);
489  }
490  }
491  primal_ray_local_sign_max_violation[shard.Index()] = local_max;
492  });
493  const double primal_ray_sign_max_violation =
494  primal_ray_local_sign_max_violation.lpNorm<Eigen::Infinity>();
495 
496  if (l_inf_primal > 0.0) {
497  VectorXd scaled_objective_product =
498  ObjectiveProduct(scaled_sharded_qp, scaled_primal_ray);
499  result.set_primal_ray_quadratic_norm(
500  LInfNorm(scaled_objective_product, scaled_sharded_qp.PrimalSharder()) /
501  l_inf_primal);
502  result.set_max_primal_ray_infeasibility(
503  std::max(primal_residuals.l_inf_residual,
504  primal_ray_sign_max_violation) /
505  l_inf_primal);
506  result.set_primal_ray_linear_objective(
507  Dot(scaled_primal_ray, qp.objective_vector,
508  scaled_sharded_qp.PrimalSharder()) /
509  l_inf_primal);
510  } else {
511  result.set_primal_ray_quadratic_norm(0.0);
512  result.set_max_primal_ray_infeasibility(0.0);
513  result.set_primal_ray_linear_objective(0.0);
514  }
515 
516  result.set_candidate_type(candidate_type);
517  return result;
518 }
519 
520 ConvergenceInformation ComputeScaledConvergenceInformation(
521  const PrimalDualHybridGradientParams& params,
522  const ShardedQuadraticProgram& sharded_qp, const VectorXd& primal_solution,
523  const VectorXd& dual_solution,
524  const double componentwise_primal_residual_offset,
525  const double componentwise_dual_residual_offset, PointType candidate_type) {
527  params, sharded_qp, OnesVector(sharded_qp.PrimalSharder()),
528  OnesVector(sharded_qp.DualSharder()), primal_solution, dual_solution,
529  componentwise_primal_residual_offset, componentwise_dual_residual_offset,
530  candidate_type);
531 }
532 
533 VectorXd ReducedCosts(const PrimalDualHybridGradientParams& params,
534  const ShardedQuadraticProgram& sharded_qp,
535  const VectorXd& primal_solution,
536  const VectorXd& dual_solution,
537  bool use_zero_primal_objective) {
538  VectorXd objective_product;
539  if (use_zero_primal_objective) {
540  objective_product = ZeroVector(sharded_qp.PrimalSharder());
541  } else {
542  objective_product = ObjectiveProduct(sharded_qp, primal_solution);
543  }
544  VectorXd reduced_costs = PrimalGradientFromObjectiveProduct(
545  sharded_qp, dual_solution, std::move(objective_product),
546  use_zero_primal_objective);
547  sharded_qp.PrimalSharder().ParallelForEachShard(
548  [&](const Sharder::Shard& shard) {
549  auto rc_shard = shard(reduced_costs);
550  const auto lower_bound_shard =
551  shard(sharded_qp.Qp().variable_lower_bounds);
552  const auto upper_bound_shard =
553  shard(sharded_qp.Qp().variable_upper_bounds);
554  const auto primal_solution_shard = shard(primal_solution);
555  for (int64_t i = 0; i < rc_shard.size(); ++i) {
556  if (rc_shard[i] != 0.0 &&
557  !HandlePrimalGradientTermAsReducedCost(
558  params, rc_shard[i], primal_solution_shard[i],
559  lower_bound_shard[i], upper_bound_shard[i])) {
560  rc_shard[i] = 0.0;
561  }
562  }
563  });
564  return reduced_costs;
565 }
566 
567 std::optional<ConvergenceInformation> GetConvergenceInformation(
568  const IterationStats& stats, PointType candidate_type) {
569  for (const auto& convergence_information : stats.convergence_information()) {
570  if (convergence_information.candidate_type() == candidate_type) {
571  return convergence_information;
572  }
573  }
574  return std::nullopt;
575 }
576 
577 std::optional<InfeasibilityInformation> GetInfeasibilityInformation(
578  const IterationStats& stats, PointType candidate_type) {
579  for (const auto& infeasibility_information :
580  stats.infeasibility_information()) {
581  if (infeasibility_information.candidate_type() == candidate_type) {
582  return infeasibility_information;
583  }
584  }
585  return std::nullopt;
586 }
587 
588 std::optional<PointMetadata> GetPointMetadata(const IterationStats& stats,
589  const PointType point_type) {
590  for (const auto& metadata : stats.point_metadata()) {
591  if (metadata.point_type() == point_type) {
592  return metadata;
593  }
594  }
595  return std::nullopt;
596 }
597 
599  const Eigen::VectorXd& primal_solution,
600  const Eigen::VectorXd& dual_solution,
601  const std::vector<int>& random_projection_seeds,
602  PointMetadata& metadata) {
603  for (const int random_projection_seed : random_projection_seeds) {
604  std::mt19937 seed_generator(random_projection_seed);
605  metadata.mutable_random_primal_projections()->Add(RandomProjection(
606  primal_solution, sharded_qp.PrimalSharder(), seed_generator));
607  metadata.mutable_random_dual_projections()->Add(RandomProjection(
608  dual_solution, sharded_qp.DualSharder(), seed_generator));
609  }
610 }
611 
612 } // namespace operations_research::pdlp
int64_t max
Definition: alldiff_cst.cc:140
static T Square(const T x)
Definition: mathutil.h:101
void ParallelForEachShard(const std::function< void(const Shard &)> &func) const
Definition: sharder.cc:104
double l_2_residual
double objective_full_correction
double objective_correction
double l_inf_residual
double l_inf_componentwise_residual
void SetZero(const Sharder &sharder, VectorXd &dest)
Definition: sharder.cc:173
double ScaledNorm(const VectorXd &vector, const VectorXd &scale, const Sharder &sharder)
Definition: sharder.cc:281
double Dot(const VectorXd &v1, const VectorXd &v2, const Sharder &sharder)
Definition: sharder.cc:225
double LInfNorm(const VectorXd &vector, const Sharder &sharder)
Definition: sharder.cc:230
VectorXd ReducedCosts(const PrimalDualHybridGradientParams &params, const ShardedQuadraticProgram &sharded_qp, const VectorXd &primal_solution, const VectorXd &dual_solution, bool use_zero_primal_objective)
VectorXd TransposedMatrixVectorProduct(const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &matrix, const VectorXd &vector, const Sharder &sharder)
Definition: sharder.cc:158
InfeasibilityInformation ComputeInfeasibilityInformation(const PrimalDualHybridGradientParams &params, const ShardedQuadraticProgram &scaled_sharded_qp, const Eigen::VectorXd &col_scaling_vec, const Eigen::VectorXd &row_scaling_vec, const Eigen::VectorXd &scaled_primal_ray, const Eigen::VectorXd &scaled_dual_ray, PointType candidate_type)
double ScaledLInfNorm(const VectorXd &vector, const VectorXd &scale, const Sharder &sharder)
Definition: sharder.cc:264
bool IsLinearProgram(const QuadraticProgram &qp)
void SetRandomProjections(const ShardedQuadraticProgram &sharded_qp, const Eigen::VectorXd &primal_solution, const Eigen::VectorXd &dual_solution, const std::vector< int > &random_projection_seeds, PointMetadata &metadata)
std::optional< PointMetadata > GetPointMetadata(const IterationStats &stats, const PointType point_type)
ConvergenceInformation ComputeScaledConvergenceInformation(const PrimalDualHybridGradientParams &params, const ShardedQuadraticProgram &sharded_qp, const VectorXd &primal_solution, const VectorXd &dual_solution, const double componentwise_primal_residual_offset, const double componentwise_dual_residual_offset, PointType candidate_type)
std::optional< InfeasibilityInformation > GetInfeasibilityInformation(const IterationStats &stats, PointType candidate_type)
std::optional< ConvergenceInformation > GetConvergenceInformation(const IterationStats &stats, PointType candidate_type)
VectorXd ZeroVector(const Sharder &sharder)
Definition: sharder.cc:179
VectorXd OnesVector(const Sharder &sharder)
Definition: sharder.cc:185
ConvergenceInformation ComputeConvergenceInformation(const PrimalDualHybridGradientParams &params, const ShardedQuadraticProgram &scaled_sharded_qp, const Eigen::VectorXd &col_scaling_vec, const Eigen::VectorXd &row_scaling_vec, const Eigen::VectorXd &scaled_primal_solution, const Eigen::VectorXd &scaled_dual_solution, const double componentwise_primal_residual_offset, const double componentwise_dual_residual_offset, PointType candidate_type)
IntVar * upper_bound
Definition: routing.cc:1087
IntVar * lower_bound
Definition: routing.cc:1086
double ApplyObjectiveScalingAndOffset(double objective) const