OR-Tools  9.3
sharded_optimization_utils_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 <cmath>
17#include <cstdint>
18#include <random>
19#include <utility>
20#include <vector>
21
22#include "Eigen/Core"
23#include "Eigen/SparseCore"
24#include "absl/types/optional.h"
25#include "gmock/gmock.h"
26#include "gtest/gtest.h"
30#include "ortools/pdlp/solve_log.pb.h"
32
34namespace {
35
36using ::Eigen::VectorXd;
37using ::testing::ElementsAre;
38using ::testing::ElementsAreArray;
39using ::testing::IsNan;
40
41TEST(ShardedWeightedAverageTest, SimpleAverage) {
42 Sharder sharder(/*num_elements=*/2, /*num_shards=*/2,
43 /*thread_pool=*/nullptr);
44 Eigen::VectorXd vec1(2), vec2(2);
45 vec1 << 4, 1;
46 vec2 << 1, 7;
47
48 ShardedWeightedAverage average(&sharder);
49 average.Add(vec1, 1.0);
50 average.Add(vec2, 2.0);
51
52 ASSERT_TRUE(average.HasNonzeroWeight());
53 EXPECT_EQ(average.NumTerms(), 2);
54
55 EXPECT_THAT(average.ComputeAverage(), ElementsAre(2.0, 5.0));
56
57 average.Clear();
58 EXPECT_FALSE(average.HasNonzeroWeight());
59 EXPECT_EQ(average.NumTerms(), 0);
60}
61
62TEST(ShardedWeightedAverageTest, MoveConstruction) {
63 Sharder sharder(/*num_elements=*/2, /*num_shards=*/2,
64 /*thread_pool=*/nullptr);
65 Eigen::VectorXd vec(2);
66 vec << 4, 1;
67
68 ShardedWeightedAverage average(&sharder);
69 average.Add(vec, 2.0);
70
71 ShardedWeightedAverage average2(std::move(average));
72 EXPECT_THAT(average2.ComputeAverage(), ElementsAre(4.0, 1.0));
73}
74
75TEST(ShardedWeightedAverageTest, MoveAssignment) {
76 Sharder sharder1(/*num_elements=*/2, /*num_shards=*/2,
77 /*thread_pool=*/nullptr);
78 Sharder sharder2(/*num_elements=*/3, /*num_shards=*/2,
79 /*thread_pool=*/nullptr);
80 Eigen::VectorXd vec1(2), vec2(2);
81 vec1 << 4, 1;
82 vec2 << 0, 3;
83
84 ShardedWeightedAverage average1(&sharder1);
85 average1.Add(vec1, 2.0);
86
87 ShardedWeightedAverage average2(&sharder2);
88
89 average2 = std::move(average1);
90 average2.Add(vec2, 2.0);
91 EXPECT_THAT(average2.ComputeAverage(), ElementsAre(2.0, 2.0));
92}
93
94TEST(ShardedWeightedAverageTest, ZeroAverage) {
95 Sharder sharder(/*num_elements=*/1, /*num_shards=*/1,
96 /*thread_pool=*/nullptr);
97
98 ShardedWeightedAverage average(&sharder);
99 ASSERT_FALSE(average.HasNonzeroWeight());
100
101 EXPECT_THAT(average.ComputeAverage(), ElementsAre(0.0));
102}
103
104// This test verifies that if we average an identical vector repeatedly the
105// average is exactly that vector, with no roundoff.
106TEST(ShardedWeightedAverageTest, AveragesEqualWithoutRoundoff) {
107 Sharder sharder(/*num_elements=*/4, /*num_shards=*/1,
108 /*thread_pool=*/nullptr);
109 ShardedWeightedAverage average(&sharder);
110 EXPECT_THAT(average.ComputeAverage(), ElementsAre(0, 0, 0, 0));
111 VectorXd data(4);
112 data << 1.0, 1.0 / 3, 3.0 / 7, 3.14159;
113 average.Add(data, 341.45);
114 EXPECT_THAT(average.ComputeAverage(), ElementsAreArray(data));
115 average.Add(data, 1.4134);
116 EXPECT_THAT(average.ComputeAverage(), ElementsAreArray(data));
117 average.Add(data, 7.23);
118 EXPECT_THAT(average.ComputeAverage(), ElementsAreArray(data));
119}
120
121// The combined bounds vector for TestLp() is [12, 7, 4, 1].
122// L_inf norm: 12.0
123// L_2 norm: sqrt(210.0) ≈ 14.49
124
125TEST(ProblemStatsTest, TestLp) {
126 ShardedQuadraticProgram lp(TestLp(), 2, 2);
127 const QuadraticProgramStats stats = ComputeStats(lp);
128
129 EXPECT_EQ(stats.num_variables(), 4);
130 EXPECT_EQ(stats.num_constraints(), 4);
131 EXPECT_DOUBLE_EQ(stats.constraint_matrix_col_min_l_inf_norm(), 1.0);
132 EXPECT_DOUBLE_EQ(stats.constraint_matrix_row_min_l_inf_norm(), 1.0);
133 EXPECT_EQ(stats.constraint_matrix_num_nonzeros(), 9);
134 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_max(), 4.0);
135 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_min(), 1.0);
136 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_avg(), 14.5 / 9.0);
137 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_max(), 5.5);
138 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_min(), 1.0);
139 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_avg(), 2.375);
140 EXPECT_DOUBLE_EQ(stats.objective_vector_l2_norm(), std::sqrt(36.25));
141 EXPECT_EQ(stats.objective_matrix_num_nonzeros(), 0);
142 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_max(), 0.0);
143 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_min(), 0.0);
144 EXPECT_THAT(stats.objective_matrix_abs_avg(), IsNan());
145 EXPECT_EQ(stats.variable_bound_gaps_num_finite(), 1);
146 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_max(), 1.0);
147 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_min(), 1.0);
148 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_avg(), 1.0);
149 EXPECT_DOUBLE_EQ(stats.combined_bounds_max(), 12.0);
150 EXPECT_DOUBLE_EQ(stats.combined_bounds_min(), 1.0);
151 EXPECT_DOUBLE_EQ(stats.combined_bounds_avg(), 6.0);
152 EXPECT_DOUBLE_EQ(stats.combined_bounds_l2_norm(), std::sqrt(210.0));
153}
154
155TEST(ProblemStatsTest, TinyLp) {
156 ShardedQuadraticProgram lp(TinyLp(), 2, 2);
157 const QuadraticProgramStats stats = ComputeStats(lp);
158
159 EXPECT_EQ(stats.num_variables(), 4);
160 EXPECT_EQ(stats.num_constraints(), 3);
161 EXPECT_DOUBLE_EQ(stats.constraint_matrix_col_min_l_inf_norm(), 1.0);
162 EXPECT_DOUBLE_EQ(stats.constraint_matrix_row_min_l_inf_norm(), 1.0);
163 EXPECT_EQ(stats.constraint_matrix_num_nonzeros(), 8);
164 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_max(), 2.0);
165 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_min(), 1.0);
166 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_avg(), 1.25);
167 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_max(), 5.0);
168 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_min(), 1.0);
169 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_avg(), 2.25);
170 EXPECT_DOUBLE_EQ(stats.objective_vector_l2_norm(), std::sqrt(31.0));
171 EXPECT_EQ(stats.objective_matrix_num_nonzeros(), 0);
172 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_max(), 0.0);
173 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_min(), 0.0);
174 EXPECT_THAT(stats.objective_matrix_abs_avg(), IsNan());
175 EXPECT_EQ(stats.variable_bound_gaps_num_finite(), 4);
176 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_max(), 6.0);
177 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_min(), 2.0);
178 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_avg(), 3.75);
179 EXPECT_DOUBLE_EQ(stats.combined_bounds_max(), 12.0);
180 EXPECT_DOUBLE_EQ(stats.combined_bounds_min(), 1.0);
181 EXPECT_DOUBLE_EQ(stats.combined_bounds_avg(), 20.0 / 3.0);
182 EXPECT_DOUBLE_EQ(stats.combined_bounds_l2_norm(), std::sqrt(194.0));
183}
184
185TEST(ProblemStatsTest, TestDiagonalQp1) {
186 ShardedQuadraticProgram qp(TestDiagonalQp1(), 2, 2);
187 const QuadraticProgramStats stats = ComputeStats(qp);
188
189 EXPECT_EQ(stats.num_variables(), 2);
190 EXPECT_EQ(stats.num_constraints(), 1);
191 EXPECT_DOUBLE_EQ(stats.constraint_matrix_col_min_l_inf_norm(), 1.0);
192 EXPECT_DOUBLE_EQ(stats.constraint_matrix_row_min_l_inf_norm(), 1.0);
193 EXPECT_EQ(stats.constraint_matrix_num_nonzeros(), 2);
194 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_max(), 1.0);
195 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_min(), 1.0);
196 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_avg(), 1.0);
197 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_max(), 1.0);
198 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_min(), 1.0);
199 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_avg(), 1.0);
200 EXPECT_DOUBLE_EQ(stats.objective_vector_l2_norm(), std::sqrt(2.0));
201 EXPECT_EQ(stats.objective_matrix_num_nonzeros(), 2);
202 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_max(), 4.0);
203 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_min(), 1.0);
204 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_avg(), 2.5);
205 EXPECT_EQ(stats.variable_bound_gaps_num_finite(), 2);
206 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_max(), 6.0);
207 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_min(), 1.0);
208 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_avg(), 3.5);
209 EXPECT_DOUBLE_EQ(stats.combined_bounds_max(), 1.0);
210 EXPECT_DOUBLE_EQ(stats.combined_bounds_min(), 1.0);
211 EXPECT_DOUBLE_EQ(stats.combined_bounds_avg(), 1.0);
212 EXPECT_DOUBLE_EQ(stats.combined_bounds_l2_norm(), 1.0);
213}
214
215TEST(ProblemStatsTest, ModifiedTestDiagonalQp1) {
216 QuadraticProgram orig_qp = TestDiagonalQp1();
217 // A case where objective_matrix_num_nonzeros doesn't match the dimension.
218 orig_qp.objective_matrix->diagonal() << 2.0, 0.0;
219 ShardedQuadraticProgram qp(orig_qp, 2, 2);
220 const QuadraticProgramStats stats = ComputeStats(qp);
221
222 EXPECT_EQ(stats.objective_matrix_num_nonzeros(), 1);
223 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_max(), 2.0);
224 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_min(), 0.0);
225 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_avg(), 1.0);
226}
227
228// This is like SmallLp, except that an infinite_bound_threshold of 10 treats
229// the first bound as infinite, leaving [7, 4, 1] as the combined bounds vector.
230TEST(ProblemStatsTest, TestLpWithInfiniteConstraintBoundThreshold) {
231 ShardedQuadraticProgram lp(TestLp(), 2, 2);
232 const QuadraticProgramStats stats =
233 ComputeStats(lp, /*infinite_constraint_bound_threshold=*/10);
234
235 EXPECT_EQ(stats.num_variables(), 4);
236 EXPECT_EQ(stats.num_constraints(), 4);
237 EXPECT_DOUBLE_EQ(stats.constraint_matrix_col_min_l_inf_norm(), 1.0);
238 EXPECT_DOUBLE_EQ(stats.constraint_matrix_row_min_l_inf_norm(), 1.0);
239 EXPECT_EQ(stats.constraint_matrix_num_nonzeros(), 9);
240 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_max(), 4.0);
241 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_min(), 1.0);
242 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_avg(), 14.5 / 9.0);
243 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_max(), 5.5);
244 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_min(), 1.0);
245 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_avg(), 2.375);
246 EXPECT_DOUBLE_EQ(stats.objective_vector_l2_norm(), std::sqrt(36.25));
247 EXPECT_EQ(stats.objective_matrix_num_nonzeros(), 0);
248 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_max(), 0.0);
249 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_min(), 0.0);
250 EXPECT_THAT(stats.objective_matrix_abs_avg(), IsNan());
251 EXPECT_EQ(stats.variable_bound_gaps_num_finite(), 1);
252 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_max(), 1.0);
253 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_min(), 1.0);
254 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_avg(), 1.0);
255 EXPECT_DOUBLE_EQ(stats.combined_bounds_max(), 7.0);
256 EXPECT_DOUBLE_EQ(stats.combined_bounds_min(), 0.0);
257 EXPECT_DOUBLE_EQ(stats.combined_bounds_avg(), 3.0);
258 EXPECT_DOUBLE_EQ(stats.combined_bounds_l2_norm(), std::sqrt(66.0));
259}
260
261TEST(ProblemStatsTest, NoFiniteGaps) {
262 ShardedQuadraticProgram lp(SmallInvalidProblemLp(), 2, 2);
263 const QuadraticProgramStats stats = ComputeStats(lp);
264 // Ensure max/min/avg take their default values when no finite gaps exist.
265 EXPECT_EQ(stats.variable_bound_gaps_num_finite(), 0);
266 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_max(), 0.0);
267 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_min(), 0.0);
268 EXPECT_THAT(stats.variable_bound_gaps_avg(), IsNan());
269}
270
271TEST(ProblemStatsTest, LpWithoutConstraints) {
272 ShardedQuadraticProgram lp(LpWithoutConstraints(), 2, 2);
273 const QuadraticProgramStats stats = ComputeStats(lp);
274 // When there are no constraints, max/min absolute values and infinity norms
275 // are assigned 0 by convention. The same is true for the combined bounds.
276 EXPECT_EQ(stats.constraint_matrix_num_nonzeros(), 0);
277 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_max(), 0.0);
278 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_min(), 0.0);
279 EXPECT_THAT(stats.constraint_matrix_abs_avg(), IsNan());
280 EXPECT_DOUBLE_EQ(stats.constraint_matrix_col_min_l_inf_norm(), 0.0);
281 EXPECT_DOUBLE_EQ(stats.constraint_matrix_row_min_l_inf_norm(), 0.0);
282 EXPECT_DOUBLE_EQ(stats.combined_bounds_max(), 0.0);
283 EXPECT_DOUBLE_EQ(stats.combined_bounds_min(), 0.0);
284 EXPECT_THAT(stats.combined_bounds_avg(), IsNan());
285}
286
287TEST(ProblemStatsTest, EmptyLp) {
288 ShardedQuadraticProgram lp(QuadraticProgram(0, 0), 2, 2);
289 const QuadraticProgramStats stats = ComputeStats(lp);
290 // When LP is empty, everything except averages should be 0 and averages
291 // should be NaN
292 EXPECT_EQ(stats.num_variables(), 0);
293 EXPECT_EQ(stats.num_constraints(), 0);
294 EXPECT_DOUBLE_EQ(stats.constraint_matrix_col_min_l_inf_norm(), 0.0);
295 EXPECT_DOUBLE_EQ(stats.constraint_matrix_row_min_l_inf_norm(), 0.0);
296 EXPECT_EQ(stats.constraint_matrix_num_nonzeros(), 0);
297 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_max(), 0.0);
298 EXPECT_DOUBLE_EQ(stats.constraint_matrix_abs_min(), 0.0);
299 EXPECT_THAT(stats.constraint_matrix_abs_avg(), IsNan());
300 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_max(), 0.0);
301 EXPECT_DOUBLE_EQ(stats.objective_vector_abs_min(), 0.0);
302 EXPECT_THAT(stats.objective_vector_abs_avg(), IsNan());
303 EXPECT_DOUBLE_EQ(stats.objective_vector_l2_norm(), 0.0);
304 EXPECT_EQ(stats.objective_matrix_num_nonzeros(), 0);
305 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_max(), 0.0);
306 EXPECT_DOUBLE_EQ(stats.objective_matrix_abs_min(), 0.0);
307 EXPECT_THAT(stats.objective_matrix_abs_avg(), IsNan());
308 EXPECT_EQ(stats.variable_bound_gaps_num_finite(), 0);
309 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_max(), 0.0);
310 EXPECT_DOUBLE_EQ(stats.variable_bound_gaps_min(), 0.0);
311 EXPECT_THAT(stats.variable_bound_gaps_avg(), IsNan());
312 EXPECT_DOUBLE_EQ(stats.combined_bounds_max(), 0.0);
313 EXPECT_DOUBLE_EQ(stats.combined_bounds_min(), 0.0);
314 EXPECT_THAT(stats.combined_bounds_avg(), IsNan());
315 EXPECT_DOUBLE_EQ(stats.combined_bounds_l2_norm(), 0.0);
316}
317
318// The test_lp matrix is [ 2 1 1 2; 1 0 1 0; 4 0 0 0; 0 0 1.5 -1],
319// the scaled matrix is [ 0 1 2 -2; 0 0 4 0; 0 0 0 0; 0 0 9 3],
320// so the row LInf norms are [2 4 0 9] and the column LInf norms are [0 1 9 3].
321// Rescaling divides the scaling vectors by sqrt(norms).
322TEST(LInfRuizRescaling, OneIteration) {
323 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
324 VectorXd row_scaling_vec(4), col_scaling_vec(4);
325 row_scaling_vec << 1, 2, 1, 3;
326 col_scaling_vec << 0, 1, 2, -1;
327 LInfRuizRescaling(lp, /*num_iterations=*/1, row_scaling_vec, col_scaling_vec);
328 EXPECT_THAT(row_scaling_vec, ElementsAre(1 / std::sqrt(2), 1.0, 1.0, 1.0));
329 EXPECT_THAT(col_scaling_vec,
330 ElementsAre(0.0, 1.0, 2.0 / 3.0, -1.0 / std::sqrt(3.0)));
331}
332
333// The test_lp matrix is [ 2 1 1 2; 1 0 1 0; 4 0 0 0; 0 0 1.5 -1],
334// the scaled matrix is [ 0 1 2 -2; 0 0 4 0; 0 0 0 0; 0 0 9 3],
335// so the row L2 norms are [3 4 0 sqrt(90)] and the column L2 norms are [0 1
336// sqrt(101) sqrt(13)]. Rescaling divides the scaling vectors by sqrt(norms).
337TEST(L2RuizRescaling, OneIteration) {
338 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
339 VectorXd row_scaling_vec(4), col_scaling_vec(4);
340 row_scaling_vec << 1, 2, 1, 3;
341 col_scaling_vec << 0, 1, 2, -1;
342 L2NormRescaling(lp, row_scaling_vec, col_scaling_vec);
343 EXPECT_THAT(row_scaling_vec, ElementsAre(1.0 / std::pow(3.0, 0.5), 1.0, 1.0,
344 3.0 / std::pow(90.0, 0.25)));
345 EXPECT_THAT(col_scaling_vec, ElementsAre(0.0, 1.0, 2.0 / std::pow(101, 0.25),
346 -1.0 / std::pow(13.0, 0.25)));
347}
348
349// The test matrix is [2 3], so the row L2 norms are [sqrt(13)] and the column
350// L2 norms are [2 3]. Rescaling divides the scaling vectors by sqrt(norms).
351TEST(L2RuizRescaling, OneIterationNonSquare) {
352 QuadraticProgram test_lp(/*num_variables=*/2, /*num_constraints=*/1);
353 std::vector<Eigen::Triplet<double, int64_t>> triplets = {{0, 0, 2.0},
354 {0, 1, 3.0}};
355 test_lp.constraint_matrix.setFromTriplets(triplets.begin(), triplets.end());
356 ShardedQuadraticProgram lp(std::move(test_lp), /*num_threads=*/2,
357 /*num_shards=*/2);
358 VectorXd row_scaling_vec = VectorXd::Ones(1);
359 VectorXd col_scaling_vec = VectorXd::Ones(2);
360 L2NormRescaling(lp, row_scaling_vec, col_scaling_vec);
361 EXPECT_THAT(row_scaling_vec, ElementsAre(1.0 / std::pow(13.0, 0.25)));
362 EXPECT_THAT(col_scaling_vec,
363 ElementsAre(1.0 / std::sqrt(2.0), 1.0 / std::sqrt(3.0)));
364}
365
366// With many iterations of LInfRuizRescaling, the scaled matrix should converge
367// to have col LInf norm 1 and row LInf norm 1.
368TEST(LInfRuizRescaling, Convergence) {
369 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
370 VectorXd row_scaling_vec(4), col_scaling_vec(4);
371 VectorXd col_norm(4), row_norm(4);
372 row_scaling_vec << 1, 1, 1, 1;
373 col_scaling_vec << 1, 1, 1, 1;
374 LInfRuizRescaling(lp, /*num_iterations=*/20, row_scaling_vec,
375 col_scaling_vec);
376 col_norm = ScaledColLInfNorm(lp.Qp().constraint_matrix, row_scaling_vec,
377 col_scaling_vec, lp.ConstraintMatrixSharder());
378 row_norm = ScaledColLInfNorm(lp.TransposedConstraintMatrix(), col_scaling_vec,
379 row_scaling_vec,
380 lp.TransposedConstraintMatrixSharder());
381 EXPECT_THAT(row_norm, EigenArrayNear<double>({1.0, 1.0, 1.0, 1.0}, 1.0e-4));
382 EXPECT_THAT(col_norm, EigenArrayNear<double>({1.0, 1.0, 1.0, 1.0}, 1.0e-4));
383}
384
385// This applies one round of l_inf and one round of L2 rescaling.
386// The test_lp matrix is [ 2 1 1 2; 1 0 1 0; 4 0 0 0; 0 0 1.5 -1],
387// so the row LInf norms are [2 1 4 1.5] and column LInf norms are [4 1 1.5 2].
388// l_inf divides by sqrt(norms), giving
389// [0.7071 0.7071 0.5773 1; 0.5 0 0.8165 0; 1 0 0 0; 0 0 1 -0.5773]
390// which has row L2 norms [1.5275 0.957429 1 1.1547] and col L2 norms
391// [1.3229 0.7071 1.4142 1.1547]. The resulting scaling vectors are
392// 1/sqrt((l_inf norms).*(l2 norms)).
393TEST(ApplyRescaling, ApplyRescalingWorksForTestLp) {
394 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
395 ScalingVectors scaling = ApplyRescaling(
396 RescalingOptions{.l_inf_ruiz_iterations = 1, .l2_norm_rescaling = true},
397 lp);
398 EXPECT_THAT(scaling.row_scaling_vec,
399 EigenArrayNear<double>(
400 {1.0 / sqrt(2.0 * 1.5275), 1.0 / sqrt(1.0 * 0.9574),
401 1.0 / sqrt(4.0 * 1.0), 1.0 / sqrt(1.5 * 1.1547)},
402 1.0e-4));
403 EXPECT_THAT(scaling.col_scaling_vec,
404 EigenArrayNear<double>(
405 {1.0 / sqrt(4.0 * 1.3229), 1.0 / sqrt(1.0 * 0.7071),
406 1.0 / sqrt(1.5 * 1.4142), 1.0 / sqrt(2.0 * 1.1547)},
407 1.0e-4));
408}
409
410TEST(ComputePrimalGradientTest, CorrectForLp) {
411 // The choice of two shards is intentional, to help catch bugs in the sharded
412 // computations.
413 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
414
415 VectorXd primal_solution(4), dual_solution(4);
416 primal_solution << 0.0, 0.0, 0.0, 3.0;
417 dual_solution << -1.0, 0.0, 1.0, 1.0;
418
419 const LagrangianPart primal_part = ComputePrimalGradient(
420 lp, primal_solution, lp.TransposedConstraintMatrix() * dual_solution);
421 // Using notation consistent with
422 // https://developers.google.com/optimization/lp/pdlp_math.
423 // c - A'y
424 EXPECT_THAT(primal_part.gradient,
425 ElementsAre(5.5 - 2.0, -2.0 + 1.0, -1.0 - 0.5, 1.0 + 3.0));
426 // c'x - y'Ax.
427 EXPECT_DOUBLE_EQ(primal_part.value, 3.0 + 9.0);
428}
429
430TEST(ComputeDualGradientTest, CorrectForLp) {
431 // The choice of two shards is intentional, to help catch bugs in the sharded
432 // computations.
433 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
434
435 VectorXd primal_solution(4), dual_solution(4);
436 primal_solution << 0.0, 0.0, 0.0, 3.0;
437 dual_solution << -1.0, 0.0, 1.0, 1.0;
438
439 const LagrangianPart dual_part = ComputeDualGradient(
440 lp, dual_solution, lp.Qp().constraint_matrix * primal_solution);
441 // Using notation consistent with
442 // https://developers.google.com/optimization/lp/pdlp_math.
443 // active_constraint_right_hand_side - Ax
444 EXPECT_THAT(dual_part.gradient,
445 ElementsAre(12.0 - 6.0, 7.0, -4.0, -1.0 + 3.0));
446 // y'active_constraint_right_hand_side
447 EXPECT_DOUBLE_EQ(dual_part.value, 12.0 * -1.0 + -4.0 * 1.0 + -1.0 * 1.0);
448}
449
450TEST(ComputeDualGradientTest, CorrectOnTwoSidedConstraints) {
451 QuadraticProgram qp = TestLp();
452 // Makes the constraints all two-sided. The primal solution is feasible in
453 // the first constraint, below the lower bound of the second constraint, and
454 // above the upper bound of the third constraint.
455 qp.constraint_lower_bounds[0] = 4;
456 qp.constraint_lower_bounds[1] = 5;
457 qp.constraint_upper_bounds[2] = -1;
458 ShardedQuadraticProgram sharded_qp(std::move(qp), /*num_threads=*/2,
459 /*num_shards=*/2);
460
461 VectorXd primal_solution(4), dual_solution(4);
462 primal_solution << 0.0, 0.0, 0.0, 3.0;
463 dual_solution << 0.0, 0.0, 0.0, -1.0;
464
465 const LagrangianPart dual_part =
466 ComputeDualGradient(sharded_qp, dual_solution,
467 sharded_qp.Qp().constraint_matrix * primal_solution);
468 // Using notation consistent with
469 // https://developers.google.com/optimization/lp/pdlp_math.
470 // active_constraint_right_hand_side - Ax
471 EXPECT_THAT(dual_part.gradient,
472 ElementsAre(0.0, 5.0 - 0.0, -1.0 - 0.0, 1.0 + 3.0));
473 // y'active_constraint_right_hand_side
474 EXPECT_DOUBLE_EQ(dual_part.value, 1.0 * -1.0);
475}
476
477TEST(HasValidBoundsTest, SmallInvalidLp) {
478 ShardedQuadraticProgram lp(SmallInvalidProblemLp(), /*num_threads=*/2,
479 /*num_shards=*/2);
480
481 bool is_valid = HasValidBounds(lp);
482 EXPECT_FALSE(is_valid);
483}
484
485TEST(HasValidBoundsTest, SmallValidLp) {
486 ShardedQuadraticProgram lp(SmallPrimalInfeasibleLp(), /*num_threads=*/2,
487 /*num_shards=*/2);
488
489 bool is_valid = HasValidBounds(lp);
490 EXPECT_TRUE(is_valid);
491}
492
493TEST(ComputePrimalGradientTest, CorrectForQp) {
494 ShardedQuadraticProgram qp(TestDiagonalQp1(), /*num_threads=*/2,
495 /*num_shards=*/2);
496
497 VectorXd primal_solution(2), dual_solution(1);
498 primal_solution << 1.0, 2.0;
499 dual_solution << -2.0;
500
501 const LagrangianPart primal_part = ComputePrimalGradient(
502 qp, primal_solution, qp.TransposedConstraintMatrix() * dual_solution);
503
504 // Using notation consistent with
505 // https://developers.google.com/optimization/lp/pdlp_math.
506 // c - A'y + Qx
507 EXPECT_THAT(primal_part.gradient,
508 ElementsAre(-1.0 + 2.0 + 4.0, -1.0 + 2.0 + 2.0));
509 // (1/2) x'Qx + c'x - y'Ax.
510 EXPECT_DOUBLE_EQ(primal_part.value, 4.0 - 3.0 + 2.0 * 3.0);
511}
512
513TEST(ComputeDualGradientTest, CorrectForQp) {
514 ShardedQuadraticProgram qp(TestDiagonalQp1(), /*num_threads=*/2,
515 /*num_shards=*/2);
516
517 VectorXd primal_solution(2), dual_solution(1);
518 primal_solution << 1.0, 2.0;
519 dual_solution << -2.0;
520
521 const LagrangianPart dual_part = ComputeDualGradient(
522 qp, dual_solution, qp.Qp().constraint_matrix * primal_solution);
523
524 // Using notation consistent with
525 // https://developers.google.com/optimization/lp/pdlp_math.
526 // active_constraint_right_hand_side - Ax
527 EXPECT_THAT(dual_part.gradient, ElementsAre(1.0 - (1.0 + 2.0)));
528 // y'active_constraint_right_hand_side
529 EXPECT_DOUBLE_EQ(dual_part.value, -2.0);
530}
531
532TEST(EstimateSingularValuesTest, CorrectForTestLp) {
533 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
534
535 // The test_lp matrix is [ 2 1 1 2; 1 0 1 0; 4 0 0 0; 0 0 1.5 -1].
536 std::mt19937 random(1);
538 lp, absl::nullopt, absl::nullopt,
539 /*desired_relative_error=*/0.01,
540 /*failure_probability=*/0.001, random);
541 EXPECT_NEAR(result.singular_value, 4.76945, 0.01);
542 EXPECT_LT(result.num_iterations, 300);
543}
544
545TEST(EstimateSingularValuesTest, CorrectForTestLpWithActivePrimalSubspace) {
546 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
547
548 VectorXd primal_solution(4);
549 // Chosen so x_1 is at its bound, and all other variables are not at bounds.
550 primal_solution << 0.0, -2.0, 0.0, 3.0;
551 // The test_lp matrix is [ 2 1 1 2; 1 0 1 0; 4 0 0 0; 0 0 1.5 -1],
552 // so the projected matrix is [ 2 1 2; 1 1 0; 4 0 0; 0 1.5 -1].
553 std::mt19937 random(1);
555 lp, primal_solution, absl::nullopt, /*desired_relative_error=*/0.01,
556 /*failure_probability=*/0.001, random);
557 EXPECT_NEAR(result.singular_value, 4.73818, 0.01);
558 EXPECT_LT(result.num_iterations, 300);
559}
560
561TEST(EstimateSingularValuesTest, CorrectForTestLpWithActiveDualSubspace) {
562 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
563
564 VectorXd dual_solution(4);
565 // Chosen so the second dual is at its bound, and all other duals are not at
566 // bounds.
567 dual_solution << 1.0, 0.0, 1.0, 3.0;
568 // The test_lp matrix is [ 2 1 1 2; 1 0 1 0; 4 0 0 0; 0 0 1.5 -1],
569 // so the projected matrix is [ 2 1 1 2; 4 0 0 0; 0 0 1.5 -1].
570 std::mt19937 random(1);
572 lp, absl::nullopt, dual_solution, /*desired_relative_error=*/0.01,
573 /*failure_probability=*/0.001, random);
574 EXPECT_NEAR(result.singular_value, 4.64203, 0.01);
575 EXPECT_LT(result.num_iterations, 300);
576}
577
578TEST(EstimateSingularValuesTest, CorrectForTestLpWithBothActiveSubspaces) {
579 ShardedQuadraticProgram lp(TestLp(), /*num_threads=*/2, /*num_shards=*/2);
580
581 VectorXd primal_solution(4), dual_solution(4);
582 // Chosen so x_1 is at its bound, and all other variables are not at bounds.
583 primal_solution << 0.0, -2.0, 0.0, 3.0;
584 // Chosen so the second dual is at its bound, and all other duals are not at
585 // bounds.
586 dual_solution << 1.0, 0.0, 1.0, 3.0;
587 // The test_lp matrix is [ 2 1 1 2; 1 0 1 0; 4 0 0 0; 0 0 1.5 -1],
588 // so the projected matrix is [ 2 1 2; 4 0 0; 0 1.5 -1].
589 std::mt19937 random(1);
591 lp, primal_solution, dual_solution, /*desired_relative_error=*/0.01,
592 /*failure_probability=*/0.001, random);
593 EXPECT_NEAR(result.singular_value, 4.60829, 0.01);
594 EXPECT_LT(result.num_iterations, 300);
595}
596
597TEST(ProjectToPrimalVariableBoundsTest, TestLp) {
598 ShardedQuadraticProgram qp(TestLp(), /*num_threads=*/2,
599 /*num_shards=*/2);
600 VectorXd primal(4);
601 primal << -3, -3, 5, 5;
603 EXPECT_THAT(primal, ElementsAre(-3, -2, 5, 3.5));
604}
605
606TEST(ProjectToDualVariableBoundsTest, TestLp) {
607 ShardedQuadraticProgram qp(TestLp(), /*num_threads=*/2,
608 /*num_shards=*/2);
609 VectorXd dual(4);
610 dual << 1, 1, -1, -1;
612 EXPECT_THAT(dual, ElementsAre(1, 0, 0, -1));
613}
614
615} // namespace
616} // namespace operations_research::pdlp
LagrangianPart ComputePrimalGradient(const ShardedQuadraticProgram &sharded_qp, const VectorXd &primal_solution, const VectorXd &dual_product)
void LInfRuizRescaling(const ShardedQuadraticProgram &sharded_qp, const int num_iterations, VectorXd &row_scaling_vec, VectorXd &col_scaling_vec)
LagrangianPart ComputeDualGradient(const ShardedQuadraticProgram &sharded_qp, const Eigen::VectorXd &dual_solution, const Eigen::VectorXd &primal_product)
VectorXd ScaledColLInfNorm(const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &matrix, const VectorXd &row_scaling_vec, const VectorXd &col_scaling_vec, const Sharder &sharder)
Definition: sharder.cc:258
bool HasValidBounds(const QuadraticProgram &qp)
QuadraticProgram TinyLp()
Definition: test_util.cc:66
SingularValueAndIterations EstimateMaximumSingularValueOfConstraintMatrix(const ShardedQuadraticProgram &sharded_qp, const absl::optional< VectorXd > &primal_solution, const absl::optional< VectorXd > &dual_solution, const double desired_relative_error, const double failure_probability, std::mt19937 &mt_generator)
void ProjectToDualVariableBounds(const ShardedQuadraticProgram &sharded_qp, VectorXd &dual)
QuadraticProgram LpWithoutConstraints()
Definition: test_util.cc:261
QuadraticProgram SmallInvalidProblemLp()
Definition: test_util.cc:190
void L2NormRescaling(const ShardedQuadraticProgram &sharded_qp, VectorXd &row_scaling_vec, VectorXd &col_scaling_vec)
ScalingVectors ApplyRescaling(const RescalingOptions &rescaling_options, ShardedQuadraticProgram &sharded_qp)
QuadraticProgramStats ComputeStats(const ShardedQuadraticProgram &qp, const double infinite_constraint_bound_threshold)
void ProjectToPrimalVariableBounds(const ShardedQuadraticProgram &sharded_qp, VectorXd &primal)
QuadraticProgram TestLp()
Definition: test_util.cc:32
QuadraticProgram SmallPrimalInfeasibleLp()
Definition: test_util.cc:216
QuadraticProgram TestDiagonalQp1()
Definition: test_util.cc:142
TEST(LinearAssignmentTest, NullMatrix)