25 #include "Eigen/SparseCore"
26 #include "absl/log/check.h"
27 #include "absl/random/distributions.h"
32 #include "ortools/pdlp/solve_log.pb.h"
33 #include "ortools/pdlp/solvers.pb.h"
38 using ::Eigen::VectorXd;
48 struct 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());
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;
105 }
else if (primal_product_shard[i] <
lower_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>(),
132 local_l_inf_componentwise_residual.lpNorm<Eigen::Infinity>(),
140 bool HandlePrimalGradientTermAsReducedCost(
141 const PrimalDualHybridGradientParams& params,
double primal_gradient,
143 if (primal_gradient == 0.0)
return true;
145 if (params.handle_some_primal_gradients_on_finite_bounds_as_residuals()) {
147 return std::abs(primal_value - active_bound) <= std::abs(primal_value);
149 return std::isfinite(active_bound);
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;
189 double sumsq_residual = 0.0;
191 for (int64_t i = 0; i < primal_gradient_shard.size(); ++i) {
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];
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;
212 if (residual > 0.0) {
216 (componentwise_residual_offset +
217 std::abs(objective_shard[i] / col_scaling_shard[i])));
221 local_dual_correction[shard.Index()] = dual_correction;
222 local_dual_full_correction[shard.Index()] = dual_full_correction;
224 local_sumsq_residual[shard.Index()] = sumsq_residual;
225 local_l_inf_componentwise_residual[shard.Index()] =
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>(),
234 local_l_inf_componentwise_residual.lpNorm<Eigen::Infinity>(),
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());
244 SetZero(sharded_qp.PrimalSharder(), result);
246 sharded_qp.PrimalSharder().ParallelForEachShard(
247 [&](
const Sharder::Shard& shard) {
249 shard(*sharded_qp.Qp().objective_matrix) * shard(primal_solution);
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());
262 Dot(objective_product, primal_solution, sharded_qp.PrimalSharder());
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());
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;
283 shard(objective_product) +=
284 shard(qp.objective_vector) -
285 shard(qp.constraint_matrix).transpose() * dual_solution;
288 return objective_product;
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) {
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);
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];
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)());
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];
342 dot_product[shard.Index()] = shard_dot_product;
343 gaussian_norm_squared[shard.Index()] = shard_norm_squared;
345 return dot_product.sum() / std::sqrt(gaussian_norm_squared.sum());
350 const PrimalDualHybridGradientParams& params,
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) {
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());
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);
376 result.set_l_inf_primal_variable(
379 result.set_l2_primal_variable(
ScaledNorm(scaled_primal_solution,
383 scaled_dual_solution, row_scaling_vec, scaled_sharded_qp.
DualSharder()));
384 result.set_l2_dual_variable(
ScaledNorm(scaled_dual_solution, row_scaling_vec,
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));
401 const double dual_objective_piece =
402 -quadratic_objective +
403 DualObjectiveBoundsTerm(scaled_sharded_qp, scaled_dual_solution);
405 ResidualNorms dual_residuals = DualResidualNorms(
406 params, scaled_sharded_qp, col_scaling_vec, scaled_primal_solution,
407 scaled_primal_gradient, componentwise_dual_residual_offset);
409 dual_objective_piece + dual_residuals.objective_correction));
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);
417 result.set_candidate_type(candidate_type);
422 const PrimalDualHybridGradientParams& params,
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) {
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());
434 double l_inf_primal =
ScaledLInfNorm(scaled_primal_ray, col_scaling_vec,
436 double l_inf_dual =
ScaledLInfNorm(scaled_dual_ray, row_scaling_vec,
438 InfeasibilityInformation result;
440 VectorXd scaled_primal_gradient = PrimalGradientFromObjectiveProduct(
441 scaled_sharded_qp, scaled_dual_ray,
446 ResidualNorms dual_residuals = DualResidualNorms(
447 params, scaled_sharded_qp, col_scaling_vec, scaled_primal_ray,
448 scaled_primal_gradient, 0.0);
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 /
458 result.set_dual_ray_objective(0.0);
459 result.set_max_dual_ray_infeasibility(0.0);
465 ResidualNorms primal_residuals =
466 PrimalResidualNorms(scaled_sharded_qp, row_scaling_vec, scaled_primal_ray,
472 VectorXd primal_ray_local_sign_max_violation(
476 const auto lower_bound_shard =
478 const auto upper_bound_shard =
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]);
487 if (std::isfinite(upper_bound_shard[i])) {
488 local_max =
std::max(local_max, ray_shard[i] * scale_shard[i]);
491 primal_ray_local_sign_max_violation[shard.
Index()] = local_max;
493 const double primal_ray_sign_max_violation =
494 primal_ray_local_sign_max_violation.lpNorm<Eigen::Infinity>();
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(
502 result.set_max_primal_ray_infeasibility(
503 std::max(primal_residuals.l_inf_residual,
504 primal_ray_sign_max_violation) /
506 result.set_primal_ray_linear_objective(
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);
516 result.set_candidate_type(candidate_type);
521 const PrimalDualHybridGradientParams& params,
523 const VectorXd& dual_solution,
524 const double componentwise_primal_residual_offset,
525 const double componentwise_dual_residual_offset, PointType candidate_type) {
529 componentwise_primal_residual_offset, componentwise_dual_residual_offset,
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) {
542 objective_product = ObjectiveProduct(sharded_qp, primal_solution);
544 VectorXd reduced_costs = PrimalGradientFromObjectiveProduct(
545 sharded_qp, dual_solution, std::move(objective_product),
546 use_zero_primal_objective);
549 auto rc_shard = shard(reduced_costs);
550 const auto lower_bound_shard =
552 const auto upper_bound_shard =
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])) {
564 return reduced_costs;
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;
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;
589 const PointType point_type) {
590 for (
const auto& metadata : stats.point_metadata()) {
591 if (metadata.point_type() == point_type) {
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));
static T Square(const T x)
const Sharder & DualSharder() const
const Sharder & PrimalSharder() const
int64_t PrimalSize() const
const QuadraticProgram & Qp() const
void ParallelForEachShard(const std::function< void(const Shard &)> &func) const
double objective_full_correction
double objective_correction
double l_inf_componentwise_residual
void SetZero(const Sharder &sharder, VectorXd &dest)
double ScaledNorm(const VectorXd &vector, const VectorXd &scale, const Sharder &sharder)
double Dot(const VectorXd &v1, const VectorXd &v2, const Sharder &sharder)
double LInfNorm(const VectorXd &vector, const Sharder &sharder)
VectorXd ReducedCosts(const PrimalDualHybridGradientParams ¶ms, 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)
InfeasibilityInformation ComputeInfeasibilityInformation(const PrimalDualHybridGradientParams ¶ms, 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)
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 ¶ms, 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)
VectorXd OnesVector(const Sharder &sharder)
ConvergenceInformation ComputeConvergenceInformation(const PrimalDualHybridGradientParams ¶ms, 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)
Eigen::VectorXd variable_upper_bounds
Eigen::VectorXd variable_lower_bounds
double ApplyObjectiveScalingAndOffset(double objective) const
Eigen::VectorXd objective_vector