Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
iteration_stats.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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"
33#include "ortools/pdlp/solve_log.pb.h"
34#include "ortools/pdlp/solvers.pb.h"
35
37namespace {
38
39using ::Eigen::VectorXd;
40
41// `ResidualNorms` contains measures of the infeasibility of a primal or dual
42// solution. `objective_correction` is the (additive) adjustment to the
43// objective function from the reduced costs. `objective_full_correction` is the
44// (additive) adjustment to the objective function if all dual residuals were
45// set to zero, while `l_inf_residual`, `l_2_residual`, and
46// `l_inf_componentwise_residual` are the L_infinity, L_2, and L_infinity
47// (componentwise) norms of the residuals (portions of the primal gradient not
48// included in the reduced costs).
49struct ResidualNorms {
55};
56
57// Computes norms of the primal residual infeasibilities (b - A x) of the
58// unscaled problem. Note the primal residuals of the unscaled problem are equal
59// to those of the scaled problem divided by `row_scaling_vec`. Does not perform
60// any corrections (so the returned `.objective_correction == 0` and
61// `.objective_full_correction == 0`). `sharded_qp` is assumed to be the scaled
62// problem. If `use_homogeneous_constraint_bounds` is set to true the residuals
63// are computed with all finite bounds mapped to zero.
64// NOTE: `componentwise_residual_offset` only affects the value of
65// `l_inf_componentwise_residual` in the returned `ResidualNorms`.
66ResidualNorms 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;
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];
110 l_inf_residual = std::max(l_inf_residual, residual);
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()),
131 .l_inf_componentwise_residual =
132 local_l_inf_componentwise_residual.lpNorm<Eigen::Infinity>(),
133 };
134}
135
136bool TreatVariableBoundAsFinite(const PrimalDualHybridGradientParams& params,
137 double primal_value, double bound) {
138 if (params.handle_some_primal_gradients_on_finite_bounds_as_residuals()) {
139 // Note that this test is always false if `bound` is infinite.
140 return std::abs(primal_value - bound) <= std::abs(primal_value);
141 } else {
142 return std::isfinite(bound);
143 }
144}
145
146struct VariableBounds {
149};
150
151VariableBounds EffectiveVariableBounds(
152 const PrimalDualHybridGradientParams& params, double primal_value,
153 double lower_bound, double upper_bound) {
154 return {.lower_bound =
155 TreatVariableBoundAsFinite(params, primal_value, lower_bound)
157 : -std::numeric_limits<double>::infinity(),
158 .upper_bound =
159 TreatVariableBoundAsFinite(params, primal_value, upper_bound)
161 : std::numeric_limits<double>::infinity()};
162}
163
164double VariableBoundForDualObjective(double primal_gradient,
165 const VariableBounds& bounds) {
166 const double primary_bound =
167 primal_gradient >= 0.0 ? bounds.lower_bound : bounds.upper_bound;
168 const double secondary_bound =
169 primal_gradient >= 0.0 ? bounds.upper_bound : bounds.lower_bound;
170 if (std::isfinite(primary_bound)) {
171 return primary_bound;
172 } else if (std::isfinite(secondary_bound)) {
173 return secondary_bound;
174 } else {
175 return 0.0;
176 }
177}
178
179// Computes norms of the dual residuals and reduced costs of the unscaled
180// problem. Note the primal gradient of the unscaled problem is equal to
181// `scaled_primal_gradient` divided by `col_scaling_vec`. `sharded_qp` is
182// assumed to be the scaled problem. See
183// https://developers.google.com/optimization/lp/pdlp_math and the documentation
184// for `PrimalDualHybridGradientParams::
185// handle_some_primal_gradients_on_finite_bounds_as_residuals` for details and
186// notation.
187// NOTE: `componentwise_residual_offset` only affects the value of
188// `l_inf_componentwise_residual` in the returned `ResidualNorms`.
189ResidualNorms DualResidualNorms(const PrimalDualHybridGradientParams& params,
190 const ShardedQuadraticProgram& sharded_qp,
191 const VectorXd& col_scaling_vec,
192 const VectorXd& scaled_primal_solution,
193 const VectorXd& scaled_primal_gradient,
194 const double componentwise_residual_offset) {
195 const QuadraticProgram& qp = sharded_qp.Qp();
196 CHECK_EQ(col_scaling_vec.size(), sharded_qp.PrimalSize());
197 CHECK_EQ(scaled_primal_gradient.size(), sharded_qp.PrimalSize());
198 VectorXd local_dual_correction(sharded_qp.PrimalSharder().NumShards());
199 VectorXd local_dual_full_correction(sharded_qp.PrimalSharder().NumShards());
200 VectorXd local_l_inf_residual(sharded_qp.PrimalSharder().NumShards());
201 VectorXd local_sumsq_residual(sharded_qp.PrimalSharder().NumShards());
202 VectorXd local_l_inf_componentwise_residual(
203 sharded_qp.PrimalSharder().NumShards());
204 sharded_qp.PrimalSharder().ParallelForEachShard(
205 [&](const Sharder::Shard& shard) {
206 const auto lower_bound_shard = shard(qp.variable_lower_bounds);
207 const auto upper_bound_shard = shard(qp.variable_upper_bounds);
208 const auto primal_gradient_shard = shard(scaled_primal_gradient);
209 const auto col_scaling_shard = shard(col_scaling_vec);
210 const auto primal_solution_shard = shard(scaled_primal_solution);
211 const auto objective_shard = shard(qp.objective_vector);
212 double dual_correction = 0.0;
213 double dual_full_correction = 0.0;
214 double l_inf_residual = 0.0;
215 double sumsq_residual = 0.0;
216 double l_inf_componentwise_residual = 0.0;
217 for (int64_t i = 0; i < primal_gradient_shard.size(); ++i) {
218 // The corrections use the scaled values because
219 // unscaled_lower_bound = lower_bound * scale and
220 // unscaled_primal_gradient = primal_gradient / scale, so the scales
221 // cancel out.
222 if (primal_gradient_shard[i] == 0.0) continue;
223 const double upper_bound = upper_bound_shard[i];
224 const double lower_bound = lower_bound_shard[i];
225 const double bound_for_rc =
226 primal_gradient_shard[i] > 0.0 ? lower_bound : upper_bound;
227 dual_full_correction += bound_for_rc * primal_gradient_shard[i];
228 VariableBounds effective_bounds = EffectiveVariableBounds(
229 params, primal_solution_shard[i], lower_bound, upper_bound);
230 // The dual correction (using the appropriate bound) is applied even
231 // if the gradient is handled as a residual, so that the dual
232 // objective is convex.
233 dual_correction += VariableBoundForDualObjective(
234 primal_gradient_shard[i], effective_bounds) *
235 primal_gradient_shard[i];
236 const double effective_bound_for_residual =
237 primal_gradient_shard[i] > 0.0 ? effective_bounds.lower_bound
238 : effective_bounds.upper_bound;
239 if (std::isinf(effective_bound_for_residual)) {
240 const double scaled_residual = std::abs(primal_gradient_shard[i]);
241 const double residual = scaled_residual / col_scaling_shard[i];
242 l_inf_residual = std::max(l_inf_residual, residual);
243 sumsq_residual += residual * residual;
244 // Special case: ignore `residual` if == 0, to avoid NaN if offset
245 // and objective are both zero.
246 if (residual > 0.0) {
249 residual /
250 (componentwise_residual_offset +
251 std::abs(objective_shard[i] / col_scaling_shard[i])));
252 }
253 }
254 }
255 local_dual_correction[shard.Index()] = dual_correction;
256 local_dual_full_correction[shard.Index()] = dual_full_correction;
257 local_l_inf_residual[shard.Index()] = l_inf_residual;
258 local_sumsq_residual[shard.Index()] = sumsq_residual;
259 local_l_inf_componentwise_residual[shard.Index()] =
261 });
262 return ResidualNorms{
263 .objective_correction = local_dual_correction.sum(),
264 .objective_full_correction = local_dual_full_correction.sum(),
265 .l_inf_residual = local_l_inf_residual.lpNorm<Eigen::Infinity>(),
266 .l_2_residual = std::sqrt(local_sumsq_residual.sum()),
267 .l_inf_componentwise_residual =
268 local_l_inf_componentwise_residual.lpNorm<Eigen::Infinity>(),
269 };
270}
271
272// Returns Qx.
273VectorXd ObjectiveProduct(const ShardedQuadraticProgram& sharded_qp,
274 const VectorXd& primal_solution) {
275 CHECK_EQ(primal_solution.size(), sharded_qp.PrimalSize());
276 VectorXd result(primal_solution.size());
277 if (IsLinearProgram(sharded_qp.Qp())) {
278 SetZero(sharded_qp.PrimalSharder(), result);
279 } else {
280 sharded_qp.PrimalSharder().ParallelForEachShard(
281 [&](const Sharder::Shard& shard) {
282 shard(result) =
283 shard(*sharded_qp.Qp().objective_matrix) * shard(primal_solution);
284 });
285 }
286 return result;
287}
288
289// Returns 1/2 x^T Q x (the quadratic term in the objective).
290double QuadraticObjective(const ShardedQuadraticProgram& sharded_qp,
291 const VectorXd& primal_solution,
292 const VectorXd& objective_product) {
293 CHECK_EQ(primal_solution.size(), sharded_qp.PrimalSize());
294 CHECK_EQ(objective_product.size(), sharded_qp.PrimalSize());
295 return 0.5 *
296 Dot(objective_product, primal_solution, sharded_qp.PrimalSharder());
297}
298
299// Returns `objective_product` + c − A^T y when `use_zero_primal_objective` is
300// false, and returns − A^T y when `use_zero_primal_objective` is true.
301// `objective_product` is passed by value, and modified in place.
302VectorXd PrimalGradientFromObjectiveProduct(
303 const ShardedQuadraticProgram& sharded_qp, const VectorXd& dual_solution,
304 VectorXd objective_product, bool use_zero_primal_objective = false) {
305 const QuadraticProgram& qp = sharded_qp.Qp();
306 CHECK_EQ(dual_solution.size(), sharded_qp.DualSize());
307 CHECK_EQ(objective_product.size(), sharded_qp.PrimalSize());
308
309 // Note that this modifies `objective_product`, replacing its entries with
310 // the primal gradient.
311 sharded_qp.ConstraintMatrixSharder().ParallelForEachShard(
312 [&](const Sharder::Shard& shard) {
313 if (use_zero_primal_objective) {
314 shard(objective_product) =
315 -shard(qp.constraint_matrix).transpose() * dual_solution;
316 } else {
317 shard(objective_product) +=
318 shard(qp.objective_vector) -
319 shard(qp.constraint_matrix).transpose() * dual_solution;
320 }
321 });
322 return objective_product;
323}
324
325// Returns the value of y term in the objective of the dual problem, that is,
326// (l^c)^T[y]_+ − (u^c)^T[y]_− in the dual objective from
327// https://developers.google.com/optimization/lp/pdlp_math.
328double DualObjectiveBoundsTerm(const ShardedQuadraticProgram& sharded_qp,
329 const VectorXd& dual_solution) {
330 const QuadraticProgram& qp = sharded_qp.Qp();
331 return sharded_qp.DualSharder().ParallelSumOverShards(
332 [&](const Sharder::Shard& shard) {
333 // This assumes that the dual variables are feasible, that is, that
334 // the term corresponding to the "y" variables in the dual objective
335 // in https://developers.google.com/optimization/lp/pdlp_math is finite.
336 const auto lower_bound_shard = shard(qp.constraint_lower_bounds);
337 const auto upper_bound_shard = shard(qp.constraint_upper_bounds);
338 const auto dual_shard = shard(dual_solution);
339 // Can't use `.dot(.cwiseMin(...))` because that gives 0 * inf = NaN.
340 double sum = 0.0;
341 for (int64_t i = 0; i < dual_shard.size(); ++i) {
342 if (dual_shard[i] > 0.0) {
343 sum += lower_bound_shard[i] * dual_shard[i];
344 } else if (dual_shard[i] < 0.0) {
345 sum += upper_bound_shard[i] * dual_shard[i];
346 }
347 }
348 return sum;
349 });
350}
351
352// Computes the projection of `vector` onto a pseudo-random vector determined
353// by `seed_generator`. `seed_generator` is used as the source of a random seed
354// for each shard's portion of the vector.
355double RandomProjection(const VectorXd& vector, const Sharder& sharder,
356 std::mt19937& seed_generator) {
357 std::vector<std::mt19937> shard_seeds;
358 shard_seeds.reserve(sharder.NumShards());
359 for (int shard = 0; shard < sharder.NumShards(); ++shard) {
360 shard_seeds.emplace_back((seed_generator)());
361 }
362 // Computes `vector` * gaussian_random_vector and ||gaussian_random_vector||^2
363 // to normalize by afterwards.
364 VectorXd dot_product(sharder.NumShards());
365 VectorXd gaussian_norm_squared(sharder.NumShards());
366 sharder.ParallelForEachShard([&](const Sharder::Shard& shard) {
367 const auto vector_shard = shard(vector);
368 double shard_dot_product = 0.0;
369 double shard_norm_squared = 0.0;
370 std::mt19937 random{shard_seeds[shard.Index()]};
371 for (int64_t i = 0; i < vector_shard.size(); ++i) {
372 const double projection_element = absl::Gaussian(random, 0.0, 1.0);
373 shard_dot_product += projection_element * vector_shard[i];
374 shard_norm_squared += MathUtil::Square(projection_element);
375 }
376 dot_product[shard.Index()] = shard_dot_product;
377 gaussian_norm_squared[shard.Index()] = shard_norm_squared;
378 });
379 return dot_product.sum() / std::sqrt(gaussian_norm_squared.sum());
380}
381} // namespace
382
383ConvergenceInformation ComputeConvergenceInformation(
384 const PrimalDualHybridGradientParams& params,
385 const ShardedQuadraticProgram& scaled_sharded_qp,
386 const Eigen::VectorXd& col_scaling_vec,
387 const Eigen::VectorXd& row_scaling_vec,
388 const Eigen::VectorXd& scaled_primal_solution,
389 const Eigen::VectorXd& scaled_dual_solution,
390 const double componentwise_primal_residual_offset,
391 const double componentwise_dual_residual_offset, PointType candidate_type) {
392 const QuadraticProgram& qp = scaled_sharded_qp.Qp();
393 CHECK_EQ(col_scaling_vec.size(), scaled_sharded_qp.PrimalSize());
394 CHECK_EQ(row_scaling_vec.size(), scaled_sharded_qp.DualSize());
395 CHECK_EQ(scaled_primal_solution.size(), scaled_sharded_qp.PrimalSize());
396 CHECK_EQ(scaled_dual_solution.size(), scaled_sharded_qp.DualSize());
397
398 // See https://developers.google.com/optimization/lp/pdlp_math#rescaling for
399 // notes describing the connection between the scaled and unscaled problem.
400
401 ConvergenceInformation result;
402 ResidualNorms primal_residuals = PrimalResidualNorms(
403 scaled_sharded_qp, row_scaling_vec, scaled_primal_solution,
404 componentwise_primal_residual_offset);
405 result.set_l_inf_primal_residual(primal_residuals.l_inf_residual);
406 result.set_l2_primal_residual(primal_residuals.l_2_residual);
407 result.set_l_inf_componentwise_primal_residual(
408 primal_residuals.l_inf_componentwise_residual);
409
410 result.set_l_inf_primal_variable(
411 ScaledLInfNorm(scaled_primal_solution, col_scaling_vec,
412 scaled_sharded_qp.PrimalSharder()));
413 result.set_l2_primal_variable(ScaledNorm(scaled_primal_solution,
414 col_scaling_vec,
415 scaled_sharded_qp.PrimalSharder()));
416 result.set_l_inf_dual_variable(ScaledLInfNorm(
417 scaled_dual_solution, row_scaling_vec, scaled_sharded_qp.DualSharder()));
418 result.set_l2_dual_variable(ScaledNorm(scaled_dual_solution, row_scaling_vec,
419 scaled_sharded_qp.DualSharder()));
420
421 VectorXd scaled_objective_product =
422 ObjectiveProduct(scaled_sharded_qp, scaled_primal_solution);
423 const double quadratic_objective = QuadraticObjective(
424 scaled_sharded_qp, scaled_primal_solution, scaled_objective_product);
425 VectorXd scaled_primal_gradient = PrimalGradientFromObjectiveProduct(
426 scaled_sharded_qp, scaled_dual_solution,
427 std::move(scaled_objective_product));
428 result.set_primal_objective(qp.ApplyObjectiveScalingAndOffset(
429 quadratic_objective + Dot(qp.objective_vector, scaled_primal_solution,
430 scaled_sharded_qp.PrimalSharder())));
431
432 // This is the dual objective from
433 // https://developers.google.com/optimization/lp/pdlp_math minus the last term
434 // (involving r). All scaling terms cancel out.
435 const double dual_objective_piece =
436 -quadratic_objective +
437 DualObjectiveBoundsTerm(scaled_sharded_qp, scaled_dual_solution);
438
439 ResidualNorms dual_residuals = DualResidualNorms(
440 params, scaled_sharded_qp, col_scaling_vec, scaled_primal_solution,
441 scaled_primal_gradient, componentwise_dual_residual_offset);
442 result.set_dual_objective(qp.ApplyObjectiveScalingAndOffset(
443 dual_objective_piece + dual_residuals.objective_correction));
444 result.set_corrected_dual_objective(qp.ApplyObjectiveScalingAndOffset(
445 dual_objective_piece + dual_residuals.objective_full_correction));
446 result.set_l_inf_dual_residual(dual_residuals.l_inf_residual);
447 result.set_l2_dual_residual(dual_residuals.l_2_residual);
448 result.set_l_inf_componentwise_dual_residual(
449 dual_residuals.l_inf_componentwise_residual);
450
451 result.set_candidate_type(candidate_type);
452 return result;
453}
454
455namespace {
456
457double PrimalRayMaxSignViolation(const ShardedQuadraticProgram& sharded_qp,
458 const VectorXd& col_scaling_vec,
459 const VectorXd& scaled_primal_ray) {
460 VectorXd primal_ray_local_max_sign_violation(
461 sharded_qp.PrimalSharder().NumShards());
462 sharded_qp.PrimalSharder().ParallelForEachShard(
463 [&](const Sharder::Shard& shard) {
464 const auto lower_bound_shard =
465 shard(sharded_qp.Qp().variable_lower_bounds);
466 const auto upper_bound_shard =
467 shard(sharded_qp.Qp().variable_upper_bounds);
468 const auto ray_shard = shard(scaled_primal_ray);
469 const auto scale_shard = shard(col_scaling_vec);
470 double local_max = 0.0;
471 for (int64_t i = 0; i < ray_shard.size(); ++i) {
472 if (std::isfinite(lower_bound_shard[i])) {
473 local_max = std::max(local_max, -ray_shard[i] * scale_shard[i]);
474 }
475 if (std::isfinite(upper_bound_shard[i])) {
476 local_max = std::max(local_max, ray_shard[i] * scale_shard[i]);
477 }
478 }
479 primal_ray_local_max_sign_violation[shard.Index()] = local_max;
480 });
481 return primal_ray_local_max_sign_violation.lpNorm<Eigen::Infinity>();
482}
483
484} // namespace
485
486InfeasibilityInformation ComputeInfeasibilityInformation(
487 const PrimalDualHybridGradientParams& params,
488 const ShardedQuadraticProgram& scaled_sharded_qp,
489 const Eigen::VectorXd& col_scaling_vec,
490 const Eigen::VectorXd& row_scaling_vec,
491 const Eigen::VectorXd& scaled_primal_ray,
492 const Eigen::VectorXd& scaled_dual_ray,
493 const Eigen::VectorXd& primal_solution_for_residual_tests,
494 PointType candidate_type) {
495 const QuadraticProgram& qp = scaled_sharded_qp.Qp();
496 CHECK_EQ(col_scaling_vec.size(), scaled_sharded_qp.PrimalSize());
497 CHECK_EQ(row_scaling_vec.size(), scaled_sharded_qp.DualSize());
498 CHECK_EQ(scaled_primal_ray.size(), scaled_sharded_qp.PrimalSize());
499 CHECK_EQ(scaled_dual_ray.size(), scaled_sharded_qp.DualSize());
500
501 double l_inf_primal = ScaledLInfNorm(scaled_primal_ray, col_scaling_vec,
502 scaled_sharded_qp.PrimalSharder());
503 double l_inf_dual = ScaledLInfNorm(scaled_dual_ray, row_scaling_vec,
504 scaled_sharded_qp.DualSharder());
505 InfeasibilityInformation result;
506 // Compute primal infeasibility information.
507 VectorXd scaled_primal_gradient = PrimalGradientFromObjectiveProduct(
508 scaled_sharded_qp, scaled_dual_ray,
509 ZeroVector(scaled_sharded_qp.PrimalSharder()),
510 /*use_zero_primal_objective=*/true);
511 // We don't use `dual_residuals.l_inf_componentwise_residual`, so don't need
512 // to set `componentwise_residual_offset` to a meaningful value.
513 ResidualNorms dual_residuals = DualResidualNorms(
514 params, scaled_sharded_qp, col_scaling_vec,
515 primal_solution_for_residual_tests, scaled_primal_gradient,
516 /*componentwise_residual_offset=*/0.0);
517
518 double dual_ray_objective =
519 DualObjectiveBoundsTerm(scaled_sharded_qp, scaled_dual_ray) +
520 dual_residuals.objective_correction;
521 if (l_inf_dual > 0) {
522 result.set_dual_ray_objective(dual_ray_objective / l_inf_dual);
523 result.set_max_dual_ray_infeasibility(dual_residuals.l_inf_residual /
524 l_inf_dual);
525 } else {
526 result.set_dual_ray_objective(0.0);
527 result.set_max_dual_ray_infeasibility(0.0);
528 }
529
530 // Compute dual infeasibility information. We don't use
531 // `primal_residuals.l_inf_componentwise_residual`, so don't need to set
532 // `componentwise_residual_offset` to a meaningful value.
533 ResidualNorms primal_residuals =
534 PrimalResidualNorms(scaled_sharded_qp, row_scaling_vec, scaled_primal_ray,
535 /*componentwise_residual_offset=*/0.0,
536 /*use_homogeneous_constraint_bounds=*/true);
537
538 // The primal ray should have been projected onto the feasibility bounds, so
539 // that it has the correct signs.
540 DCHECK_EQ(PrimalRayMaxSignViolation(scaled_sharded_qp, col_scaling_vec,
541 scaled_primal_ray),
542 0.0);
543
544 if (l_inf_primal > 0.0) {
545 VectorXd scaled_objective_product =
546 ObjectiveProduct(scaled_sharded_qp, scaled_primal_ray);
547 result.set_primal_ray_quadratic_norm(
548 LInfNorm(scaled_objective_product, scaled_sharded_qp.PrimalSharder()) /
549 l_inf_primal);
550 result.set_max_primal_ray_infeasibility(primal_residuals.l_inf_residual /
551 l_inf_primal);
552 result.set_primal_ray_linear_objective(
553 Dot(scaled_primal_ray, qp.objective_vector,
554 scaled_sharded_qp.PrimalSharder()) /
555 l_inf_primal);
556 } else {
557 result.set_primal_ray_quadratic_norm(0.0);
558 result.set_max_primal_ray_infeasibility(0.0);
559 result.set_primal_ray_linear_objective(0.0);
560 }
561
562 result.set_candidate_type(candidate_type);
563 return result;
564}
565
567 const PrimalDualHybridGradientParams& params,
568 const ShardedQuadraticProgram& sharded_qp, const VectorXd& primal_solution,
569 const VectorXd& dual_solution,
570 const double componentwise_primal_residual_offset,
571 const double componentwise_dual_residual_offset, PointType candidate_type) {
573 params, sharded_qp, OnesVector(sharded_qp.PrimalSharder()),
574 OnesVector(sharded_qp.DualSharder()), primal_solution, dual_solution,
575 componentwise_primal_residual_offset, componentwise_dual_residual_offset,
576 candidate_type);
577}
578
579VectorXd ReducedCosts(const PrimalDualHybridGradientParams& params,
580 const ShardedQuadraticProgram& sharded_qp,
581 const VectorXd& primal_solution,
582 const VectorXd& dual_solution,
583 bool use_zero_primal_objective) {
584 VectorXd objective_product;
585 if (use_zero_primal_objective) {
586 objective_product = ZeroVector(sharded_qp.PrimalSharder());
587 } else {
588 objective_product = ObjectiveProduct(sharded_qp, primal_solution);
589 }
590 return PrimalGradientFromObjectiveProduct(sharded_qp, dual_solution,
591 std::move(objective_product),
592 use_zero_primal_objective);
593}
594
595std::optional<ConvergenceInformation> GetConvergenceInformation(
596 const IterationStats& stats, PointType candidate_type) {
597 for (const auto& convergence_information : stats.convergence_information()) {
598 if (convergence_information.candidate_type() == candidate_type) {
599 return convergence_information;
600 }
601 }
602 return std::nullopt;
603}
604
605std::optional<InfeasibilityInformation> GetInfeasibilityInformation(
606 const IterationStats& stats, PointType candidate_type) {
607 for (const auto& infeasibility_information :
608 stats.infeasibility_information()) {
609 if (infeasibility_information.candidate_type() == candidate_type) {
610 return infeasibility_information;
611 }
612 }
613 return std::nullopt;
614}
615
616std::optional<PointMetadata> GetPointMetadata(const IterationStats& stats,
617 const PointType point_type) {
618 for (const auto& metadata : stats.point_metadata()) {
619 if (metadata.point_type() == point_type) {
620 return metadata;
621 }
622 }
623 return std::nullopt;
624}
625
627 const Eigen::VectorXd& primal_solution,
628 const Eigen::VectorXd& dual_solution,
629 const std::vector<int>& random_projection_seeds,
630 PointMetadata& metadata) {
631 for (const int random_projection_seed : random_projection_seeds) {
632 std::mt19937 seed_generator(random_projection_seed);
633 metadata.mutable_random_primal_projections()->Add(RandomProjection(
634 primal_solution, sharded_qp.PrimalSharder(), seed_generator));
635 metadata.mutable_random_dual_projections()->Add(RandomProjection(
636 dual_solution, sharded_qp.DualSharder(), seed_generator));
637 }
638}
639
640} // namespace operations_research::pdlp
static T Square(const T x)
Returns the square of x.
Definition mathutil.h:102
const Sharder & DualSharder() const
Returns a Sharder intended for dual vectors.
const Sharder & PrimalSharder() const
Returns a Sharder intended for primal vectors.
double upper_bound
double lower_bound
double l_2_residual
double objective_full_correction
double objective_correction
double l_inf_residual
double l_inf_componentwise_residual
Validation utilities for solvers.proto.
void SetZero(const Sharder &sharder, VectorXd &dest)
Definition sharder.cc:178
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, const Eigen::VectorXd &primal_solution_for_residual_tests, PointType candidate_type)
double ScaledNorm(const VectorXd &vector, const VectorXd &scale, const Sharder &sharder)
Definition sharder.cc:286
double Dot(const VectorXd &v1, const VectorXd &v2, const Sharder &sharder)
Definition sharder.cc:230
std::optional< ConvergenceInformation > GetConvergenceInformation(const IterationStats &stats, PointType candidate_type)
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)
double LInfNorm(const VectorXd &vector, const Sharder &sharder)
Definition sharder.cc:235
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:163
std::optional< InfeasibilityInformation > GetInfeasibilityInformation(const IterationStats &stats, PointType candidate_type)
double ScaledLInfNorm(const VectorXd &vector, const VectorXd &scale, const Sharder &sharder)
Definition sharder.cc:269
bool IsLinearProgram(const QuadraticProgram &qp)
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)
VectorXd ZeroVector(const Sharder &sharder)
Like VectorXd::Zero(sharder.NumElements()).
Definition sharder.cc:184
VectorXd OnesVector(const Sharder &sharder)
Like VectorXd::Ones(sharder.NumElements()).
Definition sharder.cc:190
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)
int64_t bound
double ApplyObjectiveScalingAndOffset(double objective) const