OR-Tools  9.2
matchers.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// 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], IsNear({{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 <sstream>
95#include <vector>
96
97#include "gtest/gtest.h"
101
102namespace operations_research {
103namespace math_opt {
104
105constexpr double kMatcherDefaultTolerance = 1e-5;
106
108// Matchers for IdMap<Variable,double> and IdMap<LinearConstraint, double>
110
111// Checks that the maps have identical keys and values within tolerance.
112testing::Matcher<VariableMap<double>> IsNear(
113 VariableMap<double> expected, double tolerance = kMatcherDefaultTolerance);
114
115// Checks that the keys of actual are a subset of the keys of expected, and that
116// for all shared keys, the values are within tolerance.
117testing::Matcher<VariableMap<double>> IsNearlySubsetOf(
118 VariableMap<double> expected, double tolerance = kMatcherDefaultTolerance);
119
120// Checks that the maps have identical keys and values within tolerance.
121testing::Matcher<LinearConstraintMap<double>> IsNear(
123 double tolerance = kMatcherDefaultTolerance);
124
125// Checks that the keys of actual are a subset of the keys of expected, and that
126// for all shared keys, the values are within tolerance.
127testing::Matcher<LinearConstraintMap<double>> IsNearlySubsetOf(
129 double tolerance = kMatcherDefaultTolerance);
130
132// Matchers for solutions
134
135// Options for IsNear(Solution).
138 bool check_primal = true;
139 bool check_dual = true;
140 bool check_basis = true;
141};
142
143testing::Matcher<Solution> IsNear(Solution expected,
144 SolutionMatcherOptions options = {});
145
146// Checks variables match and variable/objective values are within tolerance and
147// feasibility statuses are identical.
148testing::Matcher<PrimalSolution> IsNear(
149 PrimalSolution expected, double tolerance = kMatcherDefaultTolerance);
150
151// Checks dual variables, reduced costs and objective are within tolerance and
152// feasibility statuses are identical.
153testing::Matcher<DualSolution> IsNear(
154 DualSolution expected, double tolerance = kMatcherDefaultTolerance);
155
156testing::Matcher<Basis> BasisIs(const Basis& expected);
157
159// Matchers for a Rays
161
162// Checks variables match and that after rescaling, variable values are within
163// tolerance.
164testing::Matcher<PrimalRay> IsNear(PrimalRay expected,
165 double tolerance = kMatcherDefaultTolerance);
166
167// Checks variables match and that after rescaling, variable values are within
168// tolerance.
169testing::Matcher<PrimalRay> PrimalRayIsNear(
170 VariableMap<double> expected_var_values,
171 double tolerance = kMatcherDefaultTolerance);
172
173// Checks that dual variables and reduced costs are defined for the same
174// set of Variables/LinearConstraints, and that their rescaled values are within
175// tolerance.
176testing::Matcher<DualRay> IsNear(DualRay expected,
177 double tolerance = kMatcherDefaultTolerance);
178
180// Matchers for a SolveResult
182
183// Checks the following:
184// * The termination reason is optimal.
185// * If expected_objective contains a value, there is at least one feasible
186// solution and that solution has an objective value within tolerance of
187// expected_objective.
188// * If check_warnings, the result has no warnings.
189testing::Matcher<SolveResult> IsOptimal(
190 std::optional<double> expected_objective = std::nullopt,
191 bool check_warnings = true, double tolerance = kMatcherDefaultTolerance);
192
193testing::Matcher<SolveResult> IsOptimalWithSolution(
194 double expected_objective, VariableMap<double> expected_variable_values,
195 bool check_warnings = true, double tolerance = kMatcherDefaultTolerance);
196
197testing::Matcher<SolveResult> IsOptimalWithDualSolution(
198 double expected_objective, LinearConstraintMap<double> expected_dual_values,
199 VariableMap<double> expected_reduced_costs, bool check_warnings = true,
200 double tolerance = kMatcherDefaultTolerance);
201
202// Checks the following:
203// * The result has the expected termination reason.
204// * If check_warnings, the result has no warnings.
205testing::Matcher<SolveResult> TerminatesWith(TerminationReason expected,
206 bool check_warnings = true);
207
208// Checks the following:
209// * The result has one of the allowed termination reasons.
210// * If check_warnings, the result has no warnings.
211testing::Matcher<SolveResult> TerminatesWithOneOf(
212 const std::vector<TerminationReason>& allowed, bool check_warnings = true);
213
214// Checks the following:
215// * The result has termination reason kFeasible or kNoSolutionFound.
216// * The limit is expected, or is kUndetermined if allow_limit_undetermined.
217// * If check_warnings, the result has no warnings.
218testing::Matcher<SolveResult> TerminatesWithLimit(
219 Limit expected, bool allow_limit_undetermined = false,
220 bool check_warnings = true);
221
222// Checks the following:
223// * The result has termination reason kFeasible.
224// * The limit is expected, or is kUndetermined if allow_limit_undetermined.
225// * If check_warnings, the result has no warnings.
226testing::Matcher<SolveResult> TerminatesWithReasonFeasible(
227 Limit expected, bool allow_limit_undetermined = false,
228 bool check_warnings = true);
229
230// Checks the following:
231// * The result has termination reason kNoSolutionFound.
232// * The limit is expected, or is kUndetermined if allow_limit_undetermined.
233// * If check_warnings, the result has no warnings.
234testing::Matcher<SolveResult> TerminatesWithReasonNoSolutionFound(
235 Limit expected, bool allow_limit_undetermined = false,
236 bool check_warnings = true);
237
238// SolveResult has a primal solution matching expected within tolerance.
239testing::Matcher<SolveResult> HasSolution(
240 PrimalSolution expected, double tolerance = kMatcherDefaultTolerance);
241
242// SolveResult has a dual solution matching expected within
243// tolerance.
244testing::Matcher<SolveResult> HasDualSolution(
245 DualSolution expected, double tolerance = kMatcherDefaultTolerance);
246
247// Actual SolveResult contains a primal ray that matches expected within
248// tolerance.
249testing::Matcher<SolveResult> HasPrimalRay(
250 PrimalRay expected, double tolerance = kMatcherDefaultTolerance);
251
252// Actual SolveResult contains a primal ray with variable values equivalent to
253// (under L_inf scaling) expected_vars up to tolerance.
254testing::Matcher<SolveResult> HasPrimalRay(
255 VariableMap<double> expected_vars,
256 double tolerance = kMatcherDefaultTolerance);
257
258// Actual SolveResult contains a dual ray that matches expected within
259// tolerance.
260testing::Matcher<SolveResult> HasDualRay(
261 DualRay expected, double tolerance = kMatcherDefaultTolerance);
262
263// Configures SolveResult matcher IsConsistentWith() below.
265 bool check_warnings = true;
266 double tolerance = 1e-5;
268 bool check_dual = true;
269 bool check_rays = true;
270
271 // If the expected result has termination reason kInfeasible, kUnbounded, or
272 // kDualInfeasasible, the primal solution, dual solution, and basis are
273 // ignored unless check_solutions_if_inf_or_unbounded is true.
274 //
275 // TODO(b/201099290): this is perhaps not a good default. Gurobi as
276 // implemented is returning primal solutions for both unbounded and
277 // infeasible problems. We need to add unit tests that inspect this value
278 // and turn them on one solver at a time with a new parameter on
279 // SimpleLpTestParameters.
281 bool check_basis = false;
282
283 // In linear programming, the following outcomes are all possible
284 //
285 // Primal LP | Dual LP | Possible MathOpt Termination Reasons
286 // -----------------------------------------------------------------
287 // 1. Infeasible | Unbounded | kInfeasible
288 // 2. Optimal | Optimal | kOptimal
289 // 3. Unbounded | Infeasible | kUnbounded, kInfeasibleOrUnbounded
290 // 4. Infeasible | Infeasible | kInfeasible, kInfeasibleOrUnbounded
291 //
292 // (Above "Optimal" means that an optimal solution exists. This is a statement
293 // about the existence of optimal solutions and certificates of
294 // infeasibility/unboundedness, not about the outcome of applying any
295 // particular algorithm.)
296 //
297 // When writing your unit test, you can typically tell which case of 1-4 you
298 // are in, but in cases 3-4 you do not know which termination reason will be
299 // returned. In some situations, it may not be clear if you are in case 1 or
300 // case 4 as well.
301 //
302 // When inf_or_unb_soft_match=false, the matcher must exactly specify the
303 // status returned by the solver. For cases 3-4, this is implementation
304 // dependent and we do not recommend this. When
305 // inf_or_unb_soft_match=true:
306 // * kInfeasible can also match kInfeasibleOrUnbounded
307 // * kUnbounded can also match kInfeasibleOrUnbounded
308 // * kInfeasibleOrUnbounded can also match kInfeasible and kUnbounded.
309 // For case 2, inf_or_unb_soft_match has no effect.
310 //
311 // To build the strongest possible matcher (accepting the minimal set of
312 // termination reasons):
313 // * If you know you are in case 1, se inf_or_unb_soft_match=false
314 // (soft_match=true over-matches)
315 // * For case 3, use inf_or_unb_soft_match=false and
316 // termination_reason=kUnbounded (kInfeasibleOrUnbounded over-matches).
317 // * For case 4 (or if you are unsure of case 1 vs case 4), use
318 // inf_or_unb_soft_match=true and
319 // termination_reason=kInfeasible (kInfeasibleOrUnbounded over-matches).
320 // * If you cannot tell if you are in case 3 or case 4, use
321 // inf_or_unb_soft_match=true and termination reason
322 // kInfeasibleOrUnbounded.
323 //
324 // If the above is too complicated, always setting
325 // inf_or_unb_soft_match=true and using any of the expected MathOpt
326 // termination reasons from the above table will give a matcher that is
327 // slightly too lenient.
329};
330
331// Tests that two SolveResults are equivalent. Basic use:
332//
333// SolveResult expected;
334// // Fill in expected...
335// ASSERT_OK_AND_ASSIGN(SolveResult actual, Solve(model, solver_type));
336// EXPECT_THAT(actual, IsConsistentWith(expected));
337//
338// Equivalence is defined as follows:
339// * The warnings are the same (in any order).
340// - Disabled if options.check_warnings=false.
341// * The termination reasons are the same.
342// - For infeasible and unbounded problems, see
343// options.inf_or_unb_soft_match.
344// * The solve stats are ignored.
345// * For both primal and dual solutions, either expected and actual are
346// both empty, or their first entries satisfy IsNear() at options.tolerance.
347// - Not checked if options.check_solutions_if_inf_or_unbounded and the
348// problem is infeasible or unbounded (default).
349// - If options.first_solution_only is false, check the entire list of
350// solutions matches in the same order.
351// - Dual solution is not checked if options.check_dual=false
352// * For both the primal and dual rays, either expected and actual are both
353// empty, or any ray in expected IsNear() any ray in actual (which is up
354// to a rescaling) at options.tolerance.
355// - Not checked if options.check_rays=false
356// - If options.first_solution_only is false, check the entire list of
357// solutions matches in the same order.
358// * The basis is not checked by default. If enabled, checked with BasisIs().
359// - Enable with options.check_basis
360//
361// This function is symmetric in that:
362// EXPECT_THAT(actual, IsConsistentWith(expected));
363// EXPECT_THAT(expected, IsConsistentWith(actual));
364// agree on matching, they only differ in strings produced. Per gmock
365// conventions, prefer the former.
366//
367// For problems with either primal or dual infeasibility, see
368// SolveResultMatcherOptions::inf_or_unb_soft_match for guidance on how to
369// best set the termination reason and inf_or_unb_soft_match.
370testing::Matcher<SolveResult> IsConsistentWith(
371 const SolveResult& expected, const SolveResultMatcherOptions& options = {});
372
374// Rarely used
376
377// Actual UpdateResult.did_update is true.
378testing::Matcher<IncrementalSolver::UpdateResult> DidUpdate();
379
381// Implementation details
383
384// TODO(b/200835670): use the << operator on Termination instead once it
385// supports quoting/escaping on termination.detail.
386void PrintTo(const Termination& termination, std::ostream* os);
387void PrintTo(const PrimalSolution& primal_solution, std::ostream* os);
388void PrintTo(const DualSolution& dual_solution, std::ostream* os);
389void PrintTo(const PrimalRay& primal_ray, std::ostream* os);
390void PrintTo(const DualRay& dual_ray, std::ostream* os);
391void PrintTo(const Basis& basis, std::ostream* os);
392void PrintTo(const Solution& solution, std::ostream* os);
393void PrintTo(const SolveResult& result, std::ostream* os);
394
395// We do not want to rely on ::testing::internal::ContainerPrinter because we
396// want to sort the keys.
397template <typename K, typename V>
398void PrintTo(const IdMap<K, V>& id_map, std::ostream* const os) {
399 constexpr int kMaxPrint = 10;
400 int num_added = 0;
401 *os << "{";
402 for (const K k : id_map.SortedKeys()) {
403 if (num_added > 0) {
404 *os << ", ";
405 }
406 if (num_added >= kMaxPrint) {
407 *os << "...(size=" << id_map.size() << ")";
408 break;
409 }
410 *os << "{" << k << ", " << ::testing::PrintToString(id_map.at(k)) << "}";
411 ++num_added;
412 }
413 *os << "}";
414}
415
416} // namespace math_opt
417} // namespace operations_research
418
419#endif // OR_TOOLS_MATH_OPT_CPP_MATCHERS_H_
std::vector< K > SortedKeys() const
Definition: id_map.h:579
const V & at(const K &k) const
Definition: id_map.h:480
Matcher< SolveResult > HasDualSolution(DualSolution expected, const double tolerance)
Definition: matchers.cc:638
Matcher< SolveResult > HasSolution(PrimalSolution expected, const double tolerance)
Definition: matchers.cc:630
void PrintTo(const Termination &termination, std::ostream *os)
Definition: matchers.cc:79
testing::Matcher< SolveResult > TerminatesWithReasonNoSolutionFound(const Limit expected, const bool allow_limit_undetermined, const bool check_warnings)
Definition: matchers.cc:534
constexpr double kMatcherDefaultTolerance
Definition: matchers.h:105
Matcher< IncrementalSolver::UpdateResult > DidUpdate()
Definition: matchers.cc:784
testing::Matcher< SolveResult > TerminatesWithReasonFeasible(const Limit expected, const bool allow_limit_undetermined, const bool check_warnings)
Definition: matchers.cc:524
Matcher< SolveResult > IsConsistentWith(const SolveResult &expected, const SolveResultMatcherOptions &options)
Definition: matchers.cc:749
Matcher< SolveResult > IsOptimal(const std::optional< double > expected_objective, const bool check_warnings, const double tolerance)
Definition: matchers.cc:578
Matcher< VariableMap< double > > IsNearlySubsetOf(VariableMap< double > expected, double tolerance)
Definition: matchers.cc:210
Matcher< SolveResult > HasDualRay(DualRay expected, const double tolerance)
Definition: matchers.cc:658
Matcher< SolveResult > IsOptimalWithSolution(const double expected_objective, const VariableMap< double > expected_variable_values, const bool check_warnings, const double tolerance)
Definition: matchers.cc:599
Matcher< SolveResult > TerminatesWith(const TerminationReason expected, const bool check_warnings)
Definition: matchers.cc:489
Matcher< SolveResult > HasPrimalRay(PrimalRay expected, const double tolerance)
Definition: matchers.cc:646
testing::Matcher< SolveResult > TerminatesWithLimit(const Limit expected, const bool allow_limit_undetermined, const bool check_warnings)
Definition: matchers.cc:513
Matcher< VariableMap< double > > IsNear(VariableMap< double > expected, const double tolerance)
Definition: matchers.cc:216
Matcher< Basis > BasisIs(const Basis &expected)
Definition: matchers.cc:325
Matcher< PrimalRay > PrimalRayIsNear(VariableMap< double > expected_var_values, const double tolerance)
Definition: matchers.cc:412
Matcher< SolveResult > IsOptimalWithDualSolution(const double expected_objective, const LinearConstraintMap< double > expected_dual_values, const VariableMap< double > expected_reduced_costs, const bool check_warnings, const double tolerance)
Definition: matchers.cc:613
Matcher< SolveResult > TerminatesWithOneOf(const std::vector< TerminationReason > &allowed, const bool check_warnings)
Definition: matchers.cc:477
Collection of objects used to extend the Constraint Solver library.