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