OR-Tools  9.6
termination.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
15 
16 #include <atomic>
17 #include <cmath>
18 #include <limits>
19 #include <optional>
20 
21 #include "ortools/base/logging.h"
22 #include "ortools/pdlp/solve_log.pb.h"
23 #include "ortools/pdlp/solvers.pb.h"
24 
25 namespace operations_research::pdlp {
26 
28  const TerminationCriteria::DetailedOptimalityCriteria& optimality_criteria,
29  const ConvergenceInformation& stats) {
30  if (std::isinf(optimality_criteria.eps_optimal_objective_gap_absolute()) ||
31  std::isinf(optimality_criteria.eps_optimal_objective_gap_relative())) {
32  return true;
33  }
34  const double abs_obj =
35  std::abs(stats.primal_objective()) + std::abs(stats.dual_objective());
36  const double gap =
37  std::abs(stats.primal_objective() - stats.dual_objective());
38  return std::isfinite(abs_obj) &&
39  gap <= optimality_criteria.eps_optimal_objective_gap_absolute() +
40  optimality_criteria.eps_optimal_objective_gap_relative() *
41  abs_obj;
42 }
43 
45  const TerminationCriteria::DetailedOptimalityCriteria& optimality_criteria,
46  const ConvergenceInformation& stats, const OptimalityNorm optimality_norm,
47  const QuadraticProgramBoundNorms& bound_norms) {
48  double primal_err;
49  double primal_err_baseline;
50  double dual_err;
51  double dual_err_baseline;
52  double primal_absolute_epsilon =
53  optimality_criteria.eps_optimal_primal_residual_absolute();
54  double dual_absolute_epsilon =
55  optimality_criteria.eps_optimal_dual_residual_absolute();
56 
57  switch (optimality_norm) {
58  case OPTIMALITY_NORM_L_INF:
59  primal_err = stats.l_inf_primal_residual();
60  primal_err_baseline = bound_norms.l_inf_norm_constraint_bounds;
61  dual_err = stats.l_inf_dual_residual();
62  dual_err_baseline = bound_norms.l_inf_norm_primal_linear_objective;
63  break;
64  case OPTIMALITY_NORM_L2:
65  primal_err = stats.l2_primal_residual();
66  primal_err_baseline = bound_norms.l2_norm_constraint_bounds;
67  dual_err = stats.l2_dual_residual();
68  dual_err_baseline = bound_norms.l2_norm_primal_linear_objective;
69  break;
70  case OPTIMALITY_NORM_L_INF_COMPONENTWISE:
71  primal_err = stats.l_inf_componentwise_primal_residual();
72  primal_err_baseline = 1.0;
73  primal_absolute_epsilon = 0.0;
74  dual_err = stats.l_inf_componentwise_dual_residual();
75  dual_err_baseline = 1.0;
76  dual_absolute_epsilon = 0.0;
77  break;
78  default:
79  LOG(FATAL) << "Invalid optimality_norm value "
80  << OptimalityNorm_Name(optimality_norm);
81  }
82 
83  const bool primal_err_ok =
84  std::isinf(optimality_criteria.eps_optimal_primal_residual_absolute()) ||
85  std::isinf(optimality_criteria.eps_optimal_primal_residual_relative()) ||
86  primal_err <=
87  primal_absolute_epsilon +
88  optimality_criteria.eps_optimal_primal_residual_relative() *
89  primal_err_baseline;
90  const bool dual_err_ok =
91  std::isinf(optimality_criteria.eps_optimal_dual_residual_absolute()) ||
92  std::isinf(optimality_criteria.eps_optimal_dual_residual_relative()) ||
93  dual_err <= dual_absolute_epsilon +
94  optimality_criteria.eps_optimal_dual_residual_relative() *
95  dual_err_baseline;
96  return primal_err_ok && dual_err_ok &&
97  ObjectiveGapMet(optimality_criteria, stats);
98 }
99 
100 namespace {
101 
102 // Checks if the criteria for primal infeasibility are approximately
103 // satisfied; see https://developers.google.com/optimization/lp/pdlp_math for
104 // more details.
105 bool PrimalInfeasibilityCriteriaMet(double eps_primal_infeasible,
106  const InfeasibilityInformation& stats) {
107  if (stats.dual_ray_objective() <= 0.0) return false;
108  return stats.max_dual_ray_infeasibility() / stats.dual_ray_objective() <=
109  eps_primal_infeasible;
110 }
111 
112 // Checks if the criteria for dual infeasibility are approximately satisfied;
113 // see https://developers.google.com/optimization/lp/pdlp_math for more details.
114 bool DualInfeasibilityCriteriaMet(double eps_dual_infeasible,
115  const InfeasibilityInformation& stats) {
116  if (stats.primal_ray_linear_objective() >= 0.0) return false;
117  return (stats.max_primal_ray_infeasibility() /
118  -stats.primal_ray_linear_objective() <=
119  eps_dual_infeasible) &&
120  (stats.primal_ray_quadratic_norm() /
121  -stats.primal_ray_linear_objective() <=
122  eps_dual_infeasible);
123 }
124 
125 } // namespace
126 
127 TerminationCriteria::DetailedOptimalityCriteria EffectiveOptimalityCriteria(
128  const TerminationCriteria& termination_criteria) {
129  if (termination_criteria.has_detailed_optimality_criteria()) {
130  return termination_criteria.detailed_optimality_criteria();
131  }
132  TerminationCriteria::SimpleOptimalityCriteria simple_criteria;
133  if (termination_criteria.has_simple_optimality_criteria()) {
134  simple_criteria = termination_criteria.simple_optimality_criteria();
135  } else {
136  simple_criteria.set_eps_optimal_absolute(
137  termination_criteria.eps_optimal_absolute());
138  simple_criteria.set_eps_optimal_relative(
139  termination_criteria.eps_optimal_relative());
140  }
141  return EffectiveOptimalityCriteria(simple_criteria);
142 }
143 
144 TerminationCriteria::DetailedOptimalityCriteria EffectiveOptimalityCriteria(
145  const TerminationCriteria::SimpleOptimalityCriteria& simple_criteria) {
146  TerminationCriteria::DetailedOptimalityCriteria result;
147  result.set_eps_optimal_primal_residual_absolute(
148  simple_criteria.eps_optimal_absolute());
149  result.set_eps_optimal_primal_residual_relative(
150  simple_criteria.eps_optimal_relative());
151  result.set_eps_optimal_dual_residual_absolute(
152  simple_criteria.eps_optimal_absolute());
153  result.set_eps_optimal_dual_residual_relative(
154  simple_criteria.eps_optimal_relative());
155  result.set_eps_optimal_objective_gap_absolute(
156  simple_criteria.eps_optimal_absolute());
157  result.set_eps_optimal_objective_gap_relative(
158  simple_criteria.eps_optimal_relative());
159  return result;
160 }
161 
162 std::optional<TerminationReasonAndPointType> CheckSimpleTerminationCriteria(
163  const TerminationCriteria& criteria, const IterationStats& stats,
164  const std::atomic<bool>* interrupt_solve) {
165  if (stats.iteration_number() >= criteria.iteration_limit()) {
167  .reason = TERMINATION_REASON_ITERATION_LIMIT, .type = POINT_TYPE_NONE};
168  }
169  if (stats.cumulative_kkt_matrix_passes() >=
170  criteria.kkt_matrix_pass_limit()) {
172  .reason = TERMINATION_REASON_KKT_MATRIX_PASS_LIMIT,
173  .type = POINT_TYPE_NONE};
174  }
175  if (stats.cumulative_time_sec() >= criteria.time_sec_limit()) {
177  .reason = TERMINATION_REASON_TIME_LIMIT, .type = POINT_TYPE_NONE};
178  }
179  if (interrupt_solve != nullptr && interrupt_solve->load() == true) {
181  .reason = TERMINATION_REASON_INTERRUPTED_BY_USER,
182  .type = POINT_TYPE_NONE};
183  }
184  return std::nullopt;
185 }
186 
187 std::optional<TerminationReasonAndPointType> CheckIterateTerminationCriteria(
188  const TerminationCriteria& criteria, const IterationStats& stats,
189  const QuadraticProgramBoundNorms& bound_norms,
190  const bool force_numerical_termination) {
191  TerminationCriteria::DetailedOptimalityCriteria optimality_criteria =
192  EffectiveOptimalityCriteria(criteria);
193  for (const auto& convergence_stats : stats.convergence_information()) {
194  if (OptimalityCriteriaMet(optimality_criteria, convergence_stats,
195  criteria.optimality_norm(), bound_norms)) {
197  .reason = TERMINATION_REASON_OPTIMAL,
198  .type = convergence_stats.candidate_type()};
199  }
200  }
201  for (const auto& infeasibility_stats : stats.infeasibility_information()) {
202  if (PrimalInfeasibilityCriteriaMet(criteria.eps_primal_infeasible(),
203  infeasibility_stats)) {
205  .reason = TERMINATION_REASON_PRIMAL_INFEASIBLE,
206  .type = infeasibility_stats.candidate_type()};
207  }
208  if (DualInfeasibilityCriteriaMet(criteria.eps_dual_infeasible(),
209  infeasibility_stats)) {
211  .reason = TERMINATION_REASON_DUAL_INFEASIBLE,
212  .type = infeasibility_stats.candidate_type()};
213  }
214  }
215  if (force_numerical_termination) {
217  .reason = TERMINATION_REASON_NUMERICAL_ERROR, .type = POINT_TYPE_NONE};
218  }
219  return std::nullopt;
220 }
221 
223  const QuadraticProgramStats& stats) {
224  return {
225  .l2_norm_primal_linear_objective = stats.objective_vector_l2_norm(),
226  .l2_norm_constraint_bounds = stats.combined_bounds_l2_norm(),
227  .l_inf_norm_primal_linear_objective = stats.objective_vector_abs_max(),
228  .l_inf_norm_constraint_bounds = stats.combined_bounds_max()};
229 }
230 
231 double EpsilonRatio(const double epsilon_absolute,
232  const double epsilon_relative) {
233  // Handling `epsilon_absolute == epsilon_relative` explicitly avoids NANs when
234  // both values are zero or infinite.
235  return (epsilon_absolute == epsilon_relative)
236  ? 1.0
237  : epsilon_absolute / epsilon_relative;
238 }
239 
241  const TerminationCriteria::DetailedOptimalityCriteria& optimality_criteria,
242  const ConvergenceInformation& stats,
243  const QuadraticProgramBoundNorms& bound_norms) {
244  const double eps_ratio_primal =
245  EpsilonRatio(optimality_criteria.eps_optimal_primal_residual_absolute(),
246  optimality_criteria.eps_optimal_primal_residual_relative());
247  const double eps_ratio_dual =
248  EpsilonRatio(optimality_criteria.eps_optimal_dual_residual_absolute(),
249  optimality_criteria.eps_optimal_dual_residual_relative());
250  const double eps_ratio_gap =
251  EpsilonRatio(optimality_criteria.eps_optimal_objective_gap_absolute(),
252  optimality_criteria.eps_optimal_objective_gap_relative());
255  stats.l_inf_primal_residual() /
256  (eps_ratio_primal + bound_norms.l_inf_norm_constraint_bounds);
258  stats.l2_primal_residual() /
259  (eps_ratio_primal + bound_norms.l2_norm_constraint_bounds);
261  stats.l_inf_dual_residual() /
262  (eps_ratio_dual + bound_norms.l_inf_norm_primal_linear_objective);
264  stats.l2_dual_residual() /
265  (eps_ratio_dual + bound_norms.l2_norm_primal_linear_objective);
266  const double abs_obj =
267  std::abs(stats.primal_objective()) + std::abs(stats.dual_objective());
268  const double gap = stats.primal_objective() - stats.dual_objective();
269  info.relative_optimality_gap = gap / (eps_ratio_gap + abs_obj);
270 
271  return info;
272 }
273 
274 } // namespace operations_research::pdlp
double EpsilonRatio(const double epsilon_absolute, const double epsilon_relative)
Definition: termination.cc:231
bool OptimalityCriteriaMet(const TerminationCriteria::DetailedOptimalityCriteria &optimality_criteria, const ConvergenceInformation &stats, const OptimalityNorm optimality_norm, const QuadraticProgramBoundNorms &bound_norms)
Definition: termination.cc:44
bool ObjectiveGapMet(const TerminationCriteria::DetailedOptimalityCriteria &optimality_criteria, const ConvergenceInformation &stats)
Definition: termination.cc:27
TerminationCriteria::DetailedOptimalityCriteria EffectiveOptimalityCriteria(const TerminationCriteria &termination_criteria)
Definition: termination.cc:127
std::optional< TerminationReasonAndPointType > CheckIterateTerminationCriteria(const TerminationCriteria &criteria, const IterationStats &stats, const QuadraticProgramBoundNorms &bound_norms, const bool force_numerical_termination)
Definition: termination.cc:187
RelativeConvergenceInformation ComputeRelativeResiduals(const TerminationCriteria::DetailedOptimalityCriteria &optimality_criteria, const ConvergenceInformation &stats, const QuadraticProgramBoundNorms &bound_norms)
Definition: termination.cc:240
std::optional< TerminationReasonAndPointType > CheckSimpleTerminationCriteria(const TerminationCriteria &criteria, const IterationStats &stats, const std::atomic< bool > *interrupt_solve)
Definition: termination.cc:162
QuadraticProgramBoundNorms BoundNormsFromProblemStats(const QuadraticProgramStats &stats)
Definition: termination.cc:222