OR-Tools  9.3
iteration_stats_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 <limits>
18#include <utility>
19
20#include "Eigen/Core"
21#include "absl/types/optional.h"
22#include "gmock/gmock.h"
23#include "gtest/gtest.h"
27#include "ortools/pdlp/solve_log.pb.h"
29
31namespace {
32
34
35using ::testing::AllOf;
36using ::testing::Each;
37using ::testing::ElementsAre;
38using ::testing::Eq;
39using ::testing::Ge;
40using ::testing::Le;
41using ::testing::Ne;
42using ::testing::SizeIs;
43
44TEST(CorrectedDualTest, SimpleLpWithSuboptimalDual) {
45 const int num_threads = 2;
46 const int num_shards = 10;
47 ShardedQuadraticProgram sharded_qp(TestLp(), num_threads, num_shards);
48
49 Eigen::VectorXd primal_solution(4), dual_solution(4);
50 // Set the primal variables that have primal gradients at their bounds, so
51 // that the primal gradients are reduced costs.
52 primal_solution << 0, 0, 6, 2.5;
53 dual_solution << -2, 0, 2.375, 1;
54 const ConvergenceInformation stats = ComputeScaledConvergenceInformation(
55 sharded_qp, primal_solution, dual_solution, POINT_TYPE_CURRENT_ITERATE);
56 // -36.5 = -14 - 24 - 9.5 - 1 - 3 + 15
57 EXPECT_DOUBLE_EQ(stats.dual_objective(), -36.5);
58 EXPECT_DOUBLE_EQ(stats.corrected_dual_objective(), -36.5);
59}
60
61// This is similar to SimpleLpWithSuboptimalDual, except with
62// x_2 = 2. In the dual correction calculation, the corresponding bound is 6, so
63// the primal gradient will be treated as a residual of 0.5 instead of a dual
64// correction of -3, but in the corrected dual objective it is still treated as
65// a dual correction.
66TEST(CorrectedDualTest, SimpleLpWithVariableFarFromBound) {
67 const int num_threads = 2;
68 const int num_shards = 10;
69 ShardedQuadraticProgram sharded_qp(TestLp(), num_threads, num_shards);
70
71 Eigen::VectorXd primal_solution(4), dual_solution(4);
72 primal_solution << 0, 0, 2, 2.5;
73 dual_solution << -2, 0, 2.375, 1;
74 const ConvergenceInformation stats = ComputeScaledConvergenceInformation(
75 sharded_qp, primal_solution, dual_solution, POINT_TYPE_CURRENT_ITERATE);
76 // -33.5 = -14 - 24 - 9.5 - 1 + 15
77 EXPECT_DOUBLE_EQ(stats.dual_objective(), -33.5);
78 EXPECT_DOUBLE_EQ(stats.corrected_dual_objective(), -36.5);
79 EXPECT_DOUBLE_EQ(stats.l_inf_dual_residual(), 0.5);
80 EXPECT_DOUBLE_EQ(stats.l2_dual_residual(), 0.5);
81}
82
83TEST(CorrectedDualObjective, QpSuboptimal) {
84 const int num_threads = 2;
85 const int num_shards = 10;
86 ShardedQuadraticProgram sharded_qp(TestDiagonalQp1(), num_threads,
87 num_shards);
88
89 Eigen::VectorXd primal_solution(2), dual_solution(1);
90 dual_solution << -3;
91 primal_solution << -2.0, 2.0;
92 const ConvergenceInformation stats = ComputeScaledConvergenceInformation(
93 sharded_qp, primal_solution, dual_solution, POINT_TYPE_CURRENT_ITERATE);
94 // primal gradient vector: [-6, 4]
95 // Constant term: 5
96 // Quadratic term: -(16+4)/2 = -10
97 // Dual objective term: -3 * 1
98 // Primal variables at bounds term: 2*-6 + -2*4 = -20
99 // -28.0 = 5 - 10 - 3 - 20
100 EXPECT_DOUBLE_EQ(stats.corrected_dual_objective(), -28.0);
101}
102
103TEST(RandomProjectionsTest, OneRandomProjectionsOfZeroVector) {
104 const int num_threads = 2;
105 const int num_shards = 10;
106 ShardedQuadraticProgram sharded_qp(TestLp(), num_threads, num_shards);
107
108 PointMetadata metadata;
109 SetRandomProjections(sharded_qp, /*primal_solution=*/Eigen::VectorXd::Zero(4),
110 /*dual_solution=*/Eigen::VectorXd::Zero(4),
111 /*random_projection_seeds=*/{1}, metadata);
112 EXPECT_THAT(metadata.random_primal_projections(), ElementsAre(0.0));
113 EXPECT_THAT(metadata.random_dual_projections(), ElementsAre(0.0));
114}
115
116TEST(RandomProjectionsTest, TwoRandomProjectionsOfVector) {
117 const int num_threads = 2;
118 const int num_shards = 10;
119 ShardedQuadraticProgram sharded_qp(TestLp(), num_threads, num_shards);
120
121 PointMetadata metadata;
122 SetRandomProjections(sharded_qp, /*primal_solution=*/Eigen::VectorXd::Ones(4),
123 /*dual_solution=*/Eigen::VectorXd::Zero(4),
124 /*random_projection_seeds=*/{1, 2}, metadata);
125 EXPECT_THAT(metadata.random_primal_projections(), SizeIs(2));
126 EXPECT_THAT(metadata.random_dual_projections(), SizeIs(2));
127 // The primal solution has norm 2; the random projection should only reduce
128 // the norm. Obtaining 0.0 is a probability-zero event.
129 EXPECT_THAT(metadata.random_primal_projections(),
130 Each(AllOf(Ge(-2.0), Le(2.0), Ne(0.0))));
131 EXPECT_THAT(metadata.random_dual_projections(), Each(Eq(0.0)));
132}
133
134TEST(ReducedCostsTest, SimpleLp) {
135 const int num_threads = 2;
136 const int num_shards = 10;
137 ShardedQuadraticProgram sharded_qp(TestLp(), num_threads, num_shards);
138
139 Eigen::VectorXd primal_solution(4), dual_solution(4);
140 // Use a primal solution at the relevant bounds, to ensure handling as
141 // reduced costs.
142 primal_solution << 0.0, -2.0, 6.0, 3.5;
143 dual_solution << 1.0, 0.0, 0.0, -2.0;
144 // c is: [5.5, -2, -1, 1]
145 // -A'y is: [-2, -1, 2, -4]
146 // c - A'y is: [3.5, -3.0, 1.0, -3.0].
147 EXPECT_THAT(ReducedCosts(sharded_qp, primal_solution, dual_solution),
148 ElementsAre(0.0, 0.0, 0.0, -3.0));
149 EXPECT_THAT(ReducedCosts(sharded_qp, primal_solution, dual_solution,
150 /*use_zero_primal_objective=*/true),
151 ElementsAre(0.0, 0.0, 0.0, -4.0));
152}
153
154TEST(ReducedCostsTest, SimpleLpWithGapResiduals) {
155 const int num_threads = 2;
156 const int num_shards = 10;
157 ShardedQuadraticProgram sharded_qp(TestLp(), num_threads, num_shards);
158
159 Eigen::VectorXd primal_solution(4), dual_solution(4);
160 primal_solution = Eigen::VectorXd::Zero(4);
161 dual_solution << 1.0, 0.0, 0.0, -1.0;
162 // c is: [5.5, -2, -1, 1]
163 // -A'y is: [-2, -1, 0.5, -3]
164 // c - A'y is: [3.5, -3.0, -0.5, -2.0].
165 // When the primal variable is 0.0 and the bound is not 0.0, the c - A'y is
166 // always handled as a residual.
167 EXPECT_THAT(ReducedCosts(sharded_qp, primal_solution, dual_solution),
168 ElementsAre(0.0, 0.0, 0.0, 0.0));
169 // If the primal variables are closer to the bound, c - A'y is handled as a
170 // reduced cost.
171 primal_solution << 0.0, 0.0, 4.0, 3.0;
172 EXPECT_THAT(ReducedCosts(sharded_qp, primal_solution, dual_solution),
173 ElementsAre(0.0, 0.0, -0.5, -2.0));
174}
175
176TEST(ReducedCostsTest, SimpleQp) {
177 const int num_threads = 2;
178 const int num_shards = 10;
179 ShardedQuadraticProgram sharded_qp(TestDiagonalQp1(), num_threads,
180 num_shards);
181
182 Eigen::VectorXd primal_solution(2), dual_solution(1);
183 primal_solution << 1.0, 2.0;
184 dual_solution << 0.0;
185 // Q*x is: [4.0, 2.0]
186 // c is: [-1, -1]
187 // A'y is zero.
188 // The second primal gradient term is handled as a residual, not a reduced
189 // cost.
190 EXPECT_THAT(ReducedCosts(sharded_qp, primal_solution, dual_solution),
191 ElementsAre(3.0, 0.0));
192 EXPECT_THAT(ReducedCosts(sharded_qp, primal_solution, dual_solution,
193 /*use_zero_primal_objective=*/true),
194 ElementsAre(0.0, 0.0));
195}
196
197TEST(GetConvergenceInformation, GetsCorrectEntry) {
198 const auto test_stats = ParseTextOrDie<IterationStats>(R"pb(
199 convergence_information {
200 candidate_type: POINT_TYPE_CURRENT_ITERATE
201 primal_objective: 1.0
202 }
203 convergence_information {
204 candidate_type: POINT_TYPE_AVERAGE_ITERATE
205 primal_objective: 2.0
206 }
207 )pb");
208 const auto average_info =
209 GetConvergenceInformation(test_stats, POINT_TYPE_AVERAGE_ITERATE);
210 ASSERT_TRUE(average_info.has_value());
211 EXPECT_EQ(average_info->candidate_type(), POINT_TYPE_AVERAGE_ITERATE);
212 EXPECT_EQ(average_info->primal_objective(), 2.0);
213
214 const auto current_info =
215 GetConvergenceInformation(test_stats, POINT_TYPE_CURRENT_ITERATE);
216 ASSERT_TRUE(current_info.has_value());
217 EXPECT_EQ(current_info->candidate_type(), POINT_TYPE_CURRENT_ITERATE);
218 EXPECT_EQ(current_info->primal_objective(), 1.0);
219
220 EXPECT_THAT(
221 GetConvergenceInformation(test_stats, POINT_TYPE_ITERATE_DIFFERENCE),
222 Eq(absl::nullopt));
223}
224
225TEST(GetInfeasibilityInformation, GetsCorrectEntry) {
226 const auto test_stats = ParseTextOrDie<IterationStats>(R"pb(
227 infeasibility_information {
228 candidate_type: POINT_TYPE_CURRENT_ITERATE
229 primal_ray_linear_objective: 1.0
230 }
231 infeasibility_information {
232 candidate_type: POINT_TYPE_AVERAGE_ITERATE
233 primal_ray_linear_objective: 2.0
234 }
235 )pb");
236 const auto average_info =
237 GetInfeasibilityInformation(test_stats, POINT_TYPE_AVERAGE_ITERATE);
238 ASSERT_TRUE(average_info.has_value());
239 EXPECT_EQ(average_info->candidate_type(), POINT_TYPE_AVERAGE_ITERATE);
240 EXPECT_EQ(average_info->primal_ray_linear_objective(), 2.0);
241
242 const auto current_info =
243 GetInfeasibilityInformation(test_stats, POINT_TYPE_CURRENT_ITERATE);
244 ASSERT_TRUE(current_info.has_value());
245 EXPECT_EQ(current_info->candidate_type(), POINT_TYPE_CURRENT_ITERATE);
246 EXPECT_EQ(current_info->primal_ray_linear_objective(), 1.0);
247
248 EXPECT_THAT(
249 GetInfeasibilityInformation(test_stats, POINT_TYPE_ITERATE_DIFFERENCE),
250 Eq(absl::nullopt));
251}
252
253TEST(GetPointMetadata, GetsCorrectEntry) {
254 const auto test_stats = ParseTextOrDie<IterationStats>(R"pb(
255 point_metadata {
256 point_type: POINT_TYPE_CURRENT_ITERATE
257 active_primal_variable_count: 1
258 }
259 point_metadata {
260 point_type: POINT_TYPE_AVERAGE_ITERATE
261 active_primal_variable_count: 2
262 }
263 )pb");
264 const auto average_info =
265 GetPointMetadata(test_stats, POINT_TYPE_AVERAGE_ITERATE);
266 ASSERT_TRUE(average_info.has_value());
267 EXPECT_EQ(average_info->point_type(), POINT_TYPE_AVERAGE_ITERATE);
268 EXPECT_EQ(average_info->active_primal_variable_count(), 2);
269
270 const auto current_info =
271 GetPointMetadata(test_stats, POINT_TYPE_CURRENT_ITERATE);
272 ASSERT_TRUE(current_info.has_value());
273 EXPECT_EQ(current_info->point_type(), POINT_TYPE_CURRENT_ITERATE);
274 EXPECT_EQ(current_info->active_primal_variable_count(), 1);
275
276 EXPECT_THAT(GetPointMetadata(test_stats, POINT_TYPE_ITERATE_DIFFERENCE),
277 Eq(absl::nullopt));
278}
279
280} // namespace
281} // namespace operations_research::pdlp
T ParseTextOrDie(const std::string &input)
Definition: protobuf_util.h:77
ConvergenceInformation ComputeScaledConvergenceInformation(const ShardedQuadraticProgram &sharded_qp, const VectorXd &primal_solution, const VectorXd &dual_solution, PointType candidate_type)
VectorXd ReducedCosts(const ShardedQuadraticProgram &sharded_qp, const VectorXd &primal_solution, const VectorXd &dual_solution, bool use_zero_primal_objective)
absl::optional< PointMetadata > GetPointMetadata(const IterationStats &stats, const PointType point_type)
absl::optional< ConvergenceInformation > GetConvergenceInformation(const IterationStats &stats, PointType candidate_type)
void SetRandomProjections(const ShardedQuadraticProgram &sharded_qp, const Eigen::VectorXd &primal_solution, const Eigen::VectorXd &dual_solution, const std::vector< int > &random_projection_seeds, PointMetadata &metadata)
absl::optional< InfeasibilityInformation > GetInfeasibilityInformation(const IterationStats &stats, PointType candidate_type)
QuadraticProgram TestLp()
Definition: test_util.cc:32
QuadraticProgram TestDiagonalQp1()
Definition: test_util.cc:142
int64_t Zero()
NOLINT.
TEST(LinearAssignmentTest, NullMatrix)