21 #include "gmock/gmock.h"
22 #include "gtest/gtest.h"
24 #include "ortools/pdlp/solve_log.pb.h"
25 #include "ortools/pdlp/solvers.pb.h"
28 bool operator==(
const TerminationCriteria::DetailedOptimalityCriteria& lhs,
29 const TerminationCriteria::DetailedOptimalityCriteria& rhs) {
30 if (lhs.eps_optimal_primal_residual_absolute() !=
31 rhs.eps_optimal_primal_residual_absolute()) {
34 if (lhs.eps_optimal_primal_residual_relative() !=
35 rhs.eps_optimal_primal_residual_relative()) {
38 if (lhs.eps_optimal_dual_residual_absolute() !=
39 rhs.eps_optimal_dual_residual_absolute()) {
42 if (lhs.eps_optimal_dual_residual_relative() !=
43 rhs.eps_optimal_dual_residual_relative()) {
46 if (lhs.eps_optimal_objective_gap_absolute() !=
47 rhs.eps_optimal_objective_gap_absolute()) {
50 if (lhs.eps_optimal_objective_gap_relative() !=
51 rhs.eps_optimal_objective_gap_relative()) {
61 using ::testing::FieldsAre;
62 using ::testing::Optional;
64 QuadraticProgramBoundNorms TestLpBoundNorms() {
65 return {.l2_norm_primal_linear_objective = std::sqrt(36.25),
66 .l2_norm_constraint_bounds = std::sqrt(210.0),
67 .l_inf_norm_primal_linear_objective = 5.5,
68 .l_inf_norm_constraint_bounds = 12.0};
71 QuadraticProgramBoundNorms ZeroLpBoundNorms() {
72 return {.l2_norm_primal_linear_objective = 0.0,
73 .l2_norm_constraint_bounds = 0.0,
74 .l_inf_norm_primal_linear_objective = 0.0,
75 .l_inf_norm_constraint_bounds = 0.0};
78 class SimpleTerminationTest :
public testing::Test {
80 void SetUp()
override {
83 kkt_matrix_pass_limit: 2000
84 iteration_limit: 10)pb");
90 class IterateTerminationTest :
public testing::TestWithParam<OptimalityNorm> {
92 void SetUp()
override {
94 simple_optimality_criteria {
95 eps_optimal_absolute: 1.0e-4
96 eps_optimal_relative: 1.0e-4
98 eps_primal_infeasible: 1.0e-6
99 eps_dual_infeasible: 1.0e-6
101 kkt_matrix_pass_limit: 2000
102 iteration_limit: 10)pb");
109 class DetailedRelativeTerminationTest
110 :
public testing::TestWithParam<OptimalityNorm> {
112 void SetUp()
override {
114 detailed_optimality_criteria {
115 eps_optimal_primal_residual_absolute: 0.0
116 eps_optimal_primal_residual_relative: 1.0e-4
117 eps_optimal_dual_residual_absolute: 0.0
118 eps_optimal_dual_residual_relative: 1.0e-4
119 eps_optimal_objective_gap_absolute: 0.0
120 eps_optimal_objective_gap_relative: 1.0e-4
129 class DetailedAbsoluteTerminationTest
130 :
public testing::TestWithParam<OptimalityNorm> {
132 void SetUp()
override {
134 detailed_optimality_criteria {
135 eps_optimal_primal_residual_absolute: 1.0e-4
136 eps_optimal_primal_residual_relative: 0.0
137 eps_optimal_dual_residual_absolute: 1.0e-4
138 eps_optimal_dual_residual_relative: 0.0
139 eps_optimal_objective_gap_absolute: 1.0e-4
140 eps_optimal_objective_gap_relative: 0.0
149 TEST(EffectiveOptimalityCriteriaTest, SimpleOptimalityCriteriaOverload) {
150 const auto criteria =
151 ParseTextOrDie<TerminationCriteria::SimpleOptimalityCriteria>(
152 R
"pb(eps_optimal_absolute: 1.0e-4 eps_optimal_relative: 2.0e-4)pb");
155 Eq(ParseTextOrDie<TerminationCriteria::DetailedOptimalityCriteria>(
157 eps_optimal_primal_residual_absolute: 1.0e-4
158 eps_optimal_primal_residual_relative: 2.0e-4
159 eps_optimal_dual_residual_absolute: 1.0e-4
160 eps_optimal_dual_residual_relative: 2.0e-4
161 eps_optimal_objective_gap_absolute: 1.0e-4
162 eps_optimal_objective_gap_relative: 2.0e-4
166 TEST(EffectiveOptimalityCriteriaTest, SimpleOptimalityCriteriaInput) {
167 const auto criteria =
168 ParseTextOrDie<TerminationCriteria>(R
"pb(simple_optimality_criteria {
169 eps_optimal_absolute: 1.0e-4
170 eps_optimal_relative: 2.0e-4
174 Eq(ParseTextOrDie<TerminationCriteria::DetailedOptimalityCriteria>(
176 eps_optimal_primal_residual_absolute: 1.0e-4
177 eps_optimal_primal_residual_relative: 2.0e-4
178 eps_optimal_dual_residual_absolute: 1.0e-4
179 eps_optimal_dual_residual_relative: 2.0e-4
180 eps_optimal_objective_gap_absolute: 1.0e-4
181 eps_optimal_objective_gap_relative: 2.0e-4
185 TEST(EffectiveOptimalityCriteriaTest, DetailedOptimalityCriteriaInput) {
186 const auto criteria = ParseTextOrDie<TerminationCriteria>(
187 R
"pb(detailed_optimality_criteria {
188 eps_optimal_primal_residual_absolute: 1.0e-4
189 eps_optimal_primal_residual_relative: 2.0e-4
190 eps_optimal_dual_residual_absolute: 3.0e-4
191 eps_optimal_dual_residual_relative: 4.0e-4
192 eps_optimal_objective_gap_absolute: 5.0e-4
193 eps_optimal_objective_gap_relative: 6.0e-4
196 Eq(criteria.detailed_optimality_criteria()));
199 TEST(EffectiveOptimalityCriteriaTest, DeprecatedInput) {
200 const auto criteria = ParseTextOrDie<TerminationCriteria>(
201 R
"pb(eps_optimal_absolute: 1.0e-4 eps_optimal_relative: 2.0e-4)pb");
204 Eq(ParseTextOrDie<TerminationCriteria::DetailedOptimalityCriteria>(
206 eps_optimal_primal_residual_absolute: 1.0e-4
207 eps_optimal_primal_residual_relative: 2.0e-4
208 eps_optimal_dual_residual_absolute: 1.0e-4
209 eps_optimal_dual_residual_relative: 2.0e-4
210 eps_optimal_objective_gap_absolute: 1.0e-4
211 eps_optimal_objective_gap_relative: 2.0e-4
215 TEST_P(DetailedRelativeTerminationTest, TerminationWithNearOptimal) {
216 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
217 convergence_information {
218 primal_objective: 1.00019
220 l_inf_primal_residual: 11.0e-4
221 l_inf_dual_residual: 5.4e-4
222 l2_primal_residual: 14.0e-4
223 l2_dual_residual: 6.0e-4
224 l_inf_componentwise_primal_residual: 9.0e-5
225 l_inf_componentwise_dual_residual: 9.0e-5
226 candidate_type: POINT_TYPE_CURRENT_ITERATE
229 stats.convergence_information(0)));
232 stats.convergence_information(0), test_criteria_.optimality_norm(),
233 TestLpBoundNorms()));
236 Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
237 POINT_TYPE_CURRENT_ITERATE)));
240 TEST_P(DetailedRelativeTerminationTest, NoTerminationWithExcessiveGap) {
241 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
242 convergence_information {
243 primal_objective: 1.00021
245 l_inf_primal_residual: 11.0e-4
246 l_inf_dual_residual: 5.4e-4
247 l2_primal_residual: 14.0e-4
248 l2_dual_residual: 6.0e-4
249 l_inf_componentwise_primal_residual: 9.0e-5
250 l_inf_componentwise_dual_residual: 9.0e-5
251 candidate_type: POINT_TYPE_CURRENT_ITERATE
254 stats.convergence_information(0)));
257 stats.convergence_information(0), test_criteria_.optimality_norm(),
258 TestLpBoundNorms()));
264 TEST_P(DetailedRelativeTerminationTest,
265 NoTerminationWithExcessivePrimalResidual) {
266 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
267 convergence_information {
268 primal_objective: 1.00019
270 l_inf_primal_residual: 13.0e-4
271 l_inf_dual_residual: 5.4e-4
272 l2_primal_residual: 15.0e-4
273 l2_dual_residual: 6.0e-4
274 l_inf_componentwise_primal_residual: 1.1e-4
275 l_inf_componentwise_dual_residual: 9.0e-5
276 candidate_type: POINT_TYPE_CURRENT_ITERATE
280 stats.convergence_information(0), test_criteria_.optimality_norm(),
281 TestLpBoundNorms()));
287 TEST_P(DetailedRelativeTerminationTest,
288 NoTerminationWithExcessiveDualResidual) {
289 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
290 convergence_information {
291 primal_objective: 1.00019
293 l_inf_primal_residual: 11.0e-4
294 l_inf_dual_residual: 5.6e-4
295 l2_primal_residual: 14.0e-4
296 l2_dual_residual: 7.0e-4
297 l_inf_componentwise_primal_residual: 9.0e-5
298 l_inf_componentwise_dual_residual: 1.1e-4
299 candidate_type: POINT_TYPE_CURRENT_ITERATE
303 stats.convergence_information(0), test_criteria_.optimality_norm(),
304 TestLpBoundNorms()));
310 TEST_P(DetailedAbsoluteTerminationTest, TerminationWithNearOptimal) {
311 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
312 convergence_information {
313 primal_objective: 1.00009
315 l_inf_primal_residual: 9.0e-5
316 l_inf_dual_residual: 9.0e-5
317 l2_primal_residual: 9.0e-5
318 l2_dual_residual: 9.0e-5
319 l_inf_componentwise_primal_residual: 0.0
320 l_inf_componentwise_dual_residual: 0.0
321 candidate_type: POINT_TYPE_CURRENT_ITERATE
324 stats.convergence_information(0)));
327 stats.convergence_information(0), test_criteria_.optimality_norm(),
328 TestLpBoundNorms()));
331 Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
332 POINT_TYPE_CURRENT_ITERATE)));
335 TEST_P(DetailedAbsoluteTerminationTest, NoTerminationWithExcessiveGap) {
336 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
337 convergence_information {
338 primal_objective: 1.00011
340 l_inf_primal_residual: 9.0e-5
341 l_inf_dual_residual: 9.0e-5
342 l2_primal_residual: 9.0e-5
343 l2_dual_residual: 9.0e-5
344 l_inf_componentwise_primal_residual: 0.0
345 l_inf_componentwise_dual_residual: 0.0
346 candidate_type: POINT_TYPE_CURRENT_ITERATE
349 stats.convergence_information(0)));
352 stats.convergence_information(0), test_criteria_.optimality_norm(),
353 TestLpBoundNorms()));
359 TEST_P(DetailedAbsoluteTerminationTest,
360 NoTerminationWithExcessivePrimalResidual) {
361 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
362 convergence_information {
363 primal_objective: 1.00009
365 l_inf_primal_residual: 11.0e-5
366 l_inf_dual_residual: 9.0e-5
367 l2_primal_residual: 11.0e-5
368 l2_dual_residual: 9.0e-5
369 l_inf_componentwise_primal_residual: 1.0e-6
370 l_inf_componentwise_dual_residual: 0.0
371 candidate_type: POINT_TYPE_CURRENT_ITERATE
375 stats.convergence_information(0), test_criteria_.optimality_norm(),
376 TestLpBoundNorms()));
382 TEST_P(DetailedAbsoluteTerminationTest,
383 NoTerminationWithExcessiveDualResidual) {
384 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
385 convergence_information {
386 primal_objective: 1.00009
388 l_inf_primal_residual: 9.0e-5
389 l_inf_dual_residual: 11.0e-5
390 l2_primal_residual: 9.0e-5
391 l2_dual_residual: 11.0e-5
392 l_inf_componentwise_primal_residual: 0.0
393 l_inf_componentwise_dual_residual: 1.0e-6
394 candidate_type: POINT_TYPE_CURRENT_ITERATE
398 stats.convergence_information(0), test_criteria_.optimality_norm(),
399 TestLpBoundNorms()));
405 TEST_P(IterateTerminationTest, NoTerminationWithLargeGap) {
406 IterationStats stats = ParseTextOrDie<IterationStats>(R"pb(
407 convergence_information {
408 # Ensures that optimality conditions are not met.
409 primal_objective: 50.0
410 dual_objective: -50.0
417 TEST_F(SimpleTerminationTest, NoTerminationWithEmptyIterationStats) {
418 IterationStats stats;
423 TEST_P(IterateTerminationTest, NoTerminationWithEmptyIterationStats) {
424 IterationStats stats;
430 TEST_F(SimpleTerminationTest, TerminationWithInterruptSolve) {
431 IterationStats stats;
432 std::atomic<bool> interrupt_solve = true;
433 std::optional<TerminationReasonAndPointType> maybe_result =
435 EXPECT_THAT(maybe_result,
436 Optional(FieldsAre(TERMINATION_REASON_INTERRUPTED_BY_USER,
440 TEST_P(IterateTerminationTest, TerminationWithNumericalError) {
441 IterationStats stats;
442 std::optional<TerminationReasonAndPointType> maybe_result =
447 Optional(FieldsAre(TERMINATION_REASON_NUMERICAL_ERROR, POINT_TYPE_NONE)));
450 TEST_F(SimpleTerminationTest, TerminationWithTimeLimit) {
452 ParseTextOrDie<IterationStats>(R
"pb(cumulative_time_sec: 100.0)pb");
453 std::optional<TerminationReasonAndPointType> maybe_result =
455 EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_TIME_LIMIT,
459 TEST_F(SimpleTerminationTest, TerminationWithKktMatrixPassLimit) {
460 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
461 cumulative_kkt_matrix_passes: 2500)pb");
462 std::optional<TerminationReasonAndPointType> maybe_result =
464 EXPECT_THAT(maybe_result,
465 Optional(FieldsAre(TERMINATION_REASON_KKT_MATRIX_PASS_LIMIT,
469 TEST_F(SimpleTerminationTest, TerminationWithIterationLimit) {
470 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
471 iteration_number: 20)pb");
472 std::optional<TerminationReasonAndPointType> maybe_result =
476 Optional(FieldsAre(TERMINATION_REASON_ITERATION_LIMIT, POINT_TYPE_NONE)));
479 TEST_P(IterateTerminationTest, PrimalInfeasibleFromIterateDifference) {
480 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
481 infeasibility_information: {
482 dual_ray_objective: 1.0
483 max_dual_ray_infeasibility: 1.0e-16
484 candidate_type: POINT_TYPE_ITERATE_DIFFERENCE
486 std::optional<TerminationReasonAndPointType> maybe_result =
489 EXPECT_THAT(maybe_result,
490 Optional(FieldsAre(TERMINATION_REASON_PRIMAL_INFEASIBLE,
491 POINT_TYPE_ITERATE_DIFFERENCE)));
494 TEST_P(IterateTerminationTest, NoTerminationWithInfeasibleDualRay) {
495 const auto stats_infeasible_ray = ParseTextOrDie<IterationStats>(R
"pb(
496 infeasibility_information: {
497 dual_ray_objective: 1.0
498 max_dual_ray_infeasibility: 1.0e-5 # Too large
505 TEST_P(IterateTerminationTest, NoTerminationWithNegativeDualRayObjective) {
506 const auto stats_wrong_sign = ParseTextOrDie<IterationStats>(R
"pb(
507 infeasibility_information: {
508 dual_ray_objective: -1.0 # Wrong sign
509 max_dual_ray_infeasibility: 0.0
516 TEST_P(IterateTerminationTest, NoTerminationWithZeroDualRayObjective) {
517 const auto stats_objective_zero = ParseTextOrDie<IterationStats>(R
"pb(
518 infeasibility_information: {
519 dual_ray_objective: 0.0
520 max_dual_ray_infeasibility: 0.0
527 TEST_P(IterateTerminationTest, DualInfeasibleFromAverageIterate) {
528 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
529 infeasibility_information: {
530 primal_ray_linear_objective: -1.0
531 max_primal_ray_infeasibility: 1.0e-16
532 candidate_type: POINT_TYPE_AVERAGE_ITERATE
534 std::optional<TerminationReasonAndPointType> maybe_result =
537 EXPECT_THAT(maybe_result,
538 Optional(FieldsAre(TERMINATION_REASON_DUAL_INFEASIBLE,
539 POINT_TYPE_AVERAGE_ITERATE)));
542 TEST_P(IterateTerminationTest, NoTerminationWithInfeasiblePrimalRay) {
543 const auto stats_infeasible_ray = ParseTextOrDie<IterationStats>(R
"pb(
544 infeasibility_information: {
545 primal_ray_linear_objective: -1.0
546 max_primal_ray_infeasibility: 1.0e-5 # Too large
553 TEST_P(IterateTerminationTest, NoTerminationWithPositivePrimalRayObjective) {
554 const auto stats_wrong_sign = ParseTextOrDie<IterationStats>(R
"pb(
555 infeasibility_information: {
556 primal_ray_linear_objective: 1.0 # Wrong sign
557 max_primal_ray_infeasibility: 0.0
564 TEST_P(IterateTerminationTest, NoTerminationWithZeroPrimalRayObjective) {
565 const auto stats_objective_zero = ParseTextOrDie<IterationStats>(R
"pb(
566 infeasibility_information: {
567 primal_ray_linear_objective: 0.0
568 max_primal_ray_infeasibility: 0.0
575 TEST_P(IterateTerminationTest, TerminationWithOptimal) {
576 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
577 convergence_information {
578 primal_objective: 1.0
580 l_inf_primal_residual: 0.0
581 l_inf_dual_residual: 0.0
582 l2_primal_residual: 0.0
583 l2_dual_residual: 0.0
584 l_inf_componentwise_primal_residual: 0.0
585 l_inf_componentwise_dual_residual: 0.0
586 candidate_type: POINT_TYPE_CURRENT_ITERATE
589 std::optional<TerminationReasonAndPointType> maybe_result =
592 EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
593 POINT_TYPE_CURRENT_ITERATE)));
596 TEST_P(IterateTerminationTest, TerminationWithNearOptimal) {
597 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
598 convergence_information {
599 primal_objective: 1.00019
601 l_inf_primal_residual: 11.0e-4
602 l_inf_dual_residual: 5.4e-4
603 l2_primal_residual: 14.0e-4
604 l2_dual_residual: 6.0e-4
605 l_inf_componentwise_primal_residual: 9.0e-5
606 l_inf_componentwise_dual_residual: 9.0e-5
607 candidate_type: POINT_TYPE_CURRENT_ITERATE
610 std::optional<TerminationReasonAndPointType> maybe_result =
613 EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
614 POINT_TYPE_CURRENT_ITERATE)));
617 TEST_P(IterateTerminationTest,
618 TerminationWithNonOptimalInfiniteAbsoluteTolerances) {
619 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
620 convergence_information {
621 primal_objective: 1.0
623 l_inf_primal_residual: 1.0
624 l_inf_dual_residual: 1.0
625 l2_primal_residual: 1.0
626 l2_dual_residual: 1.0
627 l_inf_componentwise_primal_residual: 1.0
628 l_inf_componentwise_dual_residual: 1.0
629 candidate_type: POINT_TYPE_CURRENT_ITERATE
631 test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_absolute(
632 std::numeric_limits<double>::infinity());
633 test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_relative(
635 std::optional<TerminationReasonAndPointType> maybe_result =
638 EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
639 POINT_TYPE_CURRENT_ITERATE)));
642 TEST_P(IterateTerminationTest,
643 TerminationWithNonOptimalInfiniteRelativeTolerances) {
644 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
645 convergence_information {
646 primal_objective: 0.0
648 l_inf_primal_residual: 1.0
649 l_inf_dual_residual: 1.0
650 l2_primal_residual: 1.0
651 l2_dual_residual: 1.0
652 l_inf_componentwise_primal_residual: 1.0
653 l_inf_componentwise_dual_residual: 1.0
654 candidate_type: POINT_TYPE_CURRENT_ITERATE
656 test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_absolute(
658 test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_relative(
659 std::numeric_limits<double>::infinity());
660 std::optional<TerminationReasonAndPointType> maybe_result =
663 EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
664 POINT_TYPE_CURRENT_ITERATE)));
667 TEST_P(IterateTerminationTest,
668 TerminationWithNonOptimalInfiniteAbsoluteAndRelativeTolerances) {
669 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
670 convergence_information {
671 primal_objective: 1.0
673 l_inf_primal_residual: 1.0
674 l_inf_dual_residual: 1.0
675 l2_primal_residual: 1.0
676 l2_dual_residual: 1.0
677 l_inf_componentwise_primal_residual: 1.0
678 l_inf_componentwise_dual_residual: 1.0
679 candidate_type: POINT_TYPE_CURRENT_ITERATE
681 test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_absolute(
682 std::numeric_limits<double>::infinity());
683 test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_relative(
684 std::numeric_limits<double>::infinity());
685 std::optional<TerminationReasonAndPointType> maybe_result =
688 EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
689 POINT_TYPE_CURRENT_ITERATE)));
692 TEST_P(IterateTerminationTest, OptimalEvenWithNumericalError) {
693 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
694 convergence_information {
695 primal_objective: 1.0
697 l_inf_primal_residual: 0.0
698 l_inf_dual_residual: 0.0
699 l2_primal_residual: 0.0
700 l2_dual_residual: 0.0
701 l_inf_componentwise_primal_residual: 0.0
702 l_inf_componentwise_dual_residual: 0.0
703 candidate_type: POINT_TYPE_CURRENT_ITERATE
708 std::optional<TerminationReasonAndPointType> maybe_result =
711 EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
712 POINT_TYPE_CURRENT_ITERATE)));
715 TEST_P(IterateTerminationTest, NoTerminationWithBadGap) {
716 const auto stats_bad_gap = ParseTextOrDie<IterationStats>(R
"pb(
717 convergence_information {
718 primal_objective: 10.0
720 l_inf_primal_residual: 0.0
721 l_inf_dual_residual: 0.0
722 l2_primal_residual: 0.0
723 l2_dual_residual: 0.0
724 l_inf_componentwise_primal_residual: 0.0
725 l_inf_componentwise_dual_residual: 0.0
732 TEST_P(IterateTerminationTest, NoTerminationWithInfiniteGap) {
733 const auto stats_infinite_gap = ParseTextOrDie<IterationStats>(R
"pb(
734 convergence_information {
737 l_inf_primal_residual: 0.0
738 l_inf_dual_residual: 0.0
739 l2_primal_residual: 0.0
740 l2_dual_residual: 0.0
741 l_inf_componentwise_primal_residual: 0.0
742 l_inf_componentwise_dual_residual: 0.0
749 TEST_P(IterateTerminationTest, NoTerminationWithBadPrimalResidual) {
750 const auto stats_bad_primal = ParseTextOrDie<IterationStats>(R
"pb(
751 convergence_information {
752 primal_objective: 1.0
754 l_inf_primal_residual: 1.0
755 l_inf_dual_residual: 0.0
756 l2_primal_residual: 1.0
757 l2_dual_residual: 0.0
758 l_inf_componentwise_primal_residual: 1.0
759 l_inf_componentwise_dual_residual: 0.0
766 TEST_P(IterateTerminationTest, NoTerminationWithBadDualResidual) {
767 const auto stats_bad_dual = ParseTextOrDie<IterationStats>(R
"pb(
768 convergence_information {
769 primal_objective: 1.0
771 l_inf_primal_residual: 0.0
772 l_inf_dual_residual: 1.0
773 l2_primal_residual: 0.0
774 l2_dual_residual: 1.0
775 l_inf_componentwise_primal_residual: 0.0
776 l_inf_componentwise_dual_residual: 1.0
785 TEST_P(IterateTerminationTest, ZeroToleranceZeroError) {
786 const auto stats = ParseTextOrDie<IterationStats>(R
"pb(
787 convergence_information {
788 primal_objective: 1.0
790 l_inf_primal_residual: 0.0
791 l_inf_dual_residual: 0.0
792 l2_primal_residual: 0.0
793 l2_dual_residual: 0.0
794 l_inf_componentwise_primal_residual: 0.0
795 l_inf_componentwise_dual_residual: 0.0
796 candidate_type: POINT_TYPE_CURRENT_ITERATE
799 test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_absolute(
801 test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_relative(
804 std::optional<TerminationReasonAndPointType> maybe_result =
807 EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
808 POINT_TYPE_CURRENT_ITERATE)));
811 INSTANTIATE_TEST_SUITE_P(OptNorm, IterateTerminationTest,
812 testing::Values(OPTIMALITY_NORM_L_INF,
814 OPTIMALITY_NORM_L_INF_COMPONENTWISE));
816 INSTANTIATE_TEST_SUITE_P(DetailedRelativeOptNorm,
817 DetailedRelativeTerminationTest,
818 testing::Values(OPTIMALITY_NORM_L_INF,
820 OPTIMALITY_NORM_L_INF_COMPONENTWISE));
821 INSTANTIATE_TEST_SUITE_P(DetailedAbsoluteOptNorm,
822 DetailedAbsoluteTerminationTest,
823 testing::Values(OPTIMALITY_NORM_L_INF,
825 OPTIMALITY_NORM_L_INF_COMPONENTWISE));
827 TEST(IterateTerminationTest, OptimalityNormsDiffer) {
828 auto test_criteria = ParseTextOrDie<TerminationCriteria>(R
"pb(
829 simple_optimality_criteria { eps_optimal_relative: 1.0 })pb");
836 double primal_residual;
837 std::optional<TerminationReasonAndPointType> expected_l2;
838 std::optional<TerminationReasonAndPointType> expected_l_inf;
839 std::optional<TerminationReasonAndPointType> expected_l_inf_relative;
842 TerminationReasonAndPointType{.reason = TERMINATION_REASON_OPTIMAL,
843 .type = POINT_TYPE_CURRENT_ITERATE},
844 TerminationReasonAndPointType{.reason = TERMINATION_REASON_OPTIMAL,
845 .type = POINT_TYPE_CURRENT_ITERATE},
846 TerminationReasonAndPointType{.reason = TERMINATION_REASON_OPTIMAL,
847 .type = POINT_TYPE_CURRENT_ITERATE}},
849 TerminationReasonAndPointType{.reason = TERMINATION_REASON_OPTIMAL,
850 .type = POINT_TYPE_CURRENT_ITERATE},
851 TerminationReasonAndPointType{.reason = TERMINATION_REASON_OPTIMAL,
852 .type = POINT_TYPE_CURRENT_ITERATE},
855 TerminationReasonAndPointType{.reason = TERMINATION_REASON_OPTIMAL,
856 .type = POINT_TYPE_CURRENT_ITERATE},
857 std::nullopt, std::nullopt},
858 {15.0, std::nullopt, std::nullopt, std::nullopt}};
860 for (
const auto& config : test_configs) {
861 IterationStats stats;
862 auto* convergence_info = stats.add_convergence_information();
863 convergence_info->set_primal_objective(1.0);
864 convergence_info->set_dual_objective(1.0);
865 convergence_info->set_l_inf_primal_residual(config.primal_residual);
866 convergence_info->set_l2_primal_residual(config.primal_residual);
867 convergence_info->set_l_inf_componentwise_primal_residual(
868 config.primal_residual);
869 convergence_info->set_candidate_type(POINT_TYPE_CURRENT_ITERATE);
871 test_criteria.set_optimality_norm(OPTIMALITY_NORM_L_INF);
872 std::optional<TerminationReasonAndPointType> maybe_result =
875 ASSERT_TRUE(maybe_result.has_value() == config.expected_l_inf.has_value())
876 <<
"primal_residual: " << config.primal_residual;
877 if (config.expected_l_inf.has_value()) {
878 EXPECT_EQ(maybe_result->reason, config.expected_l_inf->reason);
879 EXPECT_EQ(maybe_result->type, config.expected_l_inf->type);
882 test_criteria.set_optimality_norm(OPTIMALITY_NORM_L2);
886 ASSERT_TRUE(maybe_result.has_value() == config.expected_l2.has_value())
887 <<
"primal_residual: " << config.primal_residual;
888 if (config.expected_l2.has_value()) {
889 EXPECT_EQ(maybe_result->reason, config.expected_l2->reason);
890 EXPECT_EQ(maybe_result->type, config.expected_l2->type);
893 test_criteria.set_optimality_norm(OPTIMALITY_NORM_L_INF_COMPONENTWISE);
896 ASSERT_TRUE(maybe_result.has_value() ==
897 config.expected_l_inf_relative.has_value())
898 <<
"primal_residual: " << config.primal_residual;
899 if (config.expected_l_inf_relative.has_value()) {
900 EXPECT_EQ(maybe_result->reason, config.expected_l_inf_relative->reason);
901 EXPECT_EQ(maybe_result->type, config.expected_l_inf_relative->type);
907 const auto qp_stats = ParseTextOrDie<QuadraticProgramStats>(R
"pb(
908 objective_vector_l2_norm: 4.0
909 combined_bounds_l2_norm: 3.0
910 objective_vector_abs_max: 1.0
911 combined_bounds_max: 2.0
914 EXPECT_EQ(norms.l2_norm_primal_linear_objective, 4.0);
915 EXPECT_EQ(norms.l2_norm_constraint_bounds, 3.0);
916 EXPECT_EQ(norms.l_inf_norm_primal_linear_objective, 1.0);
917 EXPECT_EQ(norms.l_inf_norm_constraint_bounds, 2.0);
923 EXPECT_EQ(
EpsilonRatio(std::numeric_limits<double>::infinity(),
924 std::numeric_limits<double>::infinity()),
928 EXPECT_EQ(
EpsilonRatio(0.0, std::numeric_limits<double>::infinity()), 0.0);
929 EXPECT_EQ(
EpsilonRatio(std::numeric_limits<double>::infinity(), 0.0),
930 std::numeric_limits<double>::infinity());
934 ComputesRelativeResidualsForZeroAbsoluteTolerance) {
935 ConvergenceInformation stats;
940 stats.set_primal_objective(10.0);
941 stats.set_dual_objective(5.0);
942 stats.set_l_inf_primal_residual(1.0);
943 stats.set_l2_primal_residual(1.0);
944 stats.set_l_inf_dual_residual(1.0);
945 stats.set_l2_dual_residual(1.0);
946 TerminationCriteria termination_criteria;
947 termination_criteria.mutable_simple_optimality_criteria()
948 ->set_eps_optimal_absolute(0.0);
949 termination_criteria.mutable_simple_optimality_criteria()
950 ->set_eps_optimal_relative(1.0e-6);
955 EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 1.0 / 12.0);
956 EXPECT_EQ(relative_info.relative_l2_primal_residual, 1.0 / std::sqrt(210.0));
958 EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 1.0 / 5.5);
959 EXPECT_EQ(relative_info.relative_l2_dual_residual, 1.0 / sqrt(36.25));
964 EXPECT_EQ(relative_info.relative_optimality_gap, 5.0 / 15.0);
968 ComputesRelativeResidualsForZeroRelativeTolerance) {
969 ConvergenceInformation stats;
973 stats.set_primal_objective(10.0);
974 stats.set_dual_objective(5.0);
975 stats.set_l_inf_primal_residual(1.0);
976 stats.set_l2_primal_residual(1.0);
977 stats.set_l_inf_dual_residual(1.0);
978 stats.set_l2_dual_residual(1.0);
979 TerminationCriteria::SimpleOptimalityCriteria opt_criteria;
980 opt_criteria.set_eps_optimal_absolute(1.0e-6);
981 opt_criteria.set_eps_optimal_relative(0.0);
985 EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 0.0);
986 EXPECT_EQ(relative_info.relative_l2_primal_residual, 0.0);
987 EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 0.0);
988 EXPECT_EQ(relative_info.relative_l2_dual_residual, 0.0);
989 EXPECT_EQ(relative_info.relative_optimality_gap, 0.0);
993 ComputesCorrectRelativeResidualsForEqualTolerances) {
994 ConvergenceInformation stats;
998 stats.set_primal_objective(10.0);
999 stats.set_dual_objective(5.0);
1000 stats.set_l_inf_primal_residual(1.0);
1001 stats.set_l2_primal_residual(1.0);
1002 stats.set_l_inf_dual_residual(1.0);
1003 stats.set_l2_dual_residual(1.0);
1004 TerminationCriteria::SimpleOptimalityCriteria opt_criteria;
1005 opt_criteria.set_eps_optimal_absolute(1.0e-6);
1006 opt_criteria.set_eps_optimal_relative(1.0e-6);
1010 EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 1.0 / (1.0 + 12.0));
1011 EXPECT_EQ(relative_info.relative_l2_primal_residual,
1012 1.0 / (1.0 + std::sqrt(210.0)));
1014 EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 1.0 / (1.0 + 5.5));
1015 EXPECT_EQ(relative_info.relative_l2_dual_residual, 1.0 / (1.0 + sqrt(36.25)));
1019 EXPECT_EQ(relative_info.relative_optimality_gap, 5.0 / (1.0 + 15.0));
1023 ComputesCorrectRelativeResidualsForBothTolerancesZero) {
1024 ConvergenceInformation stats;
1028 stats.set_primal_objective(10.0);
1029 stats.set_dual_objective(5.0);
1030 stats.set_l_inf_primal_residual(1.0);
1031 stats.set_l2_primal_residual(1.0);
1032 stats.set_l_inf_dual_residual(1.0);
1033 stats.set_l2_dual_residual(1.0);
1034 TerminationCriteria::SimpleOptimalityCriteria opt_criteria;
1035 opt_criteria.set_eps_optimal_absolute(0.0);
1036 opt_criteria.set_eps_optimal_relative(0.0);
1040 EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 1.0 / (1.0 + 12.0));
1041 EXPECT_EQ(relative_info.relative_l2_primal_residual,
1042 1.0 / (1.0 + std::sqrt(210.0)));
1044 EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 1.0 / (1.0 + 5.5));
1045 EXPECT_EQ(relative_info.relative_l2_dual_residual, 1.0 / (1.0 + sqrt(36.25)));
1049 EXPECT_EQ(relative_info.relative_optimality_gap, 5.0 / (1.0 + 15.0));
1053 ComputesCorrectRelativeResidualsForDetailedTerminationCriteria) {
1054 ConvergenceInformation stats;
1058 stats.set_primal_objective(10.0);
1059 stats.set_dual_objective(5.0);
1060 stats.set_l_inf_primal_residual(1.0);
1061 stats.set_l2_primal_residual(1.0);
1062 stats.set_l_inf_dual_residual(1.0);
1063 stats.set_l2_dual_residual(1.0);
1064 TerminationCriteria::DetailedOptimalityCriteria opt_criteria;
1065 opt_criteria.set_eps_optimal_primal_residual_absolute(2.0e-6);
1066 opt_criteria.set_eps_optimal_primal_residual_relative(2.0e-4);
1067 opt_criteria.set_eps_optimal_dual_residual_absolute(1.0e-3);
1068 opt_criteria.set_eps_optimal_dual_residual_relative(1.0e-4);
1069 opt_criteria.set_eps_optimal_objective_gap_absolute(3.0e-8);
1070 opt_criteria.set_eps_optimal_objective_gap_relative(3.0e-7);
1071 const RelativeConvergenceInformation relative_info =
1074 EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 1.0 / (0.01 + 12.0));
1075 EXPECT_EQ(relative_info.relative_l2_primal_residual,
1076 1.0 / (0.01 + std::sqrt(210.0)));
1078 EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 1.0 / (10.0 + 5.5));
1079 EXPECT_EQ(relative_info.relative_l2_dual_residual,
1080 1.0 / (10.0 + sqrt(36.25)));
1084 EXPECT_EQ(relative_info.relative_optimality_gap, 5.0 / (0.1 + 15.0));
1088 ComputesCorrectRelativeResidualsForInfiniteAbsoluteTolerances) {
1089 ConvergenceInformation stats;
1090 stats.set_primal_objective(10.0);
1091 stats.set_dual_objective(5.0);
1092 stats.set_l_inf_primal_residual(1.0);
1093 stats.set_l2_primal_residual(1.0);
1094 stats.set_l_inf_dual_residual(1.0);
1095 stats.set_l2_dual_residual(1.0);
1096 TerminationCriteria::SimpleOptimalityCriteria opt_criteria;
1097 opt_criteria.set_eps_optimal_absolute(
1098 std::numeric_limits<double>::infinity());
1099 opt_criteria.set_eps_optimal_relative(1.0e-6);
1104 EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 0.0);
1105 EXPECT_EQ(relative_info.relative_l2_primal_residual, 0.0);
1106 EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 0.0);
1107 EXPECT_EQ(relative_info.relative_l2_dual_residual, 0.0);
1108 EXPECT_EQ(relative_info.relative_optimality_gap, 0.0);
1112 ComputesCorrectRelativeResidualsForInfiniteRelativeTolerances) {
1113 ConvergenceInformation stats;
1114 stats.set_primal_objective(10.0);
1115 stats.set_dual_objective(5.0);
1116 stats.set_l_inf_primal_residual(1.0);
1117 stats.set_l2_primal_residual(1.0);
1118 stats.set_l_inf_dual_residual(1.0);
1119 stats.set_l2_dual_residual(1.0);
1120 TerminationCriteria::SimpleOptimalityCriteria opt_criteria;
1121 opt_criteria.set_eps_optimal_absolute(1.0e-6);
1122 opt_criteria.set_eps_optimal_relative(
1123 std::numeric_limits<double>::infinity());
1127 EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 1.0 / 12.0);
1128 EXPECT_EQ(relative_info.relative_l2_primal_residual, 1.0 / std::sqrt(210.0));
1130 EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 1.0 / 5.5);
1131 EXPECT_EQ(relative_info.relative_l2_dual_residual, 1.0 / sqrt(36.25));
1135 EXPECT_EQ(relative_info.relative_optimality_gap, 5.0 / 15.0);
1139 ComputesCorrectRelativeResidualsForInfiniteAbsoluteAndRelativeTolerances) {
1140 ConvergenceInformation stats;
1144 stats.set_primal_objective(10.0);
1145 stats.set_dual_objective(5.0);
1146 stats.set_l_inf_primal_residual(1.0);
1147 stats.set_l2_primal_residual(1.0);
1148 stats.set_l_inf_dual_residual(1.0);
1149 stats.set_l2_dual_residual(1.0);
1150 TerminationCriteria::SimpleOptimalityCriteria opt_criteria;
1151 opt_criteria.set_eps_optimal_absolute(
1152 std::numeric_limits<double>::infinity());
1153 opt_criteria.set_eps_optimal_relative(
1154 std::numeric_limits<double>::infinity());
1158 EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 1.0 / (1.0 + 12.0));
1159 EXPECT_EQ(relative_info.relative_l2_primal_residual,
1160 1.0 / (1.0 + std::sqrt(210.0)));
1162 EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 1.0 / (1.0 + 5.5));
1163 EXPECT_EQ(relative_info.relative_l2_dual_residual, 1.0 / (1.0 + sqrt(36.25)));
1167 EXPECT_EQ(relative_info.relative_optimality_gap, 5.0 / (1.0 + 15.0));
T ParseTextOrDie(const std::string &input)
double EpsilonRatio(const double epsilon_absolute, const double epsilon_relative)
bool OptimalityCriteriaMet(const TerminationCriteria::DetailedOptimalityCriteria &optimality_criteria, const ConvergenceInformation &stats, const OptimalityNorm optimality_norm, const QuadraticProgramBoundNorms &bound_norms)
bool ObjectiveGapMet(const TerminationCriteria::DetailedOptimalityCriteria &optimality_criteria, const ConvergenceInformation &stats)
TerminationCriteria::DetailedOptimalityCriteria EffectiveOptimalityCriteria(const TerminationCriteria &termination_criteria)
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)
std::optional< TerminationReasonAndPointType > CheckSimpleTerminationCriteria(const TerminationCriteria &criteria, const IterationStats &stats, const std::atomic< bool > *interrupt_solve)
QuadraticProgramBoundNorms BoundNormsFromProblemStats(const QuadraticProgramStats &stats)
bool operator==(const TerminationCriteria::DetailedOptimalityCriteria &lhs, const TerminationCriteria::DetailedOptimalityCriteria &rhs)
TEST(LinearAssignmentTest, NullMatrix)
TerminationCriteria test_criteria_