Files
ortools-clone/ortools/pdlp/termination_test.cc
2025-09-29 17:21:58 +02:00

1141 lines
47 KiB
C++

// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/pdlp/termination.h"
#include <atomic>
#include <cmath>
#include <limits>
#include <optional>
#include "gtest/gtest.h"
#include "ortools/base/gmock.h"
#include "ortools/base/parse_text_proto.h"
#include "ortools/pdlp/solve_log.pb.h"
#include "ortools/pdlp/solvers.pb.h"
namespace operations_research::pdlp {
namespace {
using ::google::protobuf::contrib::parse_proto::ParseTextOrDie;
using ::testing::EqualsProto;
using ::testing::FieldsAre;
using ::testing::Optional;
QuadraticProgramBoundNorms TestLpBoundNorms() {
return {.l2_norm_primal_linear_objective = std::sqrt(36.25),
.l2_norm_constraint_bounds = std::sqrt(210.0),
.l_inf_norm_primal_linear_objective = 5.5,
.l_inf_norm_constraint_bounds = 12.0};
}
QuadraticProgramBoundNorms ZeroLpBoundNorms() {
return {.l2_norm_primal_linear_objective = 0.0,
.l2_norm_constraint_bounds = 0.0,
.l_inf_norm_primal_linear_objective = 0.0,
.l_inf_norm_constraint_bounds = 0.0};
}
class SimpleTerminationTest : public testing::Test {
protected:
void SetUp() override {
test_criteria_ = ParseTextOrDie<TerminationCriteria>(R"pb(
time_sec_limit: 1.0
kkt_matrix_pass_limit: 2000
iteration_limit: 10)pb");
}
TerminationCriteria test_criteria_;
};
class IterateTerminationTest : public testing::TestWithParam<OptimalityNorm> {
protected:
void SetUp() override {
test_criteria_ = ParseTextOrDie<TerminationCriteria>(R"pb(
simple_optimality_criteria {
eps_optimal_absolute: 1.0e-4
eps_optimal_relative: 1.0e-4
}
eps_primal_infeasible: 1.0e-6
eps_dual_infeasible: 1.0e-6
time_sec_limit: 1.0
kkt_matrix_pass_limit: 2000
iteration_limit: 10)pb");
test_criteria_.set_optimality_norm(GetParam());
}
TerminationCriteria test_criteria_;
};
class DetailedRelativeTerminationTest
: public testing::TestWithParam<OptimalityNorm> {
protected:
void SetUp() override {
test_criteria_ = ParseTextOrDie<TerminationCriteria>(R"pb(
detailed_optimality_criteria {
eps_optimal_primal_residual_absolute: 0.0
eps_optimal_primal_residual_relative: 1.0e-4
eps_optimal_dual_residual_absolute: 0.0
eps_optimal_dual_residual_relative: 1.0e-4
eps_optimal_objective_gap_absolute: 0.0
eps_optimal_objective_gap_relative: 1.0e-4
}
)pb");
test_criteria_.set_optimality_norm(GetParam());
}
TerminationCriteria test_criteria_;
};
class DetailedAbsoluteTerminationTest
: public testing::TestWithParam<OptimalityNorm> {
protected:
void SetUp() override {
test_criteria_ = ParseTextOrDie<TerminationCriteria>(R"pb(
detailed_optimality_criteria {
eps_optimal_primal_residual_absolute: 1.0e-4
eps_optimal_primal_residual_relative: 0.0
eps_optimal_dual_residual_absolute: 1.0e-4
eps_optimal_dual_residual_relative: 0.0
eps_optimal_objective_gap_absolute: 1.0e-4
eps_optimal_objective_gap_relative: 0.0
}
)pb");
test_criteria_.set_optimality_norm(GetParam());
}
TerminationCriteria test_criteria_;
};
TEST(EffectiveOptimalityCriteriaTest, SimpleOptimalityCriteriaOverload) {
const auto criteria =
ParseTextOrDie<TerminationCriteria::SimpleOptimalityCriteria>(
R"pb(eps_optimal_absolute: 1.0e-4 eps_optimal_relative: 2.0e-4)pb");
EXPECT_THAT(EffectiveOptimalityCriteria(criteria),
EqualsProto(
R"pb(
eps_optimal_primal_residual_absolute: 1.0e-4
eps_optimal_primal_residual_relative: 2.0e-4
eps_optimal_dual_residual_absolute: 1.0e-4
eps_optimal_dual_residual_relative: 2.0e-4
eps_optimal_objective_gap_absolute: 1.0e-4
eps_optimal_objective_gap_relative: 2.0e-4
)pb"));
}
TEST(EffectiveOptimalityCriteriaTest, SimpleOptimalityCriteriaInput) {
const auto criteria =
ParseTextOrDie<TerminationCriteria>(R"pb(simple_optimality_criteria {
eps_optimal_absolute: 1.0e-4
eps_optimal_relative: 2.0e-4
})pb");
EXPECT_THAT(EffectiveOptimalityCriteria(criteria),
EqualsProto(
R"pb(
eps_optimal_primal_residual_absolute: 1.0e-4
eps_optimal_primal_residual_relative: 2.0e-4
eps_optimal_dual_residual_absolute: 1.0e-4
eps_optimal_dual_residual_relative: 2.0e-4
eps_optimal_objective_gap_absolute: 1.0e-4
eps_optimal_objective_gap_relative: 2.0e-4
)pb"));
}
TEST(EffectiveOptimalityCriteriaTest, DetailedOptimalityCriteriaInput) {
const auto criteria = ParseTextOrDie<TerminationCriteria>(
R"pb(detailed_optimality_criteria {
eps_optimal_primal_residual_absolute: 1.0e-4
eps_optimal_primal_residual_relative: 2.0e-4
eps_optimal_dual_residual_absolute: 3.0e-4
eps_optimal_dual_residual_relative: 4.0e-4
eps_optimal_objective_gap_absolute: 5.0e-4
eps_optimal_objective_gap_relative: 6.0e-4
})pb");
EXPECT_THAT(EffectiveOptimalityCriteria(criteria),
EqualsProto(criteria.detailed_optimality_criteria()));
}
TEST(EffectiveOptimalityCriteriaTest, DeprecatedInput) {
const auto criteria = ParseTextOrDie<TerminationCriteria>(
R"pb(eps_optimal_absolute: 1.0e-4 eps_optimal_relative: 2.0e-4)pb");
EXPECT_THAT(EffectiveOptimalityCriteria(criteria),
EqualsProto(
R"pb(
eps_optimal_primal_residual_absolute: 1.0e-4
eps_optimal_primal_residual_relative: 2.0e-4
eps_optimal_dual_residual_absolute: 1.0e-4
eps_optimal_dual_residual_relative: 2.0e-4
eps_optimal_objective_gap_absolute: 1.0e-4
eps_optimal_objective_gap_relative: 2.0e-4
)pb"));
}
TEST_P(DetailedRelativeTerminationTest, TerminationWithNearOptimal) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.00019
dual_objective: 1.0
l_inf_primal_residual: 11.0e-4
l_inf_dual_residual: 5.4e-4
l2_primal_residual: 14.0e-4
l2_dual_residual: 6.0e-4
l_inf_componentwise_primal_residual: 9.0e-5
l_inf_componentwise_dual_residual: 9.0e-5
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
EXPECT_TRUE(ObjectiveGapMet(test_criteria_.detailed_optimality_criteria(),
stats.convergence_information(0)));
EXPECT_TRUE(OptimalityCriteriaMet(
test_criteria_.detailed_optimality_criteria(),
stats.convergence_information(0), test_criteria_.optimality_norm(),
TestLpBoundNorms()));
EXPECT_THAT(CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms()),
Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
POINT_TYPE_CURRENT_ITERATE)));
}
TEST_P(DetailedRelativeTerminationTest, NoTerminationWithExcessiveGap) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.00021
dual_objective: 1.0
l_inf_primal_residual: 11.0e-4
l_inf_dual_residual: 5.4e-4
l2_primal_residual: 14.0e-4
l2_dual_residual: 6.0e-4
l_inf_componentwise_primal_residual: 9.0e-5
l_inf_componentwise_dual_residual: 9.0e-5
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
EXPECT_FALSE(ObjectiveGapMet(test_criteria_.detailed_optimality_criteria(),
stats.convergence_information(0)));
EXPECT_FALSE(OptimalityCriteriaMet(
test_criteria_.detailed_optimality_criteria(),
stats.convergence_information(0), test_criteria_.optimality_norm(),
TestLpBoundNorms()));
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms()),
std::nullopt);
}
TEST_P(DetailedRelativeTerminationTest,
NoTerminationWithExcessivePrimalResidual) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.00019
dual_objective: 1.0
l_inf_primal_residual: 13.0e-4
l_inf_dual_residual: 5.4e-4
l2_primal_residual: 15.0e-4
l2_dual_residual: 6.0e-4
l_inf_componentwise_primal_residual: 1.1e-4
l_inf_componentwise_dual_residual: 9.0e-5
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
EXPECT_FALSE(OptimalityCriteriaMet(
test_criteria_.detailed_optimality_criteria(),
stats.convergence_information(0), test_criteria_.optimality_norm(),
TestLpBoundNorms()));
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms()),
std::nullopt);
}
TEST_P(DetailedRelativeTerminationTest,
NoTerminationWithExcessiveDualResidual) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.00019
dual_objective: 1.0
l_inf_primal_residual: 11.0e-4
l_inf_dual_residual: 5.6e-4
l2_primal_residual: 14.0e-4
l2_dual_residual: 7.0e-4
l_inf_componentwise_primal_residual: 9.0e-5
l_inf_componentwise_dual_residual: 1.1e-4
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
EXPECT_FALSE(OptimalityCriteriaMet(
test_criteria_.detailed_optimality_criteria(),
stats.convergence_information(0), test_criteria_.optimality_norm(),
TestLpBoundNorms()));
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms()),
std::nullopt);
}
TEST_P(DetailedAbsoluteTerminationTest, TerminationWithNearOptimal) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.00009
dual_objective: 1.0
l_inf_primal_residual: 9.0e-5
l_inf_dual_residual: 9.0e-5
l2_primal_residual: 9.0e-5
l2_dual_residual: 9.0e-5
l_inf_componentwise_primal_residual: 0.0
l_inf_componentwise_dual_residual: 0.0
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
EXPECT_TRUE(ObjectiveGapMet(test_criteria_.detailed_optimality_criteria(),
stats.convergence_information(0)));
EXPECT_TRUE(OptimalityCriteriaMet(
test_criteria_.detailed_optimality_criteria(),
stats.convergence_information(0), test_criteria_.optimality_norm(),
TestLpBoundNorms()));
EXPECT_THAT(CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms()),
Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
POINT_TYPE_CURRENT_ITERATE)));
}
TEST_P(DetailedAbsoluteTerminationTest, NoTerminationWithExcessiveGap) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.00011
dual_objective: 1.0
l_inf_primal_residual: 9.0e-5
l_inf_dual_residual: 9.0e-5
l2_primal_residual: 9.0e-5
l2_dual_residual: 9.0e-5
l_inf_componentwise_primal_residual: 0.0
l_inf_componentwise_dual_residual: 0.0
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
EXPECT_FALSE(ObjectiveGapMet(test_criteria_.detailed_optimality_criteria(),
stats.convergence_information(0)));
EXPECT_FALSE(OptimalityCriteriaMet(
test_criteria_.detailed_optimality_criteria(),
stats.convergence_information(0), test_criteria_.optimality_norm(),
TestLpBoundNorms()));
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms()),
std::nullopt);
}
TEST_P(DetailedAbsoluteTerminationTest,
NoTerminationWithExcessivePrimalResidual) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.00009
dual_objective: 1.0
l_inf_primal_residual: 11.0e-5
l_inf_dual_residual: 9.0e-5
l2_primal_residual: 11.0e-5
l2_dual_residual: 9.0e-5
l_inf_componentwise_primal_residual: 1.0e-6
l_inf_componentwise_dual_residual: 0.0
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
EXPECT_FALSE(OptimalityCriteriaMet(
test_criteria_.detailed_optimality_criteria(),
stats.convergence_information(0), test_criteria_.optimality_norm(),
TestLpBoundNorms()));
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms()),
std::nullopt);
}
TEST_P(DetailedAbsoluteTerminationTest,
NoTerminationWithExcessiveDualResidual) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.00009
dual_objective: 1.0
l_inf_primal_residual: 9.0e-5
l_inf_dual_residual: 11.0e-5
l2_primal_residual: 9.0e-5
l2_dual_residual: 11.0e-5
l_inf_componentwise_primal_residual: 0.0
l_inf_componentwise_dual_residual: 1.0e-6
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
EXPECT_FALSE(OptimalityCriteriaMet(
test_criteria_.detailed_optimality_criteria(),
stats.convergence_information(0), test_criteria_.optimality_norm(),
TestLpBoundNorms()));
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms()),
std::nullopt);
}
TEST_P(IterateTerminationTest, NoTerminationWithLargeGap) {
IterationStats stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
# Ensures that optimality conditions are not met.
primal_objective: 50.0
dual_objective: -50.0
})pb");
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms()),
std::nullopt);
}
TEST_F(SimpleTerminationTest, NoTerminationWithEmptyIterationStats) {
IterationStats stats;
EXPECT_EQ(CheckSimpleTerminationCriteria(test_criteria_, stats),
std::nullopt);
}
TEST_P(IterateTerminationTest, NoTerminationWithEmptyIterationStats) {
IterationStats stats;
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms()),
std::nullopt);
}
TEST_F(SimpleTerminationTest, TerminationWithInterruptSolve) {
IterationStats stats;
std::atomic<bool> interrupt_solve = true;
std::optional<TerminationReasonAndPointType> maybe_result =
CheckSimpleTerminationCriteria(test_criteria_, stats, &interrupt_solve);
EXPECT_THAT(maybe_result,
Optional(FieldsAre(TERMINATION_REASON_INTERRUPTED_BY_USER,
POINT_TYPE_NONE)));
}
TEST_P(IterateTerminationTest, TerminationWithNumericalError) {
IterationStats stats;
std::optional<TerminationReasonAndPointType> maybe_result =
CheckIterateTerminationCriteria(test_criteria_, stats, TestLpBoundNorms(),
/*force_numerical_termination=*/true);
EXPECT_THAT(
maybe_result,
Optional(FieldsAre(TERMINATION_REASON_NUMERICAL_ERROR, POINT_TYPE_NONE)));
}
TEST_F(SimpleTerminationTest, TerminationWithTimeLimit) {
const auto stats =
ParseTextOrDie<IterationStats>(R"pb(cumulative_time_sec: 100.0)pb");
std::optional<TerminationReasonAndPointType> maybe_result =
CheckSimpleTerminationCriteria(test_criteria_, stats);
EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_TIME_LIMIT,
POINT_TYPE_NONE)));
}
TEST_F(SimpleTerminationTest, TerminationWithKktMatrixPassLimit) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
cumulative_kkt_matrix_passes: 2500)pb");
std::optional<TerminationReasonAndPointType> maybe_result =
CheckSimpleTerminationCriteria(test_criteria_, stats);
EXPECT_THAT(maybe_result,
Optional(FieldsAre(TERMINATION_REASON_KKT_MATRIX_PASS_LIMIT,
POINT_TYPE_NONE)));
}
TEST_F(SimpleTerminationTest, TerminationWithIterationLimit) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
iteration_number: 20)pb");
std::optional<TerminationReasonAndPointType> maybe_result =
CheckSimpleTerminationCriteria(test_criteria_, stats);
EXPECT_THAT(
maybe_result,
Optional(FieldsAre(TERMINATION_REASON_ITERATION_LIMIT, POINT_TYPE_NONE)));
}
TEST_P(IterateTerminationTest, PrimalInfeasibleFromIterateDifference) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
infeasibility_information: {
dual_ray_objective: 1.0
max_dual_ray_infeasibility: 1.0e-16
candidate_type: POINT_TYPE_ITERATE_DIFFERENCE
})pb");
std::optional<TerminationReasonAndPointType> maybe_result =
CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms());
EXPECT_THAT(maybe_result,
Optional(FieldsAre(TERMINATION_REASON_PRIMAL_INFEASIBLE,
POINT_TYPE_ITERATE_DIFFERENCE)));
}
TEST_P(IterateTerminationTest, NoTerminationWithInfeasibleDualRay) {
const auto stats_infeasible_ray = ParseTextOrDie<IterationStats>(R"pb(
infeasibility_information: {
dual_ray_objective: 1.0
max_dual_ray_infeasibility: 1.0e-5 # Too large
})pb");
EXPECT_EQ(CheckIterateTerminationCriteria(
test_criteria_, stats_infeasible_ray, TestLpBoundNorms()),
std::nullopt);
}
TEST_P(IterateTerminationTest, NoTerminationWithNegativeDualRayObjective) {
const auto stats_wrong_sign = ParseTextOrDie<IterationStats>(R"pb(
infeasibility_information: {
dual_ray_objective: -1.0 # Wrong sign
max_dual_ray_infeasibility: 0.0
})pb");
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats_wrong_sign,
TestLpBoundNorms()),
std::nullopt);
}
TEST_P(IterateTerminationTest, NoTerminationWithZeroDualRayObjective) {
const auto stats_objective_zero = ParseTextOrDie<IterationStats>(R"pb(
infeasibility_information: {
dual_ray_objective: 0.0
max_dual_ray_infeasibility: 0.0
})pb");
EXPECT_EQ(CheckIterateTerminationCriteria(
test_criteria_, stats_objective_zero, TestLpBoundNorms()),
std::nullopt);
}
TEST_P(IterateTerminationTest, DualInfeasibleFromAverageIterate) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
infeasibility_information: {
primal_ray_linear_objective: -1.0
max_primal_ray_infeasibility: 1.0e-16
candidate_type: POINT_TYPE_AVERAGE_ITERATE
})pb");
std::optional<TerminationReasonAndPointType> maybe_result =
CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms());
EXPECT_THAT(maybe_result,
Optional(FieldsAre(TERMINATION_REASON_DUAL_INFEASIBLE,
POINT_TYPE_AVERAGE_ITERATE)));
}
TEST_P(IterateTerminationTest, NoTerminationWithInfeasiblePrimalRay) {
const auto stats_infeasible_ray = ParseTextOrDie<IterationStats>(R"pb(
infeasibility_information: {
primal_ray_linear_objective: -1.0
max_primal_ray_infeasibility: 1.0e-5 # Too large
})pb");
EXPECT_EQ(CheckIterateTerminationCriteria(
test_criteria_, stats_infeasible_ray, TestLpBoundNorms()),
std::nullopt);
}
TEST_P(IterateTerminationTest, NoTerminationWithPositivePrimalRayObjective) {
const auto stats_wrong_sign = ParseTextOrDie<IterationStats>(R"pb(
infeasibility_information: {
primal_ray_linear_objective: 1.0 # Wrong sign
max_primal_ray_infeasibility: 0.0
})pb");
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats_wrong_sign,
TestLpBoundNorms()),
std::nullopt);
}
TEST_P(IterateTerminationTest, NoTerminationWithZeroPrimalRayObjective) {
const auto stats_objective_zero = ParseTextOrDie<IterationStats>(R"pb(
infeasibility_information: {
primal_ray_linear_objective: 0.0
max_primal_ray_infeasibility: 0.0
})pb");
EXPECT_EQ(CheckIterateTerminationCriteria(
test_criteria_, stats_objective_zero, TestLpBoundNorms()),
std::nullopt);
}
TEST_P(IterateTerminationTest, TerminationWithOptimal) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.0
dual_objective: 1.0
l_inf_primal_residual: 0.0
l_inf_dual_residual: 0.0
l2_primal_residual: 0.0
l2_dual_residual: 0.0
l_inf_componentwise_primal_residual: 0.0
l_inf_componentwise_dual_residual: 0.0
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
std::optional<TerminationReasonAndPointType> maybe_result =
CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms());
EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
POINT_TYPE_CURRENT_ITERATE)));
}
TEST_P(IterateTerminationTest, TerminationWithNearOptimal) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.00019
dual_objective: 1.0
l_inf_primal_residual: 11.0e-4
l_inf_dual_residual: 5.4e-4
l2_primal_residual: 14.0e-4
l2_dual_residual: 6.0e-4
l_inf_componentwise_primal_residual: 9.0e-5
l_inf_componentwise_dual_residual: 9.0e-5
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
std::optional<TerminationReasonAndPointType> maybe_result =
CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms());
EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
POINT_TYPE_CURRENT_ITERATE)));
}
TEST_P(IterateTerminationTest,
TerminationWithNonOptimalInfiniteAbsoluteTolerances) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.0
dual_objective: 1.0
l_inf_primal_residual: 1.0
l_inf_dual_residual: 1.0
l2_primal_residual: 1.0
l2_dual_residual: 1.0
l_inf_componentwise_primal_residual: 1.0
l_inf_componentwise_dual_residual: 1.0
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_absolute(
std::numeric_limits<double>::infinity());
test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_relative(
0);
std::optional<TerminationReasonAndPointType> maybe_result =
CheckIterateTerminationCriteria(test_criteria_, stats,
ZeroLpBoundNorms());
EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
POINT_TYPE_CURRENT_ITERATE)));
}
TEST_P(IterateTerminationTest,
TerminationWithNonOptimalInfiniteRelativeTolerances) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 0.0
dual_objective: 0.0
l_inf_primal_residual: 1.0
l_inf_dual_residual: 1.0
l2_primal_residual: 1.0
l2_dual_residual: 1.0
l_inf_componentwise_primal_residual: 1.0
l_inf_componentwise_dual_residual: 1.0
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_absolute(
0);
test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_relative(
std::numeric_limits<double>::infinity());
std::optional<TerminationReasonAndPointType> maybe_result =
CheckIterateTerminationCriteria(test_criteria_, stats,
ZeroLpBoundNorms());
EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
POINT_TYPE_CURRENT_ITERATE)));
}
TEST_P(IterateTerminationTest,
TerminationWithNonOptimalInfiniteAbsoluteAndRelativeTolerances) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.0
dual_objective: 1.0
l_inf_primal_residual: 1.0
l_inf_dual_residual: 1.0
l2_primal_residual: 1.0
l2_dual_residual: 1.0
l_inf_componentwise_primal_residual: 1.0
l_inf_componentwise_dual_residual: 1.0
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_absolute(
std::numeric_limits<double>::infinity());
test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_relative(
std::numeric_limits<double>::infinity());
std::optional<TerminationReasonAndPointType> maybe_result =
CheckIterateTerminationCriteria(test_criteria_, stats,
ZeroLpBoundNorms());
EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
POINT_TYPE_CURRENT_ITERATE)));
}
TEST_P(IterateTerminationTest, OptimalEvenWithNumericalError) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.0
dual_objective: 1.0
l_inf_primal_residual: 0.0
l_inf_dual_residual: 0.0
l2_primal_residual: 0.0
l2_dual_residual: 0.0
l_inf_componentwise_primal_residual: 0.0
l_inf_componentwise_dual_residual: 0.0
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
// Tests that `TERMINATION_REASON_OPTIMAL` overrides
// `TERMINATION_REASON_NUMERICAL_ERROR` when
// `force_numerical_termination == true`.
std::optional<TerminationReasonAndPointType> maybe_result =
CheckIterateTerminationCriteria(test_criteria_, stats, TestLpBoundNorms(),
/*force_numerical_termination=*/true);
EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
POINT_TYPE_CURRENT_ITERATE)));
}
TEST_P(IterateTerminationTest, NoTerminationWithBadGap) {
const auto stats_bad_gap = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 10.0
dual_objective: 1.0
l_inf_primal_residual: 0.0
l_inf_dual_residual: 0.0
l2_primal_residual: 0.0
l2_dual_residual: 0.0
l_inf_componentwise_primal_residual: 0.0
l_inf_componentwise_dual_residual: 0.0
})pb");
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats_bad_gap,
TestLpBoundNorms()),
std::nullopt);
}
TEST_P(IterateTerminationTest, NoTerminationWithInfiniteGap) {
const auto stats_infinite_gap = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 0
dual_objective: -Inf
l_inf_primal_residual: 0.0
l_inf_dual_residual: 0.0
l2_primal_residual: 0.0
l2_dual_residual: 0.0
l_inf_componentwise_primal_residual: 0.0
l_inf_componentwise_dual_residual: 0.0
})pb");
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats_infinite_gap,
TestLpBoundNorms()),
std::nullopt);
}
TEST_P(IterateTerminationTest, NoTerminationWithBadPrimalResidual) {
const auto stats_bad_primal = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.0
dual_objective: 1.0
l_inf_primal_residual: 1.0
l_inf_dual_residual: 0.0
l2_primal_residual: 1.0
l2_dual_residual: 0.0
l_inf_componentwise_primal_residual: 1.0
l_inf_componentwise_dual_residual: 0.0
})pb");
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats_bad_primal,
TestLpBoundNorms()),
std::nullopt);
}
TEST_P(IterateTerminationTest, NoTerminationWithBadDualResidual) {
const auto stats_bad_dual = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.0
dual_objective: 1.0
l_inf_primal_residual: 0.0
l_inf_dual_residual: 1.0
l2_primal_residual: 0.0
l2_dual_residual: 1.0
l_inf_componentwise_primal_residual: 0.0
l_inf_componentwise_dual_residual: 1.0
})pb");
EXPECT_EQ(CheckIterateTerminationCriteria(test_criteria_, stats_bad_dual,
TestLpBoundNorms()),
std::nullopt);
}
// Tests that optimality is checked with non-strict inequalities, as per the
// definitions in solvers.proto.
TEST_P(IterateTerminationTest, ZeroToleranceZeroError) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
primal_objective: 1.0
dual_objective: 1.0
l_inf_primal_residual: 0.0
l_inf_dual_residual: 0.0
l2_primal_residual: 0.0
l2_dual_residual: 0.0
l_inf_componentwise_primal_residual: 0.0
l_inf_componentwise_dual_residual: 0.0
candidate_type: POINT_TYPE_CURRENT_ITERATE
})pb");
test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_absolute(
0.0);
test_criteria_.mutable_simple_optimality_criteria()->set_eps_optimal_relative(
0.0);
std::optional<TerminationReasonAndPointType> maybe_result =
CheckIterateTerminationCriteria(test_criteria_, stats,
TestLpBoundNorms());
EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
POINT_TYPE_CURRENT_ITERATE)));
}
INSTANTIATE_TEST_SUITE_P(OptNorm, IterateTerminationTest,
testing::Values(OPTIMALITY_NORM_L_INF,
OPTIMALITY_NORM_L2,
OPTIMALITY_NORM_L_INF_COMPONENTWISE));
INSTANTIATE_TEST_SUITE_P(DetailedRelativeOptNorm,
DetailedRelativeTerminationTest,
testing::Values(OPTIMALITY_NORM_L_INF,
OPTIMALITY_NORM_L2,
OPTIMALITY_NORM_L_INF_COMPONENTWISE));
INSTANTIATE_TEST_SUITE_P(DetailedAbsoluteOptNorm,
DetailedAbsoluteTerminationTest,
testing::Values(OPTIMALITY_NORM_L_INF,
OPTIMALITY_NORM_L2,
OPTIMALITY_NORM_L_INF_COMPONENTWISE));
TEST(IterateTerminationTest, OptimalityNormsDiffer) {
auto test_criteria = ParseTextOrDie<TerminationCriteria>(R"pb(
simple_optimality_criteria { eps_optimal_relative: 1.0 })pb");
// For L2, optimality requires norm(`primal_residual`, 2) <= 14.49
// For L_inf, optimality requires norm(`primal_residual`, Inf) <= 12.0
// For L_inf_componentwise, optimality requires norm(`primal_residual`) <= 1.0
struct {
double primal_residual;
std::optional<TerminationReasonAndPointType> expected_l2;
std::optional<TerminationReasonAndPointType> expected_l_inf;
std::optional<TerminationReasonAndPointType> expected_l_inf_relative;
} test_configs[] = {
{0.5,
TerminationReasonAndPointType{.reason = TERMINATION_REASON_OPTIMAL,
.type = POINT_TYPE_CURRENT_ITERATE},
TerminationReasonAndPointType{.reason = TERMINATION_REASON_OPTIMAL,
.type = POINT_TYPE_CURRENT_ITERATE},
TerminationReasonAndPointType{.reason = TERMINATION_REASON_OPTIMAL,
.type = POINT_TYPE_CURRENT_ITERATE}},
{10.0,
TerminationReasonAndPointType{.reason = TERMINATION_REASON_OPTIMAL,
.type = POINT_TYPE_CURRENT_ITERATE},
TerminationReasonAndPointType{.reason = TERMINATION_REASON_OPTIMAL,
.type = POINT_TYPE_CURRENT_ITERATE},
std::nullopt},
{13.0,
TerminationReasonAndPointType{.reason = TERMINATION_REASON_OPTIMAL,
.type = POINT_TYPE_CURRENT_ITERATE},
std::nullopt, std::nullopt},
{15.0, std::nullopt, std::nullopt, std::nullopt}};
for (const auto& config : test_configs) {
IterationStats stats;
auto* convergence_info = stats.add_convergence_information();
convergence_info->set_primal_objective(1.0);
convergence_info->set_dual_objective(1.0);
convergence_info->set_l_inf_primal_residual(config.primal_residual);
convergence_info->set_l2_primal_residual(config.primal_residual);
convergence_info->set_l_inf_componentwise_primal_residual(
config.primal_residual);
convergence_info->set_candidate_type(POINT_TYPE_CURRENT_ITERATE);
test_criteria.set_optimality_norm(OPTIMALITY_NORM_L_INF);
std::optional<TerminationReasonAndPointType> maybe_result =
CheckIterateTerminationCriteria(test_criteria, stats,
TestLpBoundNorms());
ASSERT_TRUE(maybe_result.has_value() == config.expected_l_inf.has_value())
<< "primal_residual: " << config.primal_residual;
if (config.expected_l_inf.has_value()) {
EXPECT_EQ(maybe_result->reason, config.expected_l_inf->reason);
EXPECT_EQ(maybe_result->type, config.expected_l_inf->type);
}
test_criteria.set_optimality_norm(OPTIMALITY_NORM_L2);
maybe_result = CheckIterateTerminationCriteria(test_criteria, stats,
TestLpBoundNorms());
ASSERT_TRUE(maybe_result.has_value() == config.expected_l2.has_value())
<< "primal_residual: " << config.primal_residual;
if (config.expected_l2.has_value()) {
EXPECT_EQ(maybe_result->reason, config.expected_l2->reason);
EXPECT_EQ(maybe_result->type, config.expected_l2->type);
}
test_criteria.set_optimality_norm(OPTIMALITY_NORM_L_INF_COMPONENTWISE);
maybe_result = CheckIterateTerminationCriteria(test_criteria, stats,
TestLpBoundNorms());
ASSERT_TRUE(maybe_result.has_value() ==
config.expected_l_inf_relative.has_value())
<< "primal_residual: " << config.primal_residual;
if (config.expected_l_inf_relative.has_value()) {
EXPECT_EQ(maybe_result->reason, config.expected_l_inf_relative->reason);
EXPECT_EQ(maybe_result->type, config.expected_l_inf_relative->type);
}
}
}
TEST(BoundNormsFromProblemStats, ExtractsBoundNorms) {
const auto qp_stats = ParseTextOrDie<QuadraticProgramStats>(R"pb(
objective_vector_l2_norm: 4.0
combined_bounds_l2_norm: 3.0
objective_vector_abs_max: 1.0
combined_bounds_max: 2.0
)pb");
const QuadraticProgramBoundNorms norms = BoundNormsFromProblemStats(qp_stats);
EXPECT_EQ(norms.l2_norm_primal_linear_objective, 4.0);
EXPECT_EQ(norms.l2_norm_constraint_bounds, 3.0);
EXPECT_EQ(norms.l_inf_norm_primal_linear_objective, 1.0);
EXPECT_EQ(norms.l_inf_norm_constraint_bounds, 2.0);
}
TEST(EpsilonRatio, SimpleChecks) {
EXPECT_EQ(EpsilonRatio(0.0, 0.0), 1.0);
EXPECT_EQ(EpsilonRatio(1.0, 1.0), 1.0);
EXPECT_EQ(EpsilonRatio(std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity()),
1.0);
EXPECT_EQ(EpsilonRatio(1.0, 2.0), 0.5);
EXPECT_EQ(EpsilonRatio(2.0, 1.0), 2.0);
EXPECT_EQ(EpsilonRatio(0.0, std::numeric_limits<double>::infinity()), 0.0);
EXPECT_EQ(EpsilonRatio(std::numeric_limits<double>::infinity(), 0.0),
std::numeric_limits<double>::infinity());
}
TEST(ComputeRelativeResiduals,
ComputesRelativeResidualsForZeroAbsoluteTolerance) {
ConvergenceInformation stats;
// If the absolute error tolerance is 0.0 and the relative error tolerance is
// nonzero, the relative residuals are just the absolute residuals divided by
// the corresponding norms (the actual nonzero value of the relative error
// tolerance doesn't matter).
stats.set_primal_objective(10.0);
stats.set_dual_objective(5.0);
stats.set_l_inf_primal_residual(1.0);
stats.set_l2_primal_residual(1.0);
stats.set_l_inf_dual_residual(1.0);
stats.set_l2_dual_residual(1.0);
TerminationCriteria termination_criteria;
termination_criteria.mutable_simple_optimality_criteria()
->set_eps_optimal_absolute(0.0);
termination_criteria.mutable_simple_optimality_criteria()
->set_eps_optimal_relative(1.0e-6);
const RelativeConvergenceInformation relative_info = ComputeRelativeResiduals(
EffectiveOptimalityCriteria(termination_criteria), stats,
TestLpBoundNorms());
EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 1.0 / 12.0);
EXPECT_EQ(relative_info.relative_l2_primal_residual, 1.0 / std::sqrt(210.0));
EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 1.0 / 5.5);
EXPECT_EQ(relative_info.relative_l2_dual_residual, 1.0 / sqrt(36.25));
// The relative optimality gap should just be the objective difference divided
// by the sum of absolute values (the actual nonzero value of the relative
// error tolerance doesn't matter).
EXPECT_EQ(relative_info.relative_optimality_gap, 5.0 / 15.0);
}
TEST(ComputeRelativeResiduals,
ComputesRelativeResidualsForZeroRelativeTolerance) {
ConvergenceInformation stats;
// If the relative error tolerance is 0.0 and the absolute error tolerance
// is nonzero, all of the relative residuals and the relative optimality gap
// should be 0.0, no matter what the absolute error tolerance is.
stats.set_primal_objective(10.0);
stats.set_dual_objective(5.0);
stats.set_l_inf_primal_residual(1.0);
stats.set_l2_primal_residual(1.0);
stats.set_l_inf_dual_residual(1.0);
stats.set_l2_dual_residual(1.0);
TerminationCriteria::SimpleOptimalityCriteria opt_criteria;
opt_criteria.set_eps_optimal_absolute(1.0e-6);
opt_criteria.set_eps_optimal_relative(0.0);
const RelativeConvergenceInformation relative_info = ComputeRelativeResiduals(
EffectiveOptimalityCriteria(opt_criteria), stats, TestLpBoundNorms());
EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 0.0);
EXPECT_EQ(relative_info.relative_l2_primal_residual, 0.0);
EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 0.0);
EXPECT_EQ(relative_info.relative_l2_dual_residual, 0.0);
EXPECT_EQ(relative_info.relative_optimality_gap, 0.0);
}
TEST(ComputeRelativeResiduals,
ComputesCorrectRelativeResidualsForEqualTolerances) {
ConvergenceInformation stats;
// If the absolute error tolerance and relative error tolerance are equal (and
// nonzero), the relative residuals are the absolute residuals divided by 1.0
// plus the corresponding norms.
stats.set_primal_objective(10.0);
stats.set_dual_objective(5.0);
stats.set_l_inf_primal_residual(1.0);
stats.set_l2_primal_residual(1.0);
stats.set_l_inf_dual_residual(1.0);
stats.set_l2_dual_residual(1.0);
TerminationCriteria::SimpleOptimalityCriteria opt_criteria;
opt_criteria.set_eps_optimal_absolute(1.0e-6);
opt_criteria.set_eps_optimal_relative(1.0e-6);
const RelativeConvergenceInformation relative_info = ComputeRelativeResiduals(
EffectiveOptimalityCriteria(opt_criteria), stats, TestLpBoundNorms());
EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 1.0 / (1.0 + 12.0));
EXPECT_EQ(relative_info.relative_l2_primal_residual,
1.0 / (1.0 + std::sqrt(210.0)));
EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 1.0 / (1.0 + 5.5));
EXPECT_EQ(relative_info.relative_l2_dual_residual, 1.0 / (1.0 + sqrt(36.25)));
// The relative optimality gap should just be the objective difference divided
// by 1.0 + the sum of absolute values.
EXPECT_EQ(relative_info.relative_optimality_gap, 5.0 / (1.0 + 15.0));
}
TEST(ComputeRelativeResiduals,
ComputesCorrectRelativeResidualsForBothTolerancesZero) {
ConvergenceInformation stats;
// If the absolute error tolerance and relative error tolerance are both zero,
// the relative residuals are the same as when the tolerances are equal and
// nonzero.
stats.set_primal_objective(10.0);
stats.set_dual_objective(5.0);
stats.set_l_inf_primal_residual(1.0);
stats.set_l2_primal_residual(1.0);
stats.set_l_inf_dual_residual(1.0);
stats.set_l2_dual_residual(1.0);
TerminationCriteria::SimpleOptimalityCriteria opt_criteria;
opt_criteria.set_eps_optimal_absolute(0.0);
opt_criteria.set_eps_optimal_relative(0.0);
const RelativeConvergenceInformation relative_info = ComputeRelativeResiduals(
EffectiveOptimalityCriteria(opt_criteria), stats, TestLpBoundNorms());
EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 1.0 / (1.0 + 12.0));
EXPECT_EQ(relative_info.relative_l2_primal_residual,
1.0 / (1.0 + std::sqrt(210.0)));
EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 1.0 / (1.0 + 5.5));
EXPECT_EQ(relative_info.relative_l2_dual_residual, 1.0 / (1.0 + sqrt(36.25)));
// The relative optimality gap should just be the objective difference divided
// by 1.0 + the sum of absolute values.
EXPECT_EQ(relative_info.relative_optimality_gap, 5.0 / (1.0 + 15.0));
}
TEST(ComputeRelativeResiduals,
ComputesCorrectRelativeResidualsForDetailedTerminationCriteria) {
ConvergenceInformation stats;
// If the absolute error tolerance and relative error tolerance are equal (and
// nonzero), the relative residuals are the absolute residuals divided by 1.0
// plus the corresponding norms.
stats.set_primal_objective(10.0);
stats.set_dual_objective(5.0);
stats.set_l_inf_primal_residual(1.0);
stats.set_l2_primal_residual(1.0);
stats.set_l_inf_dual_residual(1.0);
stats.set_l2_dual_residual(1.0);
TerminationCriteria::DetailedOptimalityCriteria opt_criteria;
opt_criteria.set_eps_optimal_primal_residual_absolute(2.0e-6);
opt_criteria.set_eps_optimal_primal_residual_relative(2.0e-4);
opt_criteria.set_eps_optimal_dual_residual_absolute(1.0e-3);
opt_criteria.set_eps_optimal_dual_residual_relative(1.0e-4);
opt_criteria.set_eps_optimal_objective_gap_absolute(3.0e-8);
opt_criteria.set_eps_optimal_objective_gap_relative(3.0e-7);
const RelativeConvergenceInformation relative_info =
ComputeRelativeResiduals(opt_criteria, stats, TestLpBoundNorms());
EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 1.0 / (0.01 + 12.0));
EXPECT_EQ(relative_info.relative_l2_primal_residual,
1.0 / (0.01 + std::sqrt(210.0)));
EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 1.0 / (10.0 + 5.5));
EXPECT_EQ(relative_info.relative_l2_dual_residual,
1.0 / (10.0 + sqrt(36.25)));
// The relative optimality gap should just be the objective difference divided
// by 0.1 + the sum of absolute values.
EXPECT_EQ(relative_info.relative_optimality_gap, 5.0 / (0.1 + 15.0));
}
TEST(ComputeRelativeResiduals,
ComputesCorrectRelativeResidualsForInfiniteAbsoluteTolerances) {
ConvergenceInformation stats;
stats.set_primal_objective(10.0);
stats.set_dual_objective(5.0);
stats.set_l_inf_primal_residual(1.0);
stats.set_l2_primal_residual(1.0);
stats.set_l_inf_dual_residual(1.0);
stats.set_l2_dual_residual(1.0);
TerminationCriteria::SimpleOptimalityCriteria opt_criteria;
opt_criteria.set_eps_optimal_absolute(
std::numeric_limits<double>::infinity());
opt_criteria.set_eps_optimal_relative(1.0e-6);
const RelativeConvergenceInformation relative_info = ComputeRelativeResiduals(
EffectiveOptimalityCriteria(opt_criteria), stats, TestLpBoundNorms());
// If absolute tolerance is infinite the relative residuals are zero.
EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 0.0);
EXPECT_EQ(relative_info.relative_l2_primal_residual, 0.0);
EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 0.0);
EXPECT_EQ(relative_info.relative_l2_dual_residual, 0.0);
EXPECT_EQ(relative_info.relative_optimality_gap, 0.0);
}
TEST(ComputeRelativeResiduals,
ComputesCorrectRelativeResidualsForInfiniteRelativeTolerances) {
ConvergenceInformation stats;
stats.set_primal_objective(10.0);
stats.set_dual_objective(5.0);
stats.set_l_inf_primal_residual(1.0);
stats.set_l2_primal_residual(1.0);
stats.set_l_inf_dual_residual(1.0);
stats.set_l2_dual_residual(1.0);
TerminationCriteria::SimpleOptimalityCriteria opt_criteria;
opt_criteria.set_eps_optimal_absolute(1.0e-6);
opt_criteria.set_eps_optimal_relative(
std::numeric_limits<double>::infinity());
const RelativeConvergenceInformation relative_info = ComputeRelativeResiduals(
EffectiveOptimalityCriteria(opt_criteria), stats, TestLpBoundNorms());
EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 1.0 / 12.0);
EXPECT_EQ(relative_info.relative_l2_primal_residual, 1.0 / std::sqrt(210.0));
EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 1.0 / 5.5);
EXPECT_EQ(relative_info.relative_l2_dual_residual, 1.0 / sqrt(36.25));
// The relative optimality gap should just be the objective difference divided
// by the sum of absolute values.
EXPECT_EQ(relative_info.relative_optimality_gap, 5.0 / 15.0);
}
TEST(ComputeRelativeResiduals,
ComputesCorrectRelativeResidualsForInfiniteAbsoluteAndRelativeTolerances) {
ConvergenceInformation stats;
// If the absolute error tolerance and relative error tolerance are both
// infinity (and nonzero), the relative residuals are the absolute residuals
// divided by 1.0 plus the corresponding norms.
stats.set_primal_objective(10.0);
stats.set_dual_objective(5.0);
stats.set_l_inf_primal_residual(1.0);
stats.set_l2_primal_residual(1.0);
stats.set_l_inf_dual_residual(1.0);
stats.set_l2_dual_residual(1.0);
TerminationCriteria::SimpleOptimalityCriteria opt_criteria;
opt_criteria.set_eps_optimal_absolute(
std::numeric_limits<double>::infinity());
opt_criteria.set_eps_optimal_relative(
std::numeric_limits<double>::infinity());
const RelativeConvergenceInformation relative_info = ComputeRelativeResiduals(
EffectiveOptimalityCriteria(opt_criteria), stats, TestLpBoundNorms());
EXPECT_EQ(relative_info.relative_l_inf_primal_residual, 1.0 / (1.0 + 12.0));
EXPECT_EQ(relative_info.relative_l2_primal_residual,
1.0 / (1.0 + std::sqrt(210.0)));
EXPECT_EQ(relative_info.relative_l_inf_dual_residual, 1.0 / (1.0 + 5.5));
EXPECT_EQ(relative_info.relative_l2_dual_residual, 1.0 / (1.0 + sqrt(36.25)));
// The relative optimality gap should just be the objective difference divided
// by 1.0 + the sum of absolute values.
EXPECT_EQ(relative_info.relative_optimality_gap, 5.0 / (1.0 + 15.0));
}
} // namespace
} // namespace operations_research::pdlp