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