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