OR-Tools  9.6
matchers.cc
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 
15 
16 #include <algorithm>
17 #include <cmath>
18 #include <cstdlib>
19 #include <optional>
20 #include <ostream>
21 #include <sstream>
22 #include <string>
23 #include <type_traits>
24 #include <utility>
25 #include <vector>
26 
27 #include "absl/strings/str_cat.h"
28 #include "absl/types/span.h"
29 #include "gmock/gmock.h"
30 #include "gtest/gtest.h"
31 #include "ortools/base/logging.h"
34 
35 namespace operations_research {
36 namespace math_opt {
37 
38 namespace {
39 
40 using ::testing::AllOf;
41 using ::testing::AllOfArray;
42 using ::testing::AnyOf;
43 using ::testing::AnyOfArray;
44 using ::testing::Contains;
45 using ::testing::DoubleNear;
46 using ::testing::Eq;
47 using ::testing::ExplainMatchResult;
48 using ::testing::Field;
49 using ::testing::IsEmpty;
50 using ::testing::Matcher;
51 using ::testing::MatcherInterface;
52 using ::testing::MatchResultListener;
53 using ::testing::Optional;
54 using ::testing::PrintToString;
55 using ::testing::Property;
56 } // namespace
57 
59 // Printing
61 
62 namespace {
63 
64 template <typename T>
65 struct Printer {
66  explicit Printer(const T& t) : value(t) {}
67 
68  const T& value;
69 
70  friend std::ostream& operator<<(std::ostream& os, const Printer& printer) {
71  os << PrintToString(printer.value);
72  return os;
73  }
74 };
75 
76 template <typename T>
77 Printer<T> Print(const T& t) {
78  return Printer<T>(t);
79 }
80 
81 } // namespace
82 
83 void PrintTo(const Termination& termination, std::ostream* os) {
84  *os << "{reason: " << termination.reason;
85  if (termination.limit.has_value()) {
86  *os << ", limit: " << *termination.limit;
87  }
88  *os << ", detail: " << Print(termination.detail) << "}";
89 }
90 
91 void PrintTo(const PrimalSolution& primal_solution, std::ostream* const os) {
92  *os << "{variable_values: " << Print(primal_solution.variable_values)
93  << ", objective_value: " << Print(primal_solution.objective_value)
94  << ", feasibility_status: " << Print(primal_solution.feasibility_status)
95  << "}";
96 }
97 
98 void PrintTo(const DualSolution& dual_solution, std::ostream* const os) {
99  *os << "{dual_values: " << Print(dual_solution.dual_values)
100  << ", reduced_costs: " << Print(dual_solution.reduced_costs)
101  << ", objective_value: " << Print(dual_solution.objective_value)
102  << ", feasibility_status: " << Print(dual_solution.feasibility_status)
103  << "}";
104 }
105 
106 void PrintTo(const PrimalRay& primal_ray, std::ostream* const os) {
107  *os << "{variable_values: " << Print(primal_ray.variable_values) << "}";
108 }
109 
110 void PrintTo(const DualRay& dual_ray, std::ostream* const os) {
111  *os << "{dual_values: " << Print(dual_ray.dual_values)
112  << ", reduced_costs: " << Print(dual_ray.reduced_costs) << "}";
113 }
114 
115 void PrintTo(const Basis& basis, std::ostream* const os) {
116  *os << "{variable_status: " << Print(basis.variable_status)
117  << ", constraint_status: " << Print(basis.constraint_status)
118  << ", basic_dual_feasibility: " << Print(basis.basic_dual_feasibility)
119  << "}";
120 }
121 
122 void PrintTo(const Solution& solution, std::ostream* const os) {
123  *os << "{primal_solution: " << Print(solution.primal_solution)
124  << ", dual_solution: " << Print(solution.dual_solution)
125  << ", basis: " << Print(solution.basis) << "}";
126 }
127 
128 void PrintTo(const SolveResult& result, std::ostream* const os) {
129  *os << "{termination: " << Print(result.termination)
130  << ", solve_stats: " << Print(result.solve_stats)
131  << ", solutions: " << Print(result.solutions)
132  << ", primal_rays: " << Print(result.primal_rays)
133  << ", dual_rays: " << Print(result.dual_rays) << "}";
134 }
135 
137 // IdMap Matchers
139 
140 namespace {
141 
142 template <typename K>
143 class IdMapMatcher : public MatcherInterface<IdMap<K, double>> {
144  public:
145  IdMapMatcher(IdMap<K, double> expected, const bool all_keys,
146  const double tolerance)
147  : expected_(std::move(expected)),
148  all_keys_(all_keys),
149  tolerance_(tolerance) {
150  for (const auto [k, v] : expected_) {
151  CHECK(!std::isnan(v)) << "Illegal NaN for key: " << k;
152  }
153  }
154 
155  bool MatchAndExplain(IdMap<K, double> actual,
156  MatchResultListener* const os) const override {
157  for (const auto& [key, value] : expected_) {
158  if (!actual.contains(key)) {
159  *os << "expected key " << key << " not found";
160  return false;
161  }
162  if (!(std::abs(value - actual.at(key)) <= tolerance_)) {
163  *os << "value for key " << key
164  << " not within tolerance, expected: " << value
165  << " but found: " << actual.at(key);
166  return false;
167  }
168  }
169  // Post condition: expected_ is a subset of actual.
170  if (all_keys_ && expected_.size() != actual.size()) {
171  for (const auto& [key, value] : actual) {
172  if (!expected_.contains(key)) {
173  *os << "found unexpected key " << key << " in actual";
174  return false;
175  }
176  }
177  // expected_ subset of actual && expected_.size() != actual.size() implies
178  // that there is a member A of actual not in expected. When the loop above
179  // hits A, it will return, thus this line is unreachable.
180  LOG(FATAL) << "unreachable";
181  }
182  return true;
183  }
184 
185  void DescribeTo(std::ostream* const os) const override {
186  if (all_keys_) {
187  *os << "has identical keys to ";
188  } else {
189  *os << "keys are contained in ";
190  }
191  PrintTo(expected_, os);
192  *os << " and values within " << tolerance_;
193  }
194 
195  void DescribeNegationTo(std::ostream* const os) const override {
196  if (all_keys_) {
197  *os << "either keys differ from ";
198  } else {
199  *os << "either has a key not in ";
200  }
201  PrintTo(expected_, os);
202  *os << " or a value differs by more than " << tolerance_;
203  }
204 
205  private:
206  const IdMap<K, double> expected_;
207  const bool all_keys_;
208  const double tolerance_;
209 };
210 
211 } // namespace
212 
213 Matcher<VariableMap<double>> IsNearlySubsetOf(VariableMap<double> expected,
214  double tolerance) {
215  return Matcher<VariableMap<double>>(new IdMapMatcher<Variable>(
216  std::move(expected), /*all_keys=*/false, tolerance));
217 }
218 
219 Matcher<VariableMap<double>> IsNear(VariableMap<double> expected,
220  const double tolerance) {
221  return Matcher<VariableMap<double>>(new IdMapMatcher<Variable>(
222  std::move(expected), /*all_keys=*/true, tolerance));
223 }
224 
225 Matcher<LinearConstraintMap<double>> IsNearlySubsetOf(
226  LinearConstraintMap<double> expected, double tolerance) {
227  return Matcher<LinearConstraintMap<double>>(
228  new IdMapMatcher<LinearConstraint>(std::move(expected),
229  /*all_keys=*/false, tolerance));
230 }
231 
232 Matcher<LinearConstraintMap<double>> IsNear(
233  LinearConstraintMap<double> expected, const double tolerance) {
234  return Matcher<LinearConstraintMap<double>>(
235  new IdMapMatcher<LinearConstraint>(std::move(expected), /*all_keys=*/true,
236  tolerance));
237 }
238 
239 template <typename K>
240 Matcher<IdMap<K, double>> IsNear(IdMap<K, double> expected,
241  const double tolerance) {
242  return Matcher<IdMap<K, double>>(
243  new IdMapMatcher<K>(std::move(expected), /*all_keys=*/true, tolerance));
244 }
245 
246 template <typename K>
247 Matcher<IdMap<K, double>> IsNearlySubsetOf(IdMap<K, double> expected,
248  const double tolerance) {
249  return Matcher<IdMap<K, double>>(
250  new IdMapMatcher<K>(std::move(expected), /*all_keys=*/false, tolerance));
251 }
252 
254 // Matchers for LinearExpression and QuadraticExpression
256 
257 testing::Matcher<LinearExpression> IsIdentical(LinearExpression expected) {
258  return LinearExpressionIsNear(expected, 0.0);
259 }
260 
261 testing::Matcher<LinearExpression> LinearExpressionIsNear(
262  const LinearExpression expected, const double tolerance) {
263  CHECK(!std::isnan(expected.offset())) << "Illegal NaN-valued offset";
264  return AllOf(
265  Property("storage", &LinearExpression::storage, Eq(expected.storage())),
266  Property("offset", &LinearExpression::offset,
267  testing::DoubleNear(expected.offset(), tolerance)),
268  Property("terms", &LinearExpression::terms,
269  IsNear(expected.terms(), tolerance)));
270 }
271 
272 namespace {
273 testing::Matcher<BoundedLinearExpression> IsNearForSign(
274  const BoundedLinearExpression& expected, const double tolerance) {
275  return AllOf(Property("upper_bound_minus_offset",
277  testing::DoubleNear(expected.upper_bound_minus_offset(),
278  tolerance)),
279  Property("lower_bound_minus_offset",
281  testing::DoubleNear(expected.lower_bound_minus_offset(),
282  tolerance)),
283  Field("expression", &BoundedLinearExpression::expression,
284  Property("terms", &LinearExpression::terms,
285  IsNear(expected.expression.terms(), tolerance))));
286 }
287 } // namespace
288 
289 testing::Matcher<BoundedLinearExpression> IsNearlyEquivalent(
290  const BoundedLinearExpression& expected, const double tolerance) {
291  const BoundedLinearExpression expected_negation(
292  -expected.expression, /*lower_bound=*/-expected.upper_bound,
293  /*upper_bound=*/-expected.lower_bound);
294  return AnyOf(IsNearForSign(expected, tolerance),
295  IsNearForSign(expected_negation, tolerance));
296 }
297 
298 testing::Matcher<QuadraticExpression> IsIdentical(
299  QuadraticExpression expected) {
300  CHECK(!std::isnan(expected.offset())) << "Illegal NaN-valued offset";
301  return AllOf(
302  Property("storage", &QuadraticExpression::storage,
303  Eq(expected.storage())),
304  Property("offset", &QuadraticExpression::offset,
305  testing::Eq(expected.offset())),
306  Property("linear_terms", &QuadraticExpression::linear_terms,
307  IsNear(expected.linear_terms(), /*tolerance=*/0)),
308  Property("quadratic_terms", &QuadraticExpression::quadratic_terms,
309  IsNear(expected.quadratic_terms(), /*tolerance=*/0)));
310 }
311 
313 // Matcher helpers
315 
316 namespace {
317 
318 template <typename RayType>
319 class RayMatcher : public MatcherInterface<RayType> {
320  public:
321  RayMatcher(RayType expected, const double tolerance)
322  : expected_(std::move(expected)), tolerance_(tolerance) {}
323  void DescribeTo(std::ostream* os) const final {
324  *os << "after L_inf normalization, is within tolerance: " << tolerance_
325  << " of expected: ";
326  PrintTo(expected_, os);
327  }
328  void DescribeNegationTo(std::ostream* const os) const final {
329  *os << "after L_inf normalization, is not within tolerance: " << tolerance_
330  << " of expected: ";
331  PrintTo(expected_, os);
332  }
333 
334  protected:
335  const RayType expected_;
336  const double tolerance_;
337 };
338 
339 // Alias to use the std::optional templated adaptor.
340 Matcher<double> IsNear(double expected, const double tolerance) {
341  return DoubleNear(expected, tolerance);
342 }
343 
344 template <typename Type>
345 Matcher<std::optional<Type>> IsNear(std::optional<Type> expected,
346  const double tolerance) {
347  if (expected.has_value()) {
348  return Optional(IsNear(*expected, tolerance));
349  }
350  return testing::Eq(std::nullopt);
351 }
352 
353 // Custom std::optional for basis.
354 Matcher<std::optional<Basis>> BasisIs(const std::optional<Basis>& expected) {
355  if (expected.has_value()) {
356  return Optional(BasisIs(*expected));
357  }
358  return testing::Eq(std::nullopt);
359 }
360 
361 testing::Matcher<std::vector<Solution>> IsNear(
362  const std::vector<Solution>& expected_solutions,
363  const SolutionMatcherOptions options) {
364  if (expected_solutions.empty()) {
365  return IsEmpty();
366  }
367  std::vector<Matcher<Solution>> matchers;
368  for (const Solution& sol : expected_solutions) {
369  matchers.push_back(IsNear(sol, options));
370  }
371  return ::testing::ElementsAreArray(matchers);
372 }
373 
374 } // namespace
375 
377 // Matchers for Solutions
379 
380 Matcher<PrimalSolution> IsNear(PrimalSolution expected,
381  const double tolerance) {
382  return AllOf(Field("variable_values", &PrimalSolution::variable_values,
383  IsNear(expected.variable_values, tolerance)),
384  Field("objective_value", &PrimalSolution::objective_value,
385  IsNear(expected.objective_value, tolerance)),
386  Field("feasibility_status", &PrimalSolution::feasibility_status,
387  expected.feasibility_status));
388 }
389 
390 Matcher<DualSolution> IsNear(DualSolution expected, const double tolerance) {
391  return AllOf(Field("dual_values", &DualSolution::dual_values,
392  IsNear(expected.dual_values, tolerance)),
393  Field("reduced_costs", &DualSolution::reduced_costs,
394  IsNear(expected.reduced_costs, tolerance)),
395  Field("objective_value", &DualSolution::objective_value,
396  IsNear(expected.objective_value, tolerance)),
397  Field("feasibility_status", &DualSolution::feasibility_status,
398  expected.feasibility_status));
399 }
400 
401 Matcher<Basis> BasisIs(const Basis& expected) {
402  return AllOf(Field("variable_status", &Basis::variable_status,
403  expected.variable_status),
404  Field("constraint_status", &Basis::constraint_status,
405  expected.constraint_status),
406  Field("basic_dual_feasibility", &Basis::basic_dual_feasibility,
407  expected.basic_dual_feasibility));
408 }
409 
410 Matcher<Solution> IsNear(Solution expected,
411  const SolutionMatcherOptions options) {
412  std::vector<Matcher<Solution>> to_check;
413  if (options.check_primal) {
414  to_check.push_back(
415  Field("primal_solution", &Solution::primal_solution,
416  IsNear(expected.primal_solution, options.tolerance)));
417  }
418  if (options.check_dual) {
419  to_check.push_back(
420  Field("dual_solution", &Solution::dual_solution,
421  IsNear(expected.dual_solution, options.tolerance)));
422  }
423  if (options.check_basis) {
424  to_check.push_back(
425  Field("basis", &Solution::basis, BasisIs(expected.basis)));
426  }
427  return AllOfArray(to_check);
428 }
429 
431 // Primal Ray Matcher
433 
434 namespace {
435 
436 template <typename K>
437 double InfinityNorm(const IdMap<K, double>& vector) {
438  double infinity_norm = 0.0;
439  for (auto [id, value] : vector) {
440  infinity_norm = std::max(infinity_norm, std::abs(value));
441  }
442  return infinity_norm;
443 }
444 
445 // Returns a normalized primal ray.
446 //
447 // The normalization is done using infinity norm:
448 //
449 // ray / ||ray||_inf
450 //
451 // If the input ray norm is zero, the ray is returned unchanged.
452 PrimalRay NormalizePrimalRay(PrimalRay ray) {
453  const double norm = InfinityNorm(ray.variable_values);
454  if (norm != 0.0) {
455  for (auto entry : ray.variable_values) {
456  entry.second /= norm;
457  }
458  }
459  return ray;
460 }
461 
462 class PrimalRayMatcher : public RayMatcher<PrimalRay> {
463  public:
464  PrimalRayMatcher(PrimalRay expected, const double tolerance)
465  : RayMatcher(std::move(expected), tolerance) {}
466 
467  bool MatchAndExplain(PrimalRay actual,
468  MatchResultListener* const os) const override {
469  auto normalized_actual = NormalizePrimalRay(actual);
470  auto normalized_expected = NormalizePrimalRay(expected_);
471  if (os->IsInterested()) {
472  *os << "actual normalized: " << PrintToString(normalized_actual)
473  << ", expected normalized: " << PrintToString(normalized_expected);
474  }
475  return ExplainMatchResult(
476  IsNear(normalized_expected.variable_values, tolerance_),
477  normalized_actual.variable_values, os);
478  }
479 };
480 
481 } // namespace
482 
483 Matcher<PrimalRay> IsNear(PrimalRay expected, const double tolerance) {
484  return Matcher<PrimalRay>(
485  new PrimalRayMatcher(std::move(expected), tolerance));
486 }
487 
488 Matcher<PrimalRay> PrimalRayIsNear(VariableMap<double> expected_var_values,
489  const double tolerance) {
490  PrimalRay expected;
491  expected.variable_values = std::move(expected_var_values);
492  return IsNear(expected, tolerance);
493 }
494 
496 // Dual Ray Matcher
498 
499 namespace {
500 
501 // Returns a normalized dual ray.
502 //
503 // The normalization is done using infinity norm:
504 //
505 // ray / ||ray||_inf
506 //
507 // If the input ray norm is zero, the ray is returned unchanged.
508 DualRay NormalizeDualRay(DualRay ray) {
509  const double norm =
510  std::max(InfinityNorm(ray.dual_values), InfinityNorm(ray.reduced_costs));
511  if (norm != 0.0) {
512  for (auto entry : ray.dual_values) {
513  entry.second /= norm;
514  }
515  for (auto entry : ray.reduced_costs) {
516  entry.second /= norm;
517  }
518  }
519  return ray;
520 }
521 
522 class DualRayMatcher : public RayMatcher<DualRay> {
523  public:
524  DualRayMatcher(DualRay expected, const double tolerance)
525  : RayMatcher(std::move(expected), tolerance) {}
526 
527  bool MatchAndExplain(DualRay actual, MatchResultListener* os) const override {
528  auto normalized_actual = NormalizeDualRay(actual);
529  auto normalized_expected = NormalizeDualRay(expected_);
530  if (os->IsInterested()) {
531  *os << "actual normalized: " << PrintToString(normalized_actual)
532  << ", expected normalized: " << PrintToString(normalized_expected);
533  }
534  return ExplainMatchResult(
535  IsNear(normalized_expected.dual_values, tolerance_),
536  normalized_actual.dual_values, os) &&
537  ExplainMatchResult(
538  IsNear(normalized_expected.reduced_costs, tolerance_),
539  normalized_actual.reduced_costs, os);
540  }
541 };
542 
543 } // namespace
544 
545 Matcher<DualRay> IsNear(DualRay expected, const double tolerance) {
546  return Matcher<DualRay>(new DualRayMatcher(std::move(expected), tolerance));
547 }
548 
550 // SolveResult termination reason matchers
552 
553 Matcher<SolveResult> TerminatesWithOneOf(
554  const std::vector<TerminationReason>& allowed) {
555  return Field("termination", &SolveResult::termination,
556  Field("reason", &Termination::reason, AnyOfArray(allowed)));
557 }
558 
559 Matcher<SolveResult> TerminatesWith(const TerminationReason expected) {
560  return Field("termination", &SolveResult::termination,
561  Field("reason", &Termination::reason, expected));
562 }
563 
564 namespace {
565 testing::Matcher<SolveResult> LimitIs(const Limit expected,
566  const bool allow_limit_undetermined) {
567  if (allow_limit_undetermined) {
568  return Field("termination", &SolveResult::termination,
569  Field("limit", &Termination::limit,
570  AnyOf(Limit::kUndetermined, expected)));
571  }
572  return Field("termination", &SolveResult::termination,
573  Field("limit", &Termination::limit, expected));
574 }
575 
576 } // namespace
577 
578 testing::Matcher<SolveResult> TerminatesWithLimit(
579  const Limit expected, const bool allow_limit_undetermined) {
580  std::vector<Matcher<SolveResult>> matchers;
581  matchers.push_back(LimitIs(expected, allow_limit_undetermined));
582  matchers.push_back(TerminatesWithOneOf(
584  return ::testing::AllOfArray(matchers);
585 }
586 
587 testing::Matcher<SolveResult> TerminatesWithReasonFeasible(
588  const Limit expected, const bool allow_limit_undetermined) {
589  std::vector<Matcher<SolveResult>> matchers;
590  matchers.push_back(LimitIs(expected, allow_limit_undetermined));
591  matchers.push_back(TerminatesWith(TerminationReason::kFeasible));
592  return ::testing::AllOfArray(matchers);
593 }
594 
595 testing::Matcher<SolveResult> TerminatesWithReasonNoSolutionFound(
596  const Limit expected, const bool allow_limit_undetermined) {
597  std::vector<Matcher<SolveResult>> matchers;
598  matchers.push_back(LimitIs(expected, allow_limit_undetermined));
600  return ::testing::AllOfArray(matchers);
601 }
602 
603 template <typename MatcherType>
604 std::string MatcherToStringImpl(const MatcherType& matcher, const bool negate) {
605  std::ostringstream os;
606  if (negate) {
607  matcher.DescribeNegationTo(&os);
608  } else {
609  matcher.DescribeTo(&os);
610  }
611  return os.str();
612 }
613 
614 template <typename T>
615 std::string MatcherToString(const Matcher<T>& matcher, bool negate) {
616  return MatcherToStringImpl(matcher, negate);
617 }
618 
619 // clang-format off
620 // Polymorphic matchers do not always define DescribeTo,
621 // The <T> type may not be a matcher, but it will implement DescribeTo.
622 // clang-format on
623 template <typename T>
624 std::string MatcherToString(const ::testing::PolymorphicMatcher<T>& matcher,
625  bool negate) {
626  return MatcherToStringImpl(matcher.impl(), negate);
627 }
628 
629 MATCHER_P(FirstElementIs, first_element_matcher,
630  (negation
631  ? absl::StrCat("is empty or first element ",
632  MatcherToString(first_element_matcher, true))
633  : absl::StrCat("has at least one element and first element ",
634  MatcherToString(first_element_matcher, false)))) {
635  return ExplainMatchResult(UnorderedElementsAre(first_element_matcher),
636  absl::MakeSpan(arg).subspan(0, 1), result_listener);
637 }
638 
639 Matcher<Termination> ReasonIs(TerminationReason reason) {
640  return Field("reason", &Termination::reason, reason);
641 }
642 
643 Matcher<Termination> ReasonIsOptimal() {
645 }
646 
647 Matcher<SolveResult> IsOptimal(const std::optional<double> expected_objective,
648  const double tolerance) {
649  std::vector<Matcher<SolveResult>> matchers;
650  matchers.push_back(
651  Field("termination", &SolveResult::termination, ReasonIsOptimal()));
652  if (expected_objective.has_value()) {
653  matchers.push_back(Field(
654  "solutions", &SolveResult::solutions,
655  FirstElementIs(Field(
656  "primal_solution", &Solution::primal_solution,
657  Optional(Field("objective_value", &PrimalSolution::objective_value,
658  IsNear(*expected_objective, tolerance)))))));
659  }
660  return ::testing::AllOfArray(matchers);
661 }
662 
663 Matcher<SolveResult> IsOptimalWithSolution(
664  const double expected_objective,
665  const VariableMap<double> expected_variable_values,
666  const double tolerance) {
667  return AllOf(
668  IsOptimal(std::make_optional(expected_objective), tolerance),
669  HasSolution(
670  PrimalSolution{.variable_values = expected_variable_values,
671  .objective_value = expected_objective,
672  .feasibility_status = SolutionStatus::kFeasible},
673  tolerance));
674 }
675 
676 Matcher<SolveResult> IsOptimalWithDualSolution(
677  const double expected_objective,
678  const LinearConstraintMap<double> expected_dual_values,
679  const VariableMap<double> expected_reduced_costs, const double tolerance) {
680  return AllOf(
681  IsOptimal(std::make_optional(expected_objective), tolerance),
683  DualSolution{
684  .dual_values = expected_dual_values,
685  .reduced_costs = expected_reduced_costs,
686  .objective_value = std::make_optional(expected_objective),
687  .feasibility_status = SolutionStatus::kFeasible},
688  tolerance));
689 }
690 
691 Matcher<SolveResult> HasSolution(PrimalSolution expected,
692  const double tolerance) {
693  return ::testing::Field(
694  "solutions", &SolveResult::solutions,
695  Contains(Field("primal_solution", &Solution::primal_solution,
696  Optional(IsNear(std::move(expected), tolerance)))));
697 }
698 
699 Matcher<SolveResult> HasDualSolution(DualSolution expected,
700  const double tolerance) {
701  return ::testing::Field(
702  "solutions", &SolveResult::solutions,
703  Contains(Field("dual_solution", &Solution::dual_solution,
704  Optional(IsNear(std::move(expected), tolerance)))));
705 }
706 
707 Matcher<SolveResult> HasPrimalRay(PrimalRay expected, const double tolerance) {
708  return ::testing::Field("primal_rays", &SolveResult::primal_rays,
709  Contains(IsNear(std::move(expected), tolerance)));
710 }
711 
712 Matcher<SolveResult> HasPrimalRay(VariableMap<double> expected_vars,
713  const double tolerance) {
714  PrimalRay ray;
715  ray.variable_values = std::move(expected_vars);
716  return HasPrimalRay(std::move(ray), tolerance);
717 }
718 
719 Matcher<SolveResult> HasDualRay(DualRay expected, const double tolerance) {
720  return ::testing::Field("dual_rays", &SolveResult::dual_rays,
721  Contains(IsNear(std::move(expected), tolerance)));
722 }
723 
724 namespace {
725 
726 bool MightTerminateWithRays(const TerminationReason reason) {
727  switch (reason) {
731  return true;
732  default:
733  return false;
734  }
735 }
736 
737 std::vector<TerminationReason> CompatibleReasons(
738  const TerminationReason expected, const bool inf_or_unb_soft_match) {
739  if (!inf_or_unb_soft_match) {
740  return {expected};
741  }
742  switch (expected) {
752  default:
753  return {expected};
754  }
755 }
756 
757 Matcher<std::vector<Solution>> CheckSolutions(
758  const std::vector<Solution>& expected_solutions,
759  const SolveResultMatcherOptions& options) {
760  if (options.first_solution_only && !expected_solutions.empty()) {
761  return FirstElementIs(
762  IsNear(expected_solutions[0],
763  SolutionMatcherOptions{.tolerance = options.tolerance,
764  .check_primal = true,
765  .check_dual = options.check_dual,
766  .check_basis = options.check_basis}));
767  }
768  return IsNear(expected_solutions,
769  SolutionMatcherOptions{.tolerance = options.tolerance,
770  .check_primal = true,
771  .check_dual = options.check_dual,
772  .check_basis = options.check_basis});
773 }
774 
775 template <typename RayType>
776 Matcher<std::vector<RayType>> AnyRayNear(
777  const std::vector<RayType>& expected_rays, const double tolerance) {
778  std::vector<Matcher<RayType>> matchers;
779  for (const RayType& ray : expected_rays) {
780  matchers.push_back(IsNear(ray, tolerance));
781  }
782  return ::testing::Contains(::testing::AnyOfArray(matchers));
783 }
784 
785 template <typename RayType>
786 Matcher<std::vector<RayType>> AllRaysNear(
787  const std::vector<RayType>& expected_rays, const double tolerance) {
788  std::vector<Matcher<RayType>> matchers;
789  for (const RayType& ray : expected_rays) {
790  matchers.push_back(IsNear(ray, tolerance));
791  }
792  return ::testing::UnorderedElementsAreArray(matchers);
793 }
794 
795 template <typename RayType>
796 Matcher<std::vector<RayType>> CheckRays(
797  const std::vector<RayType>& expected_rays, const double tolerance,
798  bool check_all) {
799  if (expected_rays.empty()) {
800  return ::testing::IsEmpty();
801  }
802  if (check_all) {
803  return AllRaysNear(expected_rays, tolerance);
804  }
805  return AnyRayNear(expected_rays, tolerance);
806 }
807 
808 } // namespace
809 
810 Matcher<SolveResult> IsConsistentWith(
811  const SolveResult& expected, const SolveResultMatcherOptions& options) {
812  std::vector<Matcher<SolveResult>> to_check;
813  to_check.push_back(TerminatesWithOneOf(CompatibleReasons(
814  expected.termination.reason, options.inf_or_unb_soft_match)));
815  const bool skip_solution =
816  MightTerminateWithRays(expected.termination.reason) &&
818  if (!skip_solution) {
819  to_check.push_back(Field("solutions", &SolveResult::solutions,
820  CheckSolutions(expected.solutions, options)));
821  }
822  if (options.check_rays) {
823  to_check.push_back(Field("primal_rays", &SolveResult::primal_rays,
824  CheckRays(expected.primal_rays, options.tolerance,
825  !options.first_solution_only)));
826  to_check.push_back(Field("dual_rays", &SolveResult::dual_rays,
827  CheckRays(expected.dual_rays, options.tolerance,
828  !options.first_solution_only)));
829  }
830 
831  return AllOfArray(to_check);
832 }
833 
835 // Rarely used
837 
838 Matcher<UpdateResult> DidUpdate() {
839  return ::testing::Field("did_update", &UpdateResult::did_update,
840  ::testing::IsTrue());
841 }
842 
843 } // namespace math_opt
844 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
const QuadraticTermMap< double > & quadratic_terms() const
const T & value
Definition: matchers.cc:68
Fractional InfinityNorm(const DenseColumn &v)
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
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
std::string MatcherToString(const Matcher< T > &matcher, bool negate)
Definition: matchers.cc:615
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
std::string MatcherToStringImpl(const MatcherType &matcher, const bool negate)
Definition: matchers.cc:604
MATCHER_P(SparseVectorMatcher, pairs, "")
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
std::ostream & operator<<(std::ostream &ostr, const IndicatorConstraint &constraint)
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.
VariableMap< BasisStatus > variable_status
Definition: solution.h:232
LinearConstraintMap< BasisStatus > constraint_status
Definition: solution.h:231
LinearConstraintMap< double > dual_values
Definition: solution.h:181
VariableMap< double > reduced_costs
Definition: solution.h:182
LinearConstraintMap< double > dual_values
Definition: solution.h:142
std::optional< double > objective_value
Definition: solution.h:144
VariableMap< double > variable_values
Definition: solution.h:112
std::optional< DualSolution > dual_solution
Definition: solution.h:260
std::optional< PrimalSolution > primal_solution
Definition: solution.h:259
std::optional< Basis > basis
Definition: solution.h:261