39using ::Eigen::VectorXd;
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());
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);
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]))
95 : upper_bound_shard[
i];
96 const double lower_bound = (use_homogeneous_constraint_bounds &&
97 std::isfinite(lower_bound_shard[
i]))
99 : lower_bound_shard[
i];
100 double scaled_residual = 0.0;
101 double residual_bound = 0.0;
103 scaled_residual = primal_product_shard[
i] -
upper_bound;
106 scaled_residual =
lower_bound - primal_product_shard[
i];
109 const double residual = scaled_residual / row_scaling_shard[
i];
111 sumsq_residual += residual * residual;
114 if (residual > 0.0) {
117 residual / (componentwise_residual_offset +
118 std::abs(residual_bound / row_scaling_shard[
i])));
122 local_sumsq_residual[shard.Index()] = sumsq_residual;
123 local_l_inf_componentwise_residual[shard.Index()] =
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>(),
131 .l_inf_componentwise_residual =
132 local_l_inf_componentwise_residual.lpNorm<Eigen::Infinity>(),
136bool TreatVariableBoundAsFinite(
const PrimalDualHybridGradientParams& params,
137 double primal_value,
double bound) {
138 if (params.handle_some_primal_gradients_on_finite_bounds_as_residuals()) {
140 return std::abs(primal_value -
bound) <= std::abs(primal_value);
142 return std::isfinite(
bound);
146struct VariableBounds {
151VariableBounds EffectiveVariableBounds(
152 const PrimalDualHybridGradientParams& params,
double primal_value,
154 return {.lower_bound =
155 TreatVariableBoundAsFinite(params, primal_value,
lower_bound)
157 : -std::numeric_limits<double>::infinity(),
159 TreatVariableBoundAsFinite(params, primal_value,
upper_bound)
161 : std::numeric_limits<double>::infinity()};
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;
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;
215 double sumsq_residual = 0.0;
217 for (int64_t i = 0;
i < primal_gradient_shard.size(); ++
i) {
222 if (primal_gradient_shard[i] == 0.0)
continue;
225 const double bound_for_rc =
227 dual_full_correction += bound_for_rc * primal_gradient_shard[
i];
228 VariableBounds effective_bounds = EffectiveVariableBounds(
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];
243 sumsq_residual += residual * residual;
246 if (residual > 0.0) {
250 (componentwise_residual_offset +
251 std::abs(objective_shard[i] / col_scaling_shard[i])));
255 local_dual_correction[shard.Index()] = dual_correction;
256 local_dual_full_correction[shard.Index()] = dual_full_correction;
258 local_sumsq_residual[shard.Index()] = sumsq_residual;
259 local_l_inf_componentwise_residual[shard.Index()] =
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>(),
267 .l_inf_componentwise_residual =
268 local_l_inf_componentwise_residual.lpNorm<Eigen::Infinity>(),
273VectorXd ObjectiveProduct(
const ShardedQuadraticProgram& sharded_qp,
278 SetZero(sharded_qp.PrimalSharder(), result);
280 sharded_qp.PrimalSharder().ParallelForEachShard(
281 [&](
const Sharder::Shard& shard) {
290double QuadraticObjective(
const ShardedQuadraticProgram& sharded_qp,
292 const VectorXd& objective_product) {
294 CHECK_EQ(objective_product.size(), sharded_qp.PrimalSize());
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());
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;
317 shard(objective_product) +=
318 shard(qp.objective_vector) -
319 shard(qp.constraint_matrix).transpose() * dual_solution;
322 return objective_product;
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) {
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);
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];
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)());
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];
376 dot_product[shard.Index()] = shard_dot_product;
377 gaussian_norm_squared[shard.Index()] = shard_norm_squared;
379 return dot_product.sum() / std::sqrt(gaussian_norm_squared.sum());
384 const PrimalDualHybridGradientParams& params,
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) {
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());
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);
410 result.set_l_inf_primal_variable(
413 result.set_l2_primal_variable(
ScaledNorm(scaled_primal_solution,
417 scaled_dual_solution, row_scaling_vec, scaled_sharded_qp.
DualSharder()));
418 result.set_l2_dual_variable(
ScaledNorm(scaled_dual_solution, row_scaling_vec,
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));
435 const double dual_objective_piece =
436 -quadratic_objective +
437 DualObjectiveBoundsTerm(scaled_sharded_qp, scaled_dual_solution);
439 ResidualNorms dual_residuals = DualResidualNorms(
440 params, scaled_sharded_qp, col_scaling_vec, scaled_primal_solution,
441 scaled_primal_gradient, componentwise_dual_residual_offset);
443 dual_objective_piece + dual_residuals.objective_correction));
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);
451 result.set_candidate_type(candidate_type);
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]);
475 if (std::isfinite(upper_bound_shard[i])) {
476 local_max = std::max(local_max, ray_shard[i] * scale_shard[i]);
479 primal_ray_local_max_sign_violation[shard.Index()] = local_max;
481 return primal_ray_local_max_sign_violation.lpNorm<Eigen::Infinity>();
487 const PrimalDualHybridGradientParams& params,
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) {
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());
501 double l_inf_primal =
ScaledLInfNorm(scaled_primal_ray, col_scaling_vec,
503 double l_inf_dual =
ScaledLInfNorm(scaled_dual_ray, row_scaling_vec,
505 InfeasibilityInformation result;
507 VectorXd scaled_primal_gradient = PrimalGradientFromObjectiveProduct(
508 scaled_sharded_qp, scaled_dual_ray,
513 ResidualNorms dual_residuals = DualResidualNorms(
514 params, scaled_sharded_qp, col_scaling_vec,
515 primal_solution_for_residual_tests, scaled_primal_gradient,
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 /
526 result.set_dual_ray_objective(0.0);
527 result.set_max_dual_ray_infeasibility(0.0);
533 ResidualNorms primal_residuals =
534 PrimalResidualNorms(scaled_sharded_qp, row_scaling_vec, scaled_primal_ray,
540 DCHECK_EQ(PrimalRayMaxSignViolation(scaled_sharded_qp, col_scaling_vec,
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(
550 result.set_max_primal_ray_infeasibility(primal_residuals.l_inf_residual /
552 result.set_primal_ray_linear_objective(
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);
562 result.set_candidate_type(candidate_type);
567 const PrimalDualHybridGradientParams& params,
569 const VectorXd& dual_solution,
570 const double componentwise_primal_residual_offset,
571 const double componentwise_dual_residual_offset, PointType candidate_type) {
575 componentwise_primal_residual_offset, componentwise_dual_residual_offset,
582 const VectorXd& dual_solution,
583 bool use_zero_primal_objective) {
584 VectorXd objective_product;
585 if (use_zero_primal_objective) {
590 return PrimalGradientFromObjectiveProduct(sharded_qp, dual_solution,
591 std::move(objective_product),
592 use_zero_primal_objective);
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;
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;
617 const PointType point_type) {
618 for (
const auto& metadata : stats.point_metadata()) {
619 if (metadata.point_type() == point_type) {
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(
635 metadata.mutable_random_dual_projections()->Add(RandomProjection(
636 dual_solution, sharded_qp.
DualSharder(), seed_generator));