29 #include "Eigen/SparseCore"
30 #include "absl/log/check.h"
31 #include "absl/status/status.h"
32 #include "absl/status/statusor.h"
33 #include "absl/strings/str_cat.h"
34 #include "absl/strings/str_format.h"
35 #include "absl/strings/string_view.h"
39 #include "ortools/glop/parameters.pb.h"
41 #include "ortools/linear_solver/linear_solver.pb.h"
50 #include "ortools/pdlp/solve_log.pb.h"
51 #include "ortools/pdlp/solvers.pb.h"
60 using ::Eigen::VectorXd;
62 using IterationStatsCallback =
63 std::function<void(
const IterationCallbackInfo&)>;
67 int NumThreads(
const int num_threads,
const int num_shards,
68 const QuadraticProgram& qp) {
69 int capped_num_threads = num_threads;
71 capped_num_threads =
std::min(capped_num_threads, num_shards);
73 const int64_t problem_limit =
std::max(qp.variable_lower_bounds.size(),
74 qp.constraint_lower_bounds.size());
76 static_cast<int>(
std::min(int64_t{capped_num_threads}, problem_limit));
77 capped_num_threads =
std::max(capped_num_threads, 1);
78 if (capped_num_threads != num_threads) {
79 LOG(WARNING) <<
"Reducing num_threads from " << num_threads <<
" to "
81 <<
" because additional threads would be useless.";
83 return capped_num_threads;
89 int NumShards(
const int num_threads,
const int num_shards) {
90 if (num_shards > 0)
return num_shards;
91 return num_threads == 1 ? 1 : 4 * num_threads;
94 std::string
ToString(
const ConvergenceInformation& convergence_information,
95 const RelativeConvergenceInformation& relative_information,
96 const OptimalityNorm residual_norm) {
97 constexpr absl::string_view kFormatStr =
98 "%#12.6g %#12.6g %#12.6g | %#12.6g %#12.6g %#12.6g | %#12.6g %#12.6g | "
100 switch (residual_norm) {
101 case OPTIMALITY_NORM_L_INF:
102 return absl::StrFormat(
103 kFormatStr, relative_information.relative_l_inf_primal_residual,
104 relative_information.relative_l_inf_dual_residual,
105 relative_information.relative_optimality_gap,
106 convergence_information.l_inf_primal_residual(),
107 convergence_information.l_inf_dual_residual(),
108 convergence_information.primal_objective() -
109 convergence_information.dual_objective(),
110 convergence_information.primal_objective(),
111 convergence_information.dual_objective(),
112 convergence_information.l2_primal_variable(),
113 convergence_information.l2_dual_variable());
114 case OPTIMALITY_NORM_L2:
115 return absl::StrFormat(kFormatStr,
116 relative_information.relative_l2_primal_residual,
117 relative_information.relative_l2_dual_residual,
118 relative_information.relative_optimality_gap,
119 convergence_information.l2_primal_residual(),
120 convergence_information.l2_dual_residual(),
121 convergence_information.primal_objective() -
122 convergence_information.dual_objective(),
123 convergence_information.primal_objective(),
124 convergence_information.dual_objective(),
125 convergence_information.l2_primal_variable(),
126 convergence_information.l2_dual_variable());
127 case OPTIMALITY_NORM_L_INF_COMPONENTWISE:
128 return absl::StrFormat(
130 convergence_information.l_inf_componentwise_primal_residual(),
131 convergence_information.l_inf_componentwise_dual_residual(),
132 relative_information.relative_optimality_gap,
133 convergence_information.l_inf_primal_residual(),
134 convergence_information.l_inf_dual_residual(),
135 convergence_information.primal_objective() -
136 convergence_information.dual_objective(),
137 convergence_information.primal_objective(),
138 convergence_information.dual_objective(),
139 convergence_information.l2_primal_variable(),
140 convergence_information.l2_dual_variable());
141 case OPTIMALITY_NORM_UNSPECIFIED:
142 LOG(FATAL) <<
"Invalid residual norm.";
146 std::string ToShortString(
147 const ConvergenceInformation& convergence_information,
148 const RelativeConvergenceInformation& relative_information,
149 const OptimalityNorm residual_norm) {
150 constexpr absl::string_view kFormatStr =
151 "%#10.4g %#10.4g %#10.4g | %#10.4g %#10.4g";
152 switch (residual_norm) {
153 case OPTIMALITY_NORM_L_INF:
154 return absl::StrFormat(
155 kFormatStr, relative_information.relative_l_inf_primal_residual,
156 relative_information.relative_l_inf_dual_residual,
157 relative_information.relative_optimality_gap,
158 convergence_information.primal_objective(),
159 convergence_information.dual_objective());
160 case OPTIMALITY_NORM_L2:
161 return absl::StrFormat(kFormatStr,
162 relative_information.relative_l2_primal_residual,
163 relative_information.relative_l2_dual_residual,
164 relative_information.relative_optimality_gap,
165 convergence_information.primal_objective(),
166 convergence_information.dual_objective());
167 case OPTIMALITY_NORM_L_INF_COMPONENTWISE:
168 return absl::StrFormat(
170 convergence_information.l_inf_componentwise_primal_residual(),
171 convergence_information.l_inf_componentwise_dual_residual(),
172 relative_information.relative_optimality_gap,
173 convergence_information.primal_objective(),
174 convergence_information.dual_objective());
175 case OPTIMALITY_NORM_UNSPECIFIED:
176 LOG(FATAL) <<
"Invalid residual norm.";
185 std::string
ToString(
const IterationStats& iter_stats,
186 const TerminationCriteria& termination_criteria,
187 const QuadraticProgramBoundNorms& bound_norms,
188 PointType preferred_candidate) {
189 std::string iteration_string =
190 absl::StrFormat(
"%6d %8.1f %6.1f", iter_stats.iteration_number(),
191 iter_stats.cumulative_kkt_matrix_passes(),
192 iter_stats.cumulative_time_sec());
193 auto convergence_information =
195 if (!convergence_information.has_value() &&
196 iter_stats.convergence_information_size() > 0) {
197 convergence_information = iter_stats.convergence_information(0);
199 if (convergence_information.has_value()) {
200 const RelativeConvergenceInformation relative_information =
203 *convergence_information, bound_norms);
204 return absl::StrCat(iteration_string,
" | ",
205 ToString(*convergence_information, relative_information,
206 termination_criteria.optimality_norm()));
208 return iteration_string;
211 std::string ToShortString(
const IterationStats& iter_stats,
212 const TerminationCriteria& termination_criteria,
213 const QuadraticProgramBoundNorms& bound_norms,
214 PointType preferred_candidate) {
215 std::string iteration_string =
216 absl::StrFormat(
"%6d %6.1f", iter_stats.iteration_number(),
217 iter_stats.cumulative_time_sec());
218 auto convergence_information =
220 if (!convergence_information.has_value() &&
221 iter_stats.convergence_information_size() > 0) {
222 convergence_information = iter_stats.convergence_information(0);
224 if (convergence_information.has_value()) {
225 const RelativeConvergenceInformation relative_information =
228 *convergence_information, bound_norms);
230 iteration_string,
" | ",
231 ToShortString(*convergence_information, relative_information,
232 termination_criteria.optimality_norm()));
234 return iteration_string;
238 std::string ConvergenceInformationLabelString() {
239 return absl::StrFormat(
240 "%12s %12s %12s | %12s %12s %12s | %12s %12s | %12s %12s",
"rel_prim_res",
241 "rel_dual_res",
"rel_gap",
"prim_resid",
"dual_resid",
"obj_gap",
242 "prim_obj",
"dual_obj",
"prim_var_l2",
"dual_var_l2");
245 std::string ConvergenceInformationLabelShortString() {
246 return absl::StrFormat(
"%10s %10s %10s | %10s %10s",
"rel_p_res",
"rel_d_res",
247 "rel_gap",
"prim_obj",
"dual_obj");
250 std::string IterationStatsLabelString() {
252 absl::StrFormat(
"%6s %8s %6s",
"iter#",
"kkt_pass",
"time"),
" | ",
253 ConvergenceInformationLabelString());
256 std::string IterationStatsLabelShortString() {
257 return absl::StrCat(absl::StrFormat(
"%6s %6s",
"iter#",
"time"),
" | ",
258 ConvergenceInformationLabelShortString());
261 enum class InnerStepOutcome {
263 kForceNumericalTermination,
270 SolverResult ConstructSolverResult(VectorXd primal_solution,
271 VectorXd dual_solution,
272 const IterationStats& stats,
274 PointType output_type, SolveLog solve_log) {
275 solve_log.set_iteration_count(stats.iteration_number());
276 solve_log.set_termination_reason(termination_reason);
277 solve_log.set_solution_type(output_type);
278 solve_log.set_solve_time_sec(stats.cumulative_time_sec());
279 *solve_log.mutable_solution_stats() = stats;
280 return SolverResult{.primal_solution = std::move(primal_solution),
281 .dual_solution = std::move(dual_solution),
282 .solve_log = std::move(solve_log)};
285 class PreprocessSolver {
293 explicit PreprocessSolver(QuadraticProgram qp,
294 const PrimalDualHybridGradientParams& params);
297 PreprocessSolver(
const PreprocessSolver&) =
delete;
298 PreprocessSolver& operator=(
const PreprocessSolver&) =
delete;
299 PreprocessSolver(PreprocessSolver&&) =
delete;
300 PreprocessSolver& operator=(PreprocessSolver&&) =
delete;
309 SolverResult PreprocessAndSolve(
310 const PrimalDualHybridGradientParams& params,
311 std::optional<PrimalAndDualSolution> initial_solution,
312 const std::atomic<bool>* interrupt_solve,
313 IterationStatsCallback iteration_stats_callback);
321 std::optional<TerminationReasonAndPointType>
322 UpdateIterationStatsAndCheckTermination(
323 const PrimalDualHybridGradientParams& params,
324 bool force_numerical_termination,
const VectorXd& working_primal_current,
325 const VectorXd& working_dual_current,
326 const VectorXd* working_primal_average,
327 const VectorXd* working_dual_average,
328 const VectorXd* working_primal_delta,
const VectorXd* working_dual_delta,
329 const VectorXd& last_primal_start_point,
330 const VectorXd& last_dual_start_point,
331 const std::atomic<bool>* interrupt_solve, IterationStats& stats)
const;
335 ConvergenceInformation ComputeConvergenceInformationFromWorkingSolution(
336 const PrimalDualHybridGradientParams& params,
337 const VectorXd& working_primal,
const VectorXd& working_dual,
338 PointType candidate_type)
const;
344 SolverResult ConstructOriginalSolverResult(
345 const PrimalDualHybridGradientParams& params, SolverResult result)
const;
347 const ShardedQuadraticProgram& ShardedWorkingQp()
const {
352 double GetElapsedTime()
const {
return timer_.
Get(); }
355 struct PresolveInfo {
356 explicit PresolveInfo(ShardedQuadraticProgram original_qp,
357 const PrimalDualHybridGradientParams& params)
374 static glop::GlopParameters PreprocessorParameters(
375 const PrimalDualHybridGradientParams& params);
383 std::optional<TerminationReason> ApplyPresolveIfEnabled(
384 const PrimalDualHybridGradientParams& params,
385 std::optional<PrimalAndDualSolution>* initial_solution);
387 void ComputeAndApplyRescaling(
const PrimalDualHybridGradientParams& params,
388 VectorXd& starting_primal_solution,
389 VectorXd& starting_dual_solution);
391 void LogQuadraticProgramStats(
const QuadraticProgramStats& stats);
393 double InitialPrimalWeight(
const PrimalDualHybridGradientParams& params,
394 double l2_norm_primal_linear_objective,
395 double l2_norm_constraint_bounds)
const;
397 PrimalAndDualSolution RecoverOriginalSolution(
398 PrimalAndDualSolution working_solution)
const;
405 void AddConvergenceAndInfeasibilityInformation(
406 const PrimalDualHybridGradientParams& params,
407 const VectorXd& primal_solution,
const VectorXd& dual_solution,
408 const ShardedQuadraticProgram& sharded_qp,
409 const VectorXd& col_scaling_vec,
const VectorXd& row_scaling_vec,
410 PointType candidate_type, IterationStats& stats)
const;
413 void AddPointMetadata(
const PrimalDualHybridGradientParams& params,
414 const VectorXd& primal_solution,
415 const VectorXd& dual_solution, PointType point_type,
416 const VectorXd& last_primal_start_point,
417 const VectorXd& last_dual_start_point,
418 IterationStats& stats)
const;
420 const QuadraticProgram& Qp()
const {
return sharded_qp_.Qp(); }
422 const int num_threads_;
423 const int num_shards_;
426 QuadraticProgramBoundNorms original_bound_norms_;
434 ShardedQuadraticProgram sharded_qp_;
437 std::optional<PresolveInfo> presolve_info_;
442 VectorXd col_scaling_vec_;
443 VectorXd row_scaling_vec_;
446 IterationStatsCallback iteration_stats_callback_;
453 explicit Solver(
const PrimalDualHybridGradientParams& params,
454 VectorXd starting_primal_solution,
455 VectorXd starting_dual_solution,
double initial_step_size,
456 double initial_primal_weight,
457 const PreprocessSolver* preprocess_solver);
460 Solver(
const Solver&) =
delete;
461 Solver& operator=(
const Solver&) =
delete;
462 Solver(Solver&&) =
delete;
463 Solver& operator=(Solver&&) =
delete;
465 const QuadraticProgram& WorkingQp()
const {
return ShardedWorkingQp().Qp(); }
467 const ShardedQuadraticProgram& ShardedWorkingQp()
const {
468 return preprocess_solver_->ShardedWorkingQp();
479 SolverResult
Solve(
const std::atomic<bool>* interrupt_solve,
483 struct NextSolutionAndDelta {
489 struct DistanceBasedRestartInfo {
497 constexpr
static double kDivergentMovement = 1.0e100;
499 NextSolutionAndDelta ComputeNextPrimalSolution(
double primal_step_size)
const;
501 NextSolutionAndDelta ComputeNextDualSolution(
502 double dual_step_size,
double extrapolation_factor,
503 const NextSolutionAndDelta& next_primal)
const;
505 double ComputeMovement(
const VectorXd& delta_primal,
506 const VectorXd& delta_dual)
const;
508 double ComputeNonlinearity(
const VectorXd& delta_primal,
509 const VectorXd& next_dual_product)
const;
512 IterationStats CreateSimpleIterationStats(RestartChoice restart_used)
const;
514 RestartChoice ChooseRestartToApply(
bool is_major_iteration);
516 VectorXd PrimalAverage()
const;
518 VectorXd DualAverage()
const;
520 double ComputeNewPrimalWeight()
const;
532 SolverResult PickSolutionAndConstructSolverResult(
533 VectorXd primal_solution, VectorXd dual_solution,
535 PointType output_type, SolveLog solve_log)
const;
537 double DistanceTraveledFromLastStart(
const VectorXd& primal_solution,
538 const VectorXd& dual_solution)
const;
540 LocalizedLagrangianBounds ComputeLocalizedBoundsAtCurrent()
const;
542 LocalizedLagrangianBounds ComputeLocalizedBoundsAtAverage()
const;
546 void ApplyRestartChoice(RestartChoice restart_to_apply);
548 std::optional<SolverResult> MajorIterationAndTerminationCheck(
549 bool force_numerical_termination,
550 const std::atomic<bool>* interrupt_solve, SolveLog& solve_log);
552 bool ShouldDoAdaptiveRestartHeuristic(
double candidate_normalized_gap)
const;
554 RestartChoice DetermineDistanceBasedRestartChoice()
const;
556 void ResetAverageToCurrent();
558 void LogNumericalTermination()
const;
560 void LogInnerIterationLimitHit()
const;
569 InnerStepOutcome TakeMalitskyPockStep();
573 InnerStepOutcome TakeAdaptiveStep();
576 InnerStepOutcome TakeConstantSizeStep();
578 const PrimalDualHybridGradientParams params_;
580 VectorXd current_primal_solution_;
581 VectorXd current_dual_solution_;
582 VectorXd current_primal_delta_;
583 VectorXd current_dual_delta_;
585 ShardedWeightedAverage primal_average_;
586 ShardedWeightedAverage dual_average_;
589 double primal_weight_;
591 const PreprocessSolver* preprocess_solver_;
594 double ratio_last_two_step_sizes_;
596 double normalized_gap_at_last_trial_ =
597 std::numeric_limits<double>::infinity();
599 double normalized_gap_at_last_restart_ =
600 std::numeric_limits<double>::infinity();
601 int iterations_completed_;
602 int num_rejected_steps_;
604 VectorXd current_dual_product_;
607 VectorXd last_primal_start_point_;
610 VectorXd last_dual_start_point_;
614 DistanceBasedRestartInfo distance_based_restart_info_ = {
615 .distance_moved_last_restart_period =
616 std::numeric_limits<double>::infinity(),
617 .length_of_last_restart_period = 1,
621 PreprocessSolver::PreprocessSolver(QuadraticProgram qp,
622 const PrimalDualHybridGradientParams& params)
623 : num_threads_(NumThreads(params.num_threads(), params.num_shards(), qp)),
624 num_shards_(NumShards(num_threads_, params.num_shards())),
625 sharded_qp_(std::move(qp), num_threads_, num_shards_) {}
630 error_log.set_termination_reason(reason);
631 error_log.set_termination_string(
message);
632 LOG(WARNING) <<
"The solver did not run because of invalid input: "
634 return SolverResult{.solve_log = error_log};
637 std::optional<SolverResult> CheckProblemStats(
638 const QuadraticProgramStats& problem_stats) {
639 const double kExcessiveInputValue = 1e50;
640 const double kExcessivelySmallInputValue = 1e-50;
641 const double kMaxDynamicRange = 1e20;
642 if (std::isnan(problem_stats.constraint_matrix_l2_norm())) {
643 return ErrorSolverResult(TERMINATION_REASON_INVALID_PROBLEM,
644 "Constraint matrix has a NAN.");
646 if (problem_stats.constraint_matrix_abs_max() > kExcessiveInputValue) {
647 return ErrorSolverResult(
648 TERMINATION_REASON_INVALID_PROBLEM,
649 absl::StrCat(
"Constraint matrix has a non-zero with absolute value ",
650 problem_stats.constraint_matrix_abs_max(),
651 " which exceeds limit of ", kExcessiveInputValue,
"."));
653 if (problem_stats.constraint_matrix_abs_max() >
654 kMaxDynamicRange * problem_stats.constraint_matrix_abs_min()) {
655 LOG(WARNING) <<
"Constraint matrix has largest absolute value "
656 << problem_stats.constraint_matrix_abs_max()
657 <<
" and smallest non-zero absolute value "
658 << problem_stats.constraint_matrix_abs_min()
659 <<
" performance may suffer.";
661 if (problem_stats.constraint_matrix_col_min_l_inf_norm() > 0 &&
662 problem_stats.constraint_matrix_col_min_l_inf_norm() <
663 kExcessivelySmallInputValue) {
664 return ErrorSolverResult(
665 TERMINATION_REASON_INVALID_PROBLEM,
666 absl::StrCat(
"Constraint matrix has a column with Linf norm ",
667 problem_stats.constraint_matrix_col_min_l_inf_norm(),
668 " which is less than limit of ",
669 kExcessivelySmallInputValue,
"."));
671 if (problem_stats.constraint_matrix_row_min_l_inf_norm() > 0 &&
672 problem_stats.constraint_matrix_row_min_l_inf_norm() <
673 kExcessivelySmallInputValue) {
674 return ErrorSolverResult(
675 TERMINATION_REASON_INVALID_PROBLEM,
676 absl::StrCat(
"Constraint matrix has a row with Linf norm ",
677 problem_stats.constraint_matrix_row_min_l_inf_norm(),
678 " which is less than limit of ",
679 kExcessivelySmallInputValue,
"."));
681 if (std::isnan(problem_stats.combined_bounds_l2_norm())) {
682 return ErrorSolverResult(TERMINATION_REASON_INVALID_PROBLEM,
683 "Constraint bounds vector has a NAN.");
685 if (problem_stats.combined_bounds_max() > kExcessiveInputValue) {
686 return ErrorSolverResult(
687 TERMINATION_REASON_INVALID_PROBLEM,
688 absl::StrCat(
"Combined constraint bounds vector has a non-zero with "
690 problem_stats.combined_bounds_max(),
691 " which exceeds limit of ", kExcessiveInputValue,
"."));
693 if (problem_stats.combined_bounds_max() >
694 kMaxDynamicRange * problem_stats.combined_bounds_min()) {
696 <<
"Combined constraint bounds vector has largest absolute value "
697 << problem_stats.combined_bounds_max()
698 <<
" and smallest non-zero absolute value "
699 << problem_stats.combined_bounds_min() <<
"; performance may suffer.";
701 if (std::isnan(problem_stats.variable_bound_gaps_l2_norm())) {
702 return ErrorSolverResult(TERMINATION_REASON_INVALID_PROBLEM,
703 "Variable bounds vector has a NAN.");
705 if (problem_stats.variable_bound_gaps_max() > kExcessiveInputValue) {
706 return ErrorSolverResult(
707 TERMINATION_REASON_INVALID_PROBLEM,
708 absl::StrCat(
"Variable bound gaps vector has a finite non-zero with "
710 problem_stats.variable_bound_gaps_max(),
711 " which exceeds limit of ", kExcessiveInputValue,
"."));
713 if (problem_stats.variable_bound_gaps_max() >
714 kMaxDynamicRange * problem_stats.variable_bound_gaps_min()) {
715 LOG(WARNING) <<
"Variable bound gap vector has largest absolute value "
716 << problem_stats.variable_bound_gaps_max()
717 <<
" and smallest non-zero absolute value "
718 << problem_stats.variable_bound_gaps_min()
719 <<
"; performance may suffer.";
721 if (std::isnan(problem_stats.objective_vector_l2_norm())) {
722 return ErrorSolverResult(TERMINATION_REASON_INVALID_PROBLEM,
723 "Objective vector has a NAN.");
725 if (problem_stats.objective_vector_abs_max() > kExcessiveInputValue) {
726 return ErrorSolverResult(
727 TERMINATION_REASON_INVALID_PROBLEM,
728 absl::StrCat(
"Objective vector has a non-zero with absolute value ",
729 problem_stats.objective_vector_abs_max(),
730 " which exceeds limit of ", kExcessiveInputValue,
"."));
732 if (problem_stats.objective_vector_abs_max() >
733 kMaxDynamicRange * problem_stats.objective_vector_abs_min()) {
734 LOG(WARNING) <<
"Objective vector has largest absolute value "
735 << problem_stats.objective_vector_abs_max()
736 <<
" and smallest non-zero absolute value "
737 << problem_stats.objective_vector_abs_min()
738 <<
"; performance may suffer.";
740 if (std::isnan(problem_stats.objective_matrix_l2_norm())) {
741 return ErrorSolverResult(TERMINATION_REASON_INVALID_PROBLEM,
742 "Objective matrix has a NAN.");
744 if (problem_stats.objective_matrix_abs_max() > kExcessiveInputValue) {
745 return ErrorSolverResult(
746 TERMINATION_REASON_INVALID_PROBLEM,
747 absl::StrCat(
"Objective matrix has a non-zero with absolute value ",
748 problem_stats.objective_matrix_abs_max(),
749 " which exceeds limit of ", kExcessiveInputValue,
"."));
751 if (problem_stats.objective_matrix_abs_max() >
752 kMaxDynamicRange * problem_stats.objective_matrix_abs_min()) {
753 LOG(WARNING) <<
"Objective matrix has largest absolute value "
754 << problem_stats.objective_matrix_abs_max()
755 <<
" and smallest non-zero absolute value "
756 << problem_stats.objective_matrix_abs_min()
757 <<
"; performance may suffer.";
762 std::optional<SolverResult> CheckInitialSolution(
763 const ShardedQuadraticProgram& sharded_qp,
764 const PrimalAndDualSolution& initial_solution) {
765 const double kExcessiveInputValue = 1e50;
766 if (initial_solution.primal_solution.size() != sharded_qp.PrimalSize()) {
767 return ErrorSolverResult(
768 TERMINATION_REASON_INVALID_INITIAL_SOLUTION,
769 absl::StrCat(
"Initial primal solution has size ",
770 initial_solution.primal_solution.size(),
771 " which differs from problem primal size ",
772 sharded_qp.PrimalSize()));
775 Norm(initial_solution.primal_solution, sharded_qp.PrimalSharder()))) {
776 return ErrorSolverResult(TERMINATION_REASON_INVALID_INITIAL_SOLUTION,
777 "Initial primal solution has a NAN.");
779 if (
const double norm =
LInfNorm(initial_solution.primal_solution,
780 sharded_qp.PrimalSharder());
781 norm > kExcessiveInputValue) {
782 return ErrorSolverResult(
783 TERMINATION_REASON_INVALID_INITIAL_SOLUTION,
785 "Initial primal solution has an entry with absolute value ", norm,
786 " which exceeds limit of ", kExcessiveInputValue));
788 if (initial_solution.dual_solution.size() != sharded_qp.DualSize()) {
789 return ErrorSolverResult(
790 TERMINATION_REASON_INVALID_INITIAL_SOLUTION,
791 absl::StrCat(
"Initial dual solution has size ",
792 initial_solution.dual_solution.size(),
793 " which differs from problem dual size ",
794 sharded_qp.DualSize()));
797 Norm(initial_solution.dual_solution, sharded_qp.DualSharder()))) {
798 return ErrorSolverResult(TERMINATION_REASON_INVALID_INITIAL_SOLUTION,
799 "Initial dual solution has a NAN.");
801 if (
const double norm =
802 LInfNorm(initial_solution.dual_solution, sharded_qp.DualSharder());
803 norm > kExcessiveInputValue) {
804 return ErrorSolverResult(
805 TERMINATION_REASON_INVALID_INITIAL_SOLUTION,
806 absl::StrCat(
"Initial dual solution has an entry with absolute value ",
807 norm,
" which exceeds limit of ", kExcessiveInputValue));
812 SolverResult PreprocessSolver::PreprocessAndSolve(
813 const PrimalDualHybridGradientParams& params,
814 std::optional<PrimalAndDualSolution> initial_solution,
815 const std::atomic<bool>* interrupt_solve,
816 IterationStatsCallback iteration_stats_callback) {
818 if (Qp().problem_name.has_value()) {
819 solve_log.set_instance_name(*Qp().problem_name);
821 *solve_log.mutable_params() = params;
822 *solve_log.mutable_original_problem_stats() =
823 ComputeStats(sharded_qp_, params.infinite_constraint_bound_threshold());
824 const QuadraticProgramStats& original_problem_stats =
825 solve_log.original_problem_stats();
826 if (
auto maybe_result = CheckProblemStats(original_problem_stats);
827 maybe_result.has_value()) {
828 return *maybe_result;
830 if (initial_solution.has_value()) {
831 if (
auto maybe_result =
832 CheckInitialSolution(sharded_qp_, *initial_solution);
833 maybe_result.has_value()) {
834 return *maybe_result;
838 const std::string preprocessing_string = absl::StrCat(
839 params.presolve_options().use_glop() ?
"presolving and " :
"",
841 if (params.verbosity_level() >= 1) {
842 LOG(INFO) <<
"Problem stats before " << preprocessing_string;
843 LogQuadraticProgramStats(solve_log.original_problem_stats());
846 iteration_stats_callback_ = std::move(iteration_stats_callback);
847 std::optional<TerminationReason> maybe_terminate =
848 ApplyPresolveIfEnabled(params, &initial_solution);
849 if (maybe_terminate.has_value()) {
855 IterationStats iteration_stats;
856 iteration_stats.set_cumulative_time_sec(GetElapsedTime());
857 solve_log.set_preprocessing_time_sec(iteration_stats.cumulative_time_sec());
858 VectorXd working_primal =
ZeroVector(sharded_qp_.PrimalSharder());
859 VectorXd working_dual =
ZeroVector(sharded_qp_.DualSharder());
860 PrimalAndDualSolution original = RecoverOriginalSolution(
861 {.primal_solution = working_primal, .dual_solution = working_dual});
862 AddConvergenceAndInfeasibilityInformation(
863 params, original.primal_solution, original.dual_solution,
864 presolve_info_->sharded_original_qp,
865 presolve_info_->trivial_col_scaling_vec,
866 presolve_info_->trivial_row_scaling_vec, POINT_TYPE_PRESOLVER_SOLUTION,
868 std::optional<TerminationReasonAndPointType> earned_termination =
870 iteration_stats, original_bound_norms_,
872 if (!earned_termination.has_value()) {
874 params.termination_criteria(), iteration_stats, interrupt_solve);
877 if (earned_termination.has_value() &&
878 (earned_termination->reason == TERMINATION_REASON_OPTIMAL ||
879 earned_termination->reason == TERMINATION_REASON_PRIMAL_INFEASIBLE ||
880 earned_termination->reason == TERMINATION_REASON_DUAL_INFEASIBLE)) {
881 final_termination_reason = earned_termination->reason;
883 if (*maybe_terminate == TERMINATION_REASON_OPTIMAL) {
884 final_termination_reason = TERMINATION_REASON_NUMERICAL_ERROR;
885 LOG(WARNING) <<
"Presolve claimed to solve the LP optimally but the "
886 "solution doesn't satisfy the optimality criteria.";
888 final_termination_reason = *maybe_terminate;
891 return ConstructOriginalSolverResult(
892 params, ConstructSolverResult(
893 std::move(working_primal), std::move(working_dual),
894 std::move(iteration_stats), final_termination_reason,
895 POINT_TYPE_PRESOLVER_SOLUTION, std::move(solve_log)));
898 VectorXd starting_primal_solution;
899 VectorXd starting_dual_solution;
901 if (initial_solution.has_value()) {
902 starting_primal_solution = std::move(initial_solution->primal_solution);
903 starting_dual_solution = std::move(initial_solution->dual_solution);
905 SetZero(sharded_qp_.PrimalSharder(), starting_primal_solution);
906 SetZero(sharded_qp_.DualSharder(), starting_dual_solution);
913 ComputeAndApplyRescaling(params, starting_primal_solution,
914 starting_dual_solution);
915 *solve_log.mutable_preprocessed_problem_stats() =
916 ComputeStats(sharded_qp_, params.infinite_constraint_bound_threshold());
917 if (params.verbosity_level() >= 1) {
918 LOG(INFO) <<
"Problem stats after " << preprocessing_string;
919 LogQuadraticProgramStats(solve_log.preprocessed_problem_stats());
922 double step_size = 0.0;
923 if (params.linesearch_rule() ==
924 PrimalDualHybridGradientParams::CONSTANT_STEP_SIZE_RULE) {
925 std::mt19937 random(1);
926 double inverse_step_size;
927 const auto lipschitz_result =
929 sharded_qp_, std::nullopt, std::nullopt,
935 const double lipschitz_term_upper_bound =
936 lipschitz_result.singular_value /
937 (1.0 - lipschitz_result.estimated_relative_error);
938 inverse_step_size = lipschitz_term_upper_bound;
939 step_size = inverse_step_size > 0.0 ? 1.0 / inverse_step_size : 1.0;
954 solve_log.preprocessed_problem_stats().constraint_matrix_abs_max());
956 step_size *= params.initial_step_size_scaling();
958 const double primal_weight = InitialPrimalWeight(
959 params, solve_log.preprocessed_problem_stats().objective_vector_l2_norm(),
960 solve_log.preprocessed_problem_stats().combined_bounds_l2_norm());
961 solve_log.set_preprocessing_time_sec(GetElapsedTime());
963 Solver solver(params, starting_primal_solution, starting_dual_solution,
964 step_size, primal_weight,
this);
965 SolverResult result = solver.Solve(interrupt_solve, std::move(solve_log));
966 return ConstructOriginalSolverResult(params, std::move(result));
969 void LogInfoWithoutPrefix(absl::string_view
message) {
970 LOG(INFO).NoPrefix() <<
message;
973 glop::GlopParameters PreprocessSolver::PreprocessorParameters(
974 const PrimalDualHybridGradientParams& params) {
975 glop::GlopParameters glop_params;
977 glop_params.set_solve_dual_problem(glop::GlopParameters::NEVER_DO);
980 glop_params.set_use_implied_free_preprocessor(
false);
982 glop_params.set_use_scaling(
false);
983 if (params.presolve_options().has_glop_parameters()) {
984 glop_params.MergeFrom(params.presolve_options().glop_parameters());
991 switch (glop_status) {
993 return TERMINATION_REASON_OPTIMAL;
994 case glop::ProblemStatus::INVALID_PROBLEM:
995 return TERMINATION_REASON_INVALID_PROBLEM;
997 case glop::ProblemStatus::IMPRECISE:
998 return TERMINATION_REASON_NUMERICAL_ERROR;
999 case glop::ProblemStatus::PRIMAL_INFEASIBLE:
1000 case glop::ProblemStatus::DUAL_INFEASIBLE:
1001 case glop::ProblemStatus::INFEASIBLE_OR_UNBOUNDED:
1002 case glop::ProblemStatus::DUAL_UNBOUNDED:
1003 case glop::ProblemStatus::PRIMAL_UNBOUNDED:
1004 return TERMINATION_REASON_PRIMAL_OR_DUAL_INFEASIBLE;
1006 LOG(WARNING) <<
"Unexpected preprocessor status " << glop_status;
1007 return TERMINATION_REASON_OTHER;
1011 std::optional<TerminationReason> PreprocessSolver::ApplyPresolveIfEnabled(
1012 const PrimalDualHybridGradientParams& params,
1013 std::optional<PrimalAndDualSolution>*
const initial_solution) {
1014 const bool presolve_enabled = params.presolve_options().use_glop();
1015 if (!presolve_enabled) {
1016 return std::nullopt;
1020 <<
"Skipping presolve, which is only supported for linear programs";
1021 return std::nullopt;
1026 <<
"Skipping presolve because of error converting to MPModelProto: "
1028 return std::nullopt;
1030 if (initial_solution->has_value()) {
1031 LOG(WARNING) <<
"Ignoring initial solution. Initial solutions "
1032 "are ignored when presolve is on.";
1033 initial_solution->reset();
1035 glop::LinearProgram glop_lp;
1039 presolve_info_.emplace(std::move(sharded_qp_), params);
1043 presolve_info_->preprocessor.Run(&glop_lp);
1044 presolve_info_->presolved_problem_was_maximization =
1045 glop_lp.IsMaximizationProblem();
1046 MPModelProto output;
1049 absl::StatusOr<QuadraticProgram> presolved_qp =
1051 CHECK_OK(presolved_qp.status());
1056 presolved_qp->objective_scaling_factor = glop_lp.objective_scaling_factor();
1057 sharded_qp_ = ShardedQuadraticProgram(std::move(*presolved_qp), num_threads_,
1062 if (presolve_info_->preprocessor.status() != glop::ProblemStatus::INIT) {
1063 col_scaling_vec_ =
OnesVector(sharded_qp_.PrimalSharder());
1064 row_scaling_vec_ =
OnesVector(sharded_qp_.DualSharder());
1065 return GlopStatusToTerminationReason(presolve_info_->preprocessor.status());
1067 return std::nullopt;
1070 void PreprocessSolver::ComputeAndApplyRescaling(
1071 const PrimalDualHybridGradientParams& params,
1072 VectorXd& starting_primal_solution, VectorXd& starting_dual_solution) {
1074 RescalingOptions{.l_inf_ruiz_iterations = params.l_inf_ruiz_iterations(),
1075 .l2_norm_rescaling = params.l2_norm_rescaling()},
1077 row_scaling_vec_ = std::move(scaling.row_scaling_vec);
1078 col_scaling_vec_ = std::move(scaling.col_scaling_vec);
1081 starting_primal_solution);
1083 starting_dual_solution);
1086 void PreprocessSolver::LogQuadraticProgramStats(
1087 const QuadraticProgramStats& stats) {
1088 LOG(INFO) << absl::StrFormat(
1089 "There are %i variables, %i constraints, and %i ",
1090 stats.num_variables(), stats.num_constraints(),
1091 stats.constraint_matrix_num_nonzeros())
1092 <<
"constraint matrix nonzeros.";
1093 if (Qp().constraint_matrix.nonZeros() > 0) {
1094 LOG(INFO) <<
"Absolute values of nonzero constraint matrix elements: "
1095 << absl::StrFormat(
"largest=%f, smallest=%f, avg=%f",
1096 stats.constraint_matrix_abs_max(),
1097 stats.constraint_matrix_abs_min(),
1098 stats.constraint_matrix_abs_avg());
1099 LOG(INFO) <<
"Constraint matrix, infinity norm: "
1100 << absl::StrFormat(
"max(row & col)=%f, min_col=%f, min_row=%f",
1101 stats.constraint_matrix_abs_max(),
1102 stats.constraint_matrix_col_min_l_inf_norm(),
1103 stats.constraint_matrix_row_min_l_inf_norm());
1104 LOG(INFO) <<
"Constraint bounds statistics (max absolute value per row): "
1105 << absl::StrFormat(
"largest=%f, smallest=%f, avg=%f, l2_norm=%f",
1106 stats.combined_bounds_max(),
1107 stats.combined_bounds_min(),
1108 stats.combined_bounds_avg(),
1109 stats.combined_bounds_l2_norm());
1112 LOG(INFO) << absl::StrFormat(
1113 "There are %i nonzero diagonal coefficients in the objective matrix.",
1114 stats.objective_matrix_num_nonzeros());
1115 LOG(INFO) <<
"Absolute values of nonzero objective matrix elements: "
1116 << absl::StrFormat(
"largest=%f, smallest=%f, avg=%f",
1117 stats.objective_matrix_abs_max(),
1118 stats.objective_matrix_abs_min(),
1119 stats.objective_matrix_abs_avg());
1121 LOG(INFO) <<
"Absolute values of objective vector elements: "
1122 << absl::StrFormat(
"largest=%f, smallest=%f, avg=%f, l2_norm=%f",
1123 stats.objective_vector_abs_max(),
1124 stats.objective_vector_abs_min(),
1125 stats.objective_vector_abs_avg(),
1126 stats.objective_vector_l2_norm());
1128 LOG(INFO) <<
"Gaps between variable upper and lower bounds: "
1130 "#finite=%i of %i, largest=%f, smallest=%f, avg=%f",
1131 stats.variable_bound_gaps_num_finite(),
1132 stats.num_variables(), stats.variable_bound_gaps_max(),
1133 stats.variable_bound_gaps_min(),
1134 stats.variable_bound_gaps_avg());
1137 double PreprocessSolver::InitialPrimalWeight(
1138 const PrimalDualHybridGradientParams& params,
1139 const double l2_norm_primal_linear_objective,
1140 const double l2_norm_constraint_bounds)
const {
1141 if (params.has_initial_primal_weight()) {
1142 return params.initial_primal_weight();
1144 if (l2_norm_primal_linear_objective > 0.0 &&
1145 l2_norm_constraint_bounds > 0.0) {
1151 return l2_norm_primal_linear_objective / l2_norm_constraint_bounds;
1157 PrimalAndDualSolution PreprocessSolver::RecoverOriginalSolution(
1158 PrimalAndDualSolution working_solution)
const {
1159 glop::ProblemSolution glop_solution(glop::RowIndex{0}, glop::ColIndex{0});
1160 if (presolve_info_.has_value()) {
1167 working_solution.primal_solution);
1169 working_solution.dual_solution);
1170 if (presolve_info_.has_value()) {
1171 glop_solution.primal_values =
1173 working_solution.primal_solution.end());
1174 glop_solution.dual_values =
1176 working_solution.dual_solution.end());
1180 if (presolve_info_->presolved_problem_was_maximization) {
1181 for (glop::RowIndex i{0}; i < glop_solution.dual_values.size(); ++i) {
1182 glop_solution.dual_values[i] *= -1;
1185 presolve_info_->preprocessor.RecoverSolution(&glop_solution);
1186 PrimalAndDualSolution solution;
1187 solution.primal_solution =
1188 Eigen::Map<Eigen::VectorXd>(glop_solution.primal_values.data(),
1189 glop_solution.primal_values.size().value());
1190 solution.dual_solution =
1191 Eigen::Map<Eigen::VectorXd>(glop_solution.dual_values.data(),
1192 glop_solution.dual_values.size().value());
1198 solution.dual_solution /=
1199 presolve_info_->sharded_original_qp.Qp().objective_scaling_factor;
1203 solution.primal_solution);
1205 solution.dual_solution);
1208 return working_solution;
1212 void PreprocessSolver::AddConvergenceAndInfeasibilityInformation(
1213 const PrimalDualHybridGradientParams& params,
1214 const VectorXd& primal_solution,
const VectorXd& dual_solution,
1215 const ShardedQuadraticProgram& sharded_qp,
const VectorXd& col_scaling_vec,
1216 const VectorXd& row_scaling_vec, PointType candidate_type,
1217 IterationStats& stats)
const {
1218 const TerminationCriteria::DetailedOptimalityCriteria criteria =
1221 params, sharded_qp, col_scaling_vec, row_scaling_vec, primal_solution,
1223 EpsilonRatio(criteria.eps_optimal_primal_residual_absolute(),
1224 criteria.eps_optimal_primal_residual_relative()),
1225 EpsilonRatio(criteria.eps_optimal_dual_residual_absolute(),
1226 criteria.eps_optimal_dual_residual_relative()),
1229 params, sharded_qp, col_scaling_vec, row_scaling_vec, primal_solution,
1230 dual_solution, candidate_type);
1233 void SetActiveSetInformation(
const ShardedQuadraticProgram& sharded_qp,
1234 const VectorXd& primal_solution,
1235 const VectorXd& dual_solution,
1236 const VectorXd& primal_start_point,
1237 const VectorXd& dual_start_point,
1238 PointMetadata& metadata) {
1239 CHECK_EQ(primal_solution.size(), sharded_qp.PrimalSize());
1240 CHECK_EQ(dual_solution.size(), sharded_qp.DualSize());
1241 CHECK_EQ(primal_start_point.size(), sharded_qp.PrimalSize());
1242 CHECK_EQ(dual_start_point.size(), sharded_qp.DualSize());
1244 const QuadraticProgram& qp = sharded_qp.Qp();
1245 metadata.set_active_primal_variable_count(
1246 static_cast<int64_t
>(sharded_qp.PrimalSharder().ParallelSumOverShards(
1247 [&](
const Sharder::Shard& shard) {
1248 const auto primal_shard = shard(primal_solution);
1249 const auto lower_bound_shard = shard(qp.variable_lower_bounds);
1250 const auto upper_bound_shard = shard(qp.variable_upper_bounds);
1251 return (primal_shard.array() > lower_bound_shard.array() &&
1252 primal_shard.array() < upper_bound_shard.array())
1259 metadata.set_active_primal_variable_change(
1260 static_cast<int64_t
>(sharded_qp.PrimalSharder().ParallelSumOverShards(
1261 [&](
const Sharder::Shard& shard) {
1262 const auto primal_shard = shard(primal_solution);
1263 const auto primal_start_shard = shard(primal_start_point);
1264 const auto lower_bound_shard = shard(qp.variable_lower_bounds);
1265 const auto upper_bound_shard = shard(qp.variable_upper_bounds);
1266 return ((primal_shard.array() > lower_bound_shard.array() &&
1267 primal_shard.array() < upper_bound_shard.array()) !=
1268 (primal_start_shard.array() > lower_bound_shard.array() &&
1269 primal_start_shard.array() < upper_bound_shard.array()))
1273 metadata.set_active_dual_variable_count(
1274 static_cast<int64_t
>(sharded_qp.DualSharder().ParallelSumOverShards(
1275 [&](
const Sharder::Shard& shard) {
1276 const auto dual_shard = shard(dual_solution);
1277 const auto lower_bound_shard = shard(qp.constraint_lower_bounds);
1278 const auto upper_bound_shard = shard(qp.constraint_upper_bounds);
1279 const double kInfinity = std::numeric_limits<double>::infinity();
1280 return (dual_shard.array() != 0.0 ||
1281 (lower_bound_shard.array() == -kInfinity &&
1282 upper_bound_shard.array() == kInfinity))
1286 metadata.set_active_dual_variable_change(
1287 static_cast<int64_t
>(sharded_qp.DualSharder().ParallelSumOverShards(
1288 [&](
const Sharder::Shard& shard) {
1289 const auto dual_shard = shard(dual_solution);
1290 const auto dual_start_shard = shard(dual_start_point);
1291 const auto lower_bound_shard = shard(qp.constraint_lower_bounds);
1292 const auto upper_bound_shard = shard(qp.constraint_upper_bounds);
1293 const double kInfinity = std::numeric_limits<double>::infinity();
1294 return ((dual_shard.array() != 0.0 ||
1295 (lower_bound_shard.array() == -kInfinity &&
1296 upper_bound_shard.array() == kInfinity)) !=
1297 (dual_start_shard.array() != 0.0 ||
1298 (lower_bound_shard.array() == -kInfinity &&
1299 upper_bound_shard.array() == kInfinity)))
1304 void PreprocessSolver::AddPointMetadata(
1305 const PrimalDualHybridGradientParams& params,
1306 const VectorXd& primal_solution,
const VectorXd& dual_solution,
1307 PointType point_type,
const VectorXd& last_primal_start_point,
1308 const VectorXd& last_dual_start_point, IterationStats& stats)
const {
1309 PointMetadata metadata;
1310 metadata.set_point_type(point_type);
1311 std::vector<int> random_projection_seeds(
1312 params.random_projection_seeds().begin(),
1313 params.random_projection_seeds().end());
1315 random_projection_seeds, metadata);
1316 if (point_type != POINT_TYPE_ITERATE_DIFFERENCE) {
1317 SetActiveSetInformation(sharded_qp_, primal_solution, dual_solution,
1318 last_primal_start_point, last_dual_start_point,
1321 *stats.add_point_metadata() = metadata;
1324 std::optional<TerminationReasonAndPointType>
1325 PreprocessSolver::UpdateIterationStatsAndCheckTermination(
1326 const PrimalDualHybridGradientParams& params,
1327 bool force_numerical_termination,
const VectorXd& working_primal_current,
1328 const VectorXd& working_dual_current,
1329 const VectorXd* working_primal_average,
1330 const VectorXd* working_dual_average,
const VectorXd* working_primal_delta,
1331 const VectorXd* working_dual_delta,
const VectorXd& last_primal_start_point,
1332 const VectorXd& last_dual_start_point,
1333 const std::atomic<bool>* interrupt_solve, IterationStats& stats)
const {
1334 if (presolve_info_.has_value()) {
1336 PrimalAndDualSolution original_current =
1337 RecoverOriginalSolution({.primal_solution = working_primal_current,
1338 .dual_solution = working_dual_current});
1339 AddConvergenceAndInfeasibilityInformation(
1340 params, original_current.primal_solution,
1341 original_current.dual_solution, presolve_info_->sharded_original_qp,
1342 presolve_info_->trivial_col_scaling_vec,
1343 presolve_info_->trivial_row_scaling_vec, POINT_TYPE_CURRENT_ITERATE,
1346 if (working_primal_average !=
nullptr && working_dual_average !=
nullptr) {
1347 PrimalAndDualSolution original_average =
1348 RecoverOriginalSolution({.primal_solution = *working_primal_average,
1349 .dual_solution = *working_dual_average});
1350 AddConvergenceAndInfeasibilityInformation(
1351 params, original_average.primal_solution,
1352 original_average.dual_solution, presolve_info_->sharded_original_qp,
1353 presolve_info_->trivial_col_scaling_vec,
1354 presolve_info_->trivial_row_scaling_vec, POINT_TYPE_AVERAGE_ITERATE,
1358 AddConvergenceAndInfeasibilityInformation(
1359 params, working_primal_current, working_dual_current, sharded_qp_,
1360 col_scaling_vec_, row_scaling_vec_, POINT_TYPE_CURRENT_ITERATE, stats);
1361 if (working_primal_average !=
nullptr && working_dual_average !=
nullptr) {
1362 AddConvergenceAndInfeasibilityInformation(
1363 params, *working_primal_average, *working_dual_average, sharded_qp_,
1364 col_scaling_vec_, row_scaling_vec_, POINT_TYPE_AVERAGE_ITERATE,
1368 AddPointMetadata(params, working_primal_current, working_dual_current,
1369 POINT_TYPE_CURRENT_ITERATE, last_primal_start_point,
1370 last_dual_start_point, stats);
1371 if (working_primal_average !=
nullptr && working_dual_average !=
nullptr) {
1372 AddPointMetadata(params, *working_primal_average, *working_dual_average,
1373 POINT_TYPE_AVERAGE_ITERATE, last_primal_start_point,
1374 last_dual_start_point, stats);
1376 if (working_primal_delta !=
nullptr && working_dual_delta !=
nullptr) {
1377 if (presolve_info_.has_value()) {
1378 PrimalAndDualSolution original_delta =
1379 RecoverOriginalSolution({.primal_solution = *working_primal_delta,
1380 .dual_solution = *working_dual_delta});
1382 params, presolve_info_->sharded_original_qp,
1383 presolve_info_->trivial_col_scaling_vec,
1384 presolve_info_->trivial_row_scaling_vec,
1385 original_delta.primal_solution, original_delta.dual_solution,
1386 POINT_TYPE_ITERATE_DIFFERENCE);
1389 params, sharded_qp_, col_scaling_vec_, row_scaling_vec_,
1390 *working_primal_delta, *working_dual_delta,
1391 POINT_TYPE_ITERATE_DIFFERENCE);
1393 AddPointMetadata(params, *working_primal_delta, *working_dual_delta,
1394 POINT_TYPE_ITERATE_DIFFERENCE, last_primal_start_point,
1395 last_dual_start_point, stats);
1397 constexpr
int kLogEvery = 15;
1398 static std::atomic_int log_counter{0};
1399 if (params.verbosity_level() >= 4) {
1400 if (log_counter == 0) {
1401 LogInfoWithoutPrefix(absl::StrCat(
"I ", IterationStatsLabelString()));
1403 LogInfoWithoutPrefix(absl::StrCat(
1404 "A ",
ToString(stats, params.termination_criteria(),
1405 original_bound_norms_, POINT_TYPE_AVERAGE_ITERATE)));
1406 LogInfoWithoutPrefix(absl::StrCat(
1407 "C ",
ToString(stats, params.termination_criteria(),
1408 original_bound_norms_, POINT_TYPE_CURRENT_ITERATE)));
1409 }
else if (params.verbosity_level() >= 3) {
1410 if (log_counter == 0) {
1411 LogInfoWithoutPrefix(IterationStatsLabelString());
1413 LogInfoWithoutPrefix(
ToString(stats, params.termination_criteria(),
1414 original_bound_norms_,
1415 POINT_TYPE_AVERAGE_ITERATE));
1416 }
else if (params.verbosity_level() >= 2) {
1417 if (log_counter == 0) {
1418 LogInfoWithoutPrefix(IterationStatsLabelShortString());
1420 LogInfoWithoutPrefix(ToShortString(stats, params.termination_criteria(),
1421 original_bound_norms_,
1422 POINT_TYPE_AVERAGE_ITERATE));
1424 if (++log_counter >= kLogEvery) {
1427 if (iteration_stats_callback_ !=
nullptr) {
1428 iteration_stats_callback_(
1429 {.termination_criteria = params.termination_criteria(),
1430 .iteration_stats = stats,
1431 .bound_norms = original_bound_norms_});
1435 params.termination_criteria(), stats, original_bound_norms_,
1436 force_numerical_termination);
1437 termination.has_value()) {
1444 ConvergenceInformation
1445 PreprocessSolver::ComputeConvergenceInformationFromWorkingSolution(
1446 const PrimalDualHybridGradientParams& params,
1447 const VectorXd& working_primal,
const VectorXd& working_dual,
1448 PointType candidate_type)
const {
1449 const TerminationCriteria::DetailedOptimalityCriteria criteria =
1451 const double primal_epsilon_ratio =
1452 EpsilonRatio(criteria.eps_optimal_primal_residual_absolute(),
1453 criteria.eps_optimal_primal_residual_relative());
1454 const double dual_epsilon_ratio =
1455 EpsilonRatio(criteria.eps_optimal_dual_residual_absolute(),
1456 criteria.eps_optimal_dual_residual_relative());
1457 if (presolve_info_.has_value()) {
1458 PrimalAndDualSolution original = RecoverOriginalSolution(
1459 {.primal_solution = working_primal, .dual_solution = working_dual});
1461 params, presolve_info_->sharded_original_qp,
1462 presolve_info_->trivial_col_scaling_vec,
1463 presolve_info_->trivial_row_scaling_vec, original.primal_solution,
1464 original.dual_solution, primal_epsilon_ratio, dual_epsilon_ratio,
1468 params, sharded_qp_, col_scaling_vec_, row_scaling_vec_, working_primal,
1469 working_dual, primal_epsilon_ratio, dual_epsilon_ratio, candidate_type);
1475 SolverResult PreprocessSolver::ConstructOriginalSolverResult(
1476 const PrimalDualHybridGradientParams& params, SolverResult result)
const {
1477 const bool use_zero_primal_objective =
1478 result.solve_log.termination_reason() ==
1479 TERMINATION_REASON_PRIMAL_INFEASIBLE;
1480 if (presolve_info_.has_value()) {
1482 PrimalAndDualSolution original_solution = RecoverOriginalSolution(
1483 {.primal_solution = std::move(result.primal_solution),
1484 .dual_solution = std::move(result.dual_solution)});
1485 result.primal_solution = std::move(original_solution.primal_solution);
1486 result.dual_solution = std::move(original_solution.dual_solution);
1490 params, presolve_info_->sharded_original_qp, result.primal_solution,
1491 result.dual_solution, use_zero_primal_objective);
1493 result.reduced_costs =
1494 ReducedCosts(params, sharded_qp_, result.primal_solution,
1495 result.dual_solution, use_zero_primal_objective);
1498 result.primal_solution);
1500 result.dual_solution);
1502 col_scaling_vec_, sharded_qp_.PrimalSharder(), result.reduced_costs);
1504 if (iteration_stats_callback_ !=
nullptr) {
1505 iteration_stats_callback_(
1506 {.termination_criteria = params.termination_criteria(),
1507 .iteration_stats = result.solve_log.solution_stats(),
1508 .bound_norms = original_bound_norms_});
1511 if (params.verbosity_level() >= 1) {
1512 LOG(INFO) <<
"Termination reason: "
1513 << TerminationReason_Name(result.solve_log.termination_reason());
1514 LOG(INFO) <<
"Solution point type: "
1515 << PointType_Name(result.solve_log.solution_type());
1516 LOG(INFO) <<
"Final solution stats:";
1517 LOG(INFO) << IterationStatsLabelString();
1518 LOG(INFO) <<
ToString(result.solve_log.solution_stats(),
1519 params.termination_criteria(), original_bound_norms_,
1520 result.solve_log.solution_type());
1522 result.solve_log.solution_stats(), result.solve_log.solution_type());
1523 if (convergence_info.has_value()) {
1524 if (std::isfinite(convergence_info->corrected_dual_objective())) {
1525 LOG(INFO) <<
"Dual objective after infeasibility correction: "
1526 << convergence_info->corrected_dual_objective();
1533 Solver::Solver(
const PrimalDualHybridGradientParams& params,
1534 VectorXd starting_primal_solution,
1535 VectorXd starting_dual_solution,
const double initial_step_size,
1536 const double initial_primal_weight,
1537 const PreprocessSolver* preprocess_solver)
1539 current_primal_solution_(std::move(starting_primal_solution)),
1540 current_dual_solution_(std::move(starting_dual_solution)),
1541 primal_average_(&preprocess_solver->ShardedWorkingQp().PrimalSharder()),
1542 dual_average_(&preprocess_solver->ShardedWorkingQp().DualSharder()),
1543 step_size_(initial_step_size),
1544 primal_weight_(initial_primal_weight),
1545 preprocess_solver_(preprocess_solver) {}
1547 Solver::NextSolutionAndDelta Solver::ComputeNextPrimalSolution(
1548 double primal_step_size)
const {
1549 const int64_t primal_size = ShardedWorkingQp().PrimalSize();
1550 NextSolutionAndDelta result = {
1551 .value = VectorXd(primal_size),
1552 .delta = VectorXd(primal_size),
1554 const QuadraticProgram& qp = WorkingQp();
1563 ShardedWorkingQp().PrimalSharder().ParallelForEachShard(
1564 [&](
const Sharder::Shard& shard) {
1568 const VectorXd diagonal_scaling =
1570 shard(qp.objective_matrix->diagonal()).array() +
1572 shard(result.value) =
1573 (shard(current_primal_solution_) -
1575 (shard(qp.objective_vector) - shard(current_dual_product_)))
1577 .cwiseQuotient(diagonal_scaling)
1578 .cwiseMin(shard(qp.variable_upper_bounds))
1579 .cwiseMax(shard(qp.variable_lower_bounds));
1582 shard(result.value) =
1583 (shard(current_primal_solution_) -
1585 (shard(qp.objective_vector) - shard(current_dual_product_)))
1586 .cwiseMin(shard(qp.variable_upper_bounds))
1587 .cwiseMax(shard(qp.variable_lower_bounds));
1589 shard(result.delta) =
1590 shard(result.value) - shard(current_primal_solution_);
1595 Solver::NextSolutionAndDelta Solver::ComputeNextDualSolution(
1596 double dual_step_size,
double extrapolation_factor,
1597 const NextSolutionAndDelta& next_primal_solution)
const {
1598 const int64_t dual_size = ShardedWorkingQp().DualSize();
1599 NextSolutionAndDelta result = {
1600 .value = VectorXd(dual_size),
1601 .delta = VectorXd(dual_size),
1603 const QuadraticProgram& qp = WorkingQp();
1604 VectorXd extrapolated_primal(ShardedWorkingQp().PrimalSize());
1605 ShardedWorkingQp().PrimalSharder().ParallelForEachShard(
1606 [&](
const Sharder::Shard& shard) {
1607 shard(extrapolated_primal) =
1608 (shard(next_primal_solution.value) +
1609 extrapolation_factor * shard(next_primal_solution.delta));
1614 ShardedWorkingQp().TransposedConstraintMatrixSharder().ParallelForEachShard(
1615 [&](
const Sharder::Shard& shard) {
1617 shard(current_dual_solution_) -
1619 shard(ShardedWorkingQp().TransposedConstraintMatrix())
1621 extrapolated_primal;
1626 shard(result.value) =
1629 dual_step_size * shard(qp.constraint_upper_bounds))
1631 dual_step_size * shard(qp.constraint_lower_bounds));
1632 shard(result.delta) =
1633 (shard(result.value) - shard(current_dual_solution_));
1638 double Solver::ComputeMovement(
const VectorXd& delta_primal,
1639 const VectorXd& delta_dual)
const {
1640 const double primal_movement =
1641 (0.5 * primal_weight_) *
1642 SquaredNorm(delta_primal, ShardedWorkingQp().PrimalSharder());
1643 const double dual_movement =
1644 (0.5 / primal_weight_) *
1645 SquaredNorm(delta_dual, ShardedWorkingQp().DualSharder());
1646 return primal_movement + dual_movement;
1649 double Solver::ComputeNonlinearity(
const VectorXd& delta_primal,
1650 const VectorXd& next_dual_product)
const {
1653 return ShardedWorkingQp().PrimalSharder().ParallelSumOverShards(
1654 [&](
const Sharder::Shard& shard) {
1655 return -shard(delta_primal)
1656 .dot(shard(next_dual_product) -
1657 shard(current_dual_product_));
1661 IterationStats Solver::CreateSimpleIterationStats(
1662 RestartChoice restart_used)
const {
1663 IterationStats stats;
1664 double num_kkt_passes_per_rejected_step = 1.0;
1665 if (params_.linesearch_rule() ==
1666 PrimalDualHybridGradientParams::MALITSKY_POCK_LINESEARCH_RULE) {
1667 num_kkt_passes_per_rejected_step = 0.5;
1669 stats.set_iteration_number(iterations_completed_);
1670 stats.set_cumulative_rejected_steps(num_rejected_steps_);
1673 stats.set_cumulative_kkt_matrix_passes(iterations_completed_ +
1674 num_kkt_passes_per_rejected_step *
1675 num_rejected_steps_);
1676 stats.set_cumulative_time_sec(preprocess_solver_->GetElapsedTime());
1677 stats.set_restart_used(restart_used);
1678 stats.set_step_size(step_size_);
1679 stats.set_primal_weight(primal_weight_);
1683 double Solver::DistanceTraveledFromLastStart(
1684 const VectorXd& primal_solution,
const VectorXd& dual_solution)
const {
1685 return std::sqrt((0.5 * primal_weight_) *
1687 last_primal_start_point_,
1688 ShardedWorkingQp().PrimalSharder()) +
1689 (0.5 / primal_weight_) *
1691 ShardedWorkingQp().DualSharder()));
1694 LocalizedLagrangianBounds Solver::ComputeLocalizedBoundsAtCurrent()
const {
1695 const double distance_traveled_by_current = DistanceTraveledFromLastStart(
1696 current_primal_solution_, current_dual_solution_);
1698 ShardedWorkingQp(), current_primal_solution_, current_dual_solution_,
1699 PrimalDualNorm::kEuclideanNorm, primal_weight_,
1700 distance_traveled_by_current,
1701 nullptr, ¤t_dual_product_,
1702 params_.use_diagonal_qp_trust_region_solver(),
1703 params_.diagonal_qp_trust_region_solver_tolerance());
1706 LocalizedLagrangianBounds Solver::ComputeLocalizedBoundsAtAverage()
const {
1709 VectorXd average_primal = PrimalAverage();
1710 VectorXd average_dual = DualAverage();
1712 const double distance_traveled_by_average =
1713 DistanceTraveledFromLastStart(average_primal, average_dual);
1716 ShardedWorkingQp(), average_primal, average_dual,
1717 PrimalDualNorm::kEuclideanNorm, primal_weight_,
1718 distance_traveled_by_average,
1720 params_.use_diagonal_qp_trust_region_solver(),
1721 params_.diagonal_qp_trust_region_solver_tolerance());
1724 bool AverageHasBetterPotential(
1725 const LocalizedLagrangianBounds& local_bounds_at_average,
1726 const LocalizedLagrangianBounds& local_bounds_at_current) {
1727 return BoundGap(local_bounds_at_average) /
1729 BoundGap(local_bounds_at_current) /
1733 double NormalizedGap(
1734 const LocalizedLagrangianBounds& local_bounds_at_candidate) {
1735 const double distance_traveled_by_candidate =
1736 local_bounds_at_candidate.radius;
1737 return BoundGap(local_bounds_at_candidate) / distance_traveled_by_candidate;
1741 bool Solver::ShouldDoAdaptiveRestartHeuristic(
1742 double candidate_normalized_gap)
const {
1743 const double gap_reduction_ratio =
1744 candidate_normalized_gap / normalized_gap_at_last_restart_;
1745 if (gap_reduction_ratio < params_.sufficient_reduction_for_restart()) {
1748 if (gap_reduction_ratio < params_.necessary_reduction_for_restart() &&
1749 candidate_normalized_gap > normalized_gap_at_last_trial_) {
1757 RestartChoice Solver::DetermineDistanceBasedRestartChoice()
const {
1759 if (primal_average_.NumTerms() == 0) {
1760 return RESTART_CHOICE_NO_RESTART;
1761 }
else if (distance_based_restart_info_.length_of_last_restart_period == 0) {
1762 return RESTART_CHOICE_RESTART_TO_AVERAGE;
1764 const int restart_period_length = primal_average_.NumTerms();
1765 const double distance_moved_this_restart_period_by_average =
1766 DistanceTraveledFromLastStart(primal_average_.ComputeAverage(),
1767 dual_average_.ComputeAverage());
1769 distance_based_restart_info_.distance_moved_last_restart_period;
1775 if ((distance_moved_this_restart_period_by_average / restart_period_length) <
1776 params_.sufficient_reduction_for_restart() *
1778 distance_based_restart_info_.length_of_last_restart_period)) {
1781 if (AverageHasBetterPotential(ComputeLocalizedBoundsAtAverage(),
1782 ComputeLocalizedBoundsAtCurrent())) {
1783 return RESTART_CHOICE_RESTART_TO_AVERAGE;
1785 return RESTART_CHOICE_WEIGHTED_AVERAGE_RESET;
1788 return RESTART_CHOICE_NO_RESTART;
1792 RestartChoice Solver::ChooseRestartToApply(
const bool is_major_iteration) {
1793 if (!primal_average_.HasNonzeroWeight() &&
1794 !dual_average_.HasNonzeroWeight()) {
1795 return RESTART_CHOICE_NO_RESTART;
1803 const int restart_length = primal_average_.NumTerms();
1804 if (restart_length >= iterations_completed_ / 2 &&
1805 params_.restart_strategy() ==
1806 PrimalDualHybridGradientParams::ADAPTIVE_HEURISTIC) {
1807 if (AverageHasBetterPotential(ComputeLocalizedBoundsAtAverage(),
1808 ComputeLocalizedBoundsAtCurrent())) {
1809 return RESTART_CHOICE_RESTART_TO_AVERAGE;
1811 return RESTART_CHOICE_WEIGHTED_AVERAGE_RESET;
1814 if (is_major_iteration) {
1815 switch (params_.restart_strategy()) {
1816 case PrimalDualHybridGradientParams::NO_RESTARTS:
1817 return RESTART_CHOICE_WEIGHTED_AVERAGE_RESET;
1818 case PrimalDualHybridGradientParams::EVERY_MAJOR_ITERATION:
1819 return RESTART_CHOICE_RESTART_TO_AVERAGE;
1820 case PrimalDualHybridGradientParams::ADAPTIVE_HEURISTIC: {
1821 const LocalizedLagrangianBounds local_bounds_at_average =
1822 ComputeLocalizedBoundsAtAverage();
1823 const LocalizedLagrangianBounds local_bounds_at_current =
1824 ComputeLocalizedBoundsAtCurrent();
1825 double normalized_gap;
1826 RestartChoice choice;
1827 if (AverageHasBetterPotential(local_bounds_at_average,
1828 local_bounds_at_current)) {
1829 normalized_gap = NormalizedGap(local_bounds_at_average);
1830 choice = RESTART_CHOICE_RESTART_TO_AVERAGE;
1832 normalized_gap = NormalizedGap(local_bounds_at_current);
1833 choice = RESTART_CHOICE_WEIGHTED_AVERAGE_RESET;
1835 if (ShouldDoAdaptiveRestartHeuristic(normalized_gap)) {
1838 normalized_gap_at_last_trial_ = normalized_gap;
1839 return RESTART_CHOICE_NO_RESTART;
1842 case PrimalDualHybridGradientParams::ADAPTIVE_DISTANCE_BASED: {
1843 return DetermineDistanceBasedRestartChoice();
1846 LOG(FATAL) <<
"Unrecognized restart_strategy "
1847 << params_.restart_strategy();
1848 return RESTART_CHOICE_UNSPECIFIED;
1851 return RESTART_CHOICE_NO_RESTART;
1855 VectorXd Solver::PrimalAverage()
const {
1856 if (primal_average_.HasNonzeroWeight()) {
1857 return primal_average_.ComputeAverage();
1859 return current_primal_solution_;
1863 VectorXd Solver::DualAverage()
const {
1864 if (dual_average_.HasNonzeroWeight()) {
1865 return dual_average_.ComputeAverage();
1867 return current_dual_solution_;
1871 double Solver::ComputeNewPrimalWeight()
const {
1872 const double primal_distance =
1873 Distance(current_primal_solution_, last_primal_start_point_,
1874 ShardedWorkingQp().PrimalSharder());
1875 const double dual_distance =
1876 Distance(current_dual_solution_, last_dual_start_point_,
1877 ShardedWorkingQp().DualSharder());
1882 constexpr
double kNonzeroTol = 1.0e-10;
1883 if (primal_distance <= kNonzeroTol || primal_distance >= 1.0 / kNonzeroTol ||
1884 dual_distance <= kNonzeroTol || dual_distance >= 1.0 / kNonzeroTol) {
1885 return primal_weight_;
1887 const double smoothing_param = params_.primal_weight_update_smoothing();
1888 const double unsmoothed_new_primal_weight = dual_distance / primal_distance;
1889 const double new_primal_weight =
1890 std::exp(smoothing_param * std::log(unsmoothed_new_primal_weight) +
1891 (1.0 - smoothing_param) * std::log(primal_weight_));
1892 LOG_IF(INFO, params_.verbosity_level() >= 4)
1893 <<
"New computed primal weight is " << new_primal_weight
1894 <<
" at iteration " << iterations_completed_;
1895 return new_primal_weight;
1898 SolverResult Solver::PickSolutionAndConstructSolverResult(
1899 VectorXd primal_solution, VectorXd dual_solution,
1901 PointType output_type, SolveLog solve_log)
const {
1902 switch (output_type) {
1903 case POINT_TYPE_CURRENT_ITERATE:
1904 AssignVector(current_primal_solution_, ShardedWorkingQp().PrimalSharder(),
1906 AssignVector(current_dual_solution_, ShardedWorkingQp().DualSharder(),
1909 case POINT_TYPE_ITERATE_DIFFERENCE:
1910 AssignVector(current_primal_delta_, ShardedWorkingQp().PrimalSharder(),
1912 AssignVector(current_dual_delta_, ShardedWorkingQp().DualSharder(),
1915 case POINT_TYPE_AVERAGE_ITERATE:
1916 case POINT_TYPE_PRESOLVER_SOLUTION:
1920 output_type = POINT_TYPE_AVERAGE_ITERATE;
1923 return ConstructSolverResult(
1924 std::move(primal_solution), std::move(dual_solution), stats,
1925 termination_reason, output_type, std::move(solve_log));
1928 void Solver::ApplyRestartChoice(
const RestartChoice restart_to_apply) {
1929 switch (restart_to_apply) {
1930 case RESTART_CHOICE_UNSPECIFIED:
1931 case RESTART_CHOICE_NO_RESTART:
1933 case RESTART_CHOICE_WEIGHTED_AVERAGE_RESET:
1934 LOG_IF(INFO, params_.verbosity_level() >= 4)
1935 <<
"Restarted to current on iteration " << iterations_completed_
1936 <<
" after " << primal_average_.NumTerms() <<
" iterations";
1938 case RESTART_CHOICE_RESTART_TO_AVERAGE:
1939 LOG_IF(INFO, params_.verbosity_level() >= 4)
1940 <<
"Restarted to average on iteration " << iterations_completed_
1941 <<
" after " << primal_average_.NumTerms() <<
" iterations";
1942 current_primal_solution_ = primal_average_.ComputeAverage();
1943 current_dual_solution_ = dual_average_.ComputeAverage();
1945 WorkingQp().constraint_matrix, current_dual_solution_,
1946 ShardedWorkingQp().ConstraintMatrixSharder());
1949 primal_weight_ = ComputeNewPrimalWeight();
1950 ratio_last_two_step_sizes_ = 1;
1951 if (params_.restart_strategy() ==
1952 PrimalDualHybridGradientParams::ADAPTIVE_HEURISTIC) {
1955 const LocalizedLagrangianBounds local_bounds_at_last_restart =
1956 ComputeLocalizedBoundsAtCurrent();
1957 const double distance_traveled_since_last_restart =
1958 local_bounds_at_last_restart.radius;
1959 normalized_gap_at_last_restart_ =
BoundGap(local_bounds_at_last_restart) /
1960 distance_traveled_since_last_restart;
1961 normalized_gap_at_last_trial_ = std::numeric_limits<double>::infinity();
1962 }
else if (params_.restart_strategy() ==
1963 PrimalDualHybridGradientParams::ADAPTIVE_DISTANCE_BASED) {
1965 distance_based_restart_info_ = {
1966 .distance_moved_last_restart_period = DistanceTraveledFromLastStart(
1967 current_primal_solution_, current_dual_solution_),
1968 .length_of_last_restart_period = primal_average_.NumTerms()};
1970 primal_average_.Clear();
1971 dual_average_.Clear();
1972 AssignVector(current_primal_solution_, ShardedWorkingQp().PrimalSharder(),
1973 last_primal_start_point_);
1974 AssignVector(current_dual_solution_, ShardedWorkingQp().DualSharder(),
1975 last_dual_start_point_);
1978 std::optional<SolverResult> Solver::MajorIterationAndTerminationCheck(
1979 bool force_numerical_termination,
const std::atomic<bool>* interrupt_solve,
1980 SolveLog& solve_log) {
1981 const int major_iteration_cycle =
1982 iterations_completed_ % params_.major_iteration_frequency();
1983 const bool is_major_iteration =
1984 major_iteration_cycle == 0 && iterations_completed_ > 0;
1987 const RestartChoice restart = force_numerical_termination
1988 ? RESTART_CHOICE_NO_RESTART
1989 : ChooseRestartToApply(is_major_iteration);
1990 IterationStats stats = CreateSimpleIterationStats(restart);
1991 const bool check_termination =
1992 major_iteration_cycle % params_.termination_check_frequency() == 0 ||
1996 force_numerical_termination;
1998 DCHECK(!is_major_iteration || check_termination);
1999 if (check_termination) {
2003 VectorXd primal_average = PrimalAverage();
2004 VectorXd dual_average = DualAverage();
2006 const std::optional<TerminationReasonAndPointType>
2007 maybe_termination_reason =
2008 preprocess_solver_->UpdateIterationStatsAndCheckTermination(
2009 params_, force_numerical_termination, current_primal_solution_,
2010 current_dual_solution_,
2011 primal_average_.HasNonzeroWeight() ? &primal_average :
nullptr,
2012 dual_average_.HasNonzeroWeight() ? &dual_average :
nullptr,
2013 current_primal_delta_.size() > 0 ? ¤t_primal_delta_
2015 current_dual_delta_.size() > 0 ? ¤t_dual_delta_ :
nullptr,
2016 last_primal_start_point_, last_dual_start_point_,
2017 interrupt_solve, stats);
2018 if (params_.record_iteration_stats()) {
2019 *solve_log.add_iteration_stats() = stats;
2022 if (maybe_termination_reason.has_value()) {
2023 return PickSolutionAndConstructSolverResult(
2024 std::move(primal_average), std::move(dual_average), stats,
2025 maybe_termination_reason->reason, maybe_termination_reason->type,
2026 std::move(solve_log));
2028 }
else if (params_.record_iteration_stats()) {
2030 *solve_log.add_iteration_stats() = stats;
2032 ApplyRestartChoice(restart);
2033 return std::nullopt;
2036 void Solver::ResetAverageToCurrent() {
2037 primal_average_.Clear();
2038 dual_average_.Clear();
2039 primal_average_.Add(current_primal_solution_, 1.0);
2040 dual_average_.Add(current_dual_solution_, 1.0);
2043 void Solver::LogNumericalTermination()
const {
2044 LOG(WARNING) <<
"Forced numerical termination at iteration "
2045 << iterations_completed_;
2048 void Solver::LogInnerIterationLimitHit()
const {
2049 LOG(WARNING) <<
"Inner iteration limit reached at iteration "
2050 << iterations_completed_;
2053 InnerStepOutcome Solver::TakeMalitskyPockStep() {
2054 InnerStepOutcome outcome = InnerStepOutcome::kSuccessful;
2055 const double primal_step_size = step_size_ / primal_weight_;
2056 NextSolutionAndDelta next_primal_solution =
2057 ComputeNextPrimalSolution(primal_step_size);
2062 double dilating_coeff =
2063 1 + (params_.malitsky_pock_parameters().step_size_interpolation() *
2064 (sqrt(1 + ratio_last_two_step_sizes_) - 1));
2065 double new_primal_step_size = primal_step_size * dilating_coeff;
2066 double step_size_downscaling =
2067 params_.malitsky_pock_parameters().step_size_downscaling_factor();
2068 double contraction_factor =
2069 params_.malitsky_pock_parameters().linesearch_contraction_factor();
2070 const double dual_weight = primal_weight_ * primal_weight_;
2071 int inner_iterations = 0;
2072 for (
bool accepted_step =
false; !accepted_step; ++inner_iterations) {
2073 if (inner_iterations >= 60) {
2074 LogInnerIterationLimitHit();
2075 ResetAverageToCurrent();
2076 outcome = InnerStepOutcome::kForceNumericalTermination;
2079 const double new_last_two_step_sizes_ratio =
2080 new_primal_step_size / primal_step_size;
2081 NextSolutionAndDelta next_dual_solution = ComputeNextDualSolution(
2082 dual_weight * new_primal_step_size, new_last_two_step_sizes_ratio,
2083 next_primal_solution);
2086 WorkingQp().constraint_matrix, next_dual_solution.value,
2087 ShardedWorkingQp().ConstraintMatrixSharder());
2088 double delta_dual_norm =
2089 Norm(next_dual_solution.delta, ShardedWorkingQp().DualSharder());
2090 double delta_dual_prod_norm =
2091 Distance(current_dual_product_, next_dual_product,
2092 ShardedWorkingQp().PrimalSharder());
2093 if (primal_weight_ * new_primal_step_size * delta_dual_prod_norm <=
2094 contraction_factor * delta_dual_norm) {
2096 step_size_ = new_primal_step_size * primal_weight_;
2097 ratio_last_two_step_sizes_ = new_last_two_step_sizes_ratio;
2102 if (!primal_average_.HasNonzeroWeight()) {
2103 primal_average_.Add(
2104 current_primal_solution_,
2105 new_primal_step_size * new_last_two_step_sizes_ratio);
2108 current_primal_solution_ = std::move(next_primal_solution.value);
2109 current_dual_solution_ = std::move(next_dual_solution.value);
2110 current_dual_product_ = std::move(next_dual_product);
2111 primal_average_.Add(current_primal_solution_,
2112 new_primal_step_size);
2113 dual_average_.Add(current_dual_solution_,
2114 new_primal_step_size);
2115 const double movement =
2116 ComputeMovement(next_primal_solution.delta, next_dual_solution.delta);
2117 if (movement == 0.0) {
2118 LogNumericalTermination();
2119 ResetAverageToCurrent();
2120 outcome = InnerStepOutcome::kForceNumericalTermination;
2121 }
else if (movement > kDivergentMovement) {
2122 LogNumericalTermination();
2123 outcome = InnerStepOutcome::kForceNumericalTermination;
2125 current_primal_delta_ = std::move(next_primal_solution.delta);
2126 current_dual_delta_ = std::move(next_dual_solution.delta);
2129 new_primal_step_size = step_size_downscaling * new_primal_step_size;
2133 num_rejected_steps_ += inner_iterations;
2137 InnerStepOutcome Solver::TakeAdaptiveStep() {
2138 bool force_numerical_termination =
false;
2139 for (
bool accepted_step =
false; !accepted_step;) {
2140 const double primal_step_size = step_size_ / primal_weight_;
2141 const double dual_step_size = step_size_ * primal_weight_;
2142 NextSolutionAndDelta next_primal_solution =
2143 ComputeNextPrimalSolution(primal_step_size);
2144 NextSolutionAndDelta next_dual_solution = ComputeNextDualSolution(
2145 dual_step_size, 1.0, next_primal_solution);
2146 const double movement =
2147 ComputeMovement(next_primal_solution.delta, next_dual_solution.delta);
2148 if (movement == 0.0) {
2149 LogNumericalTermination();
2150 ResetAverageToCurrent();
2151 force_numerical_termination =
true;
2153 }
else if (movement > kDivergentMovement) {
2154 LogNumericalTermination();
2155 force_numerical_termination =
true;
2159 WorkingQp().constraint_matrix, next_dual_solution.value,
2160 ShardedWorkingQp().ConstraintMatrixSharder());
2161 const double nonlinearity =
2162 ComputeNonlinearity(next_primal_solution.delta, next_dual_product);
2165 const double step_size_limit =
2166 nonlinearity > 0 ? movement / nonlinearity
2167 : std::numeric_limits<double>::infinity();
2169 if (step_size_ <= step_size_limit) {
2170 current_primal_solution_ = std::move(next_primal_solution.value);
2171 current_dual_solution_ = std::move(next_dual_solution.value);
2172 current_dual_product_ = std::move(next_dual_product);
2173 current_primal_delta_ = std::move(next_primal_solution.delta);
2174 current_dual_delta_ = std::move(next_dual_solution.delta);
2175 primal_average_.Add(current_primal_solution_, step_size_);
2176 dual_average_.Add(current_dual_solution_, step_size_);
2177 accepted_step =
true;
2179 const double total_steps_attempted =
2180 num_rejected_steps_ + iterations_completed_ + 1;
2184 const double first_term =
2185 (1 - std::pow(total_steps_attempted + 1.0,
2186 -params_.adaptive_linesearch_parameters()
2187 .step_size_reduction_exponent())) *
2189 const double second_term =
2190 (1 + std::pow(total_steps_attempted + 1.0,
2191 -params_.adaptive_linesearch_parameters()
2192 .step_size_growth_exponent())) *
2203 step_size_ =
std::min(first_term, second_term);
2204 if (!accepted_step) {
2205 ++num_rejected_steps_;
2208 if (force_numerical_termination) {
2209 return InnerStepOutcome::kForceNumericalTermination;
2211 return InnerStepOutcome::kSuccessful;
2214 InnerStepOutcome Solver::TakeConstantSizeStep() {
2215 const double primal_step_size = step_size_ / primal_weight_;
2216 const double dual_step_size = step_size_ * primal_weight_;
2217 NextSolutionAndDelta next_primal_solution =
2218 ComputeNextPrimalSolution(primal_step_size);
2219 NextSolutionAndDelta next_dual_solution = ComputeNextDualSolution(
2220 dual_step_size, 1.0, next_primal_solution);
2221 const double movement =
2222 ComputeMovement(next_primal_solution.delta, next_dual_solution.delta);
2223 if (movement == 0.0) {
2224 LogNumericalTermination();
2225 ResetAverageToCurrent();
2226 return InnerStepOutcome::kForceNumericalTermination;
2227 }
else if (movement > kDivergentMovement) {
2228 LogNumericalTermination();
2229 return InnerStepOutcome::kForceNumericalTermination;
2232 WorkingQp().constraint_matrix, next_dual_solution.value,
2233 ShardedWorkingQp().ConstraintMatrixSharder());
2234 current_primal_solution_ = std::move(next_primal_solution.value);
2235 current_dual_solution_ = std::move(next_dual_solution.value);
2236 current_dual_product_ = std::move(next_dual_product);
2237 current_primal_delta_ = std::move(next_primal_solution.delta);
2238 current_dual_delta_ = std::move(next_dual_solution.delta);
2239 primal_average_.Add(current_primal_solution_, step_size_);
2240 dual_average_.Add(current_dual_solution_, step_size_);
2241 return InnerStepOutcome::kSuccessful;
2244 SolverResult
Solver::Solve(
const std::atomic<bool>* interrupt_solve,
2245 SolveLog solve_log) {
2246 last_primal_start_point_ =
2247 CloneVector(current_primal_solution_, ShardedWorkingQp().PrimalSharder());
2248 last_dual_start_point_ =
2249 CloneVector(current_dual_solution_, ShardedWorkingQp().DualSharder());
2253 ratio_last_two_step_sizes_ = 1;
2255 WorkingQp().constraint_matrix, current_dual_solution_,
2256 ShardedWorkingQp().ConstraintMatrixSharder());
2260 bool force_numerical_termination =
false;
2262 num_rejected_steps_ = 0;
2264 for (iterations_completed_ = 0;; ++iterations_completed_) {
2268 const std::optional<SolverResult> maybe_result =
2269 MajorIterationAndTerminationCheck(force_numerical_termination,
2270 interrupt_solve, solve_log);
2271 if (maybe_result.has_value()) {
2272 return maybe_result.value();
2279 InnerStepOutcome outcome;
2280 switch (params_.linesearch_rule()) {
2281 case PrimalDualHybridGradientParams::MALITSKY_POCK_LINESEARCH_RULE:
2282 outcome = TakeMalitskyPockStep();
2284 case PrimalDualHybridGradientParams::ADAPTIVE_LINESEARCH_RULE:
2285 outcome = TakeAdaptiveStep();
2287 case PrimalDualHybridGradientParams::CONSTANT_STEP_SIZE_RULE:
2288 outcome = TakeConstantSizeStep();
2291 LOG(FATAL) <<
"Unrecognized linesearch rule "
2292 << params_.linesearch_rule();
2294 if (outcome == InnerStepOutcome::kForceNumericalTermination) {
2295 force_numerical_termination =
true;
2304 const std::atomic<bool>* interrupt_solve,
2305 IterationStatsCallback iteration_stats_callback) {
2308 std::move(iteration_stats_callback));
2313 std::optional<PrimalAndDualSolution> initial_solution,
2314 const std::atomic<bool>* interrupt_solve,
2315 IterationStatsCallback iteration_stats_callback) {
2316 const absl::Status params_status =
2318 if (!params_status.ok()) {
2319 return ErrorSolverResult(TERMINATION_REASON_INVALID_PARAMETER,
2320 params_status.ToString());
2323 return ErrorSolverResult(TERMINATION_REASON_INVALID_PROBLEM,
2324 "constraint_matrix must be in compressed format. "
2325 "Call constraint_matrix.makeCompressed()");
2328 if (!dimensions_status.ok()) {
2329 return ErrorSolverResult(TERMINATION_REASON_INVALID_PROBLEM,
2330 dimensions_status.ToString());
2333 return ErrorSolverResult(TERMINATION_REASON_INVALID_PROBLEM,
2334 "The input problem has inconsistent bounds.");
2337 return ErrorSolverResult(TERMINATION_REASON_INVALID_PROBLEM,
2338 "The objective scaling factor cannot be zero.");
2340 PreprocessSolver solver(std::move(qp), params);
2341 return solver.PreprocessAndSolve(params, std::move(initial_solution),
2343 std::move(iteration_stats_callback));
2355 glop_solution.
status = glop::ProblemStatus::IMPRECISE;
2356 for (glop::RowIndex i{0}; i.value() < solution.
dual_solution.size(); ++i) {
2360 glop::ConstraintStatus::FIXED_VALUE;
2363 glop::ConstraintStatus::AT_LOWER_BOUND;
2366 glop::ConstraintStatus::AT_UPPER_BOUND;
2372 for (glop::ColIndex i{0}; i.value() < solution.
primal_solution.size(); ++i) {
2384 glop::VariableStatus::AT_LOWER_BOUND;
2389 glop::VariableStatus::AT_UPPER_BOUND;
2395 return glop_solution;
Fractional Square(Fractional f)
Fractional SquaredNorm(const SparseColumn &v)
void MPModelProtoToLinearProgram(const MPModelProto &input, LinearProgram *output)
StrictITIVector< ColIndex, Fractional > DenseRow
void LinearProgramToMPModelProto(const LinearProgram &input, MPModelProto *output)
StrictITIVector< RowIndex, Fractional > DenseColumn
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
glop::ProblemSolution ComputeStatuses(const QuadraticProgram &qp, const PrimalAndDualSolution &solution)
absl::StatusOr< QuadraticProgram > QpFromMpModelProto(const MPModelProto &proto, bool relax_integer_variables, bool include_names)
double EpsilonRatio(const double epsilon_absolute, const double epsilon_relative)
void SetZero(const Sharder &sharder, VectorXd &dest)
absl::Status ValidateQuadraticProgramDimensions(const QuadraticProgram &qp)
double SquaredDistance(const VectorXd &vector1, const VectorXd &vector2, const Sharder &sharder)
double LInfNorm(const VectorXd &vector, const Sharder &sharder)
double Distance(const VectorXd &vector1, const VectorXd &vector2, 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)
SolverResult PrimalDualHybridGradient(QuadraticProgram qp, const PrimalDualHybridGradientParams ¶ms, std::optional< PrimalAndDualSolution > initial_solution, const std::atomic< bool > *interrupt_solve, IterationStatsCallback iteration_stats_callback)
SingularValueAndIterations EstimateMaximumSingularValueOfConstraintMatrix(const ShardedQuadraticProgram &sharded_qp, const std::optional< VectorXd > &primal_solution, const std::optional< VectorXd > &dual_solution, const double desired_relative_error, const double failure_probability, std::mt19937 &mt_generator)
TerminationCriteria::DetailedOptimalityCriteria EffectiveOptimalityCriteria(const TerminationCriteria &termination_criteria)
bool HasValidBounds(const QuadraticProgram &qp)
absl::StatusOr< MPModelProto > QpToMpModelProto(const QuadraticProgram &qp)
std::optional< TerminationReasonAndPointType > CheckIterateTerminationCriteria(const TerminationCriteria &criteria, const IterationStats &stats, const QuadraticProgramBoundNorms &bound_norms, const bool force_numerical_termination)
RelativeConvergenceInformation ComputeRelativeResiduals(const TerminationCriteria::DetailedOptimalityCriteria &optimality_criteria, const ConvergenceInformation &stats, const QuadraticProgramBoundNorms &bound_norms)
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)
void ProjectToDualVariableBounds(const ShardedQuadraticProgram &sharded_qp, VectorXd &dual)
void CoefficientWiseProductInPlace(const VectorXd &scale, const Sharder &sharder, VectorXd &dest)
void CoefficientWiseQuotientInPlace(const VectorXd &scale, const Sharder &sharder, VectorXd &dest)
std::optional< TerminationReasonAndPointType > CheckSimpleTerminationCriteria(const TerminationCriteria &criteria, const IterationStats &stats, const std::atomic< bool > *interrupt_solve)
VectorXd CloneVector(const VectorXd &vec, const Sharder &sharder)
ScalingVectors ApplyRescaling(const RescalingOptions &rescaling_options, ShardedQuadraticProgram &sharded_qp)
QuadraticProgramStats ComputeStats(const ShardedQuadraticProgram &qp, const double infinite_constraint_bound_threshold)
std::optional< ConvergenceInformation > GetConvergenceInformation(const IterationStats &stats, PointType candidate_type)
QuadraticProgramBoundNorms BoundNormsFromProblemStats(const QuadraticProgramStats &stats)
void ProjectToPrimalVariableBounds(const ShardedQuadraticProgram &sharded_qp, VectorXd &primal)
LocalizedLagrangianBounds ComputeLocalizedLagrangianBounds(const ShardedQuadraticProgram &sharded_qp, const VectorXd &primal_solution, const VectorXd &dual_solution, const PrimalDualNorm primal_dual_norm, const double primal_weight, const double radius, const VectorXd *primal_product, const VectorXd *dual_product, const bool use_diagonal_qp_trust_region_solver, const double diagonal_qp_trust_region_solver_tolerance)
double Norm(const VectorXd &vector, const Sharder &sharder)
VectorXd ZeroVector(const Sharder &sharder)
void AssignVector(const VectorXd &vec, const Sharder &sharder, VectorXd &dest)
VectorXd OnesVector(const Sharder &sharder)
double BoundGap(const LocalizedLagrangianBounds &bounds)
absl::Status ValidatePrimalDualHybridGradientParams(const PrimalDualHybridGradientParams ¶ms)
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)
const absl::string_view ToString(MPSolver::OptimizationProblemType optimization_problem_type)
bool presolved_problem_was_maximization
glop::MainLpPreprocessor preprocessor
const VectorXd trivial_row_scaling_vec
ShardedQuadraticProgram sharded_original_qp
const VectorXd trivial_col_scaling_vec
double distance_moved_last_restart_period
int length_of_last_restart_period
glop::GlopParameters preprocessor_parameters
VariableStatusRow variable_statuses
ConstraintStatusColumn constraint_statuses
Eigen::VectorXd dual_solution
Eigen::VectorXd primal_solution
Eigen::VectorXd variable_upper_bounds
Eigen::VectorXd variable_lower_bounds
double objective_scaling_factor
Eigen::VectorXd constraint_lower_bounds
Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > constraint_matrix
Eigen::VectorXd constraint_upper_bounds