OR-Tools  9.3
trust_region_test.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cmath>
18#include <cstdint>
19#include <limits>
20#include <vector>
21
22#include "Eigen/Core"
23#include "Eigen/SparseCore"
24#include "gmock/gmock.h"
25#include "gtest/gtest.h"
31
33namespace {
34
35using ::Eigen::VectorXd;
36using ::testing::DoubleNear;
37using ::testing::ElementsAre;
38
39constexpr double kInfinity = std::numeric_limits<double>::infinity();
40
41class TrustRegion : public testing::TestWithParam<
42 /*use_diagonal_solver=*/bool> {};
43
44INSTANTIATE_TEST_SUITE_P(
45 TrustRegionSolvers, TrustRegion, testing::Bool(),
46 [](const testing::TestParamInfo<TrustRegion::ParamType>& info) {
47 return (info.param) ? "UseApproximateTRSolver" : "UseLinearTimeTRSolver";
48 });
49
50TEST_P(TrustRegion, SolvesWithoutVariableBounds) {
51 // min x + y
52 // ||(x - 2.0, y - (-5.0))||_2 <= sqrt(2)
53 // [x*, y*] = [1.0, -6.0]
58 center_point << 2.0, -5.0;
59 objective_vector << 1.0, 1.0;
60 const double target_radius = std::sqrt(2.0);
61
62 Sharder sharder(/*num_elements=*/2, /*num_shards=*/2, nullptr);
63
64 VectorXd expected_solution(2);
65 expected_solution << 1.0, -6.0;
66 const double expected_objective_value = -2.0;
67
68 if (GetParam()) {
69 TrustRegionResult result = SolveDiagonalTrustRegion(
70 objective_vector, /*objective_matrix_diagonal=*/VectorXd::Zero(2),
72 /*norm_weights=*/VectorXd::Ones(2), target_radius, sharder,
73 /*solve_tolerance=*/1.0e-8);
74 EXPECT_THAT(result.solution, EigenArrayNear(expected_solution, 1.0e-6));
75 EXPECT_NEAR(result.objective_value, expected_objective_value, 1.0e-6);
76 } else {
77 TrustRegionResult result = SolveTrustRegion(
79 center_point, /*norm_weights=*/VectorXd::Ones(2), target_radius,
80 sharder);
81 EXPECT_THAT(result.solution, EigenArrayEq(expected_solution));
82 EXPECT_DOUBLE_EQ(result.objective_value, expected_objective_value);
83 }
84}
85
86TEST_P(TrustRegion, SolvesWithVariableBounds) {
87 // min x - y + z
88 // ||(x - 2.0, y - (-5.0), z - 1.0)||_2 <= sqrt(2.0)
89 // x >= 2.0
90 // [x*, y*, z*] = [2.0, -4.0, 0.0]
95 center_point << 2.0, -5.0, 1.0;
96 objective_vector << 1.0, -1.0, 1.0;
97 const double target_radius = std::sqrt(2.0);
98
99 Sharder sharder(/*num_elements=*/3, /*num_shards=*/2, nullptr);
100
101 VectorXd expected_solution(3);
102 expected_solution << 2.0, -4.0, 0.0;
103 const double expected_objective_value = -2.0;
104
105 if (GetParam()) {
106 TrustRegionResult result = SolveDiagonalTrustRegion(
107 objective_vector, /*objective_matrix_diagonal=*/VectorXd::Zero(3),
109 /*norm_weights=*/VectorXd::Ones(3), target_radius, sharder,
110 /*solve_tolerance=*/1.0e-6);
111 EXPECT_THAT(result.solution, EigenArrayNear(expected_solution, 1.0e-6));
112 EXPECT_NEAR(result.objective_value, expected_objective_value, 1.0e-6);
113 } else {
114 TrustRegionResult result = SolveTrustRegion(
116 center_point, /*norm_weights=*/VectorXd::Ones(3), target_radius,
117 sharder);
118
119 EXPECT_THAT(result.solution, EigenArrayEq(expected_solution));
120 EXPECT_DOUBLE_EQ(result.objective_value, expected_objective_value);
121 }
122}
123
124TEST_P(TrustRegion, SolvesAtVariableBounds) {
125 // min x - y
126 // ||(x - 2.0, y - (-5.0))||_2 <= 1
127 // x >= 2.0, y <= -5.0
128 // [x*, y*] = [2.0, -5.0]
129 // The bound constraints block movement from the center point.
134 center_point << 2.0, -5.0;
135 objective_vector << 1.0, -1.0;
136 const double target_radius = 1.0;
137
138 Sharder sharder(/*num_elements=*/2, /*num_shards=*/2, nullptr);
139
140 VectorXd expected_solution(2);
141 expected_solution << 2.0, -5.0;
142 const double expected_objective_value = 0.0;
143
144 if (GetParam()) {
145 TrustRegionResult result = SolveDiagonalTrustRegion(
146 objective_vector, /*objective_matrix_diagonal=*/VectorXd::Zero(2),
148 /*norm_weights=*/VectorXd::Ones(2), target_radius, sharder,
149 /*solve_tolerance=*/1.0e-6);
150
151 EXPECT_THAT(result.solution, EigenArrayNear(expected_solution, 1.0e-6));
152 EXPECT_NEAR(result.objective_value, expected_objective_value, 1.0e-6);
153 } else {
154 TrustRegionResult result = SolveTrustRegion(
156 center_point, /*norm_weights=*/VectorXd::Ones(2), target_radius,
157 sharder);
158
159 EXPECT_THAT(result.solution, EigenArrayEq(expected_solution));
160 EXPECT_DOUBLE_EQ(result.objective_value, expected_objective_value);
161 }
162}
163
164TEST_P(TrustRegion, SolvesWithInactiveRadius) {
165 // min x - y + z
166 // ||(x - 2.0, y - (-5.0), z - 1.0)||_2 <= 1
167 // x >= 2.0, y <= -5.0, z >= 0.5
168 // [x*, y*, z*] = [2.0, -5.0, 0.5]
169 // This is a corner case where the radius constraint is not active at the
170 // solution.
173 variable_lower_bounds << 2.0, -kInfinity, 0.5;
175 center_point << 2.0, -5.0, 1.0;
176 objective_vector << 1.0, -1.0, 1.0;
177 const double target_radius = 1.0;
178
179 Sharder sharder(/*num_elements=*/3, /*num_shards=*/2, nullptr);
180
181 VectorXd expected_solution(3);
182 expected_solution << 2.0, -5.0, 0.5;
183 const double expected_objective_value = -0.5;
184
185 if (GetParam()) {
186 TrustRegionResult result = SolveDiagonalTrustRegion(
187 objective_vector, /*objective_matrix_diagonal=*/VectorXd::Zero(3),
189 /*norm_weights=*/VectorXd::Ones(3), target_radius, sharder,
190 /*solve_tolerance=*/1.0e-6);
191
192 EXPECT_THAT(result.solution, EigenArrayNear(expected_solution, 1.0e-6));
193 EXPECT_NEAR(result.objective_value, expected_objective_value, 1.0e-6);
194 } else {
195 TrustRegionResult result = SolveTrustRegion(
197 center_point, /*norm_weights=*/VectorXd::Ones(3), target_radius,
198 sharder);
199
200 EXPECT_THAT(result.solution, EigenArrayEq(expected_solution));
201 EXPECT_DOUBLE_EQ(result.objective_value, expected_objective_value);
202 }
203}
204
205TEST_P(TrustRegion, SolvesWithInfiniteRadius) {
206 // min x - y + z
207 // ||(x - 2.0, y - (-5.0), z - 1.0)||_2 <= Infinity
208 // x >= 2.0, y <= -5.0, z >= 0.5
209 // [x*, y*, z*] = [2.0, -5.0, 0.5]
212 variable_lower_bounds << 2.0, -kInfinity, 0.5;
214 center_point << 2.0, -5.0, 1.0;
215 objective_vector << 1.0, -1.0, 1.0;
216 const double target_radius = kInfinity;
217
218 Sharder sharder(/*num_elements=*/3, /*num_shards=*/2, nullptr);
219
220 VectorXd expected_solution(3);
221 expected_solution << 2.0, -5.0, 0.5;
222 const double expected_objective_value = -0.5;
223
224 if (GetParam()) {
225 TrustRegionResult result = SolveDiagonalTrustRegion(
226 objective_vector, /*objective_matrix_diagonal=*/VectorXd::Zero(3),
228 /*norm_weights=*/VectorXd::Ones(3), target_radius, sharder,
229 /*solve_tolerance=*/1.0e-6);
230
231 EXPECT_THAT(result.solution, EigenArrayNear(expected_solution, 1.0e-6));
232 EXPECT_NEAR(result.objective_value, expected_objective_value, 1.0e-6);
233 } else {
234 TrustRegionResult result = SolveTrustRegion(
236 center_point, /*norm_weights=*/VectorXd::Ones(3), target_radius,
237 sharder);
238
239 EXPECT_THAT(result.solution, EigenArrayEq(expected_solution));
240 EXPECT_DOUBLE_EQ(result.objective_value, expected_objective_value);
241 }
242}
243
244TEST_P(TrustRegion, SolvesWithMixedObjective) {
245 // min 2x + y
246 // ||(x - 2.0, y - 1.0)||_2 <= sqrt(1.25)
247 // x >= 1.0, y >= 0
248 // [x*, y*] = [1.0, 0.5]
249 // We take a positive step in all coordinates. Only the first coordinate
250 // hits its bound.
253 variable_lower_bounds << 1.0, 0.0;
255 center_point << 2.0, 1.0;
256 objective_vector << 2.0, 1.0;
257 const double target_radius = std::sqrt(1.25);
258
259 Sharder sharder(/*num_elements=*/2, /*num_shards=*/2, nullptr);
260
261 VectorXd expected_solution(2);
262 expected_solution << 1.0, 0.5;
263 const double expected_objective_value = -2.5;
264
265 if (GetParam()) {
266 TrustRegionResult result = SolveDiagonalTrustRegion(
267 objective_vector, /*objective_matrix_diagonal=*/VectorXd::Zero(2),
269 /*norm_weights=*/VectorXd::Ones(2), target_radius, sharder,
270 /*solve_tolerance=*/1.0e-6);
271 EXPECT_THAT(result.solution, EigenArrayNear(expected_solution, 1.0e-6));
272 EXPECT_NEAR(result.objective_value, expected_objective_value, 2.0e-6);
273 } else {
274 TrustRegionResult result = SolveTrustRegion(
276 center_point, /*norm_weights=*/VectorXd::Ones(2), target_radius,
277 sharder);
278
279 EXPECT_THAT(result.solution, EigenArrayEq(expected_solution));
280 EXPECT_DOUBLE_EQ(result.objective_value, expected_objective_value);
281 }
282}
283
284TEST_P(TrustRegion, SolvesWithZeroObjectiveNoBounds) {
285 // min 0*x
286 // ||(x - 2.0)||_2 <= 1
287 // x* = 2.0
292 center_point << 2.0;
293 objective_vector << 0.0;
294 const double target_radius = 1.0;
295
296 Sharder sharder(/*num_elements=*/1, /*num_shards=*/1, nullptr);
297
298 VectorXd expected_solution(1);
299 expected_solution << 2.0;
300 const double expected_objective_value = 0.0;
301
302 if (GetParam()) {
303 TrustRegionResult result = SolveDiagonalTrustRegion(
304 objective_vector, /*objective_matrix_diagonal=*/VectorXd::Zero(1),
306 /*norm_weights=*/VectorXd::Ones(1), target_radius, sharder,
307 /*solve_tolerance=*/1.0e-6);
308
309 EXPECT_THAT(result.solution, EigenArrayNear(expected_solution, 1.0e-6));
310 EXPECT_NEAR(result.objective_value, expected_objective_value, 1.0e-6);
311 } else {
312 TrustRegionResult result = SolveTrustRegion(
314 center_point, /*norm_weights=*/VectorXd::Ones(1), target_radius,
315 sharder);
316
317 EXPECT_THAT(result.solution, EigenArrayEq(expected_solution));
318 EXPECT_DOUBLE_EQ(result.objective_value, expected_objective_value);
319 }
320}
321
322class TrustRegionWithWeights : public testing::TestWithParam<
323 /*use_diagonal_solver=*/bool> {};
324
325INSTANTIATE_TEST_SUITE_P(
326 TrustRegionSolverWithWeights, TrustRegionWithWeights, testing::Bool(),
327 [](const testing::TestParamInfo<TrustRegion::ParamType>& info) {
328 return (info.param) ? "UseApproximateTRSolver" : "UseLinearTimeTRSolver";
329 });
330
331TEST_P(TrustRegionWithWeights, SolvesWithoutVariableBounds) {
332 // min x + 2.0 y
333 // ||(x - 2.0, y - (-5.0))||_W <= sqrt(3)
334 // norm_weights = [1.0, 2.0]
335 // [x*, y*] = [1.0, -6.0]
340 center_point << 2.0, -5.0;
341 objective_vector << 1.0, 2.0;
342 norm_weights << 1.0, 2.0;
343 const double target_radius = std::sqrt(3.0);
344
345 Sharder sharder(/*num_elements=*/2, /*num_shards=*/2, nullptr);
346
347 VectorXd expected_solution(2);
348 expected_solution << 1.0, -6.0;
349 const double expected_objective_value = -3.0;
350
351 if (GetParam()) {
352 TrustRegionResult result = SolveDiagonalTrustRegion(
353 objective_vector, /*objective_matrix_diagonal=*/VectorXd::Zero(2),
355 norm_weights, target_radius, sharder, /*solve_tolerance=*/1.0e-6);
356
357 EXPECT_THAT(result.solution, EigenArrayNear(expected_solution, 1.0e-6));
358 EXPECT_NEAR(result.objective_value, expected_objective_value, 1.0e-5);
359 } else {
360 TrustRegionResult result = SolveTrustRegion(
362 center_point, norm_weights, target_radius, sharder);
363
364 EXPECT_THAT(result.solution, EigenArrayEq(expected_solution));
365 EXPECT_DOUBLE_EQ(result.objective_value, expected_objective_value);
366 }
367}
368
369TEST_P(TrustRegionWithWeights, SolvesWithVariableBounds) {
370 // min 0.5 x - 2.0 y + 3.0 z
371 // ||(x - 2.0, y - (-5.0), z - 1.0)||_W <= sqrt(5)
372 // x >= 2.0
373 // norm_weights = [0.5, 2.0, 3.0]
374 // [x*, y*, z*] = [2.0, -4.0, 0.0]
379 center_point << 2.0, -5.0, 1.0;
380 objective_vector << 0.5, -2.0, 3.0;
381 norm_weights << 0.5, 2.0, 3.0;
382 const double target_radius = std::sqrt(5.0);
383
384 Sharder sharder(/*num_elements=*/3, /*num_shards=*/2, nullptr);
385
386 VectorXd expected_solution(3);
387 expected_solution << 2.0, -4.0, 0.0;
388 const double expected_objective_value = -5.0;
389
390 if (GetParam()) {
391 TrustRegionResult result = SolveDiagonalTrustRegion(
392 objective_vector, /*objective_matrix_diagonal=*/VectorXd::Zero(3),
394 norm_weights, target_radius, sharder, /*solve_tolerance=*/1.0e-6);
395
396 EXPECT_THAT(result.solution, EigenArrayNear(expected_solution, 1.0e-6));
397 EXPECT_NEAR(result.objective_value, expected_objective_value, 1.0e-5);
398 } else {
399 TrustRegionResult result = SolveTrustRegion(
401 center_point, norm_weights, target_radius, sharder);
402
403 EXPECT_THAT(result.solution, EigenArrayEq(expected_solution));
404 EXPECT_DOUBLE_EQ(result.objective_value, expected_objective_value);
405 }
406}
407
408TEST_P(TrustRegionWithWeights, SolvesWithVariableThatHitsBounds) {
409 // min x + 2y
410 // ||(x - 2.0, y - 1.0)||_2 <= 1
411 // x >= 1.0, y >= 0
412 // [x*, y*] = [1.0, 0.5]
413 // norm_weights = [0.5, 2.0]
414 // We take a positive step in all coordinates. Only the first coordinate
415 // hits its bound.
418 variable_lower_bounds << 1.0, 0.0;
420 center_point << 2.0, 1.0;
421 objective_vector << 1.0, 2.0;
422 norm_weights << 0.5, 2.0;
423 const double target_radius = 1;
424
425 Sharder sharder(/*num_elements=*/2, /*num_shards=*/2, nullptr);
426
427 VectorXd expected_solution(2);
428 expected_solution << 1.0, 0.5;
429 const double expected_objective_value = -2.0;
430
431 if (GetParam()) {
432 TrustRegionResult result = SolveDiagonalTrustRegion(
433 objective_vector, /*objective_matrix_diagonal=*/VectorXd::Zero(2),
435 norm_weights, target_radius, sharder,
436 /*solve_tolerance=*/1.0e-6);
437
438 EXPECT_THAT(result.solution, EigenArrayNear(expected_solution, 1.0e-6));
439 EXPECT_NEAR(result.objective_value, expected_objective_value, 1.0e-6);
440 } else {
441 TrustRegionResult result = SolveTrustRegion(
443 center_point, norm_weights, target_radius, sharder);
444
445 EXPECT_THAT(result.solution,
446 ElementsAre(expected_solution[0],
447 DoubleNear(expected_solution[1], 1.0e-13)));
448 EXPECT_DOUBLE_EQ(result.objective_value, expected_objective_value);
449 }
450}
451
452TEST_P(TrustRegionWithWeights, SolvesWithLargeWeight) {
453 // min 1000.0 x + 2y
454 // ||(x - 2.0, y - 1.0)||_W <= sqrt(500.5)
455 // x >= 1.0, y >= 0
456 // [x*, y*] = [1.0, 0.5]
457 // norm_weights = [500.0, 2.0]
458 // We take a positive step in all coordinates. Only the first coordinate
459 // hits its bound. The large norm weight stresses the code.
462 variable_lower_bounds << 1.0, 0.0;
464 center_point << 2.0, 1.0;
465 objective_vector << 1000.0, 2.0;
466 norm_weights << 500.0, 2.0;
467 const double target_radius = std::sqrt(500.5);
468
469 Sharder sharder(/*num_elements=*/2, /*num_shards=*/2, nullptr);
470
471 VectorXd expected_solution(2);
472 expected_solution << 1.0, 0.5;
473 const double expected_objective_value = -1001.0;
474
475 if (GetParam()) {
476 TrustRegionResult result = SolveDiagonalTrustRegion(
477 objective_vector, /*objective_matrix_diagonal=*/VectorXd::Zero(2),
479 norm_weights, target_radius, sharder,
480 /*solve_tolerance=*/1.0e-6);
481
482 EXPECT_THAT(result.solution, EigenArrayNear(expected_solution, 1.0e-6));
483 EXPECT_NEAR(result.objective_value, expected_objective_value, 1.0e-6);
484 } else {
485 TrustRegionResult result = SolveTrustRegion(
487 center_point, norm_weights, target_radius, sharder);
488
489 EXPECT_THAT(result.solution,
490 ElementsAre(expected_solution[0],
491 DoubleNear(expected_solution[1], 1.0e-13)));
492 EXPECT_DOUBLE_EQ(result.objective_value, -1001.0);
493 }
494}
495
496TEST(TrustRegionDeathTest, CheckFailsWithNonPositiveWeights) {
497 // min x + y
498 // ||(x - 2.0, y - (-5.0))||_2 <= sqrt(2)
499 // [x*, y*] = [1.0, -6.0]
504 center_point << 2.0, -5.0;
505 objective_vector << 1.0, 1.0;
506 norm_weights << 0.0, 1.0;
507 const double target_radius = std::sqrt(2.0);
508
509 Sharder sharder(/*num_elements=*/2, /*num_shards=*/2, nullptr);
510
511 EXPECT_DEATH(TrustRegionResult result =
514 norm_weights, target_radius, sharder),
515 "Check failed: norm_weights_are_positive");
516}
517
518TEST(TrustRegionDeathTest, CheckFailsWithNonPositiveWeightsForDiagonalSolver) {
519 // min x + y
520 // ||(x - 2.0, y - (-5.0))||_2 <= sqrt(2)
521 // [x*, y*] = [1.0, -6.0]
526 center_point << 2.0, -5.0;
527 objective_vector << 1.0, 1.0;
528 norm_weights << 0.0, 1.0;
529 const double target_radius = std::sqrt(2.0);
530
531 Sharder sharder(/*num_elements=*/2, /*num_shards=*/2, nullptr);
532
533 EXPECT_DEATH(
534 TrustRegionResult result = SolveDiagonalTrustRegion(
535 objective_vector, /*objective_matrix_diagonal=*/VectorXd::Zero(2),
537 norm_weights, target_radius, sharder,
538 /*solve_tolerance=*/1.0e-6),
539 "Check failed: norm_weights_are_positive");
540}
541
542TEST(TrustRegionDeathTest, CheckFailsWithNegativeRadius) {
543 // min x + y
544 // ||(x - 2.0, y - (-5.0))||_2 <= sqrt(2)
545 // [x*, y*] = [1.0, -6.0]
550 center_point << 2.0, -5.0;
551 objective_vector << 1.0, 1.0;
552 const double target_radius = -std::sqrt(2.0);
553
554 Sharder sharder(/*num_elements=*/2, /*num_shards=*/2, nullptr);
555
556 EXPECT_DEATH(TrustRegionResult result = SolveTrustRegion(
559 /*norm_weights=*/VectorXd::Ones(2), target_radius, sharder),
560 "Check failed: target_radius >= 0.0");
561}
562
563TEST(TrustRegionDeathTest, CheckFailsWithNegativeRadiusForDiagonalSolver) {
564 // min x + y
565 // ||(x - 2.0, y - (-5.0))||_2 <= sqrt(2)
566 // [x*, y*] = [1.0, -6.0]
571 center_point << 2.0, -5.0;
572 objective_vector << 1.0, 1.0;
573 const double target_radius = -std::sqrt(2.0);
574
575 Sharder sharder(/*num_elements=*/2, /*num_shards=*/2, nullptr);
576
577 EXPECT_DEATH(
578 TrustRegionResult result = SolveDiagonalTrustRegion(
579 objective_vector, /*objective_matrix_diagonal=*/VectorXd::Zero(2),
581 /*norm_weights=*/VectorXd::Ones(2), target_radius, sharder,
582 /*solve_tolerance=*/1.0e-6),
583 "Check failed: target_radius >= 0.0");
584}
585
586class ComputeLocalizedLagrangianBoundsTest
587 : public testing::TestWithParam<std::tuple<PrimalDualNorm, bool>> {
588 protected:
589 void SetUp() override {
590 const auto [primal_dual_norm, use_diagonal_qp_trust_region_solver] =
591 GetParam();
592 if (use_diagonal_qp_trust_region_solver &&
593 (primal_dual_norm == PrimalDualNorm::kMaxNorm)) {
594 GTEST_SKIP() << "The diagonal QP trust region solver can only be used "
595 << "when the underlying norms are Euclidean.";
596 }
597 }
598};
599
600INSTANTIATE_TEST_SUITE_P(
601 TrustRegionNorm, ComputeLocalizedLagrangianBoundsTest,
602 testing::Combine(testing::Values(PrimalDualNorm::kEuclideanNorm,
604 testing::Bool()),
605 [](const testing::TestParamInfo<
606 ComputeLocalizedLagrangianBoundsTest::ParamType>& info) {
607 const absl::string_view suffix =
608 std::get<1>(info.param) ? "DiagonalTRSolver" : "LinearTimeTRSolver";
609 switch (std::get<0>(info.param)) {
611 return absl::StrCat("EuclideanNorm", "_", suffix);
613 return absl::StrCat("MaxNorm", "_", suffix);
614 }
615 });
616
617TEST_P(ComputeLocalizedLagrangianBoundsTest, ZeroGapAtOptimal) {
618 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
619
620 VectorXd primal_solution(4), dual_solution(4);
621 primal_solution << -1.0, 8.0, 1.0, 2.5;
622 dual_solution << -2.0, 0.0, 2.375, 2.0 / 3.0;
623
624 const auto [primal_dual_norm, use_diagonal_qp_solver] = GetParam();
625
626 LocalizedLagrangianBounds bounds = ComputeLocalizedLagrangianBounds(
627 lp, primal_solution, dual_solution, primal_dual_norm,
628 /*primal_weight=*/1.0, /*radius=*/1.0,
629 /*primal_product=*/nullptr,
630 /*dual_product=*/nullptr, use_diagonal_qp_solver,
631 /*diagonal_qp_trust_region_solver_tolerance=*/1.0e-2);
632
633 EXPECT_DOUBLE_EQ(bounds.radius, 1.0);
634 EXPECT_DOUBLE_EQ(bounds.lagrangian_value, -20.0);
635 EXPECT_DOUBLE_EQ(bounds.lower_bound, -20.0);
636 EXPECT_DOUBLE_EQ(bounds.upper_bound, -20.0);
637}
638
639// Sets the radius to the exact distance to optimal and checks that the
640// optimal lagrangian value is contained in the computed interval.
641TEST_P(ComputeLocalizedLagrangianBoundsTest, OptimalInBoundRange) {
642 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
643
644 // x_3 has a lower bound of 2.5.
645 VectorXd primal_solution(4);
646 primal_solution << 0.0, 0.0, 0.0, 3.0;
647 VectorXd dual_solution = VectorXd::Zero(4);
648
649 const auto [primal_dual_norm, use_diagonal_qp_solver] = GetParam();
650
651 const double primal_distance_squared_to_optimal =
652 0.5 * (1.0 + 8.0 * 8.0 + 1.0 + 0.5 * 0.5);
653 const double dual_distance_squared_to_optimal =
654 0.5 * (4.0 + 2.375 * 2.375 + 4.0 / 9.0);
655 const double distance_to_optimal =
656 primal_dual_norm == PrimalDualNorm::kEuclideanNorm
657 ? std::sqrt(primal_distance_squared_to_optimal +
658 dual_distance_squared_to_optimal)
659 : std::sqrt(std::max(primal_distance_squared_to_optimal,
660 dual_distance_squared_to_optimal));
661
662 LocalizedLagrangianBounds bounds = ComputeLocalizedLagrangianBounds(
663 lp, primal_solution, dual_solution, primal_dual_norm,
664 /*primal_weight=*/1.0,
665 /*radius=*/distance_to_optimal,
666 /*primal_product=*/nullptr,
667 /*dual_product=*/nullptr, use_diagonal_qp_solver,
668 /*diagonal_qp_trust_region_solver_tolerance=*/1.0e-6);
669
670 EXPECT_DOUBLE_EQ(bounds.lagrangian_value, 3.0);
671 EXPECT_LE(bounds.lower_bound, -20.0);
672 EXPECT_GE(bounds.upper_bound, -20.0);
673}
674
675// When the radius is too small, the optimal value will not be contained in
676// the computed interval.
677TEST_P(ComputeLocalizedLagrangianBoundsTest, OptimalNotInBoundRange) {
678 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
679
680 // x_3 has a lower bound of 2.5.
681 VectorXd primal_solution(4);
682 primal_solution << 0.0, 0.0, 0.0, 3.0;
683 VectorXd dual_solution = VectorXd::Zero(4);
684
685 const auto [primal_dual_norm, use_diagonal_qp_solver] = GetParam();
686
687 LocalizedLagrangianBounds bounds = ComputeLocalizedLagrangianBounds(
688 lp, primal_solution, dual_solution, primal_dual_norm,
689 /*primal_weight=*/1.0,
690 /*radius=*/0.1,
691 /*primal_product=*/nullptr,
692 /*dual_product=*/nullptr, use_diagonal_qp_solver,
693 /*diagonal_qp_trust_region_solver_tolerance=*/1.0e-6);
694 const double expected_lagrangian = 3.0;
695 EXPECT_DOUBLE_EQ(bounds.lagrangian_value, expected_lagrangian);
696
697 // Because the dual solution is all zero, the primal gradient is just the
698 // objective, [5.5, -2, -1, 1]. The dual gradient is the dual subgradient
699 // coefficient minus the primal product. With a zero dual, for one-sided
700 // constraints, the dual subgradient coefficient is the bound, and for
701 // two-sided constraints it is the violated bound (or zero if feasible). Thus,
702 // the dual subgradient coefficients are [12, 7, -4, -1], and the primal
703 // product is [6, 0, 0, -3], giving a dual gradient of [6, 7, -4, 2].
704
705 switch (primal_dual_norm) {
707 // The target_radius r = sqrt(2) * 0.1 ≈ 0.14, and the projected primal
708 // direction is d=[-5.5, 2, 1, -1]. The resulting delta is d / ||d|| * r,
709 // giving an objective delta of
710 // ||d|| * r.
711 EXPECT_NEAR(bounds.lower_bound,
712 expected_lagrangian - 0.1 * sqrt(2) * sqrt(36.25), 1.0e-6);
713 // The target_radius r = sqrt(2) * 0.1 ≈ 0.14, and the projected dual
714 // direction is d=[6, 0, 0, 2]. The resulting delta is d / ||d|| * r,
715 // giving an objective delta of
716 // ||d|| * r.
717 EXPECT_NEAR(bounds.upper_bound,
718 expected_lagrangian + 0.1 * sqrt(2) * sqrt(40.0), 1.0e-6);
719 break;
721 // In this case, r = target_radius * sqrt(2) (because the euclidean norm
722 // includes a factor of 0.5). The projected combined direction is d=[-5.5,
723 // 2, 1, -1; 6, 0, 0, 2]. The resulting primal delta is d[primal] / ||d||
724 // * r, and the resulting dual delta is d[dual] / ||d|| * r.
725 EXPECT_NEAR(bounds.lower_bound,
726 expected_lagrangian - 0.1 * sqrt(2) * 36.25 / sqrt(76.25),
727 1.0e-6);
728 EXPECT_NEAR(bounds.upper_bound,
729 expected_lagrangian + 0.1 * sqrt(2) * 40 / sqrt(76.25),
730 1.0e-6);
731 break;
732 }
733}
734
735// kEuclideanNorm isn't covered by this test because the analysis of the
736// correct solution is more complex.
737TEST(ComputeLocalizedLagrangianBoundsTest, ProcessesPrimalWeight) {
738 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
739
740 // x_3 has a lower bound of 2.5.
741 VectorXd primal_solution(4);
742 primal_solution << 0.0, 0.0, 0.0, 3.0;
743 VectorXd dual_solution = VectorXd::Zero(4);
744
745 LocalizedLagrangianBounds bounds = ComputeLocalizedLagrangianBounds(
746 lp, primal_solution, dual_solution, PrimalDualNorm::kMaxNorm,
747 /*primal_weight=*/100.0,
748 /*radius=*/0.1,
749 /*primal_product=*/nullptr,
750 /*dual_product=*/nullptr,
751 /*use_diagonal_qp_trust_region_solver=*/false,
752 /*diagonal_qp_trust_region_solver_tolerance=*/0.0);
753 const double expected_lagrangian = 3.0;
754 EXPECT_DOUBLE_EQ(bounds.lagrangian_value, expected_lagrangian);
755
756 // Compared with OptimalNotInBoundRange, a primal weight of 100.0 translates
757 // to a 10x smaller radius in the primal and 10x larger radius in the dual.
758 EXPECT_LE(bounds.lower_bound, expected_lagrangian - 0.028);
759 EXPECT_GE(bounds.lower_bound, expected_lagrangian - 0.28);
760 EXPECT_GE(bounds.upper_bound, expected_lagrangian + 2.8);
761 EXPECT_LE(bounds.upper_bound, expected_lagrangian + 28);
762}
763
764// Same as OptimalInBoundRange but providing primal_product and dual_product.
765TEST_P(ComputeLocalizedLagrangianBoundsTest, AcceptsCachedProducts) {
766 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
767
768 // x_3 has a lower bound of 2.5.
769 VectorXd primal_solution(4);
770 primal_solution << 0.0, 0.0, 0.0, 3.0;
771 VectorXd dual_solution = VectorXd::Zero(4);
772
773 VectorXd primal_product(4);
774 primal_product << 6.0, 0.0, 0.0, -3.0;
775 VectorXd dual_product = VectorXd::Zero(4);
776
777 const auto [primal_dual_norm, use_diagonal_qp_solver] = GetParam();
778
779 const double primal_distance_squared_to_optimal =
780 0.5 * (1.0 + 8.0 * 8.0 + 1.0 + 0.5 * 0.5);
781 const double dual_distance_squared_to_optimal =
782 0.5 * (4.0 + 2.375 * 2.375 + 4.0 / 9.0);
783 const double distance_to_optimal =
784 primal_dual_norm == PrimalDualNorm::kEuclideanNorm
785 ? std::sqrt(primal_distance_squared_to_optimal +
786 dual_distance_squared_to_optimal)
787 : std::sqrt(std::max(primal_distance_squared_to_optimal,
788 dual_distance_squared_to_optimal));
789
790 LocalizedLagrangianBounds bounds = ComputeLocalizedLagrangianBounds(
791 lp, primal_solution, dual_solution, primal_dual_norm,
792 /*primal_weight=*/1.0,
793 /*radius=*/distance_to_optimal,
794 /*primal_product=*/&primal_product,
795 /*dual_product=*/&dual_product, use_diagonal_qp_solver,
796 /*diagonal_qp_trust_region_solver_tolerance=*/1.0e-6);
797
798 EXPECT_DOUBLE_EQ(bounds.lagrangian_value, 3.0);
799 EXPECT_LE(bounds.lower_bound, -20.0);
800 EXPECT_GE(bounds.upper_bound, -20.0);
801}
802
803// The LP:
804// minimize 1.0 x
805// s.t. 0 <= x <= 1 (as a constraint, not variable bound).
806QuadraticProgram OneDimLp() {
807 QuadraticProgram lp(1, 1);
808 lp.constraint_lower_bounds << 0;
809 lp.constraint_upper_bounds << 1;
810 lp.variable_lower_bounds << -kInfinity;
811 lp.variable_upper_bounds << kInfinity;
812 std::vector<Eigen::Triplet<double, int64_t>> triplets = {{0, 0, 1}};
813 lp.constraint_matrix.setFromTriplets(triplets.begin(), triplets.end());
814 lp.objective_vector << 1.0;
815 return lp;
816}
817
818// The QP:
819// minimize 1.0 x + 1.0 * x^2
820// s.t. 0 <= x <= 1 (as a constraint, not variable bound).
821QuadraticProgram OneDimQp() {
822 QuadraticProgram qp(1, 1);
823 qp.constraint_lower_bounds << 0;
824 qp.constraint_upper_bounds << 1;
825 qp.variable_lower_bounds << -kInfinity;
826 qp.variable_upper_bounds << kInfinity;
827 std::vector<Eigen::Triplet<double, int64_t>> constraint_matrix_triplets = {
828 {0, 0, 1}};
829 qp.constraint_matrix.setFromTriplets(constraint_matrix_triplets.begin(),
830 constraint_matrix_triplets.end());
831 qp.objective_matrix.emplace();
832 qp.objective_matrix->resize(1);
833 qp.objective_matrix->diagonal() << 2;
834 qp.objective_vector << 1;
835 return qp;
836}
837
838// Helper functions to compute the primal and dual gradient at a given point.
839VectorXd GetPrimalGradient(const ShardedQuadraticProgram& sharded_qp,
840 const VectorXd& primal_solution,
841 const VectorXd& dual_solution) {
842 const auto dual_product = TransposedMatrixVectorProduct(
843 sharded_qp.Qp().constraint_matrix, dual_solution,
844 sharded_qp.ConstraintMatrixSharder());
845 return ComputePrimalGradient(sharded_qp, primal_solution, dual_product)
846 .gradient;
847}
848
849VectorXd GetDualGradient(const ShardedQuadraticProgram& sharded_qp,
850 const VectorXd& primal_solution,
851 const VectorXd& dual_solution) {
852 const auto primal_product = TransposedMatrixVectorProduct(
853 sharded_qp.TransposedConstraintMatrix(), primal_solution,
854 sharded_qp.TransposedConstraintMatrixSharder());
855 return ComputeDualGradient(sharded_qp, dual_solution, primal_product)
856 .gradient;
857}
858
859struct TestProblemData {
862 VectorXd center_point;
865 VectorXd norm_weights;
866};
867
868// Generates the problem data corresponding to OneDimLp() as raw vectors with
869// center point [x, y] = [0, -1].
870TestProblemData GenerateTestLpProblemData(const double primal_weight) {
871 // Extract objective vector from primal and dual gradients.
872 VectorXd objective_vector(2), center_point(2), norm_weights(2),
874 objective_vector << 2, -1;
875 center_point << 0, -1;
876 norm_weights << 0.5 * primal_weight, 0.5 / primal_weight;
879 return {.objective_vector = objective_vector,
880 .objective_matrix_diagonal = VectorXd::Zero(2),
881 .center_point = center_point,
882 .variable_lower_bounds = variable_lower_bounds,
883 .variable_upper_bounds = variable_upper_bounds,
884 .norm_weights = norm_weights};
885}
886
887// Generates the problem data corresponding to OneDimQp() as raw vectors with
888// center point [x, y] = [0, -1].
889TestProblemData GenerateTestQpProblemData(const double primal_weight) {
890 TestProblemData lp_data = GenerateTestLpProblemData(primal_weight);
891 lp_data.objective_matrix_diagonal[0] = 2.0;
892 return lp_data;
893}
894
895// This is a tiny problem where we can compute the exact solution, checking
896// that kMaxNorm and kEuclideanNorm give different answers.
897TEST_P(ComputeLocalizedLagrangianBoundsTest, NormsBehaveDifferently) {
898 ShardedQuadraticProgram lp(OneDimLp(), /*num_threads=*/2, /*num_shards=*/2);
899
900 VectorXd primal_solution = VectorXd::Zero(1);
901 VectorXd dual_solution(1);
902 dual_solution << -1; // The upper bound is active.
903
904 // The primal gradient is [2], and the dual gradient is [1]. Hence, the norm
905 // of the gradient is sqrt(5).
906
907 const auto [primal_dual_norm, use_diagonal_qp_solver] = GetParam();
908
909 LocalizedLagrangianBounds bounds = ComputeLocalizedLagrangianBounds(
910 lp, primal_solution, dual_solution, primal_dual_norm,
911 /*primal_weight=*/1.0, /*radius=*/1.0 / std::sqrt(2.0),
912 /*primal_product=*/nullptr,
913 /*dual_product=*/nullptr, use_diagonal_qp_solver,
914 /*diagonal_qp_trust_region_solver_tolerance=*/1.0e-6);
915 const double expected_lagrangian = -1;
916 EXPECT_DOUBLE_EQ(bounds.lagrangian_value, expected_lagrangian);
917
918 switch (primal_dual_norm) {
920 EXPECT_DOUBLE_EQ(bounds.lower_bound, expected_lagrangian - 2.0);
921 EXPECT_DOUBLE_EQ(bounds.upper_bound, expected_lagrangian + 1.0);
922 break;
924 if (use_diagonal_qp_solver) {
925 EXPECT_NEAR(bounds.lower_bound,
926 expected_lagrangian - 4.0 / std::sqrt(5), 1.0e-6);
927 EXPECT_NEAR(bounds.upper_bound,
928 expected_lagrangian + 1.0 / std::sqrt(5), 1.0e-6);
929 } else {
930 EXPECT_DOUBLE_EQ(bounds.lower_bound,
931 expected_lagrangian - 4.0 / std::sqrt(5));
932 EXPECT_DOUBLE_EQ(bounds.upper_bound,
933 expected_lagrangian + 1.0 / std::sqrt(5));
934 }
935 break;
936 }
937}
938
939// Like NormsBehaveDifferently but with a larger primal weight.
940TEST_P(ComputeLocalizedLagrangianBoundsTest,
941 NormsBehaveDifferentlyWithLargePrimalWeight) {
942 ShardedQuadraticProgram lp(OneDimLp(), /*num_threads=*/2, /*num_shards=*/2);
943
944 VectorXd primal_solution = VectorXd::Zero(1);
945 VectorXd dual_solution(1);
946 dual_solution << -1; // The upper bound is active.
947
948 // The primal gradient is [2], and the dual gradient is [1].
949
950 const auto [primal_dual_norm, use_diagonal_qp_solver] = GetParam();
951
952 LocalizedLagrangianBounds bounds = ComputeLocalizedLagrangianBounds(
953 lp, primal_solution, dual_solution, primal_dual_norm,
954 /*primal_weight=*/100.0, /*radius=*/1.0 / std::sqrt(2.0),
955 /*primal_product=*/nullptr,
956 /*dual_product=*/nullptr, use_diagonal_qp_solver,
957 /*diagonal_qp_trust_region_solver_tolerance=*/1.0e-8);
958 const double expected_lagrangian = -1;
959 EXPECT_DOUBLE_EQ(bounds.lagrangian_value, expected_lagrangian);
960
961 switch (primal_dual_norm) {
963 EXPECT_DOUBLE_EQ(bounds.lower_bound, expected_lagrangian - 0.2);
964 EXPECT_DOUBLE_EQ(bounds.upper_bound, expected_lagrangian + 10.0);
965 break;
967 // Given c = [2.0, -1], w = [100.0, 0.01], this value is
968 // dot(c, (c ./ w) / norm(c ./ sqrt.(w))) (in Julia syntax).
969 if (use_diagonal_qp_solver) {
970 EXPECT_NEAR(bounds.upper_bound - bounds.lower_bound, 10.00199980003999,
971 10.002 * 1.0e-8);
972 } else {
973 EXPECT_DOUBLE_EQ(bounds.upper_bound - bounds.lower_bound,
974 10.00199980003999);
975 }
976 break;
977 }
978}
979
980TEST(DiagonalTrustRegionSolverTest, JointSolverWorksWithOneDimQpUnitWeight) {
981 ShardedQuadraticProgram sharded_qp(OneDimQp(), /*num_threads=*/2,
982 /*num_shards=*/2);
983 const auto problem_data = GenerateTestQpProblemData(/*primal_weight=*/1.0);
984 TrustRegionResult result = SolveDiagonalTrustRegion(
985 problem_data.objective_vector, problem_data.objective_matrix_diagonal,
986 problem_data.variable_lower_bounds, problem_data.variable_upper_bounds,
987 problem_data.center_point, problem_data.norm_weights,
988 /*target_radius=*/0.5,
989 Sharder(/*num_elements=*/2, /*num_shards=*/2, nullptr),
990 /*solve_tolerance=*/1.0e-6);
991 EXPECT_THAT(result.solution,
992 ElementsAre(DoubleNear(-0.5, 1.0e-6), DoubleNear(-0.5, 1.0e-6)));
993 EXPECT_NEAR(result.solution_step_size, 4.0, 4.0 * 1.0e-6);
994 EXPECT_NEAR(result.objective_value, -1.25, 1.0e-6);
995}
996
997TEST(DiagonalTrustRegionSolverTest,
998 DiagonalQpSolverWorksWithOneDimQpUnitWeight) {
999 ShardedQuadraticProgram sharded_qp(OneDimQp(), /*num_threads=*/2,
1000 /*num_shards=*/2);
1001 VectorXd primal_solution = VectorXd::Zero(1);
1002 VectorXd dual_solution = -1.0 * VectorXd::Ones(1);
1003 VectorXd primal_gradient =
1004 GetPrimalGradient(sharded_qp, primal_solution, dual_solution);
1005 VectorXd dual_gradient =
1006 GetDualGradient(sharded_qp, primal_solution, dual_solution);
1007 TrustRegionResult result = SolveDiagonalQpTrustRegion(
1008 sharded_qp, primal_solution, dual_solution, primal_gradient,
1009 dual_gradient, /*primal_weight=*/1.0, /*target_radius=*/0.5,
1010 /*solve_tolerance=*/1.0e-6);
1011 EXPECT_THAT(result.solution,
1012 ElementsAre(DoubleNear(-0.5, 1.0e-6), DoubleNear(-0.5, 1.0e-6)));
1013 EXPECT_NEAR(result.solution_step_size, 4.0, 4.0 * 1.0e-6);
1014 EXPECT_NEAR(result.objective_value, -1.25, 1.0e-6);
1015}
1016
1017TEST(DiagonalTrustRegionSolverTest, JointSolverWorksWithOneDimQpLargeWeight) {
1018 ShardedQuadraticProgram sharded_qp(OneDimQp(), /*num_threads=*/2,
1019 /*num_shards=*/2);
1020 const auto problem_data = GenerateTestQpProblemData(/*primal_weight=*/100.0);
1021 TrustRegionResult result = SolveDiagonalTrustRegion(
1022 problem_data.objective_vector, problem_data.objective_matrix_diagonal,
1023 problem_data.variable_lower_bounds, problem_data.variable_upper_bounds,
1024 problem_data.center_point, problem_data.norm_weights,
1025 /*target_radius=*/std::sqrt(2705.0 / 2) * (5.0 / 13),
1026 Sharder(/*num_elements=*/2, /*num_shards=*/2, nullptr),
1027 /*solve_tolerance=*/1.0e-6);
1028 EXPECT_NEAR(result.solution_step_size, 1.0, 1.0e-6);
1029}
1030
1031TEST(DiagonalTrustRegionSolverTest,
1032 DiagonalQpSolverWorksWithOneDimQpLargeWeight) {
1033 ShardedQuadraticProgram sharded_qp(OneDimQp(), /*num_threads=*/2,
1034 /*num_shards=*/2);
1035 VectorXd primal_solution = VectorXd::Zero(1);
1036 VectorXd dual_solution = -1.0 * VectorXd::Ones(1);
1037 VectorXd primal_gradient =
1038 GetPrimalGradient(sharded_qp, primal_solution, dual_solution);
1039 VectorXd dual_gradient =
1040 GetDualGradient(sharded_qp, primal_solution, dual_solution);
1041 TrustRegionResult result = SolveDiagonalQpTrustRegion(
1042 sharded_qp, primal_solution, dual_solution, primal_gradient,
1043 dual_gradient, /*primal_weight=*/100.0,
1044 /*target_radius=*/std::sqrt(2705.0 / 2.0) * (5.0 / 13),
1045 /*solve_tolerance=*/1.0e-6);
1046 EXPECT_NEAR(result.solution_step_size, 1.0, 1.0e-6);
1047}
1048
1049TEST(DiagonalTrustRegionSolverTest, JointSolverWorksWithOneDimQpSmallWeight) {
1050 ShardedQuadraticProgram sharded_qp(OneDimQp(), /*num_threads=*/2,
1051 /*num_shards=*/2);
1052 const auto problem_data = GenerateTestQpProblemData(/*primal_weight=*/0.01);
1053 TrustRegionResult result = SolveDiagonalTrustRegion(
1054 problem_data.objective_vector, problem_data.objective_matrix_diagonal,
1055 problem_data.variable_lower_bounds, problem_data.variable_upper_bounds,
1056 problem_data.center_point, problem_data.norm_weights,
1057 /*target_radius=*/0.71063,
1058 Sharder(/*num_elements=*/2, /*num_shards=*/2, nullptr),
1059 /*solve_tolerance=*/1.0e-6);
1060 EXPECT_THAT(result.solution, ElementsAre(DoubleNear(-0.99950025, 1.0e-6),
1061 DoubleNear(-0.9, 1.0e-6)));
1062 EXPECT_NEAR(result.solution_step_size, 0.2, 1.0e-6);
1063 EXPECT_NEAR(result.objective_value, -1.0999996, 1.0e-6);
1064}
1065
1066TEST(DiagonalTrustRegionSolverTest,
1067 DiagonalQpSolverWorksWithOneDimQpSmallWeight) {
1068 ShardedQuadraticProgram sharded_qp(OneDimQp(), /*num_threads=*/2,
1069 /*num_shards=*/2);
1070 VectorXd primal_solution = VectorXd::Zero(1);
1071 VectorXd dual_solution = -1.0 * VectorXd::Ones(1);
1072 VectorXd primal_gradient =
1073 GetPrimalGradient(sharded_qp, primal_solution, dual_solution);
1074 VectorXd dual_gradient =
1075 GetDualGradient(sharded_qp, primal_solution, dual_solution);
1076 TrustRegionResult result = SolveDiagonalQpTrustRegion(
1077 sharded_qp, primal_solution, dual_solution, primal_gradient,
1078 dual_gradient, /*primal_weight=*/0.01,
1079 /*target_radius=*/0.71063,
1080 /*solve_tolerance=*/1.0e-6);
1081 EXPECT_THAT(result.solution, ElementsAre(DoubleNear(-0.99950025, 1.0e-6),
1082 DoubleNear(-0.9, 1.0e-6)));
1083 EXPECT_NEAR(result.solution_step_size, 0.2, 1.0e-6);
1084 EXPECT_NEAR(result.objective_value, -1.0999996, 1.0e-6);
1085}
1086
1087// This is a tiny QP where we can compute the exact solution.
1088TEST(ComputeLocalizedLagrangianBoundsTest, SolvesForTestQpUnitWeight) {
1089 ShardedQuadraticProgram qp(OneDimQp(), /*num_threads=*/2, /*num_shards=*/2);
1090
1091 VectorXd primal_solution = VectorXd::Zero(1);
1092 VectorXd dual_solution(1);
1093 dual_solution << -1; // The upper bound is active.
1094
1095 // The primal gradient is [2], and the dual gradient is [1]. Hence, the norm
1096 // of the gradient is sqrt(5).
1097
1098 LocalizedLagrangianBounds bounds = ComputeLocalizedLagrangianBounds(
1099 qp, primal_solution, dual_solution, PrimalDualNorm::kEuclideanNorm,
1100 /*primal_weight=*/1.0, /*radius=*/0.5,
1101 /*primal_product=*/nullptr,
1102 /*dual_product=*/nullptr, /*use_diagonal_qp_trust_region_solver=*/true,
1103 /*diagonal_qp_trust_region_solver_tolerance=*/1.0e-6);
1104 const double expected_lagrangian = -1;
1105 EXPECT_DOUBLE_EQ(bounds.lagrangian_value, expected_lagrangian);
1106 EXPECT_NEAR(bounds.upper_bound, expected_lagrangian + 0.5, 1.0e-5);
1107 EXPECT_NEAR(bounds.lower_bound, expected_lagrangian - 0.75, 1.0e-5);
1108}
1109
1110} // namespace
1111} // namespace operations_research::pdlp
int64_t max
Definition: alldiff_cst.cc:140
SharedBoundsManager * bounds
LagrangianPart ComputePrimalGradient(const ShardedQuadraticProgram &sharded_qp, const VectorXd &primal_solution, const VectorXd &dual_product)
EigenArrayNearMatcherP2< Eigen::Array< T, Eigen::Dynamic, 1 >, double > EigenArrayNear(absl::Span< const T > data, double tolerance)
Definition: test_util.h:375
VectorXd TransposedMatrixVectorProduct(const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &matrix, const VectorXd &vector, const Sharder &sharder)
Definition: sharder.cc:151
TrustRegionResult SolveDiagonalTrustRegion(const VectorXd &objective_vector, const VectorXd &objective_matrix_diagonal, const VectorXd &variable_lower_bounds, const VectorXd &variable_upper_bounds, const VectorXd &center_point, const VectorXd &norm_weights, const double target_radius, const Sharder &sharder, const double solve_tolerance)
LagrangianPart ComputeDualGradient(const ShardedQuadraticProgram &sharded_qp, const Eigen::VectorXd &dual_solution, const Eigen::VectorXd &primal_product)
TrustRegionResult SolveDiagonalQpTrustRegion(const ShardedQuadraticProgram &sharded_qp, const VectorXd &primal_solution, const VectorXd &dual_solution, const VectorXd &primal_gradient, const VectorXd &dual_gradient, const double primal_weight, double target_radius, const double solve_tolerance)
EigenArrayEqMatcherP< Eigen::Array< T, Eigen::Dynamic, 1 > > EigenArrayEq(absl::Span< const T > data)
Definition: test_util.h:389
TrustRegionResult SolveTrustRegion(const VectorXd &objective_vector, const VectorXd &variable_lower_bounds, const VectorXd &variable_upper_bounds, const VectorXd &center_point, const VectorXd &norm_weights, const double target_radius, const Sharder &sharder)
QuadraticProgram TestLp()
Definition: test_util.cc:32
LocalizedLagrangianBounds ComputeLocalizedLagrangianBounds(const ShardedQuadraticProgram &sharded_qp, const VectorXd &primal_solution, const VectorXd &dual_solution, const PrimalDualNorm primal_dual_norm, const double primal_weight, const double radius, const VectorXd *primal_product, const VectorXd *dual_product, const bool use_diagonal_qp_trust_region_solver, const double diagonal_qp_trust_region_solver_tolerance)
int64_t Zero()
NOLINT.
TEST(LinearAssignmentTest, NullMatrix)
STL namespace.
VectorXd variable_lower_bounds
VectorXd center_point
VectorXd variable_upper_bounds
VectorXd objective_vector
VectorXd norm_weights
VectorXd objective_matrix_diagonal