28 #include "Eigen/SparseCore"
29 #include "absl/log/check.h"
30 #include "absl/status/status.h"
31 #include "absl/status/statusor.h"
32 #include "absl/strings/str_cat.h"
33 #include "gmock/gmock.h"
34 #include "gtest/gtest.h"
36 #include "ortools/glop/parameters.pb.h"
37 #include "ortools/linear_solver/linear_solver.pb.h"
44 #include "ortools/pdlp/solve_log.pb.h"
45 #include "ortools/pdlp/solvers.pb.h"
55 using ::testing::AnyNumber;
56 using ::testing::AnyOf;
57 using ::testing::DoubleNear;
58 using ::testing::ElementsAre;
60 using ::testing::HasSubstr;
61 using ::testing::IsEmpty;
63 using ::testing::SizeIs;
65 const double kInfinity = std::numeric_limits<double>::infinity();
66 PrimalDualHybridGradientParams CreateSolverParams(
67 const int iteration_limit,
const double eps_optimal_absolute,
68 const bool enable_scaling,
const int num_threads,
69 const bool use_iteration_limit,
const bool use_malitsky_pock_linesearch,
70 const bool use_diagonal_qp_trust_region_solver) {
71 PrimalDualHybridGradientParams params;
72 if (!enable_scaling) {
73 params.set_l2_norm_rescaling(
false);
74 params.set_l_inf_ruiz_iterations(0);
76 if (use_malitsky_pock_linesearch) {
77 params.set_linesearch_rule(
78 PrimalDualHybridGradientParams::MALITSKY_POCK_LINESEARCH_RULE);
81 params.mutable_termination_criteria()
82 ->mutable_simple_optimality_criteria()
83 ->set_eps_optimal_relative(0.0);
84 if (use_iteration_limit) {
86 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
87 params.mutable_termination_criteria()
88 ->mutable_simple_optimality_criteria()
89 ->set_eps_optimal_absolute(0.0);
91 params.mutable_termination_criteria()
92 ->mutable_simple_optimality_criteria()
93 ->set_eps_optimal_absolute(eps_optimal_absolute);
95 if (use_diagonal_qp_trust_region_solver) {
96 params.set_use_diagonal_qp_trust_region_solver(
true);
97 params.set_diagonal_qp_trust_region_solver_tolerance(1.0e-8);
99 params.set_num_threads(num_threads);
114 void VerifyTerminationReasonAndIterationCount(
115 const PrimalDualHybridGradientParams& params,
const SolverResult& output,
116 const bool use_iteration_limit) {
117 if (use_iteration_limit) {
127 output.solve_log.termination_reason(),
128 AnyOf(TERMINATION_REASON_ITERATION_LIMIT,
129 TERMINATION_REASON_NUMERICAL_ERROR, TERMINATION_REASON_OPTIMAL));
130 if (output.solve_log.termination_reason() ==
131 TERMINATION_REASON_ITERATION_LIMIT) {
132 EXPECT_EQ(output.solve_log.iteration_count(),
133 params.termination_criteria().iteration_limit());
135 EXPECT_LE(output.solve_log.iteration_count(),
136 params.termination_criteria().iteration_limit());
139 EXPECT_EQ(output.solve_log.termination_reason(),
140 TERMINATION_REASON_OPTIMAL);
141 EXPECT_LE(output.solve_log.iteration_count(),
142 params.termination_criteria().iteration_limit());
147 void VerifyObjectiveValues(
const SolverResult& result,
149 const double tolerance) {
151 result.solve_log.solution_stats(), result.solve_log.solution_type());
152 ASSERT_TRUE(convergence_info.has_value());
153 EXPECT_THAT(convergence_info->primal_objective(),
155 EXPECT_THAT(convergence_info->dual_objective(),
159 class PrimalDualHybridGradientLPTest
160 :
public testing::TestWithParam<
161 std::tuple<bool, int,
165 PrimalDualHybridGradientParams CreateSolverParamsForFixture(
166 const int iteration_limit,
const double eps_optimal_absolute) {
167 const auto [enable_scaling, num_threads, use_iteration_limit,
168 use_malitsky_pock_linesearch] = GetParam();
169 return CreateSolverParams(iteration_limit, eps_optimal_absolute,
170 enable_scaling, num_threads, use_iteration_limit,
171 use_malitsky_pock_linesearch,
175 void VerifyTerminationReasonAndIterationCountForFixture(
176 const PrimalDualHybridGradientParams& params,
177 const SolverResult& output) {
178 const auto [enable_scaling, num_threads, use_iteration_limit,
179 use_malitsky_pock_linesearch] = GetParam();
180 VerifyTerminationReasonAndIterationCount(params, output,
181 use_iteration_limit);
185 class PrimalDualHybridGradientDiagonalQPTest
186 :
public testing::TestWithParam<
187 std::tuple<bool, int,
192 PrimalDualHybridGradientParams CreateSolverParamsForFixture(
193 const int iteration_limit,
const double eps_optimal_absolute) {
194 const auto [enable_scaling, num_threads, use_iteration_limit,
195 use_malitsky_pock_linesearch,
196 use_diagonal_qp_trust_region_solver] = GetParam();
197 return CreateSolverParams(iteration_limit, eps_optimal_absolute,
198 enable_scaling, num_threads, use_iteration_limit,
199 use_malitsky_pock_linesearch,
200 use_diagonal_qp_trust_region_solver);
202 void VerifyTerminationReasonAndIterationCountForFixture(
203 const PrimalDualHybridGradientParams& params,
204 const SolverResult& output) {
205 const auto [enable_scaling, num_threads, use_iteration_limit,
206 use_malitsky_pock_linesearch,
207 use_diagonal_qp_trust_region_solver] = GetParam();
208 VerifyTerminationReasonAndIterationCount(params, output,
209 use_iteration_limit);
213 class PrimalDualHybridGradientVerbosityTest
214 :
public testing::TestWithParam<int> {};
216 class PresolveDualScalingTest
217 :
public testing::TestWithParam<
221 INSTANTIATE_TEST_SUITE_P(
222 QP, PrimalDualHybridGradientDiagonalQPTest,
223 testing::Combine(testing::Bool(), testing::Values(1, 4), testing::Bool(),
224 testing::Bool(), testing::Bool()),
225 [](
const testing::TestParamInfo<
226 PrimalDualHybridGradientDiagonalQPTest::ParamType>& info) {
228 std::get<0>(info.param) ?
"Scaling" :
"NoScaling",
"_",
229 std::get<1>(info.param),
"Threads_",
230 std::get<2>(info.param) ?
"IterationLimit" :
"NoIterationLimit",
"_",
231 std::get<3>(info.param) ?
"MalitskyPockLinesearch"
232 :
"AdaptiveLinesearch",
233 "_", std::get<4>(info.param) ?
"TRSolverDiag" :
"TRSolverNoDiag");
236 INSTANTIATE_TEST_SUITE_P(
237 LP, PrimalDualHybridGradientLPTest,
238 testing::Combine(testing::Bool(), testing::Values(1, 4), testing::Bool(),
240 [](
const testing::TestParamInfo<PrimalDualHybridGradientLPTest::ParamType>&
243 std::get<0>(info.param) ?
"Scaling" :
"NoScaling",
"_",
244 std::get<1>(info.param),
"Threads_",
245 std::get<2>(info.param) ?
"IterationLimit" :
"NoIterationLimit",
"_",
246 std::get<3>(info.param) ?
"MalitskyPockLinesearch"
247 :
"AdaptiveLinesearch");
250 INSTANTIATE_TEST_SUITE_P(Verbosity, PrimalDualHybridGradientVerbosityTest,
251 testing::Values(0, 1, 2, 3, 4));
253 INSTANTIATE_TEST_SUITE_P(
254 PresolveDualScaling, PresolveDualScalingTest,
255 testing::Combine(testing::Bool(), testing::Bool()),
256 [](
const testing::TestParamInfo<PresolveDualScalingTest::ParamType>& info) {
257 return absl::StrCat(std::get<1>(info.param) ?
"Dualize" :
"NoDualize",
258 std::get<0>(info.param) ?
"NegateAndScaleObjective"
259 :
"NoObjectiveScaling");
262 TEST_P(PrimalDualHybridGradientLPTest, UnboundedVariables) {
263 const int iteration_upperbound = 980;
264 PrimalDualHybridGradientParams params =
265 CreateSolverParamsForFixture(iteration_upperbound,
267 params.set_major_iteration_frequency(100);
270 VerifyTerminationReasonAndIterationCountForFixture(params, output);
271 VerifyObjectiveValues(output, -34.0, 1.0e-6);
272 EXPECT_THAT(output.primal_solution,
273 EigenArrayNear<double>({-1, 8, 1, 2.5}, 1.0e-4));
274 EXPECT_THAT(output.dual_solution,
275 EigenArrayNear<double>({-2, 0, 2.375, 2.0 / 3}, 1.0e-4));
277 EXPECT_EQ(output.solve_log.original_problem_stats().num_variables(), 4);
278 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_variables(), 4);
279 EXPECT_EQ(output.solve_log.original_problem_stats().num_constraints(), 4);
280 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_constraints(), 4);
283 TEST_P(PrimalDualHybridGradientLPTest, Tiny) {
284 const int iteration_upperbound = 300;
285 PrimalDualHybridGradientParams params =
286 CreateSolverParamsForFixture(iteration_upperbound,
288 params.set_major_iteration_frequency(60);
291 VerifyTerminationReasonAndIterationCountForFixture(params, output);
292 VerifyObjectiveValues(output, -1.0, 1.0e-4);
293 EXPECT_THAT(output.primal_solution,
294 EigenArrayNear<double>({1, 0, 6, 2}, 1.0e-4));
295 EXPECT_THAT(output.dual_solution,
296 EigenArrayNear<double>({0.5, 4.0, 0.0}, 1.0e-4));
297 EXPECT_THAT(output.reduced_costs,
298 EigenArrayNear<double>({0.0, 1.5, -3.5, 0.0}, 1.0e-4));
299 EXPECT_EQ(output.solve_log.original_problem_stats().num_variables(), 4);
300 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_variables(), 4);
301 EXPECT_EQ(output.solve_log.original_problem_stats().num_constraints(), 3);
302 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_constraints(), 3);
305 TEST_P(PrimalDualHybridGradientLPTest, CorrelationClusteringOne) {
306 const int iteration_upperbound = 9;
307 PrimalDualHybridGradientParams params =
308 CreateSolverParamsForFixture(iteration_upperbound,
310 params.set_major_iteration_frequency(2);
312 SolverResult output =
314 VerifyTerminationReasonAndIterationCountForFixture(params, output);
315 VerifyObjectiveValues(output, 1.0, 1.0e-14);
316 EXPECT_THAT(output.primal_solution,
317 EigenArrayNear<double>({1, 1, 0, 1, 0, 0}, 1.0e-14));
318 ASSERT_EQ(output.dual_solution.size(), 3);
320 EXPECT_GE(output.dual_solution[0], 0);
321 EXPECT_GE(output.dual_solution[1], 0);
322 EXPECT_GE(output.dual_solution[2], 0);
323 EXPECT_GE(output.dual_solution[0] + output.dual_solution[1], 1 - 1.0e-14);
326 output.solve_log.solution_stats(), output.solve_log.solution_type());
327 ASSERT_TRUE(convergence_information.has_value());
328 EXPECT_THAT(convergence_information->corrected_dual_objective(),
329 DoubleNear(1, 1.0e-14));
330 EXPECT_EQ(output.solve_log.original_problem_stats().num_variables(), 6);
331 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_variables(), 6);
332 EXPECT_EQ(output.solve_log.original_problem_stats().num_constraints(), 3);
333 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_constraints(), 3);
336 TEST_P(PrimalDualHybridGradientLPTest, CorrelationClusteringStar) {
337 const int iteration_upperbound = 45;
338 PrimalDualHybridGradientParams params =
339 CreateSolverParamsForFixture(iteration_upperbound,
341 params.set_major_iteration_frequency(5);
343 SolverResult output =
345 VerifyTerminationReasonAndIterationCountForFixture(params, output);
346 VerifyObjectiveValues(output, 1.5, 1.0e-6);
347 EXPECT_THAT(output.primal_solution,
348 EigenArrayNear<double>({0.5, 0.5, 0.5, 0.0, 0.0, 0.0}, 1.0e-6));
349 EXPECT_THAT(output.dual_solution,
350 EigenArrayNear<double>({0.5, 0.5, 0.5}, 1.0e-6));
351 EXPECT_EQ(output.solve_log.original_problem_stats().num_variables(), 6);
352 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_variables(), 6);
353 EXPECT_EQ(output.solve_log.original_problem_stats().num_constraints(), 3);
354 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_constraints(), 3);
361 TEST_P(PrimalDualHybridGradientLPTest, InactiveTwoSidedConstraint) {
362 const int iteration_upperbound = 500;
363 PrimalDualHybridGradientParams params =
364 CreateSolverParamsForFixture(iteration_upperbound,
366 params.set_major_iteration_frequency(60);
368 QuadraticProgram qp =
TestLp();
371 qp.constraint_lower_bounds[1] = -10;
373 VerifyTerminationReasonAndIterationCountForFixture(params, output);
374 VerifyObjectiveValues(output, -34.0, 1.0e-6);
375 EXPECT_THAT(output.primal_solution,
376 EigenArrayNear<double>({-1, 8, 1, 2.5}, 1.0e-7));
377 EXPECT_THAT(output.dual_solution,
378 EigenArrayNear<double>({-2.0, 0.0, 2.375, 2.0 / 3}, 1.0e-7));
381 TEST_P(PrimalDualHybridGradientLPTest, InfeasiblePrimal) {
385 const int iteration_upperbound = 2000;
386 PrimalDualHybridGradientParams params =
387 CreateSolverParamsForFixture(iteration_upperbound,
389 params.set_major_iteration_frequency(5);
390 params.mutable_termination_criteria()->set_eps_primal_infeasible(1.0e-6);
392 SolverResult output =
394 EXPECT_EQ(output.solve_log.termination_reason(),
395 TERMINATION_REASON_PRIMAL_INFEASIBLE);
396 const auto& dual = output.dual_solution;
400 EXPECT_THAT(dual[0] / dual[1], DoubleNear(1, 1.0e-6));
401 EXPECT_LT(dual[1], 0.0);
407 EXPECT_THAT(output.reduced_costs,
408 EigenArrayNear<double>({std::max(dual[1] - dual[0], 0.0),
409 std::max(dual[0] - dual[1], 0.0)},
411 EXPECT_LE(output.solve_log.iteration_count(), iteration_upperbound);
412 EXPECT_EQ(output.solve_log.original_problem_stats().num_variables(), 2);
413 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_variables(), 2);
414 EXPECT_EQ(output.solve_log.original_problem_stats().num_constraints(), 2);
415 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_constraints(), 2);
418 TEST_P(PrimalDualHybridGradientLPTest, InfeasibleDual) {
419 const int iteration_upperbound = 500;
420 PrimalDualHybridGradientParams params =
421 CreateSolverParamsForFixture(iteration_upperbound,
423 params.set_major_iteration_frequency(5);
425 SolverResult output =
427 EXPECT_EQ(output.solve_log.termination_reason(),
428 TERMINATION_REASON_DUAL_INFEASIBLE);
432 EXPECT_THAT(output.primal_solution[0] / output.primal_solution[1],
433 DoubleNear(1, 1.0e-6));
434 EXPECT_GT(output.primal_solution[1], 0.0);
435 EXPECT_LE(output.solve_log.iteration_count(), iteration_upperbound);
438 TEST_P(PrimalDualHybridGradientLPTest, InfeasiblePrimalDual) {
439 const int iteration_upperbound = 600;
440 PrimalDualHybridGradientParams params =
441 CreateSolverParamsForFixture(iteration_upperbound,
445 params.set_restart_strategy(PrimalDualHybridGradientParams::NO_RESTARTS);
446 params.set_major_iteration_frequency(5);
448 SolverResult output =
450 EXPECT_THAT(output.solve_log.termination_reason(),
451 AnyOf(Eq(TERMINATION_REASON_DUAL_INFEASIBLE),
452 Eq(TERMINATION_REASON_PRIMAL_INFEASIBLE)));
453 EXPECT_LE(output.solve_log.iteration_count(), iteration_upperbound);
456 TEST_P(PrimalDualHybridGradientDiagonalQPTest, DiagonalQp1) {
457 const int iteration_upperbound = 96;
458 PrimalDualHybridGradientParams params =
459 CreateSolverParamsForFixture(iteration_upperbound,
461 params.set_major_iteration_frequency(12);
464 VerifyTerminationReasonAndIterationCountForFixture(params, output);
465 VerifyObjectiveValues(output, 6.0, 1.0e-6);
466 EXPECT_THAT(output.primal_solution,
467 EigenArrayNear<double>({1.0, 0.0}, 1.0e-6));
468 EXPECT_THAT(output.dual_solution,
470 EXPECT_THAT(output.reduced_costs, EigenArrayNear<double>({4.0, 0.0}, 1.0e-6));
471 EXPECT_EQ(output.solve_log.original_problem_stats().num_variables(), 2);
472 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_variables(), 2);
473 EXPECT_EQ(output.solve_log.original_problem_stats().num_constraints(), 1);
474 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_constraints(), 1);
477 TEST_P(PrimalDualHybridGradientDiagonalQPTest, DiagonalQp2) {
478 const int iteration_upperbound = 240;
479 PrimalDualHybridGradientParams params =
480 CreateSolverParamsForFixture(iteration_upperbound,
482 params.set_major_iteration_frequency(12);
485 VerifyTerminationReasonAndIterationCountForFixture(params, output);
486 VerifyObjectiveValues(output, -5.0, 1.0e-6);
487 EXPECT_THAT(output.primal_solution,
488 EigenArrayNear<double>({3.0, 1.0}, 1.0e-6));
489 EXPECT_THAT(output.dual_solution, ElementsAre(DoubleNear(0.0, 1.0e-6)));
490 EXPECT_THAT(output.reduced_costs, EigenArrayNear<double>({0.0, 0.0}, 1.0e-6));
493 TEST_P(PrimalDualHybridGradientDiagonalQPTest, DiagonalQp3) {
494 const int iteration_upperbound = 300;
495 PrimalDualHybridGradientParams params =
496 CreateSolverParamsForFixture(iteration_upperbound,
498 params.set_major_iteration_frequency(15);
501 VerifyTerminationReasonAndIterationCountForFixture(params, output);
502 VerifyObjectiveValues(output, 2.0, 1.0e-6);
503 EXPECT_THAT(output.primal_solution,
504 EigenArrayNear<double>({2.0, 0.0, 1.0}, 1.0e-6));
505 EXPECT_THAT(output.dual_solution,
506 EigenArrayNear<double>({-1.0, 1.0}, 1.0e-6));
507 EXPECT_THAT(output.reduced_costs, EigenArrayNear<double>({0, 0, 0}, 1.0e-6));
512 TEST_P(PrimalDualHybridGradientDiagonalQPTest, QpWarmStart) {
513 const int iteration_upperbound = 35;
514 PrimalDualHybridGradientParams params =
515 CreateSolverParamsForFixture(iteration_upperbound,
517 params.set_major_iteration_frequency(5);
522 params.set_primal_weight_update_smoothing(0.0);
524 PrimalAndDualSolution initial_solution;
525 initial_solution.primal_solution.resize(2);
526 initial_solution.primal_solution << 0.999, 0.001;
527 initial_solution.dual_solution.resize(1);
528 initial_solution.dual_solution << -0.999;
530 std::move(initial_solution));
531 VerifyTerminationReasonAndIterationCountForFixture(params, output);
532 VerifyObjectiveValues(output, 6.0, 1.0e-6);
533 EXPECT_THAT(output.primal_solution,
534 EigenArrayNear<double>({1.0, 0.0}, 1.0e-6));
535 EXPECT_THAT(output.dual_solution,
537 EXPECT_THAT(output.reduced_costs, EigenArrayNear<double>({4.0, 0.0}, 1.0e-6));
542 const int iteration_upperbound = 2;
543 PrimalDualHybridGradientParams params =
544 CreateSolverParamsForFixture(iteration_upperbound,
547 QuadraticProgram qp(3, 0);
548 qp.variable_lower_bounds << -1, -
kInfinity, -2;
549 qp.variable_upper_bounds <<
kInfinity, 4, 10;
550 qp.objective_vector << 1, -1, 2;
553 VerifyTerminationReasonAndIterationCountForFixture(params, output);
554 VerifyObjectiveValues(output, -9.0, 1.0e-6);
555 EXPECT_THAT(output.primal_solution,
556 EigenArrayNear<double>({-1, 4, -2}, 1.0e-6));
557 EXPECT_THAT(output.dual_solution, SizeIs(0));
558 EXPECT_EQ(output.solve_log.original_problem_stats().num_variables(), 3);
559 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_variables(), 3);
560 EXPECT_EQ(output.solve_log.original_problem_stats().num_constraints(), 0);
561 EXPECT_EQ(output.solve_log.preprocessed_problem_stats().num_constraints(), 0);
565 TEST_P(PrimalDualHybridGradientLPTest, LpWithoutVariables) {
566 const int iteration_upperbound = 2;
567 PrimalDualHybridGradientParams params =
568 CreateSolverParamsForFixture(iteration_upperbound,
571 QuadraticProgram qp(0, 3);
572 qp.constraint_lower_bounds << -1, -
kInfinity, -2;
573 qp.constraint_upper_bounds <<
kInfinity, 4, 10;
576 VerifyTerminationReasonAndIterationCountForFixture(params, output);
577 VerifyObjectiveValues(output, 0.0, 1.0e-6);
578 EXPECT_THAT(output.primal_solution, SizeIs(0));
579 EXPECT_THAT(output.dual_solution, EigenArrayNear<double>({0, 0, 0}, 1.0e-6));
580 EXPECT_EQ(output.solve_log.original_problem_stats().num_variables(), 0);
581 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_variables(), 0);
582 EXPECT_EQ(output.solve_log.original_problem_stats().num_constraints(), 3);
583 EXPECT_EQ(output.solve_log.preprocessed_problem_stats().num_constraints(), 3);
586 TEST_P(PrimalDualHybridGradientLPTest, LpWithOnlyFixedVariable) {
587 const int iteration_upperbound = 2;
588 PrimalDualHybridGradientParams params =
589 CreateSolverParamsForFixture(iteration_upperbound,
592 QuadraticProgram qp(1, 0);
593 qp.variable_lower_bounds << 1;
594 qp.variable_upper_bounds << 1;
595 qp.objective_vector << 1;
598 EXPECT_EQ(output.solve_log.termination_reason(), TERMINATION_REASON_OPTIMAL);
599 EXPECT_THAT(output.primal_solution,
601 EXPECT_THAT(output.dual_solution, testing::SizeIs(0));
602 EXPECT_LE(output.solve_log.iteration_count(), iteration_upperbound);
605 TEST_P(PrimalDualHybridGradientLPTest, InfeasibleLpWithoutVariables) {
606 const int iteration_upperbound = 2;
607 PrimalDualHybridGradientParams params =
608 CreateSolverParamsForFixture(iteration_upperbound,
611 QuadraticProgram qp(0, 1);
612 qp.constraint_lower_bounds << -1;
613 qp.constraint_upper_bounds << -1;
616 EXPECT_EQ(output.solve_log.termination_reason(),
617 TERMINATION_REASON_PRIMAL_INFEASIBLE);
618 EXPECT_THAT(output.primal_solution, SizeIs(0));
619 EXPECT_LT(output.dual_solution[0], 0.0);
620 EXPECT_EQ(output.solve_log.original_problem_stats().num_variables(), 0);
621 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_variables(), 0);
622 EXPECT_EQ(output.solve_log.original_problem_stats().num_constraints(), 1);
623 EXPECT_EQ(output.solve_log.preprocessed_problem_stats().num_constraints(), 1);
626 PrimalDualHybridGradientParams ParamsWithNoLimits() {
627 PrimalDualHybridGradientParams params;
630 params.mutable_termination_criteria()
631 ->mutable_simple_optimality_criteria()
632 ->set_eps_optimal_relative(0.0);
633 params.mutable_termination_criteria()
634 ->mutable_simple_optimality_criteria()
635 ->set_eps_optimal_absolute(0.0);
636 params.set_record_iteration_stats(
true);
640 TEST(PrimalDualHybridGradientTest, ClearsRunningAverage) {
642 const int major_iteration_frequency = 17;
643 const int iteration_limit = 100;
644 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
645 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
646 params.set_termination_check_frequency(1);
647 params.set_major_iteration_frequency(major_iteration_frequency);
648 params.set_restart_strategy(PrimalDualHybridGradientParams::NO_RESTARTS);
651 ASSERT_EQ(output.solve_log.iteration_count(), iteration_limit);
653 ASSERT_EQ(output.solve_log.iteration_stats_size(), iteration_limit + 1);
655 for (
int i = 0; i < output.solve_log.iteration_stats_size(); ++i) {
656 const auto& stats = output.solve_log.iteration_stats(i);
657 const int iterations_completed = stats.iteration_number();
658 EXPECT_EQ(iterations_completed, i);
659 if (iterations_completed == 0) {
660 EXPECT_EQ(stats.restart_used(), RESTART_CHOICE_NO_RESTART);
661 }
else if (iterations_completed % major_iteration_frequency == 0) {
662 EXPECT_EQ(stats.restart_used(), RESTART_CHOICE_WEIGHTED_AVERAGE_RESET)
663 <<
"iteration = " << i;
665 EXPECT_EQ(stats.restart_used(), RESTART_CHOICE_NO_RESTART)
666 <<
"iteration = " << i;
671 TEST(PrimalDualHybridGradientTest, RestartsEveryMajorIteration) {
673 const int major_iteration_frequency = 17;
674 const int iteration_limit = 100;
675 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
676 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
677 params.set_termination_check_frequency(1);
678 params.set_major_iteration_frequency(major_iteration_frequency);
679 params.set_restart_strategy(
680 PrimalDualHybridGradientParams::EVERY_MAJOR_ITERATION);
683 ASSERT_EQ(output.solve_log.iteration_count(), iteration_limit);
685 ASSERT_EQ(output.solve_log.iteration_stats_size(), iteration_limit + 1);
687 for (
int i = 0; i < output.solve_log.iteration_stats_size(); ++i) {
688 const auto& stats = output.solve_log.iteration_stats(i);
689 const int iterations_completed = stats.iteration_number();
690 EXPECT_EQ(iterations_completed, i);
691 if (iterations_completed == 0) {
692 EXPECT_EQ(stats.restart_used(), RESTART_CHOICE_NO_RESTART);
693 }
else if (iterations_completed % major_iteration_frequency == 0) {
694 EXPECT_EQ(stats.restart_used(), RESTART_CHOICE_RESTART_TO_AVERAGE)
695 <<
"iteration = " << i;
697 EXPECT_EQ(stats.restart_used(), RESTART_CHOICE_NO_RESTART)
698 <<
"iteration = " << i;
703 TEST(PrimalDualHybridGradientTest, SolveLogIncludesNameForNamedQP) {
704 PrimalDualHybridGradientParams params;
705 params.mutable_termination_criteria()->set_iteration_limit(1);
707 QuadraticProgram test_lp =
TestLp();
708 test_lp.problem_name =
"Test LP";
711 EXPECT_EQ(output.solve_log.instance_name(),
"Test LP");
714 TEST(PrimalDualHybridGradientTest, SolveLogOmitsNameForUnnamedQP) {
715 PrimalDualHybridGradientParams params;
716 params.mutable_termination_criteria()->set_iteration_limit(1);
718 QuadraticProgram unnamed_test_lp =
TestLp();
721 EXPECT_FALSE(output.solve_log.has_instance_name());
724 TEST(PrimalDualHybridGradientTest, SolveLogIncludesParameters) {
725 PrimalDualHybridGradientParams params;
726 params.mutable_termination_criteria()->set_iteration_limit(1);
729 EXPECT_EQ(output.solve_log.params().termination_criteria().iteration_limit(),
733 TEST(PrimalDualHybridGradientTest, AdaptiveDistanceBasedRestartsWorkOnTestLp) {
734 PrimalDualHybridGradientParams params;
735 params.set_major_iteration_frequency(16);
736 params.mutable_termination_criteria()->set_iteration_limit(128);
737 params.set_restart_strategy(
738 PrimalDualHybridGradientParams::ADAPTIVE_DISTANCE_BASED);
740 params.set_necessary_reduction_for_restart(0.99);
743 EXPECT_EQ(output.solve_log.termination_reason(), TERMINATION_REASON_OPTIMAL);
744 EXPECT_THAT(output.primal_solution,
745 EigenArrayNear<double>({-1, 8, 1, 2.5}, 1.0e-4));
746 EXPECT_THAT(output.dual_solution,
747 EigenArrayNear<double>({-2, 0, 2.375, 2.0 / 3}, 1.0e-4));
749 output.solve_log.solution_stats(), output.solve_log.solution_type());
750 ASSERT_TRUE(convergence_info.has_value());
751 EXPECT_THAT(convergence_info->primal_objective(), DoubleNear(-34.0, 1.0e-4));
752 EXPECT_THAT(convergence_info->dual_objective(), DoubleNear(-34.0, 1.0e-4));
755 TEST(PrimalDualHybridGradientTest, AdaptiveDistanceBasedRestartsWorkOnTestQp) {
756 PrimalDualHybridGradientParams params;
757 params.set_major_iteration_frequency(16);
758 params.mutable_termination_criteria()->set_iteration_limit(128);
759 params.set_restart_strategy(
760 PrimalDualHybridGradientParams::ADAPTIVE_DISTANCE_BASED);
762 params.set_necessary_reduction_for_restart(0.99);
765 EXPECT_EQ(output.solve_log.termination_reason(), TERMINATION_REASON_OPTIMAL);
768 TEST(PrimalDualHybridGradientTest, AdaptiveDistanceBasedRestartsToAverage) {
770 const int major_iteration_frequency = 13;
771 const int iteration_limit = 100;
772 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
773 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
774 params.set_termination_check_frequency(1);
775 params.set_major_iteration_frequency(major_iteration_frequency);
776 params.set_restart_strategy(
777 PrimalDualHybridGradientParams::ADAPTIVE_DISTANCE_BASED);
778 params.set_necessary_reduction_for_restart(0.75);
781 ASSERT_EQ(output.solve_log.iteration_count(), iteration_limit);
783 ASSERT_EQ(output.solve_log.iteration_stats_size(), iteration_limit + 1);
785 for (
int i = 0; i < output.solve_log.iteration_stats_size(); ++i) {
786 const auto& stats = output.solve_log.iteration_stats(i);
787 const int iterations_completed = stats.iteration_number();
788 EXPECT_EQ(iterations_completed, i);
789 if (iterations_completed == 0) {
790 EXPECT_EQ(stats.restart_used(), RESTART_CHOICE_NO_RESTART);
791 }
else if (iterations_completed == major_iteration_frequency) {
794 EXPECT_THAT(stats.restart_used(),
795 AnyOf(RESTART_CHOICE_RESTART_TO_AVERAGE,
796 RESTART_CHOICE_WEIGHTED_AVERAGE_RESET))
797 <<
"iteration = " << i;
798 }
else if (iterations_completed % major_iteration_frequency != 0) {
800 EXPECT_EQ(stats.restart_used(), RESTART_CHOICE_NO_RESTART);
805 TEST(PrimalDualHybridGradientTest, PrimalWeightFrozen) {
807 const int major_iteration_frequency = 17;
808 const int iteration_limit = 100;
809 const double initial_primal_weight = 1.5;
810 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
811 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
812 params.set_major_iteration_frequency(major_iteration_frequency);
813 params.set_restart_strategy(
814 PrimalDualHybridGradientParams::EVERY_MAJOR_ITERATION);
815 params.set_initial_primal_weight(initial_primal_weight);
816 params.set_primal_weight_update_smoothing(0.0);
820 for (
const auto& stats : output.solve_log.iteration_stats()) {
821 EXPECT_EQ(stats.primal_weight(), initial_primal_weight)
822 <<
"iteration = " << stats.iteration_number();
826 TEST(PrimalDualHybridGradientTest, ConstantStepSize) {
827 const int iteration_limit = 100;
828 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
829 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
830 params.set_termination_check_frequency(1);
831 params.set_linesearch_rule(
832 PrimalDualHybridGradientParams::CONSTANT_STEP_SIZE_RULE);
836 ASSERT_FALSE(output.solve_log.iteration_stats().empty());
837 const double initial_step_size =
838 output.solve_log.iteration_stats(0).step_size();
839 for (
const auto& stats : output.solve_log.iteration_stats()) {
840 EXPECT_EQ(stats.step_size(), initial_step_size)
841 <<
"iteration = " << stats.iteration_number();
845 TEST(PrimalDualHybridGradientTest, StepSizeScaling) {
846 const int iteration_limit = 1;
847 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
848 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
849 params.set_termination_check_frequency(1);
850 params.set_linesearch_rule(
851 PrimalDualHybridGradientParams::CONSTANT_STEP_SIZE_RULE);
855 ASSERT_FALSE(unscaled_output.solve_log.iteration_stats().empty());
856 const double initial_step_size =
857 unscaled_output.solve_log.iteration_stats(0).step_size();
859 const double kStepSizeScaling = 0.5;
860 params.set_initial_step_size_scaling(kStepSizeScaling);
863 ASSERT_FALSE(scaled_output.solve_log.iteration_stats().empty());
864 EXPECT_EQ(scaled_output.solve_log.iteration_stats(0).step_size(),
865 initial_step_size * kStepSizeScaling);
869 TEST(PrimalDualHybridGradientTest, KktMatrixPassTermination) {
870 const int kkt_matrix_pass_limit = 13;
871 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
872 params.mutable_termination_criteria()->set_kkt_matrix_pass_limit(
873 kkt_matrix_pass_limit);
874 params.set_linesearch_rule(
875 PrimalDualHybridGradientParams::CONSTANT_STEP_SIZE_RULE);
879 ASSERT_FALSE(output.solve_log.iteration_stats().empty());
880 EXPECT_EQ(output.solve_log.termination_reason(),
881 TERMINATION_REASON_KKT_MATRIX_PASS_LIMIT);
882 EXPECT_EQ(output.solve_log.solution_stats().cumulative_kkt_matrix_passes(),
883 kkt_matrix_pass_limit);
886 TEST(PrimalDualHybridGradientTest,
887 StatsAtEachIterationWithRecordIterationStatsOn) {
889 const int major_iteration_frequency = 17;
890 const int iteration_limit = 100;
891 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
892 params.set_record_iteration_stats(
true);
895 params.set_termination_check_frequency(1);
896 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
897 params.set_major_iteration_frequency(major_iteration_frequency);
900 EXPECT_EQ(output.solve_log.iteration_stats().size(), iteration_limit + 1);
901 for (
const auto& stats : output.solve_log.iteration_stats()) {
908 if (stats.iteration_number() > 0) {
925 TEST(PrimalDualHybridGradientTest,
926 NoIterationStatsWithRecordIterationStatsOff) {
928 const int major_iteration_frequency = 17;
929 const int iteration_limit = 100;
930 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
931 params.set_record_iteration_stats(
false);
932 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
933 params.set_major_iteration_frequency(major_iteration_frequency);
936 params.add_random_projection_seeds(1);
939 EXPECT_EQ(output.solve_log.iteration_stats().size(), 0);
942 TEST(PrimalDualHybridGradientTest, NoRandomProjectionsIfNotRequested) {
944 const int major_iteration_frequency = 17;
945 const int iteration_limit = 100;
946 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
947 params.set_record_iteration_stats(
true);
948 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
949 params.set_major_iteration_frequency(major_iteration_frequency);
950 params.set_termination_check_frequency(1);
953 EXPECT_EQ(output.solve_log.iteration_stats().size(), iteration_limit + 1);
954 for (
const auto& stats : output.solve_log.iteration_stats()) {
955 EXPECT_THAT(stats.point_metadata(),
Not(IsEmpty()));
956 for (
const auto& metadata : stats.point_metadata()) {
957 EXPECT_THAT(metadata.random_primal_projections(), IsEmpty());
958 EXPECT_THAT(metadata.random_dual_projections(), IsEmpty());
963 TEST(PrimalDualHybridGradientTest, HasRandomProjectionsIfRequested) {
965 const int major_iteration_frequency = 17;
966 const int iteration_limit = 100;
967 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
968 params.set_record_iteration_stats(
true);
969 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
970 params.set_major_iteration_frequency(major_iteration_frequency);
971 params.set_termination_check_frequency(1);
972 params.add_random_projection_seeds(1);
973 params.add_random_projection_seeds(2);
976 EXPECT_EQ(output.solve_log.iteration_stats().size(), iteration_limit + 1);
977 for (
const auto& stats : output.solve_log.iteration_stats()) {
978 for (
const auto& metadata : stats.point_metadata()) {
981 EXPECT_THAT(metadata.random_primal_projections(), SizeIs(2));
982 EXPECT_THAT(metadata.random_dual_projections(), SizeIs(2));
987 TEST(PrimalDualHybridGradientTest, ProjectInitialPointPrimalBounds) {
988 const int iteration_limit = 5;
989 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
990 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
994 SolverResult output =
996 EXPECT_EQ(output.solve_log.iteration_stats().size(), iteration_limit + 1);
997 EXPECT_GT(output.primal_solution[0], 0.0);
1000 TEST(PrimalDualHybridGradientTest, ProjectInitialPointDualBounds) {
1001 const int iteration_limit = 5;
1002 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
1003 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
1006 PrimalAndDualSolution initial_solution;
1007 initial_solution.primal_solution = Eigen::VectorXd(2);
1008 initial_solution.primal_solution << 1.0, 0.0;
1009 initial_solution.dual_solution = -Eigen::VectorXd::Ones(2);
1012 EXPECT_EQ(output_nonzero_init.solve_log.iteration_stats().size(),
1013 iteration_limit + 1);
1014 EXPECT_LE(output_nonzero_init.dual_solution[0], 0.0);
1017 TEST(PrimalDualHybridGradientTest, DetectsProblemWithInconsistentBounds) {
1020 EXPECT_EQ(output.solve_log.termination_reason(),
1021 TERMINATION_REASON_INVALID_PROBLEM);
1024 TEST(PrimalDualHybridGradientTest, DetectsProblemWithInconsistentSizes) {
1025 QuadraticProgram qp =
TinyLp();
1026 qp.objective_vector.resize(0);
1027 SolverResult output =
1029 EXPECT_EQ(output.solve_log.termination_reason(),
1030 TERMINATION_REASON_INVALID_PROBLEM);
1033 TEST(PrimalDualHybridGradientTest, DetectsUncompressedConstraintMatrix) {
1034 QuadraticProgram qp =
TinyLp();
1035 qp.constraint_matrix.uncompress();
1036 SolverResult output =
1038 EXPECT_EQ(output.solve_log.termination_reason(),
1039 TERMINATION_REASON_INVALID_PROBLEM);
1042 TEST(PrimalDualHybridGradientTest, DetectsInvalidParameters) {
1043 PrimalDualHybridGradientParams params;
1044 params.set_num_threads(0);
1046 EXPECT_EQ(output.solve_log.termination_reason(),
1047 TERMINATION_REASON_INVALID_PARAMETER);
1050 TEST(PrimalDualHybridGradientTest, DetectsNanInConstraintMatrix) {
1051 QuadraticProgram qp =
TestLp();
1052 qp.constraint_matrix.coeffRef(0, 0) =
1053 std::numeric_limits<double>::quiet_NaN();
1054 SolverResult output =
1056 EXPECT_EQ(output.solve_log.termination_reason(),
1057 TERMINATION_REASON_INVALID_PROBLEM);
1060 TEST(PrimalDualHybridGradientTest, DetectsExcessiveConstraintMatrix) {
1061 QuadraticProgram qp =
TestLp();
1062 qp.constraint_matrix.coeffRef(0, 0) = 1.0e60;
1063 SolverResult output =
1065 EXPECT_EQ(output.solve_log.termination_reason(),
1066 TERMINATION_REASON_INVALID_PROBLEM);
1069 TEST(PrimalDualHybridGradientTest,
1070 DetectsExcessivelySmallColNormConstraintMatrix) {
1071 QuadraticProgram qp =
TestLp();
1072 qp.constraint_matrix.coeffRef(0, 1) = 1.0e-60;
1073 SolverResult output =
1075 EXPECT_EQ(output.solve_log.termination_reason(),
1076 TERMINATION_REASON_INVALID_PROBLEM);
1079 TEST(PrimalDualHybridGradientTest,
1080 DetectsExcessivelySmallRowNormConstraintMatrix) {
1081 QuadraticProgram qp =
TestLp();
1082 qp.constraint_matrix.coeffRef(2, 0) = 1.0e-60;
1083 SolverResult output =
1085 EXPECT_EQ(output.solve_log.termination_reason(),
1086 TERMINATION_REASON_INVALID_PROBLEM);
1089 TEST(PrimalDualHybridGradientTest, DetectsNanInConstraintBounds) {
1090 QuadraticProgram qp =
TestLp();
1091 qp.constraint_upper_bounds[1] = std::numeric_limits<double>::quiet_NaN();
1092 SolverResult output =
1094 EXPECT_EQ(output.solve_log.termination_reason(),
1095 TERMINATION_REASON_INVALID_PROBLEM);
1098 TEST(PrimalDualHybridGradientTest, DetectsExcessiveConstraintUpperBound) {
1099 QuadraticProgram qp =
TestLp();
1100 qp.constraint_upper_bounds[1] = 1.0e60;
1101 SolverResult output =
1103 EXPECT_EQ(output.solve_log.termination_reason(),
1104 TERMINATION_REASON_INVALID_PROBLEM);
1107 TEST(PrimalDualHybridGradientTest, DetectsExcessiveConstraintLowerBound) {
1108 QuadraticProgram qp =
TestLp();
1109 qp.constraint_lower_bounds[2] = -1.0e60;
1110 SolverResult output =
1112 EXPECT_EQ(output.solve_log.termination_reason(),
1113 TERMINATION_REASON_INVALID_PROBLEM);
1116 TEST(PrimalDualHybridGradientTest, DetectsNanInVariableBound) {
1117 QuadraticProgram qp =
TestLp();
1118 qp.variable_lower_bounds[3] = std::numeric_limits<double>::quiet_NaN();
1119 SolverResult output =
1121 EXPECT_EQ(output.solve_log.termination_reason(),
1122 TERMINATION_REASON_INVALID_PROBLEM);
1125 TEST(PrimalDualHybridGradientTest, DetectsExcessiveVariableBoundGap) {
1126 QuadraticProgram qp =
TestLp();
1127 qp.variable_lower_bounds[3] = -1.0e60;
1128 SolverResult output =
1130 EXPECT_EQ(output.solve_log.termination_reason(),
1131 TERMINATION_REASON_INVALID_PROBLEM);
1134 TEST(PrimalDualHybridGradientTest, DetectsNanInObjectiveVector) {
1135 QuadraticProgram qp =
TestLp();
1136 qp.objective_vector[3] = std::numeric_limits<double>::quiet_NaN();
1137 SolverResult output =
1139 EXPECT_EQ(output.solve_log.termination_reason(),
1140 TERMINATION_REASON_INVALID_PROBLEM);
1143 TEST(PrimalDualHybridGradientTest, DetectsExcessiveObjectiveVector) {
1144 QuadraticProgram qp =
TestLp();
1145 qp.objective_vector[3] = -1.0e60;
1146 SolverResult output =
1148 EXPECT_EQ(output.solve_log.termination_reason(),
1149 TERMINATION_REASON_INVALID_PROBLEM);
1152 TEST(PrimalDualHybridGradientTest, DetectsNanInObjectiveMatrix) {
1154 qp.objective_matrix->diagonal()[0] = std::numeric_limits<double>::quiet_NaN();
1155 SolverResult output =
1157 EXPECT_EQ(output.solve_log.termination_reason(),
1158 TERMINATION_REASON_INVALID_PROBLEM);
1161 TEST(PrimalDualHybridGradientTest, DetectsExcessiveObjectiveMatrix) {
1163 qp.objective_matrix->diagonal()[0] = 1.0e60;
1164 SolverResult output =
1166 EXPECT_EQ(output.solve_log.termination_reason(),
1167 TERMINATION_REASON_INVALID_PROBLEM);
1170 TEST(PrimalDualHybridGradientTest, DetectsNanInInitialPrimalSolution) {
1171 QuadraticProgram qp =
TestLp();
1172 PrimalAndDualSolution initial_solution = {
1175 {1.0, std::numeric_limits<double>::quiet_NaN(), 1.0, 1.0}},
1176 .dual_solution = Eigen::VectorXd{{1.0, 1.0, 1.0, 1.0}},
1179 qp, PrimalDualHybridGradientParams(), initial_solution);
1180 EXPECT_EQ(output.solve_log.termination_reason(),
1181 TERMINATION_REASON_INVALID_INITIAL_SOLUTION);
1184 TEST(PrimalDualHybridGradientTest,
1185 DetectsExcessiveValueInInitialPrimalSolution) {
1186 QuadraticProgram qp =
TestLp();
1187 PrimalAndDualSolution initial_solution = {
1188 .primal_solution = Eigen::VectorXd{{1.0, 1.0e100, 1.0, 1.0}},
1189 .dual_solution = Eigen::VectorXd{{1.0, 1.0, 1.0, 1.0}},
1193 qp, PrimalDualHybridGradientParams(), initial_solution);
1194 EXPECT_EQ(output.solve_log.termination_reason(),
1195 TERMINATION_REASON_INVALID_INITIAL_SOLUTION);
1198 TEST(PrimalDualHybridGradientTest, DetectsIncorrectSizeInitialPrimalSolution) {
1199 QuadraticProgram qp =
TestLp();
1200 PrimalAndDualSolution initial_solution = {
1201 .primal_solution = Eigen::VectorXd{{1.0, 1.0, 1.0}},
1202 .dual_solution = Eigen::VectorXd{{1.0, 1.0, 1.0, 1.0}},
1206 qp, PrimalDualHybridGradientParams(), initial_solution);
1207 EXPECT_EQ(output.solve_log.termination_reason(),
1208 TERMINATION_REASON_INVALID_INITIAL_SOLUTION);
1211 TEST(PrimalDualHybridGradientTest, DetectsNanInInitialDualSolution) {
1212 QuadraticProgram qp =
TestLp();
1213 PrimalAndDualSolution initial_solution = {
1214 .primal_solution = Eigen::VectorXd{{1.0, 1.0, 1.0, 1.0}},
1217 {1.0, std::numeric_limits<double>::quiet_NaN(), 1.0, 1.0}},
1220 qp, PrimalDualHybridGradientParams(), initial_solution);
1221 EXPECT_EQ(output.solve_log.termination_reason(),
1222 TERMINATION_REASON_INVALID_INITIAL_SOLUTION);
1225 TEST(PrimalDualHybridGradientTest, DetectsExcessiveValueInInitialDualSolution) {
1226 QuadraticProgram qp =
TestLp();
1227 PrimalAndDualSolution initial_solution = {
1228 .primal_solution = Eigen::VectorXd{{1.0, 1.0, 1.0, 1.0}},
1229 .dual_solution = Eigen::VectorXd{{1.0, 1.0e100, 1.0, 1.0}},
1233 qp, PrimalDualHybridGradientParams(), initial_solution);
1234 EXPECT_EQ(output.solve_log.termination_reason(),
1235 TERMINATION_REASON_INVALID_INITIAL_SOLUTION);
1238 TEST(PrimalDualHybridGradientTest, DetectsIncorrectSizeInitialDualSolution) {
1239 QuadraticProgram qp =
TestLp();
1240 PrimalAndDualSolution initial_solution = {
1241 .primal_solution = Eigen::VectorXd{{1.0, 1.0, 1.0, 1.0}},
1242 .dual_solution = Eigen::VectorXd{{1.0, 1.0, 1.0}},
1246 qp, PrimalDualHybridGradientParams(), initial_solution);
1247 EXPECT_EQ(output.solve_log.termination_reason(),
1248 TERMINATION_REASON_INVALID_INITIAL_SOLUTION);
1251 TEST(PrimalDualHybridGradientTest, DetectsZeroObjectiveScalingFactor) {
1252 QuadraticProgram qp =
TestLp();
1253 qp.objective_scaling_factor = 0.0;
1255 SolverResult output =
1257 EXPECT_EQ(output.solve_log.termination_reason(),
1258 TERMINATION_REASON_INVALID_PROBLEM);
1261 TEST(PrimalDualHybridGradientTest, ChecksTerminationAtCorrectFrequency) {
1264 const int major_iteration_frequency = 5;
1265 const int termination_check_frequency = 2;
1266 const int iteration_limit = 16;
1267 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
1268 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
1269 params.set_termination_check_frequency(termination_check_frequency);
1270 params.set_major_iteration_frequency(major_iteration_frequency);
1273 ASSERT_EQ(output.solve_log.iteration_count(), iteration_limit);
1275 std::vector<int> termination_checked_at_iteration;
1277 for (
const auto& stats : output.solve_log.iteration_stats()) {
1278 if (stats.convergence_information_size() > 0) {
1279 termination_checked_at_iteration.push_back(stats.iteration_number());
1286 EXPECT_THAT(termination_checked_at_iteration,
1287 ElementsAre(0, 2, 4, 5, 7, 9, 10, 12, 14, 15, 16));
1290 TEST(PrimalDualHybridGradientTest, CallsCallback) {
1293 const int major_iteration_frequency = 5;
1294 const int termination_check_frequency = 5;
1295 const int iteration_limit = 16;
1296 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
1297 params.mutable_termination_criteria()->set_iteration_limit(iteration_limit);
1298 params.set_termination_check_frequency(termination_check_frequency);
1299 params.set_major_iteration_frequency(major_iteration_frequency);
1301 int callback_count = 0;
1303 TestLp(), params,
nullptr,
1304 [&callback_count](
const IterationCallbackInfo& callback_info) {
1307 ASSERT_EQ(output.solve_log.iteration_count(), iteration_limit);
1311 CHECK_EQ(callback_count, 6);
1315 PrimalAndDualSolution TinyLpSolution() {
1316 PrimalAndDualSolution solution;
1317 solution.primal_solution.resize(4);
1318 solution.primal_solution << 1.0, 0.0, 6.0, 2.0;
1319 solution.dual_solution.resize(3);
1320 solution.dual_solution << 0.5, 4.0, 0.0;
1324 TEST(PrimalDualHybridGradientTest, WarmStartedAtOptimum) {
1325 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
1326 params.mutable_termination_criteria()
1327 ->mutable_simple_optimality_criteria()
1328 ->set_eps_optimal_absolute(1.0e-10);
1330 SolverResult output =
1334 EXPECT_THAT(output.primal_solution,
1335 EigenArrayNear<double>({1, 0, 6, 2}, 1.0e-10));
1336 EXPECT_THAT(output.dual_solution,
1337 EigenArrayNear<double>({0.5, 4.0, 0.0}, 1.0e-10));
1338 EXPECT_LE(output.solve_log.iteration_count(), 0);
1339 EXPECT_EQ(output.solve_log.termination_reason(), TERMINATION_REASON_OPTIMAL);
1342 TEST(PrimalDualHybridGradientTest, NoMovementWhenStartedAtOptimum) {
1343 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
1346 params.set_l2_norm_rescaling(
false);
1347 params.set_l_inf_ruiz_iterations(0);
1349 PrimalAndDualSolution initial_solution = TinyLpSolution();
1350 SolverResult output =
1352 EXPECT_THAT(output.primal_solution, ElementsAre(1, 0, 6, 2));
1353 EXPECT_THAT(output.dual_solution, ElementsAre(0.5, 4.0, 0.0));
1354 EXPECT_LE(output.solve_log.iteration_count(), 1);
1355 EXPECT_EQ(output.solve_log.termination_reason(), TERMINATION_REASON_OPTIMAL);
1358 TEST(PrimalDualHybridGradientTest, EmptyQp) {
1360 absl::StatusOr<QuadraticProgram> qp =
1362 ASSERT_TRUE(qp.ok()) << qp.status();
1363 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
1365 EXPECT_THAT(output.primal_solution, ElementsAre());
1366 EXPECT_THAT(output.dual_solution, ElementsAre());
1367 EXPECT_EQ(output.solve_log.iteration_count(), 0);
1368 EXPECT_EQ(output.solve_log.termination_reason(), TERMINATION_REASON_OPTIMAL);
1371 TEST(PrimalDualHybridGradientTest, RespectsInterrupt) {
1372 std::atomic<bool> interrupt_solve;
1373 PrimalDualHybridGradientParams params;
1374 params.mutable_termination_criteria()
1375 ->mutable_simple_optimality_criteria()
1376 ->set_eps_optimal_absolute(0.0);
1377 params.mutable_termination_criteria()
1378 ->mutable_simple_optimality_criteria()
1379 ->set_eps_optimal_relative(0.0);
1381 interrupt_solve.store(
true);
1382 const SolverResult output =
1384 EXPECT_EQ(output.solve_log.termination_reason(),
1385 TERMINATION_REASON_INTERRUPTED_BY_USER);
1388 TEST(PrimalDualHybridGradientTest, RespectsInterruptFromCallback) {
1389 std::atomic<bool> interrupt_solve;
1390 PrimalDualHybridGradientParams params;
1391 params.mutable_termination_criteria()
1392 ->mutable_simple_optimality_criteria()
1393 ->set_eps_optimal_absolute(0.0);
1394 params.mutable_termination_criteria()
1395 ->mutable_simple_optimality_criteria()
1396 ->set_eps_optimal_relative(0.0);
1398 interrupt_solve.store(
false);
1399 auto callback = [&](
const IterationCallbackInfo& info) {
1400 if (info.iteration_stats.iteration_number() >= 10) {
1401 interrupt_solve.store(
true);
1405 const SolverResult output =
1407 EXPECT_EQ(output.solve_log.termination_reason(),
1408 TERMINATION_REASON_INTERRUPTED_BY_USER);
1409 EXPECT_GE(output.solve_log.iteration_count(), 10);
1412 TEST(PrimalDualHybridGradientTest, IgnoresFalseInterrupt) {
1413 std::atomic<bool> interrupt_solve;
1414 PrimalDualHybridGradientParams params;
1415 params.mutable_termination_criteria()
1416 ->mutable_simple_optimality_criteria()
1417 ->set_eps_optimal_absolute(0.0);
1418 params.mutable_termination_criteria()
1419 ->mutable_simple_optimality_criteria()
1420 ->set_eps_optimal_relative(0.0);
1421 params.mutable_termination_criteria()->set_kkt_matrix_pass_limit(1);
1423 interrupt_solve.store(
false);
1424 const SolverResult output =
1426 EXPECT_EQ(output.solve_log.termination_reason(),
1427 TERMINATION_REASON_KKT_MATRIX_PASS_LIMIT);
1430 TEST(PrimalDualHybridGradientTest, HugeNumThreads) {
1431 const int iteration_upperbound = 10;
1432 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
1433 params.mutable_termination_criteria()->set_iteration_limit(
1434 iteration_upperbound);
1435 params.set_num_threads(1'000'000'000);
1438 EXPECT_EQ(output.solve_log.termination_reason(),
1439 TERMINATION_REASON_ITERATION_LIMIT);
1442 TEST(PrimalDualHybridGradientTest, HugeNumShards) {
1443 const int iteration_upperbound = 10;
1444 PrimalDualHybridGradientParams params = ParamsWithNoLimits();
1445 params.mutable_termination_criteria()->set_iteration_limit(
1446 iteration_upperbound);
1447 params.set_num_shards(1'000'000'000);
1450 EXPECT_EQ(output.solve_log.termination_reason(),
1451 TERMINATION_REASON_ITERATION_LIMIT);
1454 TEST(PrimalDualHybridGradientTest, DetailedTerminationCriteria) {
1455 const int iteration_upperbound = 300;
1456 PrimalDualHybridGradientParams params = CreateSolverParams(
1457 iteration_upperbound,
1462 params.set_major_iteration_frequency(60);
1463 params.mutable_termination_criteria()->clear_simple_optimality_criteria();
1464 auto* opt_criteria = params.mutable_termination_criteria()
1465 ->mutable_detailed_optimality_criteria();
1466 opt_criteria->set_eps_optimal_primal_residual_absolute(1.0e-5);
1467 opt_criteria->set_eps_optimal_primal_residual_relative(0.0);
1468 opt_criteria->set_eps_optimal_dual_residual_absolute(1.0e-5);
1469 opt_criteria->set_eps_optimal_dual_residual_relative(0.0);
1470 opt_criteria->set_eps_optimal_objective_gap_absolute(1.0e-5);
1471 opt_criteria->set_eps_optimal_objective_gap_relative(0.0);
1474 VerifyTerminationReasonAndIterationCount(params, output,
1476 VerifyObjectiveValues(output, -1.0, 1.0e-4);
1477 EXPECT_THAT(output.primal_solution,
1478 EigenArrayNear<double>({1, 0, 6, 2}, 1.0e-4));
1479 EXPECT_THAT(output.dual_solution,
1480 EigenArrayNear<double>({0.5, 4.0, 0.0}, 1.0e-4));
1481 EXPECT_THAT(output.reduced_costs,
1482 EigenArrayNear<double>({0.0, 1.5, -3.5, 0.0}, 1.0e-4));
1488 void VerifyBoundConstraints(
const QuadraticProgram& qp,
1489 const Eigen::VectorXd primal_solution,
1490 const Eigen::VectorXd dual_solution) {
1491 for (int64_t i = 0; i < primal_solution.size(); ++i) {
1492 ASSERT_TRUE(std::isfinite(primal_solution[i]))
1493 << i <<
" " << primal_solution[i];
1494 ASSERT_GE(primal_solution[i], qp.variable_lower_bounds[i]);
1495 ASSERT_LE(primal_solution[i], qp.variable_upper_bounds[i]);
1497 for (
int i = 0; i < dual_solution.size(); ++i) {
1498 ASSERT_TRUE(std::isfinite(dual_solution[i]))
1499 << i <<
" " << dual_solution[i];
1500 if (!std::isfinite(qp.constraint_lower_bounds[i])) {
1501 ASSERT_LE(dual_solution[i], 0);
1503 if (!std::isfinite(qp.constraint_upper_bounds[i])) {
1504 ASSERT_GE(dual_solution[i], 0);
1513 TEST_P(PrimalDualHybridGradientVerbosityTest,
TinyLp) {
1514 const int iteration_upperbound = 300;
1515 const int verbosity_level = GetParam();
1516 PrimalDualHybridGradientParams params =
1517 CreateSolverParams(iteration_upperbound,
1524 params.set_major_iteration_frequency(60);
1525 params.set_verbosity_level(verbosity_level);
1528 VerifyTerminationReasonAndIterationCount(params, output,
1530 VerifyObjectiveValues(output, -1.0, 1.0e-4);
1531 EXPECT_THAT(output.primal_solution,
1532 EigenArrayNear<double>({1, 0, 6, 2}, 1.0e-4));
1533 EXPECT_THAT(output.dual_solution,
1534 EigenArrayNear<double>({0.5, 4.0, 0.0}, 1.0e-4));
1535 EXPECT_THAT(output.reduced_costs,
1536 EigenArrayNear<double>({0.0, 1.5, -3.5, 0.0}, 1.0e-4));
1537 EXPECT_EQ(output.solve_log.original_problem_stats().num_variables(), 4);
1538 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_variables(), 4);
1539 EXPECT_EQ(output.solve_log.original_problem_stats().num_constraints(), 3);
1540 EXPECT_LE(output.solve_log.preprocessed_problem_stats().num_constraints(), 3);
1543 TEST(PresolveTest, DetectsProblemWithInconsistentBounds) {
1544 PrimalDualHybridGradientParams params;
1545 params.mutable_presolve_options()->set_use_glop(
true);
1546 SolverResult output =
1548 EXPECT_EQ(output.solve_log.termination_reason(),
1549 TERMINATION_REASON_INVALID_PROBLEM);
1552 TEST(PresolveTest, PresolveSolvesToOptimality) {
1553 PrimalDualHybridGradientParams params;
1554 params.mutable_presolve_options()->set_use_glop(
true);
1556 EXPECT_EQ(output.solve_log.termination_reason(), TERMINATION_REASON_OPTIMAL);
1557 EXPECT_EQ(output.solve_log.iteration_count(), 0);
1558 ASSERT_EQ(output.solve_log.solution_type(), POINT_TYPE_PRESOLVER_SOLUTION);
1559 EXPECT_THAT(output.primal_solution,
1560 EigenArrayNear<double>({-1, 8, 1, 2.5}, 1.0e-10));
1561 EXPECT_THAT(output.dual_solution,
1562 EigenArrayNear<double>({-2, 0, 2.375, 2.0 / 3}, 1.0e-10));
1564 output.solve_log.solution_stats(), POINT_TYPE_PRESOLVER_SOLUTION);
1565 ASSERT_TRUE(convergence_info.has_value());
1566 EXPECT_EQ(convergence_info->candidate_type(), POINT_TYPE_PRESOLVER_SOLUTION);
1567 EXPECT_DOUBLE_EQ(convergence_info->primal_objective(), -34.0);
1568 EXPECT_DOUBLE_EQ(convergence_info->dual_objective(), -34.0);
1569 EXPECT_DOUBLE_EQ(convergence_info->corrected_dual_objective(), -34.0);
1570 EXPECT_DOUBLE_EQ(convergence_info->l_inf_primal_residual(), 0.0);
1571 EXPECT_DOUBLE_EQ(convergence_info->l2_primal_residual(), 0.0);
1572 EXPECT_DOUBLE_EQ(convergence_info->l_inf_dual_residual(), 0.0);
1573 EXPECT_DOUBLE_EQ(convergence_info->l2_dual_residual(), 0.0);
1574 EXPECT_DOUBLE_EQ(convergence_info->l_inf_primal_variable(), 8.0);
1575 EXPECT_DOUBLE_EQ(convergence_info->l2_primal_variable(), 8.5);
1576 EXPECT_DOUBLE_EQ(convergence_info->l_inf_dual_variable(), 2.375);
1577 EXPECT_THAT(convergence_info->l2_dual_variable(),
1578 DoubleNear(3.17569983538, 1.0e-10));
1581 TEST(PresolveTest, SolvesWithGlopScalingEnabled) {
1582 PrimalDualHybridGradientParams params;
1583 params.mutable_presolve_options()->set_use_glop(
true);
1584 params.mutable_presolve_options()->mutable_glop_parameters()->set_use_scaling(
1586 params.mutable_presolve_options()
1587 ->mutable_glop_parameters()
1588 ->set_use_preprocessing(
false);
1589 QuadraticProgram test_lp =
TestLp();
1592 test_lp.objective_scaling_factor = 0.1;
1594 VerifyTerminationReasonAndIterationCount(params, output,
1596 VerifyObjectiveValues(output, -3.4, 1.0e-6);
1597 EXPECT_THAT(output.primal_solution,
1598 EigenArrayNear<double>({-1, 8, 1, 2.5}, 1.0e-4));
1599 EXPECT_THAT(output.dual_solution,
1600 EigenArrayNear<double>({-2, 0, 2.375, 2.0 / 3}, 1.0e-4));
1603 TEST(PresolveTest, PresolveInfeasible) {
1604 PrimalDualHybridGradientParams params;
1605 params.mutable_presolve_options()->set_use_glop(
true);
1606 SolverResult output =
1608 EXPECT_EQ(output.solve_log.termination_reason(),
1609 TERMINATION_REASON_PRIMAL_OR_DUAL_INFEASIBLE);
1610 EXPECT_EQ(output.solve_log.solution_type(), POINT_TYPE_PRESOLVER_SOLUTION);
1611 EXPECT_EQ(output.solve_log.iteration_count(), 0);
1614 TEST_P(PresolveDualScalingTest, Dualize) {
1615 auto [dualize, negate_and_scale_objective] = GetParam();
1616 PrimalDualHybridGradientParams params;
1617 params.mutable_presolve_options()->set_use_glop(
true);
1618 params.mutable_presolve_options()
1619 ->mutable_glop_parameters()
1620 ->set_solve_dual_problem(dualize ? glop::GlopParameters::ALWAYS_DO
1621 : glop::GlopParameters::NEVER_DO);
1622 params.mutable_termination_criteria()->set_iteration_limit(1000);
1624 if (negate_and_scale_objective) {
1625 qp.objective_scaling_factor = -3;
1628 EXPECT_EQ(output.solve_log.termination_reason(), TERMINATION_REASON_OPTIMAL);
1629 EXPECT_GT(output.solve_log.iteration_count(), 0);
1630 EXPECT_THAT(output.primal_solution,
1631 EigenArrayNear<double>({0.5, 0.5, 0.5, 0.0, 0.0, 0.0}, 1.0e-10));
1632 EXPECT_THAT(output.dual_solution,
1633 EigenArrayNear<double>({0.5, 0.5, 0.5}, 1.0e-10));
1636 TEST(PresolveTest, PresolveParametersAreUsed) {
1638 PrimalDualHybridGradientParams params;
1639 params.mutable_termination_criteria()->set_iteration_limit(0);
1640 params.mutable_presolve_options()->set_use_glop(
true);
1642 params.mutable_presolve_options()
1643 ->mutable_glop_parameters()
1644 ->set_solve_dual_problem(glop::GlopParameters::ALWAYS_DO);
1646 EXPECT_NE(output_1.solve_log.preprocessed_problem_stats().num_variables(),
1647 output_2.solve_log.preprocessed_problem_stats().num_variables());
1648 EXPECT_NE(output_1.solve_log.preprocessed_problem_stats().num_constraints(),
1649 output_2.solve_log.preprocessed_problem_stats().num_constraints());
1652 TEST(ComputeStatusesTest, AtOptimum) {
1653 QuadraticProgram lp =
TestLp();
1654 PrimalAndDualSolution solution;
1655 solution.primal_solution.resize(4);
1656 solution.primal_solution << -1, 8, 1, 2.5;
1657 solution.dual_solution.resize(4);
1658 solution.dual_solution << -2, 0, 2.375, 2.0 / 3;
1661 glop_solution.constraint_statuses,
1662 ElementsAre(ConstraintStatus::FIXED_VALUE, ConstraintStatus::BASIC,
1663 ConstraintStatus::AT_LOWER_BOUND,
1664 ConstraintStatus::AT_LOWER_BOUND));
1666 glop_solution.variable_statuses,
1667 ElementsAre(VariableStatus::BASIC, VariableStatus::BASIC,
1668 VariableStatus::BASIC, VariableStatus::AT_LOWER_BOUND));
1671 TEST(ComputeStatusesTest, CoverMoreCases) {
1672 QuadraticProgram lp =
TestLp();
1673 lp.variable_upper_bounds[3] = 2.5;
1674 lp.variable_upper_bounds[1] = 8;
1675 PrimalAndDualSolution solution;
1676 solution.primal_solution.resize(4);
1677 solution.primal_solution << -1, 8, 1, 2.5;
1678 solution.dual_solution.resize(4);
1679 solution.dual_solution << -2, 0, 2.375, -1;
1682 glop_solution.constraint_statuses,
1683 ElementsAre(ConstraintStatus::FIXED_VALUE, ConstraintStatus::BASIC,
1684 ConstraintStatus::AT_LOWER_BOUND,
1685 ConstraintStatus::AT_UPPER_BOUND));
1686 EXPECT_THAT(glop_solution.variable_statuses,
1687 ElementsAre(VariableStatus::BASIC, VariableStatus::AT_UPPER_BOUND,
1688 VariableStatus::BASIC, VariableStatus::FIXED_VALUE));
glop::ProblemSolution ComputeStatuses(const QuadraticProgram &qp, const PrimalAndDualSolution &solution)
absl::StatusOr< QuadraticProgram > QpFromMpModelProto(const MPModelProto &proto, bool relax_integer_variables, bool include_names)
QuadraticProgram SmallDualInfeasibleLp()
QuadraticProgram TestDiagonalQp2()
QuadraticProgram CorrelationClusteringStarLp()
QuadraticProgram TestDiagonalQp3()
constexpr double kInfinity
QuadraticProgram TinyLp()
std::optional< PointMetadata > GetPointMetadata(const IterationStats &stats, const PointType point_type)
QuadraticProgram LpWithoutConstraints()
QuadraticProgram SmallInvalidProblemLp()
EigenArrayNearMatcherP2< Eigen::Array< T, Eigen::Dynamic, 1 >, double > EigenArrayNear(absl::Span< const T > data, double tolerance)
QuadraticProgram SmallPrimalDualInfeasibleLp()
std::optional< InfeasibilityInformation > GetInfeasibilityInformation(const IterationStats &stats, PointType candidate_type)
SolverResult PrimalDualHybridGradient(QuadraticProgram qp, const PrimalDualHybridGradientParams ¶ms, const std::atomic< bool > *interrupt_solve, IterationStatsCallback iteration_stats_callback)
std::optional< ConvergenceInformation > GetConvergenceInformation(const IterationStats &stats, PointType candidate_type)
QuadraticProgram TestLp()
QuadraticProgram SmallInitializationLp()
QuadraticProgram SmallPrimalInfeasibleLp()
QuadraticProgram TestDiagonalQp1()
QuadraticProgram CorrelationClusteringLp()
BoolVar Not(BoolVar x)
A convenient wrapper so we can write Not(x) instead of x.Not() which is sometimes clearer.
TEST(LinearAssignmentTest, NullMatrix)