OR-Tools  9.6
test_util.h
Go to the documentation of this file.
1 // Copyright 2010-2022 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 
14 #ifndef PDLP_TEST_UTIL_H_
15 #define PDLP_TEST_UTIL_H_
16 
17 #include <cstdint>
18 #include <sstream>
19 #include <string>
20 #include <tuple>
21 
22 #include "Eigen/Core"
23 #include "Eigen/SparseCore"
24 #include "absl/log/check.h"
25 #include "absl/types/span.h"
26 #include "gmock/gmock.h"
27 #include "gtest/gtest.h"
29 
30 namespace operations_research::pdlp {
31 
32 // Returns a small LP with all 4 patterns of which lower and upper bounds on the
33 // constraints are finite and similarly for the variables.
34 // min 5.5 x_0 - 2 x_1 - x_2 + x_3 - 14 s.t.
35 // 2 x_0 + x_1 + x_2 + 2 x_3 = 12
36 // x_0 + x_2 <= 7
37 // 4 x_0 >= -4
38 // -1 <= 1.5 x_2 - x_3 <= 1
39 // -infinity <= x_0 <= infinity
40 // -2 <= x_1 <= infinity
41 // -infinity <= x_2 <= 6
42 // 2.5 <= x_3 <= 3.5
43 //
44 // Optimal solutions:
45 // Primal: [-1, 8, 1, 2.5]
46 // Dual: [-2, 0, 2.375, 2.0/3]
47 // Value: -5.5 - 16 -1 + 2.5 - 14 = -34
48 QuadraticProgram TestLp();
49 
50 // Verifies that the given QuadraticProgram equals TestLp().
51 void VerifyTestLp(const QuadraticProgram& qp, bool maximize = false);
52 
53 // Returns a "tiny" test LP.
54 // min 5 x_1 + 2 x_2 + x_3 + x_4 - 14 s.t.
55 // 2 x_1 + x_2 + x_3 + 2 x_4 = 12
56 // x_1 + x_3 >= 7
57 // x_3 - x_4 >= 1
58 // 0 <= x_1 <= 2
59 // 0 <= x_2 <= 4
60 // 0 <= x_3 <= 6
61 // 0 <= x_4 <= 3
62 //
63 // Optimum solutions:
64 // Primal: x_1 = 1, x_2 = 0, x_3 = 6, x_4 = 2. Value: 5 + 0 + 6 + 2 - 14 = -1.
65 // Dual: [0.5, 4.0, 0.0] Value: 6 + 28 - 3.5*6 - 14 = -1
66 // Reduced costs: [0.0, 1.5, -3.5, 0.0]
67 QuadraticProgram TinyLp();
68 
69 // Returns a correlation clustering LP.
70 // This is the LP for minimizing disagreements for correlation clustering for
71 // the 4-vertex graph
72 // 1 - 3 - 4
73 // | /
74 // 2
75 // In integer solutions x_ij is 1 if i and j are in the same cluster and 0
76 // otherwise. The 6 variables are in the order
77 // x_12, x_13, x_14, x_23, x_24, x_34.
78 // For any distinct i,j,k there's a triangle inequality
79 // (1-x_ik) <= (1-x_ij) + (1-x_jk)
80 // i.e.
81 // -x_ij - x_jk + x_ik >= -1.
82 // For brevity we only include 3 out of the 12 possible triangle inequalities:
83 // two needed in the optimal solution and 1 other.
84 //
85 // Optimal solutions:
86 // Primal: [1, 1, 0, 1, 0, 0]
87 // Dual: Multiple.
88 // Value: 1.
89 QuadraticProgram CorrelationClusteringLp();
90 
91 // Returns another 4-vertex correlation clustering LP.
92 //
93 // The variables are x_12, x_13, x_14, x_23, x_24, and x_34.
94 // This time the graph is a star centered at vertex 1.
95 // Only the three triangle inequalities that are needed are included.
96 // Optimal solutions:
97 // Primal: [0.5, 0.5, 0.5, 0.0, 0.0, 0.0]
98 // Dual: [0.5, 0.5, 0.5]
99 // Value: 1.5
100 QuadraticProgram CorrelationClusteringStarLp();
101 
102 // Returns a small test QP.
103 // min 2 x_0^2 + 0.5 x_1^2 - x_0 - x_1 + 5 s.t.
104 // x_0 + x_1 <= 1
105 // 1 <= x_0 <= 2
106 // -2 <= x_1 <= 4
107 //
108 // Optimal solutions:
109 // Primal: [1.0, 0.0]
110 // Dual: [-1.0]
111 // Reduced costs: [4.0, 0.0]
112 // Value: 2 - 1 + 5 = 6
113 QuadraticProgram TestDiagonalQp1();
114 
115 // Verifies that the given QuadraticProgram equals TestDiagonalQp1().
116 void VerifyTestDiagonalQp1(const QuadraticProgram& qp, bool maximize = false);
117 
118 // Returns a small diagonal QP.
119 // min 0.5 x_0^2 + 0.5 x_1^2 - 3 x_0 - x_1 s.t.
120 // x_0 - x_1 = 2
121 // x_0 >= 0
122 // x_1 >= 0
123 // Optimal solutions:
124 // Primal: [3, 1]
125 // Dual: [0]
126 // Value: -5
127 // Reduced costs: [0, 0]
128 QuadraticProgram TestDiagonalQp2();
129 
130 // Returns a small diagonal QP.
131 // min 0.5 x_1^2 + x_2^2 + x_0 - x_2 s.t.
132 // x_0 - x_2 = 1
133 // 2x_0 = 4
134 // x_0, x_1, x_2 >= 0
135 // Optimal solutions:
136 // Primal: [2, 0, 1]
137 // Dual: [-1, 1]
138 // Value: 2
139 // Reduced costs: [0, 0, 0]
140 QuadraticProgram TestDiagonalQp3();
141 
142 // Returns a small invalid LP.
143 // min x_0 + x_1 s.t.
144 // 2.0 <= x_0 - x_1 <= 1.0
145 // 0.0 <= x_0
146 // 0.0 <= x_1
147 QuadraticProgram SmallInvalidProblemLp();
148 
149 // Returns a small LP that's invalid due to inconsistent variable bounds.
150 // min x_0 + x_1 s.t.
151 // x_0 - x_1 <= 1.0
152 // 2.0 <= x_0 <= 1.0
153 // 0.0 <= x_1
154 QuadraticProgram SmallInconsistentVariableBoundsLp();
155 
156 // Returns a small test LP with infeasible primal.
157 // min x_0 + x_1 s.t.
158 // x_0 - x_1 <= 1.0
159 // -x_0 + x_1 <= -2.0
160 // 0.0 <= x_0
161 // 0.0 <= x_1
162 QuadraticProgram SmallPrimalInfeasibleLp();
163 
164 // Returns a small test LP with infeasible dual.
165 // min - x_0 - x_1 s.t.
166 // x_0 - x_1 <= 1.0
167 // -x_0 + x_1 <= 2.0
168 // 0.0 <= x_0
169 // 0.0 <= x_1
170 // This is the SmallPrimalInfeasibleLp with the objective vector negated and
171 // with the second constraint changed to make it feasible.
172 QuadraticProgram SmallDualInfeasibleLp();
173 
174 // Returns a small test LP with infeasible primal and dual.
175 // min - x_0 - x_1 s.t.
176 // x_0 - x_1 <= 1.0
177 // -x_0 + x_1 <= -2.0
178 // 0.0 <= x_0
179 // 0.0 <= x_1
180 // This is just the SmallPrimalInfeasibleLp with the objective vector
181 // negated.
182 QuadraticProgram SmallPrimalDualInfeasibleLp();
183 
184 // Returns a small lp for which optimality conditions are met by x=(0, 0), y=(0,
185 // 0) if one doesn't check that x satisfies the variable bounds. Analogously,
186 // the assignment x=(1, 0), y = -(1, 1) also satisfies the optimality conditions
187 // if one doesn't check dual variable bounds.
188 // min -4 x_0 s.t.
189 // x_0 + x_1 <= 2.0
190 // x_0 + 2x_1 <= 2.0
191 // 0.5 <= x_0 <= 2.0
192 // 0.5 <= x_1 <= 2.0
193 QuadraticProgram SmallInitializationLp();
194 
195 // Returns a small LP with 2 variables and zero constraints (excluding variable
196 // bounds), resulting in an empty constraint matrix (zero rows) and empty lower
197 // and upper constraint bounds.
198 // min 4 x_0 s.t.
199 // 0 <= x_0
200 // x_1 <= 0
201 QuadraticProgram LpWithoutConstraints();
202 
203 // Converts a sparse matrix into a dense matrix in the format suitable for
204 // the matcher `EigenArrayEq`. Example usage:
205 // `EXPECT_THAT(ToDense(sparse_mat), EigenArrayEq<double>({{1, 1}}));`
206 ::Eigen::ArrayXXd ToDense(
207  const Eigen::SparseMatrix<double, Eigen::ColMajor, int64_t>& sparse_mat);
208 
209 // gMock matchers for Eigen.
210 
211 namespace internal {
212 
213 MATCHER_P(TupleIsNear, tolerance, "is near") {
214  return std::abs(std::get<0>(arg) - std::get<1>(arg)) <= tolerance;
215 }
216 
217 MATCHER(TupleFloatEq, "is almost equal to") {
218  testing::Matcher<float> matcher = testing::FloatEq(std::get<1>(arg));
219  return matcher.Matches(std::get<0>(arg));
220 }
221 
222 // Convert a nested `absl::Span` to a 2D `Eigen::Array`. Spans are implicitly
223 // constructable from initializer lists and vectors, so this conversion is used
224 // in `EigenArrayNear` and `EigenArrayEq` to support syntax like
225 // `EXPECT_THAT(array2d, EigenArrayNear<int>({{1, 2}, {3, 4}}, tolerance);`
226 // This conversion creates a copy of the slice data, so it is safe to use the
227 // result even after the original slices vanish.
228 template <typename T>
229 Eigen::Array<T, Eigen::Dynamic, Eigen::Dynamic> EigenArray2DFromNestedSpans(
230  absl::Span<const absl::Span<const T>> rows) {
231  Eigen::Array<T, Eigen::Dynamic, Eigen::Dynamic> result(0, rows.size());
232  if (!rows.empty()) {
233  result.resize(rows.size(), rows[0].size());
234  }
235  for (int i = 0; i < rows.size(); ++i) {
236  CHECK_EQ(rows[0].size(), rows[i].size());
237  result.row(i) = Eigen::Map<const Eigen::Array<T, Eigen::Dynamic, 1>>(
238  &rows[i][0], rows[i].size());
239  }
240  return result;
241 }
242 
243 } // namespace internal
244 
245 // Defines a gMock matcher that tests whether two numeric arrays are
246 // approximately equal in the sense of maximum absolute difference. The element
247 // value type may be float, double, or integral types.
248 //
249 // Example:
250 // `vector<double> output = ComputeVector();`
251 // `vector<double> expected({-1.5333, sqrt(2), M_PI});`
252 // `EXPECT_THAT(output, FloatArrayNear(expected, 1.0e-3));`
253 template <typename ContainerType>
254 decltype(testing::Pointwise(internal::TupleIsNear(0.0), ContainerType()))
255 FloatArrayNear(const ContainerType& container, double tolerance) {
256  return testing::Pointwise(internal::TupleIsNear(tolerance), container);
257 }
258 
259 // Defines a gMock matcher acting as an elementwise version of `FloatEq()` for
260 // arrays of real floating point types. It tests whether two arrays are
261 // pointwise equal within 4 units in the last place (ULP) in float precision
262 // [http://en.wikipedia.org/wiki/Unit_in_the_last_place]. Roughly, 4 ULPs is
263 // 2^-21 times the absolute value, or 0.00005%. Exceptionally, zero matches
264 // values with magnitude less than 5.6e-45 (2^-147), infinities match infinities
265 // of the same sign, and NaNs don't match anything.
266 //
267 // Example:
268 // `vector<float> output = ComputeVector();`
269 // `vector<float> expected({-1.5333, sqrt(2), M_PI});`
270 // `EXPECT_THAT(output, FloatArrayEq(expected));`
271 template <typename ContainerType>
272 decltype(testing::Pointwise(internal::TupleFloatEq(), ContainerType()))
273 FloatArrayEq(const ContainerType& container) {
274  return testing::Pointwise(internal::TupleFloatEq(), container);
275 }
276 
277 // Call `.eval()` on input and convert it to a column major representation.
278 template <typename EigenType>
279 Eigen::Array<typename EigenType::Scalar, Eigen::Dynamic, Eigen::Dynamic,
280  Eigen::ColMajor>
281 EvalAsColMajorEigenArray(const EigenType& input) {
282  return input.eval();
283 }
284 
285 // Wrap a column major `Eigen::Array` as an `absl::Span`.
286 template <typename Scalar>
287 absl::Span<const Scalar> EigenArrayAsSpan(
288  const Eigen::Array<Scalar, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>&
289  array) {
290  return absl::Span<const Scalar>(array.data(), array.size());
291 }
292 
293 // Gmock matcher to test whether all elements in an array match expected_array
294 // within the specified tolerance, and print a detailed error message pointing
295 // to the first mismatched element if they do not. Essentially an elementwise
296 // version of `testing::DoubleNear` for Eigen arrays.
297 //
298 // Example:
299 // `Eigen::ArrayXf expected = ...`
300 // `EXPECT_THAT(actual_arrayxf, EigenArrayNear(expected, 1.0e-5));`
301 MATCHER_P2(EigenArrayNear, expected_array, tolerance,
302  "array is near " + testing::PrintToString(expected_array) +
303  " within tolerance " + testing::PrintToString(tolerance)) {
304  if (arg.rows() != expected_array.rows() ||
305  arg.cols() != expected_array.cols()) {
306  *result_listener << "where shape (" << expected_array.rows() << ", "
307  << expected_array.cols() << ") doesn't match ("
308  << arg.rows() << ", " << arg.cols() << ")";
309  return false;
310  }
311  // Call `.eval()` to allow callers to pass in Eigen expressions and possibly
312  // noncontiguous objects, e.g. `Eigen::ArrayXf::Zero(10)` or `Map` with a
313  // stride. Arrays are represented in column major order for consistent
314  // comparison.
315  auto realized_expected_array = EvalAsColMajorEigenArray(expected_array);
316  auto realized_actual_array = EvalAsColMajorEigenArray(arg);
317  return ExplainMatchResult(
318  FloatArrayNear(EigenArrayAsSpan(realized_expected_array), tolerance),
319  EigenArrayAsSpan(realized_actual_array), result_listener);
320 }
321 
322 // Gmock matcher to test whether all elements in an array match expected_array
323 // within 4 units of least precision (ULP) in float precision. Essentially an
324 // elementwise version of `testing::FloatEq` for Eigen arrays.
325 //
326 // Example:
327 // `Eigen::ArrayXf expected = ...`
328 // `EXPECT_THAT(actual_arrayxf, EigenArrayEq(expected));`
329 MATCHER_P(EigenArrayEq, expected_array,
330  "array is almost equal to " +
331  testing::PrintToString(expected_array)) {
332  if (arg.rows() != expected_array.rows() ||
333  arg.cols() != expected_array.cols()) {
334  *result_listener << "where shape (" << expected_array.rows() << ", "
335  << expected_array.cols() << ") doesn't match ("
336  << arg.rows() << ", " << arg.cols() << ")";
337  return false;
338  }
339  // Call `.eval()` to allow callers to pass in Eigen expressions and possibly
340  // noncontiguous objects, e.g. `Eigen::ArrayXf::Zero(10)` or `Map` with a
341  // stride. Arrays are represented in column major order for consistent
342  // comparison.
343  auto realized_expected_array = EvalAsColMajorEigenArray(expected_array);
344  auto realized_actual_array = EvalAsColMajorEigenArray(arg);
345  return ExplainMatchResult(
346  FloatArrayEq(EigenArrayAsSpan(realized_expected_array)),
347  EigenArrayAsSpan(realized_actual_array), result_listener);
348 }
349 
350 // The next few functions are syntactic sugar for `EigenArrayNear` and
351 // `EigenArrayEq` to allow callers to pass in non-Eigen types that can be
352 // statically initialized like (nested in the 2D case) initializer_lists, or
353 // vectors, etc. For example this specialization lets one make calls inlining
354 // expected_array like:
355 // `EXPECT_THAT(array1d, EigenArrayNear<float>({0.1, 0.2}, tolerance));`
356 // or in the 2D case:
357 // `EXPECT_THAT(array2d, EigenArrayNear<int>({{1, 2}, {3, 4}}, tolerance);`
358 
359 template <typename T>
360 EigenArrayNearMatcherP2<Eigen::Array<T, Eigen::Dynamic, 1>, double>
361 EigenArrayNear(absl::Span<const T> data, double tolerance) {
362  Eigen::Array<T, Eigen::Dynamic, 1> temp_array =
363  Eigen::Map<const Eigen::Array<T, Eigen::Dynamic, 1>>(&data[0],
364  data.size());
365  return EigenArrayNear(temp_array, tolerance);
366 }
367 
368 template <typename T>
369 EigenArrayNearMatcherP2<Eigen::Array<T, Eigen::Dynamic, Eigen::Dynamic>, double>
370 EigenArrayNear(absl::Span<const absl::Span<const T>> rows, double tolerance) {
371  return EigenArrayNear(internal::EigenArray2DFromNestedSpans(rows), tolerance);
372 }
373 
374 template <typename T>
375 EigenArrayEqMatcherP<Eigen::Array<T, Eigen::Dynamic, 1>> EigenArrayEq(
376  absl::Span<const T> data) {
377  Eigen::Array<T, Eigen::Dynamic, 1> temp_array =
378  Eigen::Map<const Eigen::Array<T, Eigen::Dynamic, 1>>(&data[0],
379  data.size());
380  return EigenArrayEq(temp_array);
381 }
382 
383 template <typename T>
384 EigenArrayEqMatcherP<Eigen::Array<T, Eigen::Dynamic, Eigen::Dynamic>>
385 EigenArrayEq(absl::Span<const absl::Span<const T>> rows) {
387 }
388 
389 } // namespace operations_research::pdlp
390 
391 namespace Eigen {
392 // Pretty prints an `Eigen::Array` on a gunit test failures. See
393 // https://google.github.io/googletest/advanced.html#teaching-googletest-how-to-print-your-values
394 template <typename Scalar, int Rows, int Cols, int Options, int MaxRows,
395  int MaxCols>
396 void PrintTo(const Array<Scalar, Rows, Cols, Options, MaxRows, MaxCols>& array,
397  std::ostream* os) {
398  IOFormat format(StreamPrecision, 0, ", ", ",\n", "[", "]", "[", "]");
399  *os << "\n" << array.format(format);
400 }
401 } // namespace Eigen
402 
403 #endif // PDLP_TEST_UTIL_H_
void PrintTo(const Array< Scalar, Rows, Cols, Options, MaxRows, MaxCols > &array, std::ostream *os)
Definition: test_util.h:396
int Options
Definition: base/file.h:121
MATCHER_P(TupleIsNear, tolerance, "is near")
Definition: test_util.h:213
MATCHER(TupleFloatEq, "is almost equal to")
Definition: test_util.h:217
Eigen::Array< T, Eigen::Dynamic, Eigen::Dynamic > EigenArray2DFromNestedSpans(absl::Span< const absl::Span< const T >> rows)
Definition: test_util.h:229
QuadraticProgram SmallDualInfeasibleLp()
Definition: test_util.cc:233
QuadraticProgram TestDiagonalQp2()
Definition: test_util.cc:159
Eigen::Array< typename EigenType::Scalar, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor > EvalAsColMajorEigenArray(const EigenType &input)
Definition: test_util.h:281
::Eigen::ArrayXXd ToDense(const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &sparse_mat)
Definition: test_util.cc:285
absl::Span< const Scalar > EigenArrayAsSpan(const Eigen::Array< Scalar, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor > &array)
Definition: test_util.h:287
QuadraticProgram CorrelationClusteringStarLp()
Definition: test_util.cc:108
MATCHER_P2(EigenArrayNear, expected_array, tolerance, "array is near "+testing::PrintToString(expected_array)+" within tolerance "+testing::PrintToString(tolerance))
Definition: test_util.h:301
decltype(testing::Pointwise(internal::TupleFloatEq(), ContainerType())) FloatArrayEq(const ContainerType &container)
Definition: test_util.h:273
QuadraticProgram TestDiagonalQp3()
Definition: test_util.cc:175
QuadraticProgram TinyLp()
Definition: test_util.cc:67
void VerifyTestLp(const QuadraticProgram &qp, bool maximize)
Definition: test_util.cc:49
QuadraticProgram LpWithoutConstraints()
Definition: test_util.cc:262
QuadraticProgram SmallInvalidProblemLp()
Definition: test_util.cc:191
void VerifyTestDiagonalQp1(const QuadraticProgram &qp, bool maximize)
Definition: test_util.cc:270
EigenArrayNearMatcherP2< Eigen::Array< T, Eigen::Dynamic, 1 >, double > EigenArrayNear(absl::Span< const T > data, double tolerance)
Definition: test_util.h:361
MATCHER_P(EigenArrayEq, expected_array, "array is almost equal to "+testing::PrintToString(expected_array))
Definition: test_util.h:329
QuadraticProgram SmallPrimalDualInfeasibleLp()
Definition: test_util.cc:240
EigenArrayEqMatcherP< Eigen::Array< T, Eigen::Dynamic, 1 > > EigenArrayEq(absl::Span< const T > data)
Definition: test_util.h:375
QuadraticProgram TestLp()
Definition: test_util.cc:33
QuadraticProgram SmallInitializationLp()
Definition: test_util.cc:246
QuadraticProgram SmallPrimalInfeasibleLp()
Definition: test_util.cc:217
QuadraticProgram SmallInconsistentVariableBoundsLp()
Definition: test_util.cc:204
decltype(testing::Pointwise(internal::TupleIsNear(0.0), ContainerType())) FloatArrayNear(const ContainerType &container, double tolerance)
Definition: test_util.h:255
QuadraticProgram TestDiagonalQp1()
Definition: test_util.cc:143
QuadraticProgram CorrelationClusteringLp()
Definition: test_util.cc:87
static int input(yyscan_t yyscanner)