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