OR-Tools  9.6
matchers.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 // Matchers for MathOpt types, specifically SolveResult and nested fields.
15 //
16 // The matchers defined here are useful for writing unit tests checking that the
17 // result of Solve(), absl::StatusOr<SolveResult>, meets expectations. We give
18 // some examples below. All code is assumed with the following setup:
19 //
20 // namespace operations_research::math_opt {
21 // using ::testing::status::IsOkAndHolds;
22 //
23 // Model model;
24 // const Variable x = model.AddContinuousVariable(0.0, 1.0);
25 // const Variable y = model.AddContinuousVariable(0.0, 1.0);
26 // const LinearConstraint c = model.AddLinearConstraint(x + y <= 1);
27 // model.Maximize(2*x + y);
28 //
29 // Example 1.a: result is OK, optimal, and objective value approximately 42.
30 // EXPECT_THAT(Solve(model, SOLVER_TYPE_GLOP), IsOkAndHolds(IsOptimal(42)));
31 //
32 // Example 1.b: equivalent to 1.a.
33 // ASSERT_OK_AND_ASSIGN(const SolveResult result,
34 // Solve(model, SOLVER_TYPE_GLOP));
35 // EXPECT_THAT(result, IsOptimal(42));
36 //
37 // Example 2: result is OK, optimal, and best solution is x=1, y=0.
38 // ASSERT_OK_AND_ASSIGN(const SolveResult result,
39 // Solve(model, SOLVER_TYPE_GLOP));
40 // ASSERT_THAT(result, IsOptimal());
41 // EXPECT_THAT(result.variable_value(), IsNear({{x, 1}, {y, 0}});
42 // Note: the second ASSERT ensures that if the solution is not optimal, then
43 // result.variable_value() will not run (the function will crash if the solver
44 // didn't find a solution). Further, MathOpt guarantees there is a solution
45 // when the termination reason is optimal.
46 //
47 // Example 3: result is OK, check the solution without specifying termination.
48 // ASSERT_OK_AND_ASSIGN(const SolveResult result,
49 // Solve(model, SOLVER_TYPE_GLOP));
50 // EXPECT_THAT(result, HasBestSolution({{x, 1}, {y, 0}}));
51 //
52 // Example 4: multiple possible termination reason, primal ray optional:
53 // ASSERT_OK_AND_ASSIGN(const SolveResult result,
54 // Solve(model, SOLVER_TYPE_GLOP));
55 // ASSERT_THAT(result, TerminatesWithOneOf(
56 // TerminationReason::kUnbounded,
57 // TerminationReason::kInfeasibleOrUnbounded));
58 // if(!result.primal_rays.empty()) {
59 // EXPECT_THAT(result.primal_rays[0], PrimalRayIsNear({{x, 1,}, {y, 0}}));
60 // }
61 //
62 //
63 // Tips on writing good tests:
64 // * Use ASSERT_OK_AND_ASSIGN(const SolveResult result, Solve(...)) to ensure
65 // the test terminates immediately if Solve() does not return OK.
66 // * If you ASSERT_THAT(result, IsOptimal()), you can assume that you have a
67 // feasible primal solution afterwards. Otherwise, make no assumptions on
68 // the contents of result (e.g. do not assume result contains a primal ray
69 // just because the termination reason was UNBOUNDED).
70 // * For problems that are infeasible, the termination reasons INFEASIBLE and
71 // DUAL_INFEASIBLE are both possible. Likewise, for unbounded problems, you
72 // can get both UNBOUNDED and DUAL_INFEASIBLE. See TerminatesWithOneOf()
73 // below to make assertions in this case. Note also that some solvers have
74 // solver specific parameters to ensure that DUAL_INFEASIBLE will not be
75 // returned (e.g. for Gurobi, use DualReductions or InfUnbdInfo).
76 // * The objective value and variable values should always be compared up to
77 // a tolerance, even if your decision variables are integer. The matchers
78 // defined have a configurable tolerance with default value 1e-5.
79 // * Primal and dual rays are unique only up to a constant scaling. The
80 // matchers provided rescale both expected and actual before comparing.
81 // * Take care on problems with multiple optimal solutions. Do not rely on a
82 // particular solution being returned in your test, as the test will break
83 // when we upgrade the solver.
84 //
85 // This file also defines functions to let gunit print various MathOpt types.
86 //
87 // To see the error messages these matchers generate, run
88 // blaze test experimental/users/rander/math_opt:matchers_error_messages
89 // which is a fork of matchers_test.cc where the assertions are all negated
90 // (note that every test should fail).
91 #ifndef OR_TOOLS_MATH_OPT_CPP_MATCHERS_H_
92 #define OR_TOOLS_MATH_OPT_CPP_MATCHERS_H_
93 
94 #include <optional>
95 #include <ostream>
96 #include <sstream>
97 #include <vector>
98 
99 #include "gtest/gtest.h"
103 
104 namespace operations_research {
105 namespace math_opt {
106 
107 constexpr double kMatcherDefaultTolerance = 1e-5;
108 
110 // Matchers for IdMap
112 
113 // Checks that the maps have identical keys and values within tolerance. This
114 // factory will CHECK-fail if expected contains any NaN values.
115 testing::Matcher<VariableMap<double>> IsNear(
116  VariableMap<double> expected, double tolerance = kMatcherDefaultTolerance);
117 
118 // Checks that the keys of actual are a subset of the keys of expected, and that
119 // for all shared keys, the values are within tolerance. This factory will
120 // CHECK-fail if expected contains any NaN values, and any NaN values in the
121 // expression compared against will result in the matcher failing.
122 testing::Matcher<VariableMap<double>> IsNearlySubsetOf(
123  VariableMap<double> expected, double tolerance = kMatcherDefaultTolerance);
124 
125 // Checks that the maps have identical keys and values within tolerance. This
126 // factory will CHECK-fail if expected contains any NaN values, and any NaN
127 // values in the expression compared against will result in the matcher failing.
128 testing::Matcher<LinearConstraintMap<double>> IsNear(
130  double tolerance = kMatcherDefaultTolerance);
131 
132 // Checks that the keys of actual are a subset of the keys of expected, and that
133 // for all shared keys, the values are within tolerance. This factory will
134 // CHECK-fail if expected contains any NaN values, and any NaN values in the
135 // expression compared against will result in the matcher failing.
136 testing::Matcher<LinearConstraintMap<double>> IsNearlySubsetOf(
138  double tolerance = kMatcherDefaultTolerance);
139 
140 // Checks that the maps have identical keys and values within tolerance. Works
141 // for VariableMap, LinearConstraintMap, among other realizations of IdMap. This
142 // factory will CHECK-fail if expected contains any NaN values, and any NaN
143 // values in the expression compared against will result in the matcher failing.
144 template <typename K>
145 testing::Matcher<IdMap<K, double>> IsNear(
146  IdMap<K, double> expected,
147  const double tolerance = kMatcherDefaultTolerance);
148 
149 // Checks that the keys of actual are a subset of the keys of expected, and that
150 // for all shared keys, the values are within tolerance. Works for VariableMap,
151 // LinearConstraintMap, among other realizations of IdMap. This factory will
152 // CHECK-fail if expected contains any NaN values, and any NaN values in the
153 // expression compared against will result in the matcher failing.
154 template <typename K>
155 testing::Matcher<IdMap<K, double>> IsNearlySubsetOf(
156  IdMap<K, double> expected,
157  const double tolerance = kMatcherDefaultTolerance);
158 
160 // Matchers for various Variable expressions (e.g. LinearExpression)
162 
163 // Checks that the expressions are structurally identical (i.e., internal maps
164 // have the same keys and storage, coefficients are exactly equal). This factory
165 // will CHECK-fail if expected contains any NaN values, and any NaN values in
166 // the expression compared against will result in the matcher failing.
167 testing::Matcher<LinearExpression> IsIdentical(LinearExpression expected);
168 
169 testing::Matcher<LinearExpression> LinearExpressionIsNear(
170  LinearExpression expected, double tolerance = kMatcherDefaultTolerance);
171 
172 // Checks that the bounded linear expression is equivalent to expected, where
173 // equivalence is maintained by:
174 // * adding alpha to the lower bound, the linear expression and upper bound
175 // * multiplying the lower bound, linear expression, by -1 (and flipping the
176 // inequalities).
177 // Note that, as implemented, we do not allow for arbitrary multiplicative
178 // rescalings (this makes additive tolerance complicated).
179 testing::Matcher<BoundedLinearExpression> IsNearlyEquivalent(
180  const BoundedLinearExpression& expected,
181  double tolerance = kMatcherDefaultTolerance);
182 
183 // Checks that the expressions are structurally identical (i.e., internal maps
184 // have the same keys and storage, coefficients are exactly equal). This factory
185 // will CHECK-fail if expected contains any NaN values, and any NaN values in
186 // the expression compared against will result in the matcher failing.
187 testing::Matcher<QuadraticExpression> IsIdentical(QuadraticExpression expected);
188 
190 // Matchers for solutions
192 
193 // Options for IsNear(Solution).
196  bool check_primal = true;
197  bool check_dual = true;
198  bool check_basis = true;
199 };
200 
201 testing::Matcher<Solution> IsNear(Solution expected,
202  SolutionMatcherOptions options = {});
203 
204 // Checks variables match and variable/objective values are within tolerance and
205 // feasibility statuses are identical.
206 testing::Matcher<PrimalSolution> IsNear(
207  PrimalSolution expected, double tolerance = kMatcherDefaultTolerance);
208 
209 // Checks dual variables, reduced costs and objective are within tolerance and
210 // feasibility statuses are identical.
211 testing::Matcher<DualSolution> IsNear(
212  DualSolution expected, double tolerance = kMatcherDefaultTolerance);
213 
214 testing::Matcher<Basis> BasisIs(const Basis& expected);
215 
217 // Matchers for a Rays
219 
220 // Checks variables match and that after rescaling, variable values are within
221 // tolerance.
222 testing::Matcher<PrimalRay> IsNear(PrimalRay expected,
223  double tolerance = kMatcherDefaultTolerance);
224 
225 // Checks variables match and that after rescaling, variable values are within
226 // tolerance.
227 testing::Matcher<PrimalRay> PrimalRayIsNear(
228  VariableMap<double> expected_var_values,
229  double tolerance = kMatcherDefaultTolerance);
230 
231 // Checks that dual variables and reduced costs are defined for the same
232 // set of Variables/LinearConstraints, and that their rescaled values are within
233 // tolerance.
234 testing::Matcher<DualRay> IsNear(DualRay expected,
235  double tolerance = kMatcherDefaultTolerance);
236 
238 // Matchers for a Termination
240 
241 testing::Matcher<Termination> ReasonIs(TerminationReason reason);
242 
243 testing::Matcher<Termination> ReasonIsOptimal();
244 
246 // Matchers for a SolveResult
248 
249 // Checks the following:
250 // * The termination reason is optimal.
251 // * If expected_objective contains a value, there is at least one feasible
252 // solution and that solution has an objective value within tolerance of
253 // expected_objective.
254 testing::Matcher<SolveResult> IsOptimal(
255  std::optional<double> expected_objective = std::nullopt,
256  double tolerance = kMatcherDefaultTolerance);
257 
258 testing::Matcher<SolveResult> IsOptimalWithSolution(
259  double expected_objective, VariableMap<double> expected_variable_values,
260  double tolerance = kMatcherDefaultTolerance);
261 
262 testing::Matcher<SolveResult> IsOptimalWithDualSolution(
263  double expected_objective, LinearConstraintMap<double> expected_dual_values,
264  VariableMap<double> expected_reduced_costs,
265  double tolerance = kMatcherDefaultTolerance);
266 
267 // Checks the following:
268 // * The result has the expected termination reason.
269 testing::Matcher<SolveResult> TerminatesWith(TerminationReason expected);
270 
271 // Checks that the result has one of the allowed termination reasons.
272 testing::Matcher<SolveResult> TerminatesWithOneOf(
273  const std::vector<TerminationReason>& allowed);
274 
275 // Checks the following:
276 // * The result has termination reason kFeasible or kNoSolutionFound.
277 // * The limit is expected, or is kUndetermined if allow_limit_undetermined.
278 testing::Matcher<SolveResult> TerminatesWithLimit(
279  Limit expected, bool allow_limit_undetermined = false);
280 
281 // Checks the following:
282 // * The result has termination reason kFeasible.
283 // * The limit is expected, or is kUndetermined if allow_limit_undetermined.
284 testing::Matcher<SolveResult> TerminatesWithReasonFeasible(
285  Limit expected, bool allow_limit_undetermined = false);
286 
287 // Checks the following:
288 // * The result has termination reason kNoSolutionFound.
289 // * The limit is expected, or is kUndetermined if allow_limit_undetermined.
290 testing::Matcher<SolveResult> TerminatesWithReasonNoSolutionFound(
291  Limit expected, bool allow_limit_undetermined = false);
292 
293 // SolveResult has a primal solution matching expected within tolerance.
294 testing::Matcher<SolveResult> HasSolution(
295  PrimalSolution expected, double tolerance = kMatcherDefaultTolerance);
296 
297 // SolveResult has a dual solution matching expected within
298 // tolerance.
299 testing::Matcher<SolveResult> HasDualSolution(
300  DualSolution expected, double tolerance = kMatcherDefaultTolerance);
301 
302 // Actual SolveResult contains a primal ray that matches expected within
303 // tolerance.
304 testing::Matcher<SolveResult> HasPrimalRay(
305  PrimalRay expected, double tolerance = kMatcherDefaultTolerance);
306 
307 // Actual SolveResult contains a primal ray with variable values equivalent to
308 // (under L_inf scaling) expected_vars up to tolerance.
309 testing::Matcher<SolveResult> HasPrimalRay(
310  VariableMap<double> expected_vars,
311  double tolerance = kMatcherDefaultTolerance);
312 
313 // Actual SolveResult contains a dual ray that matches expected within
314 // tolerance.
315 testing::Matcher<SolveResult> HasDualRay(
316  DualRay expected, double tolerance = kMatcherDefaultTolerance);
317 
318 // Configures SolveResult matcher IsConsistentWith() below.
320  double tolerance = 1e-5;
321  bool first_solution_only = true;
322  bool check_dual = true;
323  bool check_rays = true;
324 
325  // If the expected result has termination reason kInfeasible, kUnbounded, or
326  // kDualInfeasasible, the primal solution, dual solution, and basis are
327  // ignored unless check_solutions_if_inf_or_unbounded is true.
328  //
329  // TODO(b/201099290): this is perhaps not a good default. Gurobi as
330  // implemented is returning primal solutions for both unbounded and
331  // infeasible problems. We need to add unit tests that inspect this value
332  // and turn them on one solver at a time with a new parameter on
333  // SimpleLpTestParameters.
335  bool check_basis = false;
336 
337  // In linear programming, the following outcomes are all possible
338  //
339  // Primal LP | Dual LP | Possible MathOpt Termination Reasons
340  // -----------------------------------------------------------------
341  // 1. Infeasible | Unbounded | kInfeasible
342  // 2. Optimal | Optimal | kOptimal
343  // 3. Unbounded | Infeasible | kUnbounded, kInfeasibleOrUnbounded
344  // 4. Infeasible | Infeasible | kInfeasible, kInfeasibleOrUnbounded
345  //
346  // (Above "Optimal" means that an optimal solution exists. This is a statement
347  // about the existence of optimal solutions and certificates of
348  // infeasibility/unboundedness, not about the outcome of applying any
349  // particular algorithm.)
350  //
351  // When writing your unit test, you can typically tell which case of 1-4 you
352  // are in, but in cases 3-4 you do not know which termination reason will be
353  // returned. In some situations, it may not be clear if you are in case 1 or
354  // case 4 as well.
355  //
356  // When inf_or_unb_soft_match=false, the matcher must exactly specify the
357  // status returned by the solver. For cases 3-4, this is implementation
358  // dependent and we do not recommend this. When
359  // inf_or_unb_soft_match=true:
360  // * kInfeasible can also match kInfeasibleOrUnbounded
361  // * kUnbounded can also match kInfeasibleOrUnbounded
362  // * kInfeasibleOrUnbounded can also match kInfeasible and kUnbounded.
363  // For case 2, inf_or_unb_soft_match has no effect.
364  //
365  // To build the strongest possible matcher (accepting the minimal set of
366  // termination reasons):
367  // * If you know you are in case 1, se inf_or_unb_soft_match=false
368  // (soft_match=true over-matches)
369  // * For case 3, use inf_or_unb_soft_match=false and
370  // termination_reason=kUnbounded (kInfeasibleOrUnbounded over-matches).
371  // * For case 4 (or if you are unsure of case 1 vs case 4), use
372  // inf_or_unb_soft_match=true and
373  // termination_reason=kInfeasible (kInfeasibleOrUnbounded over-matches).
374  // * If you cannot tell if you are in case 3 or case 4, use
375  // inf_or_unb_soft_match=true and termination reason
376  // kInfeasibleOrUnbounded.
377  //
378  // If the above is too complicated, always setting
379  // inf_or_unb_soft_match=true and using any of the expected MathOpt
380  // termination reasons from the above table will give a matcher that is
381  // slightly too lenient.
383 };
384 
385 // Tests that two SolveResults are equivalent. Basic use:
386 //
387 // SolveResult expected;
388 // // Fill in expected...
389 // ASSERT_OK_AND_ASSIGN(SolveResult actual, Solve(model, solver_type));
390 // EXPECT_THAT(actual, IsConsistentWith(expected));
391 //
392 // Equivalence is defined as follows:
393 // * The termination reasons are the same.
394 // - For infeasible and unbounded problems, see
395 // options.inf_or_unb_soft_match.
396 // * The solve stats are ignored.
397 // * For both primal and dual solutions, either expected and actual are
398 // both empty, or their first entries satisfy IsNear() at options.tolerance.
399 // - Not checked if options.check_solutions_if_inf_or_unbounded and the
400 // problem is infeasible or unbounded (default).
401 // - If options.first_solution_only is false, check the entire list of
402 // solutions matches in the same order.
403 // - Dual solution is not checked if options.check_dual=false
404 // * For both the primal and dual rays, either expected and actual are both
405 // empty, or any ray in expected IsNear() any ray in actual (which is up
406 // to a rescaling) at options.tolerance.
407 // - Not checked if options.check_rays=false
408 // - If options.first_solution_only is false, check the entire list of
409 // solutions matches in the same order.
410 // * The basis is not checked by default. If enabled, checked with BasisIs().
411 // - Enable with options.check_basis
412 //
413 // This function is symmetric in that:
414 // EXPECT_THAT(actual, IsConsistentWith(expected));
415 // EXPECT_THAT(expected, IsConsistentWith(actual));
416 // agree on matching, they only differ in strings produced. Per gmock
417 // conventions, prefer the former.
418 //
419 // For problems with either primal or dual infeasibility, see
420 // SolveResultMatcherOptions::inf_or_unb_soft_match for guidance on how to
421 // best set the termination reason and inf_or_unb_soft_match.
422 testing::Matcher<SolveResult> IsConsistentWith(
423  const SolveResult& expected, const SolveResultMatcherOptions& options = {});
424 
426 // Rarely used
428 
429 // Actual UpdateResult.did_update is true.
430 testing::Matcher<UpdateResult> DidUpdate();
431 
433 // Implementation details
435 
436 // TODO(b/200835670): use the << operator on Termination instead once it
437 // supports quoting/escaping on termination.detail.
438 void PrintTo(const Termination& termination, std::ostream* os);
439 void PrintTo(const PrimalSolution& primal_solution, std::ostream* os);
440 void PrintTo(const DualSolution& dual_solution, std::ostream* os);
441 void PrintTo(const PrimalRay& primal_ray, std::ostream* os);
442 void PrintTo(const DualRay& dual_ray, std::ostream* os);
443 void PrintTo(const Basis& basis, std::ostream* os);
444 void PrintTo(const Solution& solution, std::ostream* os);
445 void PrintTo(const SolveResult& result, std::ostream* os);
446 
447 // We do not want to rely on ::testing::internal::ContainerPrinter because we
448 // want to sort the keys.
449 template <typename K, typename V>
450 void PrintTo(const IdMap<K, V>& id_map, std::ostream* const os) {
451  constexpr int kMaxPrint = 10;
452  int num_added = 0;
453  *os << "{";
454  for (const K k : id_map.SortedKeys()) {
455  if (num_added > 0) {
456  *os << ", ";
457  }
458  if (num_added >= kMaxPrint) {
459  *os << "...(size=" << id_map.size() << ")";
460  break;
461  }
462  *os << "{" << k << ", " << ::testing::PrintToString(id_map.at(k)) << "}";
463  ++num_added;
464  }
465  *os << "}";
466 }
467 
468 } // namespace math_opt
469 } // namespace operations_research
470 
471 #endif // OR_TOOLS_MATH_OPT_CPP_MATCHERS_H_
std::vector< K > SortedKeys() const
Definition: id_map.h:596
const V & at(const K &k) const
Definition: id_map.h:497
Matcher< SolveResult > HasDualSolution(DualSolution expected, const double tolerance)
Definition: matchers.cc:699
Matcher< SolveResult > HasSolution(PrimalSolution expected, const double tolerance)
Definition: matchers.cc:691
testing::Matcher< SolveResult > TerminatesWithReasonNoSolutionFound(const Limit expected, const bool allow_limit_undetermined)
Definition: matchers.cc:595
Matcher< SolveResult > IsOptimal(const std::optional< double > expected_objective, const double tolerance)
Definition: matchers.cc:647
void PrintTo(const Termination &termination, std::ostream *os)
Definition: matchers.cc:83
Matcher< Termination > ReasonIsOptimal()
Definition: matchers.cc:643
testing::Matcher< LinearExpression > IsIdentical(LinearExpression expected)
Definition: matchers.cc:257
constexpr double kMatcherDefaultTolerance
Definition: matchers.h:107
Matcher< SolveResult > IsOptimalWithSolution(const double expected_objective, const VariableMap< double > expected_variable_values, const double tolerance)
Definition: matchers.cc:663
Matcher< SolveResult > TerminatesWithOneOf(const std::vector< TerminationReason > &allowed)
Definition: matchers.cc:553
Matcher< SolveResult > IsConsistentWith(const SolveResult &expected, const SolveResultMatcherOptions &options)
Definition: matchers.cc:810
testing::Matcher< SolveResult > TerminatesWithReasonFeasible(const Limit expected, const bool allow_limit_undetermined)
Definition: matchers.cc:587
Matcher< Termination > ReasonIs(TerminationReason reason)
Definition: matchers.cc:639
testing::Matcher< LinearExpression > LinearExpressionIsNear(const LinearExpression expected, const double tolerance)
Definition: matchers.cc:261
Matcher< VariableMap< double > > IsNearlySubsetOf(VariableMap< double > expected, double tolerance)
Definition: matchers.cc:213
Matcher< UpdateResult > DidUpdate()
Definition: matchers.cc:838
testing::Matcher< SolveResult > TerminatesWithLimit(const Limit expected, const bool allow_limit_undetermined)
Definition: matchers.cc:578
Matcher< SolveResult > HasDualRay(DualRay expected, const double tolerance)
Definition: matchers.cc:719
Matcher< SolveResult > IsOptimalWithDualSolution(const double expected_objective, const LinearConstraintMap< double > expected_dual_values, const VariableMap< double > expected_reduced_costs, const double tolerance)
Definition: matchers.cc:676
testing::Matcher< BoundedLinearExpression > IsNearlyEquivalent(const BoundedLinearExpression &expected, const double tolerance)
Definition: matchers.cc:289
Matcher< SolveResult > TerminatesWith(const TerminationReason expected)
Definition: matchers.cc:559
Matcher< SolveResult > HasPrimalRay(PrimalRay expected, const double tolerance)
Definition: matchers.cc:707
Matcher< VariableMap< double > > IsNear(VariableMap< double > expected, const double tolerance)
Definition: matchers.cc:219
Matcher< Basis > BasisIs(const Basis &expected)
Definition: matchers.cc:401
Matcher< PrimalRay > PrimalRayIsNear(VariableMap< double > expected_var_values, const double tolerance)
Definition: matchers.cc:488
Collection of objects used to extend the Constraint Solver library.