OR-Tools  9.3
test_util.h
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
14#ifndef PDLP_TEST_UTIL_H_
15#define PDLP_TEST_UTIL_H_
16
17#include <cstdint>
18#include <sstream>
19#include <string>
20#include <type_traits>
21
22#include "Eigen/Core"
23#include "Eigen/SparseCore"
24#include "absl/types/span.h"
25#include "gmock/gmock.h"
26#include "gtest/gtest.h"
29
31
32// 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
48QuadraticProgram TestLp();
49
50// Verifies that the given QuadraticProgram equals TestLp().
51void VerifyTestLp(const QuadraticProgram& qp, bool maximize = false);
52
53// Returns a small test LP.
54// The 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]
68QuadraticProgram TinyLp();
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.
90QuadraticProgram CorrelationClusteringLp();
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
101QuadraticProgram CorrelationClusteringStarLp();
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
114QuadraticProgram TestDiagonalQp1();
115
116// Returns a small diagonal QP.
117// min 0.5 x_0^2 + 0.5 x_1^2 - 3 x_0 - x_1 s.t.
118// x_0 - x_1 = 2
119// x_0 >= 0
120// x_1 >= 0
121// Optimal solutions:
122// Primal: [3, 1]
123// Dual: [0]
124// Value: 0
125// Reduced costs: [0, 0]
126QuadraticProgram TestDiagonalQp2();
127
128// Returns a small diagonal QP.
129// min 0.5 x_1^2 + x_2^2 + x_0 - x_2 s.t.
130// x_0 - x_2 = 1
131// 2x_0 = 4
132// x_0, x_1, x_2 >= 0
133// Optimal solutions:
134// Primal: [2, 0, 1]
135// Dual: [-1, 1]
136// Value: 3
137// Reduced costs: [0, 0, 0]
138QuadraticProgram TestDiagonalQp3();
139
140// Returns a small invalid LP.
141// min x_0 + x_1 s.t.
142// 2.0 <= x_0 - x_1 <= 1.0
143// 0.0 <= x_0
144// 0.0 <= x_1
145QuadraticProgram SmallInvalidProblemLp();
146
147// Returns a small LP that's invalid due to inconsistent variable bounds.
148// min x_0 + x_1 s.t.
149// x_0 - x_1 <= 1.0
150// 2.0 <= x_0 <= 1.0
151// 0.0 <= x_1
152QuadraticProgram SmallInconsistentVariableBoundsLp();
153
154// Returns a small test LP with infeasible primal.
155// min x_0 + x_1 s.t.
156// x_0 - x_1 <= 1.0
157// -x_0 + x_1 <= -2.0
158// 0.0 <= x_0
159// 0.0 <= x_1
160QuadraticProgram SmallPrimalInfeasibleLp();
161
162// Returns a small test LP with infeasible dual.
163// min - x_0 - x_1 s.t.
164// x_0 - x_1 <= 1.0
165// -x_0 + x_1 <= 2.0
166// 0.0 <= x_0
167// 0.0 <= x_1
168// This is the SmallPrimalInfeasibleLp with the objective vector negated and
169// with the second constraint changed to make it feasible.
170QuadraticProgram SmallDualInfeasibleLp();
171
172// Returns a small test LP with infeasible primal and dual.
173// min - x_0 - x_1 s.t.
174// x_0 - x_1 <= 1.0
175// -x_0 + x_1 <= -2.0
176// 0.0 <= x_0
177// 0.0 <= x_1
178// This is just the SmallPrimalInfeasibleLp with the objective vector
179// negated.
180QuadraticProgram SmallPrimalDualInfeasibleLp();
181
182// This is a small lp for which optimality conditions are met by x=(0, 0), y=(0,
183// 0) if one doesn't check that x satisfies the variable bounds. Analogously,
184// the assignment x=(1, 0), y = -(1, 1) also satisfies the optimality conditions
185// if one doesn't check dual variable bounds.
186// min -4 x_0 s.t.
187// x_0 + x_1 <= 2.0
188// x_0 + 2x_1 <= 2.0
189// 0.5 <= x_0 <= 2.0
190// 0.5 <= x_1 <= 2.0
191QuadraticProgram SmallInitializationLp();
192
193// This is a small LP with 2 variables and zero constraints (excluding variable
194// bounds), resulting in an empty constraint matrix (zero rows) and empty lower
195// and upper constraint bounds.
196// min 4 x_0 s.t.
197// 0 <= x_0
198// x_1 <= 0
199QuadraticProgram LpWithoutConstraints();
200
201// Verifies that the given QuadraticProgram equals TestQp().
202void VerifyTestQp(const QuadraticProgram& qp, bool maximize = false);
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 nested 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 syntaxes 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// Get a matcher's description as a string. To produce the description for
245// EigenEach(inner_matcher), this function is called to get the description of
246// inner_matcher.
247template <typename LhsType>
249 const testing::Matcher<LhsType>& matcher, bool negation) {
250 std::stringstream ss;
251 if (negation) {
252 matcher.DescribeNegationTo(&ss);
253 } else {
254 matcher.DescribeTo(&ss);
255 }
256 return ss.str();
257}
258
259} // namespace internal
260
261// Defines a gMock matcher that tests whether two numeric arrays are
262// approximately equal in the sense of maximum absolute difference. The element
263// value type may be float, double, or integral types.
264//
265// Example:
266// vector<double> output = ComputeVector();
267// vector<double> expected({-1.5333, sqrt(2), M_PI});
268// EXPECT_THAT(output, FloatArrayNear(expected, 1e-3));
269template <typename ContainerType>
270decltype(testing::Pointwise(internal::TupleIsNear(0.0), ContainerType()))
271FloatArrayNear(const ContainerType& container, double tolerance) {
272 return testing::Pointwise(internal::TupleIsNear(tolerance), container);
273}
274
275// Defines a gMock matcher acting as an elementwise version of FloatEq() for
276// arrays of real floating point types. It tests whether two arrays are
277// pointwise equal within 4 units in the last place (ULP) in float precision
278// [http://en.wikipedia.org/wiki/Unit_in_the_last_place]. Roughly, 4 ULPs is
279// 2^-21 times the absolute value, or 0.00005%. Exceptionally, zero matches
280// values with magnitude less than 5.6e-45 (2^-147), infinities match infinities
281// of the same sign, and NaNs don't match anything.
282//
283// Example:
284// vector<float> output = ComputeVector();
285// vector<float> expected({-1.5333, sqrt(2), M_PI});
286// EXPECT_THAT(output, FloatArrayEq(expected));
287template <typename ContainerType>
288decltype(testing::Pointwise(internal::TupleFloatEq(), ContainerType()))
289FloatArrayEq(const ContainerType& container) {
290 return testing::Pointwise(internal::TupleFloatEq(), container);
291}
292
293// Call .eval() on input and convert it to a column major representation.
294template <typename EigenType>
295Eigen::Array<typename EigenType::Scalar, Eigen::Dynamic, Eigen::Dynamic,
296 Eigen::ColMajor>
298 return input.eval();
299}
300
301// Wrap a column major Eigen Array as a Span.
302template <typename Scalar>
303absl::Span<const Scalar> EigenArrayAsSpan(
304 const Eigen::Array<Scalar, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>&
305 array) {
306 return absl::Span<const Scalar>(array.data(), array.size());
307}
308
309// Gmock matcher to test whether all elements in an array match expected_array
310// within the specified tolerance, and print a detailed error message pointing
311// to the first mismatched element if they do not. Essentially an elementwise
312// version of testing::DoubleNear for Eigen arrays.
313//
314// Example:
315// Eigen::ArrayXf expected = ...
316// EXPECT_THAT(actual_arrayxf, EigenArrayNear(expected, 1e-5));
317MATCHER_P2(EigenArrayNear, expected_array, tolerance,
318 "array is near " + testing::PrintToString(expected_array) +
319 " within tolerance " + testing::PrintToString(tolerance)) {
320 if (arg.rows() != expected_array.rows() ||
321 arg.cols() != expected_array.cols()) {
322 *result_listener << "where shape (" << expected_array.rows() << ", "
323 << expected_array.cols() << ") doesn't match ("
324 << arg.rows() << ", " << arg.cols() << ")";
325 return false;
326 }
327 // Call .eval() to allow callers to pass in Eigen expressions and possibly
328 // noncontiguous objects, e.g. Eigen::ArrayXf::Zero(10) or Map with a stride.
329 // Arrays are represented in column major order for consistent comparison.
330 auto realized_expected_array = EvalAsColMajorEigenArray(expected_array);
331 auto realized_actual_array = EvalAsColMajorEigenArray(arg);
332 return ExplainMatchResult(
333 FloatArrayNear(EigenArrayAsSpan(realized_expected_array), tolerance),
334 EigenArrayAsSpan(realized_actual_array), result_listener);
335}
336
337// Gmock matcher to test whether all elements in an array match expected_array
338// within 4 units of least precision (ULP) in float precision. Essentially an
339// elementwise version of testing::FloatEq for Eigen arrays.
340//
341// Example:
342// Eigen::ArrayXf expected = ...
343// EXPECT_THAT(actual_arrayxf, EigenArrayEq(expected));
344MATCHER_P(EigenArrayEq, expected_array,
345 "array is almost equal to " +
346 testing::PrintToString(expected_array)) {
347 if (arg.rows() != expected_array.rows() ||
348 arg.cols() != expected_array.cols()) {
349 *result_listener << "where shape (" << expected_array.rows() << ", "
350 << expected_array.cols() << ") doesn't match ("
351 << arg.rows() << ", " << arg.cols() << ")";
352 return false;
353 }
354 // Call .eval() to allow callers to pass in Eigen expressions and possibly
355 // noncontiguous objects, e.g. Eigen::ArrayXf::Zero(10) or Map with a stride.
356 // Arrays are represented in column major order for consistent comparison.
357 auto realized_expected_array = EvalAsColMajorEigenArray(expected_array);
358 auto realized_actual_array = EvalAsColMajorEigenArray(arg);
359 return ExplainMatchResult(
360 FloatArrayEq(EigenArrayAsSpan(realized_expected_array)),
361 EigenArrayAsSpan(realized_actual_array), result_listener);
362}
363
364// The next few functions are syntactic sugar for EigenArrayNear and
365// EigenArrayEq to allow callers to pass in non-Eigen types that can be
366// statically initialized like (nested in the 2D case) initializer_lists, or
367// vectors, etc. For example this specialization lets one make calls inlining
368// expected_array like:
369// EXPECT_THAT(array1d, EigenArrayNear<float>({0.1, 0.2}, tolerance));
370// or in the 2D case:
371// EXPECT_THAT(array2d, EigenArrayNear<int>({{1, 2}, {3, 4}}, tolerance);
372
373template <typename T>
374EigenArrayNearMatcherP2<Eigen::Array<T, Eigen::Dynamic, 1>, double>
375EigenArrayNear(absl::Span<const T> data, double tolerance) {
376 Eigen::Array<T, Eigen::Dynamic, 1> temp_array =
377 Eigen::Map<const Eigen::Array<T, Eigen::Dynamic, 1>>(&data[0],
378 data.size());
379 return EigenArrayNear(temp_array, tolerance);
380}
381
382template <typename T>
383EigenArrayNearMatcherP2<Eigen::Array<T, Eigen::Dynamic, Eigen::Dynamic>, double>
384EigenArrayNear(absl::Span<const absl::Span<const T>> rows, double tolerance) {
386}
387
388template <typename T>
389EigenArrayEqMatcherP<Eigen::Array<T, Eigen::Dynamic, 1>> EigenArrayEq(
390 absl::Span<const T> data) {
391 Eigen::Array<T, Eigen::Dynamic, 1> temp_array =
392 Eigen::Map<const Eigen::Array<T, Eigen::Dynamic, 1>>(&data[0],
393 data.size());
394 return EigenArrayEq(temp_array);
395}
396
397template <typename T>
398EigenArrayEqMatcherP<Eigen::Array<T, Eigen::Dynamic, Eigen::Dynamic>>
399EigenArrayEq(absl::Span<const absl::Span<const T>> rows) {
401}
402
403} // namespace operations_research::pdlp
404
405namespace Eigen {
406// Pretty prints an Eigen::Array on a gunit test failures. See
407// https://google.github.io/googletest/advanced.html#teaching-googletest-how-to-print-your-values
408template <typename Scalar, int Rows, int Cols, int Options, int MaxRows,
409 int MaxCols>
410void PrintTo(const Array<Scalar, Rows, Cols, Options, MaxRows, MaxCols>& array,
411 std::ostream* os) {
412 IOFormat format(StreamPrecision, 0, ", ", ",\n", "[", "]", "[", "]");
413 *os << "\n" << array.format(format);
414}
415} // namespace Eigen
416
417#endif // PDLP_TEST_UTIL_H_
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
void PrintTo(const Array< Scalar, Rows, Cols, Options, MaxRows, MaxCols > &array, std::ostream *os)
Definition: test_util.h:410
MATCHER_P(TupleIsNear, tolerance, "is near")
Definition: test_util.h:214
MATCHER(TupleFloatEq, "is almost equal to")
Definition: test_util.h:218
Eigen::Array< T, Eigen::Dynamic, Eigen::Dynamic > EigenArray2DFromNestedSpans(absl::Span< const absl::Span< const T > > rows)
Definition: test_util.h:230
std::string GetMatcherDescriptionAsString(const testing::Matcher< LhsType > &matcher, bool negation)
Definition: test_util.h:248
QuadraticProgram SmallDualInfeasibleLp()
Definition: test_util.cc:232
QuadraticProgram TestDiagonalQp2()
Definition: test_util.cc:158
::Eigen::ArrayXXd ToDense(const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &sparse_mat)
Definition: test_util.cc:284
EigenArrayNearMatcherP2< Eigen::Array< T, Eigen::Dynamic, 1 >, double > EigenArrayNear(absl::Span< const T > data, double tolerance)
Definition: test_util.h:375
void VerifyTestQp(const QuadraticProgram &qp, bool maximize)
Definition: test_util.cc:269
QuadraticProgram CorrelationClusteringStarLp()
Definition: test_util.cc:107
MATCHER_P2(EigenArrayNear, expected_array, tolerance, "array is near "+testing::PrintToString(expected_array)+" within tolerance "+testing::PrintToString(tolerance))
Definition: test_util.h:317
decltype(testing::Pointwise(internal::TupleFloatEq(), ContainerType())) FloatArrayEq(const ContainerType &container)
Definition: test_util.h:289
QuadraticProgram TestDiagonalQp3()
Definition: test_util.cc:174
EigenArrayEqMatcherP< Eigen::Array< T, Eigen::Dynamic, 1 > > EigenArrayEq(absl::Span< const T > data)
Definition: test_util.h:389
QuadraticProgram TinyLp()
Definition: test_util.cc:66
void VerifyTestLp(const QuadraticProgram &qp, bool maximize)
Definition: test_util.cc:48
Eigen::Array< typename EigenType::Scalar, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor > EvalAsColMajorEigenArray(const EigenType &input)
Definition: test_util.h:297
QuadraticProgram LpWithoutConstraints()
Definition: test_util.cc:261
QuadraticProgram SmallInvalidProblemLp()
Definition: test_util.cc:190
MATCHER_P(EigenArrayEq, expected_array, "array is almost equal to "+testing::PrintToString(expected_array))
Definition: test_util.h:344
QuadraticProgram SmallPrimalDualInfeasibleLp()
Definition: test_util.cc:239
QuadraticProgram TestLp()
Definition: test_util.cc:32
QuadraticProgram SmallInitializationLp()
Definition: test_util.cc:245
absl::Span< const Scalar > EigenArrayAsSpan(const Eigen::Array< Scalar, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor > &array)
Definition: test_util.h:303
QuadraticProgram SmallPrimalInfeasibleLp()
Definition: test_util.cc:216
QuadraticProgram SmallInconsistentVariableBoundsLp()
Definition: test_util.cc:203
decltype(testing::Pointwise(internal::TupleIsNear(0.0), ContainerType())) FloatArrayNear(const ContainerType &container, double tolerance)
Definition: test_util.h:271
QuadraticProgram TestDiagonalQp1()
Definition: test_util.cc:142
QuadraticProgram CorrelationClusteringLp()
Definition: test_util.cc:86
static int input(yyscan_t yyscanner)