export pdlp from google3

This commit is contained in:
Corentin Le Molgat
2022-09-26 10:27:38 +02:00
parent 779d5912c5
commit ae43368aaf
7 changed files with 304 additions and 71 deletions

View File

@@ -32,6 +32,7 @@
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "ortools/base/check.h"
#include "ortools/base/logging.h"
#include "ortools/base/mathutil.h"
#include "ortools/base/timer.h"
@@ -175,8 +176,8 @@ std::string ToString(const IterationStats& iter_stats,
if (convergence_information.has_value()) {
const RelativeConvergenceInformation relative_information =
ComputeRelativeResiduals(
EffectiveOptimalityCriteria(termination_criteria), bound_norms,
*convergence_information);
EffectiveOptimalityCriteria(termination_criteria),
*convergence_information, bound_norms);
return absl::StrCat(iteration_string, " | ",
ToString(*convergence_information, relative_information,
termination_criteria.optimality_norm()));
@@ -200,8 +201,8 @@ std::string ToShortString(const IterationStats& iter_stats,
if (convergence_information.has_value()) {
const RelativeConvergenceInformation relative_information =
ComputeRelativeResiduals(
EffectiveOptimalityCriteria(termination_criteria), bound_norms,
*convergence_information);
EffectiveOptimalityCriteria(termination_criteria),
*convergence_information, bound_norms);
return absl::StrCat(
iteration_string, " | ",
ToShortString(*convergence_information, relative_information,

View File

@@ -150,7 +150,6 @@ TEST(SharderTest, UniformSharderExcessiveShards) {
VerifySharder(sharder, 7, {1, 1, 1, 1, 1});
}
// Regression test for b/225385276
TEST(SharderTest, UniformSharderHugeNumShards) {
Sharder sharder(/*num_elements=*/5, /*num_shards=*/1'000'000'000, nullptr);
EXPECT_THAT(sharder.ShardStartsForTesting(), ElementsAre(0, 1, 2, 3, 4, 5));

View File

@@ -36,6 +36,7 @@ absl::Status CheckNonNegative(const double value,
}
return absl::OkStatus();
}
absl::Status ValidateTerminationCriteria(const TerminationCriteria& criteria) {
if (criteria.optimality_norm() != OPTIMALITY_NORM_L_INF &&
criteria.optimality_norm() != OPTIMALITY_NORM_L2) {
@@ -100,11 +101,13 @@ absl::Status ValidateTerminationCriteria(const TerminationCriteria& criteria) {
CheckNonNegative(criteria.eps_dual_infeasible(), "eps_dual_infeasible"));
RETURN_IF_ERROR(
CheckNonNegative(criteria.time_sec_limit(), "time_sec_limit"));
if (criteria.iteration_limit() < 0) {
return InvalidArgumentError("iteration_limit must be non-negative");
}
RETURN_IF_ERROR(CheckNonNegative(criteria.kkt_matrix_pass_limit(),
"kkt_matrix_pass_limit"));
return OkStatus();
}

View File

@@ -28,6 +28,7 @@ namespace operations_research::pdlp {
namespace {
using ::google::protobuf::util::ParseTextOrDie;
using ::testing::HasSubstr;
TEST(ValidateTerminationCriteria, DefaultIsValid) {

View File

@@ -24,18 +24,27 @@
namespace operations_research::pdlp {
namespace {
bool OptimalityCriteriaMet(
const OptimalityNorm optimality_norm,
bool ObjectiveGapMet(
const TerminationCriteria::DetailedOptimalityCriteria& optimality_criteria,
const ConvergenceInformation& stats,
const QuadraticProgramBoundNorms& bound_norms) {
const ConvergenceInformation& stats) {
if (std::isinf(optimality_criteria.eps_optimal_objective_gap_absolute()) ||
std::isinf(optimality_criteria.eps_optimal_objective_gap_relative())) {
return true;
}
const double abs_obj =
std::abs(stats.primal_objective()) + std::abs(stats.dual_objective());
const double gap =
std::abs(stats.primal_objective() - stats.dual_objective());
return std::isfinite(abs_obj) &&
gap <= optimality_criteria.eps_optimal_objective_gap_absolute() +
optimality_criteria.eps_optimal_objective_gap_relative() *
abs_obj;
}
bool OptimalityCriteriaMet(
const TerminationCriteria::DetailedOptimalityCriteria& optimality_criteria,
const ConvergenceInformation& stats, const OptimalityNorm optimality_norm,
const QuadraticProgramBoundNorms& bound_norms) {
double primal_err;
double primal_err_baseline;
double dual_err;
@@ -59,20 +68,25 @@ bool OptimalityCriteriaMet(
<< OptimalityNorm_Name(optimality_norm);
}
return dual_err <=
optimality_criteria.eps_optimal_dual_residual_absolute() +
optimality_criteria.eps_optimal_dual_residual_relative() *
dual_err_baseline &&
primal_err <=
optimality_criteria.eps_optimal_primal_residual_absolute() +
optimality_criteria.eps_optimal_primal_residual_relative() *
primal_err_baseline &&
std::isfinite(abs_obj) &&
gap <= optimality_criteria.eps_optimal_objective_gap_absolute() +
optimality_criteria.eps_optimal_objective_gap_relative() *
abs_obj;
const bool primal_err_ok =
std::isinf(optimality_criteria.eps_optimal_primal_residual_absolute()) ||
std::isinf(optimality_criteria.eps_optimal_primal_residual_relative()) ||
primal_err <=
optimality_criteria.eps_optimal_primal_residual_absolute() +
optimality_criteria.eps_optimal_primal_residual_relative() *
primal_err_baseline;
const bool dual_err_ok =
std::isinf(optimality_criteria.eps_optimal_dual_residual_absolute()) ||
std::isinf(optimality_criteria.eps_optimal_dual_residual_relative()) ||
dual_err <= optimality_criteria.eps_optimal_dual_residual_absolute() +
optimality_criteria.eps_optimal_dual_residual_relative() *
dual_err_baseline;
return primal_err_ok && dual_err_ok &&
ObjectiveGapMet(optimality_criteria, stats);
}
namespace {
// Checks if the criteria for primal infeasibility are approximately
// satisfied; see https://developers.google.com/optimization/lp/pdlp_math for
// more details.
@@ -97,6 +111,7 @@ bool DualInfeasibilityCriteriaMet(double eps_dual_infeasible,
}
} // namespace
TerminationCriteria::DetailedOptimalityCriteria EffectiveOptimalityCriteria(
const TerminationCriteria& termination_criteria) {
if (termination_criteria.has_detailed_optimality_criteria()) {
@@ -165,8 +180,8 @@ std::optional<TerminationReasonAndPointType> CheckTerminationCriteria(
TerminationCriteria::DetailedOptimalityCriteria optimality_criteria =
EffectiveOptimalityCriteria(criteria);
for (const auto& convergence_stats : stats.convergence_information()) {
if (OptimalityCriteriaMet(criteria.optimality_norm(), optimality_criteria,
convergence_stats, bound_norms)) {
if (OptimalityCriteriaMet(optimality_criteria, convergence_stats,
criteria.optimality_norm(), bound_norms)) {
return TerminationReasonAndPointType{
.reason = TERMINATION_REASON_OPTIMAL,
.type = convergence_stats.candidate_type()};
@@ -209,11 +224,14 @@ QuadraticProgramBoundNorms BoundNormsFromProblemStats(
RelativeConvergenceInformation ComputeRelativeResiduals(
const TerminationCriteria::DetailedOptimalityCriteria& optimality_criteria,
const QuadraticProgramBoundNorms& norms,
const ConvergenceInformation& stats) {
const ConvergenceInformation& stats,
const QuadraticProgramBoundNorms& bound_norms) {
auto eps_ratio = [](const double absolute, const double relative) {
return relative == 0.0 ? std::numeric_limits<double>::infinity()
: absolute / relative;
// The both-infinite case avoids a NAN.
return (std::isinf(absolute) && std::isinf(relative))
? 1.0
: (relative == 0.0 ? std::numeric_limits<double>::infinity()
: absolute / relative);
};
const double eps_ratio_primal =
eps_ratio(optimality_criteria.eps_optimal_primal_residual_absolute(),
@@ -227,16 +245,16 @@ RelativeConvergenceInformation ComputeRelativeResiduals(
RelativeConvergenceInformation info;
info.relative_l_inf_primal_residual =
stats.l_inf_primal_residual() /
(eps_ratio_primal + norms.l_inf_norm_constraint_bounds);
(eps_ratio_primal + bound_norms.l_inf_norm_constraint_bounds);
info.relative_l2_primal_residual =
stats.l2_primal_residual() /
(eps_ratio_primal + norms.l2_norm_constraint_bounds);
(eps_ratio_primal + bound_norms.l2_norm_constraint_bounds);
info.relative_l_inf_dual_residual =
stats.l_inf_dual_residual() /
(eps_ratio_dual + norms.l_inf_norm_primal_linear_objective);
(eps_ratio_dual + bound_norms.l_inf_norm_primal_linear_objective);
info.relative_l2_dual_residual =
stats.l2_dual_residual() /
(eps_ratio_dual + norms.l2_norm_primal_linear_objective);
(eps_ratio_dual + bound_norms.l2_norm_primal_linear_objective);
const double abs_obj =
std::abs(stats.primal_objective()) + std::abs(stats.dual_objective());
const double gap = stats.primal_objective() - stats.dual_objective();

View File

@@ -44,6 +44,7 @@ TerminationCriteria::DetailedOptimalityCriteria EffectiveOptimalityCriteria(
// unit tests where no TerminationCriteria is naturally available.
TerminationCriteria::DetailedOptimalityCriteria EffectiveOptimalityCriteria(
const TerminationCriteria::SimpleOptimalityCriteria& simple_criteria);
// Checks if any of the simple termination criteria are satisfied by `stats`,
// and returns a termination reason if so, and nullopt otherwise. The "simple"
// termination criteria are `time_sec_limit`, `iteration_limit`,
@@ -108,9 +109,19 @@ struct RelativeConvergenceInformation {
RelativeConvergenceInformation ComputeRelativeResiduals(
const TerminationCriteria::DetailedOptimalityCriteria& optimality_criteria,
const QuadraticProgramBoundNorms& norms,
const ConvergenceInformation& stats,
const QuadraticProgramBoundNorms& bound_norms);
bool ObjectiveGapMet(
const TerminationCriteria::DetailedOptimalityCriteria& optimality_criteria,
const ConvergenceInformation& stats);
// Determines if the optimality criteria are met.
bool OptimalityCriteriaMet(
const TerminationCriteria::DetailedOptimalityCriteria& optimality_criteria,
const ConvergenceInformation& stats, const OptimalityNorm optimality_norm,
const QuadraticProgramBoundNorms& bound_norms);
} // namespace operations_research::pdlp
#endif // PDLP_TERMINATION_H_

View File

@@ -15,6 +15,7 @@
#include <atomic>
#include <cmath>
#include <limits>
#include <optional>
#include "gmock/gmock.h"
@@ -24,10 +25,8 @@
#include "ortools/pdlp/solvers.pb.h"
namespace operations_research::pdlp {
bool operator==(
const TerminationCriteria::DetailedOptimalityCriteria& lhs,
const TerminationCriteria::DetailedOptimalityCriteria& rhs) {
bool operator==(const TerminationCriteria::DetailedOptimalityCriteria& lhs,
const TerminationCriteria::DetailedOptimalityCriteria& rhs) {
if (lhs.eps_optimal_primal_residual_absolute() !=
rhs.eps_optimal_primal_residual_absolute()) {
return false;
@@ -69,6 +68,13 @@ QuadraticProgramBoundNorms TestLpBoundNorms() {
.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 {
@@ -99,6 +105,7 @@ class TerminationTest : public testing::TestWithParam<OptimalityNorm> {
TerminationCriteria test_criteria_;
};
class DetailedRelativeTerminationTest
: public testing::TestWithParam<OptimalityNorm> {
protected:
@@ -143,15 +150,17 @@ 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),
Eq(ParseTextOrDie<TerminationCriteria::DetailedOptimalityCriteria>(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")));
EXPECT_THAT(
EffectiveOptimalityCriteria(criteria),
Eq(ParseTextOrDie<TerminationCriteria::DetailedOptimalityCriteria>(
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) {
@@ -160,15 +169,17 @@ TEST(EffectiveOptimalityCriteriaTest, SimpleOptimalityCriteriaInput) {
eps_optimal_absolute: 1.0e-4
eps_optimal_relative: 2.0e-4
})pb");
EXPECT_THAT(EffectiveOptimalityCriteria(criteria),
Eq(ParseTextOrDie<TerminationCriteria::DetailedOptimalityCriteria>(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")));
EXPECT_THAT(
EffectiveOptimalityCriteria(criteria),
Eq(ParseTextOrDie<TerminationCriteria::DetailedOptimalityCriteria>(
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) {
@@ -182,21 +193,23 @@ TEST(EffectiveOptimalityCriteriaTest, DetailedOptimalityCriteriaInput) {
eps_optimal_objective_gap_relative: 6.0e-4
})pb");
EXPECT_THAT(EffectiveOptimalityCriteria(criteria),
Eq(criteria.detailed_optimality_criteria()));
Eq(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),
Eq(ParseTextOrDie<TerminationCriteria::DetailedOptimalityCriteria>(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")));
EXPECT_THAT(
EffectiveOptimalityCriteria(criteria),
Eq(ParseTextOrDie<TerminationCriteria::DetailedOptimalityCriteria>(
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) {
@@ -210,6 +223,12 @@ TEST_P(DetailedRelativeTerminationTest, TerminationWithNearOptimal) {
l2_dual_residual: 6e-4
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(
CheckTerminationCriteria(test_criteria_, stats, TestLpBoundNorms()),
Optional(
@@ -227,6 +246,12 @@ TEST_P(DetailedRelativeTerminationTest, NoTerminationWithExcessiveGap) {
l2_dual_residual: 6e-4
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(CheckTerminationCriteria(test_criteria_, stats, TestLpBoundNorms()),
absl::nullopt);
}
@@ -243,6 +268,10 @@ TEST_P(DetailedRelativeTerminationTest,
l2_dual_residual: 6e-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(CheckTerminationCriteria(test_criteria_, stats, TestLpBoundNorms()),
absl::nullopt);
}
@@ -259,6 +288,10 @@ TEST_P(DetailedRelativeTerminationTest,
l2_dual_residual: 7e-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(CheckTerminationCriteria(test_criteria_, stats, TestLpBoundNorms()),
absl::nullopt);
}
@@ -274,6 +307,12 @@ TEST_P(DetailedAbsoluteTerminationTest, TerminationWithNearOptimal) {
l2_dual_residual: 9e-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(
CheckTerminationCriteria(test_criteria_, stats, TestLpBoundNorms()),
Optional(
@@ -291,6 +330,12 @@ TEST_P(DetailedAbsoluteTerminationTest, NoTerminationWithExcessiveGap) {
l2_dual_residual: 9e-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(CheckTerminationCriteria(test_criteria_, stats, TestLpBoundNorms()),
absl::nullopt);
}
@@ -307,6 +352,10 @@ TEST_P(DetailedAbsoluteTerminationTest,
l2_dual_residual: 9e-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(CheckTerminationCriteria(test_criteria_, stats, TestLpBoundNorms()),
absl::nullopt);
}
@@ -323,6 +372,10 @@ TEST_P(DetailedAbsoluteTerminationTest,
l2_dual_residual: 11e-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(CheckTerminationCriteria(test_criteria_, stats, TestLpBoundNorms()),
absl::nullopt);
}
@@ -540,6 +593,70 @@ TEST_P(TerminationTest, TerminationWithNearOptimal) {
POINT_TYPE_CURRENT_ITERATE)));
}
TEST_P(TerminationTest, 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
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 =
CheckTerminationCriteria(test_criteria_, stats, ZeroLpBoundNorms());
EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
POINT_TYPE_CURRENT_ITERATE)));
}
TEST_P(TerminationTest, 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
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 =
CheckTerminationCriteria(test_criteria_, stats, ZeroLpBoundNorms());
EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
POINT_TYPE_CURRENT_ITERATE)));
}
TEST_P(TerminationTest,
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
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 =
CheckTerminationCriteria(test_criteria_, stats, ZeroLpBoundNorms());
EXPECT_THAT(maybe_result, Optional(FieldsAre(TERMINATION_REASON_OPTIMAL,
POINT_TYPE_CURRENT_ITERATE)));
}
TEST_P(TerminationTest, OptimalEvenWithNumericalError) {
const auto stats = ParseTextOrDie<IterationStats>(R"pb(
convergence_information {
@@ -766,8 +883,8 @@ TEST(ComputeRelativeResiduals,
termination_criteria.mutable_simple_optimality_criteria()
->set_eps_optimal_relative(1.0e-6);
const RelativeConvergenceInformation relative_info = ComputeRelativeResiduals(
EffectiveOptimalityCriteria(termination_criteria), TestLpBoundNorms(),
stats);
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));
@@ -797,7 +914,7 @@ TEST(ComputeRelativeResiduals,
opt_criteria.set_eps_optimal_absolute(0.0);
opt_criteria.set_eps_optimal_relative(0.0);
const RelativeConvergenceInformation relative_info = ComputeRelativeResiduals(
EffectiveOptimalityCriteria(opt_criteria), TestLpBoundNorms(), stats);
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);
@@ -822,7 +939,7 @@ TEST(ComputeRelativeResiduals,
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), TestLpBoundNorms(), stats);
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,
@@ -856,7 +973,7 @@ TEST(ComputeRelativeResiduals,
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, TestLpBoundNorms(), stats);
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,
@@ -871,5 +988,88 @@ TEST(ComputeRelativeResiduals,
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