26#include "gmock/gmock.h"
27#include "gtest/gtest.h"
28#include "absl/strings/str_cat.h"
29#include "absl/types/span.h"
37using ::testing::AllOf;
38using ::testing::AllOfArray;
39using ::testing::AnyOf;
40using ::testing::AnyOfArray;
41using ::testing::Contains;
42using ::testing::DoubleNear;
44using ::testing::ExplainMatchResult;
45using ::testing::Field;
46using ::testing::IsEmpty;
47using ::testing::Matcher;
48using ::testing::MatcherInterface;
49using ::testing::MatchResultListener;
50using ::testing::Optional;
51using ::testing::PrintToString;
62 explicit Printer(
const T& t) :
value(t) {}
66 friend std::ostream&
operator<<(std::ostream& os,
const Printer& printer) {
67 os << PrintToString(printer.value);
73Printer<T> Print(
const T& t) {
80 *os <<
"{reason: " << termination.
reason;
81 if (termination.
limit.has_value()) {
82 *os <<
", limit: " << *termination.
limit;
84 *os <<
", detail: " << Print(termination.
detail) <<
"}";
95 *os <<
"{dual_values: " << Print(dual_solution.
dual_values)
103 *os <<
"{variable_values: " << Print(primal_ray.
variable_values) <<
"}";
107 *os <<
"{dual_values: " << Print(dual_ray.
dual_values)
108 <<
", reduced_costs: " << Print(dual_ray.
reduced_costs) <<
"}";
121 <<
", basis: " << Print(solution.
basis) <<
"}";
125 *os <<
"{termination: " << Print(result.
termination)
126 <<
", warnings: " << Print(result.
warnings)
128 <<
", solutions: " << Print(result.
solutions)
130 <<
", dual_rays: " << Print(result.
dual_rays) <<
"}";
140class IdMapMatcher :
public MatcherInterface<IdMap<K, double>> {
142 IdMapMatcher(IdMap<K, double> expected,
const bool all_keys,
143 const double tolerance)
144 : expected_(
std::move(expected)),
146 tolerance_(tolerance) {
147 for (
const auto [k, v] : expected_) {
148 CHECK(!std::isnan(v)) <<
"Illegal NaN for key: " << k;
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";
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);
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";
182 void DescribeTo(std::ostream*
const os)
const override {
184 *os <<
"has identical keys to ";
186 *os <<
"keys are contained in ";
189 *os <<
" and values within " << tolerance_;
192 void DescribeNegationTo(std::ostream*
const os)
const override {
194 *os <<
"either keys differ from ";
196 *os <<
"either has a key not in ";
199 *os <<
" or a value differs by more than " << tolerance_;
203 const IdMap<K, double> expected_;
204 const bool all_keys_;
205 const double tolerance_;
212 return Matcher<VariableMap<double>>(
new IdMapMatcher<Variable>(
213 std::move(expected),
false, tolerance));
217 const double tolerance) {
218 return Matcher<VariableMap<double>>(
new IdMapMatcher<Variable>(
219 std::move(expected),
true, tolerance));
224 return Matcher<LinearConstraintMap<double>>(
225 new IdMapMatcher<LinearConstraint>(std::move(expected),
229Matcher<LinearConstraintMap<double>>
IsNear(
231 return Matcher<LinearConstraintMap<double>>(
232 new IdMapMatcher<LinearConstraint>(std::move(expected),
true,
242template <
typename RayType>
243class RayMatcher :
public MatcherInterface<RayType> {
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_
252 void DescribeNegationTo(std::ostream*
const os)
const final {
253 *os <<
"after L_inf normalization, is not within tolerance: " << tolerance_
259 const RayType expected_;
260 const double tolerance_;
264Matcher<double>
IsNear(
double expected,
const double tolerance) {
265 return DoubleNear(expected, tolerance);
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));
274 return testing::Eq(std::nullopt);
278Matcher<std::optional<Basis>>
BasisIs(
const std::optional<Basis>& expected) {
279 if (expected.has_value()) {
280 return Optional(
BasisIs(*expected));
282 return testing::Eq(std::nullopt);
285testing::Matcher<std::vector<Solution>>
IsNear(
286 const std::vector<Solution>& expected_solutions,
287 const SolutionMatcherOptions options) {
288 if (expected_solutions.empty()) {
291 std::vector<Matcher<Solution>> matchers;
292 for (
const Solution& sol : expected_solutions) {
293 matchers.push_back(
IsNear(sol, options));
295 return ::testing::ElementsAreArray(matchers);
305 const double tolerance) {
336 std::vector<Matcher<Solution>> to_check;
351 return AllOfArray(to_check);
362 double infinity_norm = 0.0;
363 for (
auto [
id,
value] : vector) {
366 return infinity_norm;
376PrimalRay NormalizePrimalRay(PrimalRay ray) {
379 for (
auto entry : ray.variable_values) {
380 entry.second /= norm;
386class PrimalRayMatcher :
public RayMatcher<PrimalRay> {
388 PrimalRayMatcher(PrimalRay expected,
const double tolerance)
389 : RayMatcher(
std::move(expected), tolerance) {}
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);
399 return ExplainMatchResult(
400 IsNear(normalized_expected.variable_values, tolerance_),
401 normalized_actual.variable_values, os);
408 return Matcher<PrimalRay>(
409 new PrimalRayMatcher(std::move(expected), tolerance));
413 const double tolerance) {
416 return IsNear(expected, tolerance);
432DualRay NormalizeDualRay(DualRay ray) {
436 for (
auto entry : ray.dual_values) {
437 entry.second /= norm;
439 for (
auto entry : ray.reduced_costs) {
440 entry.second /= norm;
446class DualRayMatcher :
public RayMatcher<DualRay> {
448 DualRayMatcher(DualRay expected,
const double tolerance)
449 : RayMatcher(
std::move(expected), tolerance) {}
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);
458 return ExplainMatchResult(
459 IsNear(normalized_expected.dual_values, tolerance_),
460 normalized_actual.dual_values, os) &&
462 IsNear(normalized_expected.reduced_costs, tolerance_),
463 normalized_actual.reduced_costs, os);
470 return Matcher<DualRay>(
new DualRayMatcher(std::move(expected), tolerance));
478 const std::vector<TerminationReason>& allowed,
const bool check_warnings) {
479 std::vector<Matcher<SolveResult>> matchers;
483 if (check_warnings) {
486 return ::testing::AllOfArray(matchers);
490 const bool check_warnings) {
491 std::vector<Matcher<SolveResult>> matchers;
494 if (check_warnings) {
497 return ::testing::AllOfArray(matchers);
501testing::Matcher<SolveResult> LimitIs(
const Limit expected,
502 const bool allow_limit_undetermined) {
503 if (allow_limit_undetermined) {
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));
521 return ::testing::AllOfArray(matchers);
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));
531 return ::testing::AllOfArray(matchers);
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));
541 return ::testing::AllOfArray(matchers);
544template <
typename MatcherType>
546 std::ostringstream os;
548 matcher.DescribeNegationTo(&os);
550 matcher.DescribeTo(&os);
570 ? absl::StrCat(
"is empty or first element ",
572 : absl::StrCat(
"has at least one element and first element ",
574 return ExplainMatchResult(UnorderedElementsAre(first_element_matcher),
575 absl::MakeSpan(arg).subspan(0, 1), result_listener);
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(
585 if (check_warnings) {
588 if (expected_objective.has_value()) {
589 matchers.push_back(Field(
591 FirstElementIs(Field(
594 IsNear(*expected_objective, tolerance)))))));
596 return ::testing::AllOfArray(matchers);
600 const double expected_objective,
602 const bool check_warnings,
const double tolerance) {
604 IsOptimal(std::make_optional(expected_objective), check_warnings,
608 .objective_value = expected_objective,
614 const double expected_objective,
617 const double tolerance) {
619 IsOptimal(std::make_optional(expected_objective), check_warnings,
624 .reduced_costs = expected_reduced_costs,
625 .objective_value = std::make_optional(expected_objective),
631 const double tolerance) {
632 return ::testing::Field(
635 Optional(
IsNear(std::move(expected), tolerance)))));
639 const double tolerance) {
640 return ::testing::Field(
643 Optional(
IsNear(std::move(expected), tolerance)))));
648 Contains(
IsNear(std::move(expected), tolerance)));
652 const double tolerance) {
660 Contains(
IsNear(std::move(expected), tolerance)));
676std::vector<TerminationReason> CompatibleReasons(
678 if (!inf_or_unb_soft_match) {
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}));
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});
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));
721 return ::testing::Contains(::testing::AnyOfArray(matchers));
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));
731 return ::testing::UnorderedElementsAreArray(matchers);
734template <
typename RayType>
735Matcher<std::vector<RayType>> CheckRays(
736 const std::vector<RayType>& expected_rays,
const double tolerance,
738 if (expected_rays.empty()) {
739 return ::testing::IsEmpty();
742 return AllRaysNear(expected_rays, tolerance);
744 return AnyRayNear(expected_rays, tolerance);
751 std::vector<Matcher<SolveResult>> to_check;
759 ::testing::UnorderedElementsAreArray(expected.
warnings)));
761 const bool skip_solution =
764 if (!skip_solution) {
766 CheckSolutions(expected.
solutions, options)));
777 return AllOfArray(to_check);
785 return ::testing::Field(
"did_update",
787 ::testing::IsTrue());
Fractional InfinityNorm(const DenseColumn &v)
Matcher< SolveResult > HasDualSolution(DualSolution expected, const double tolerance)
Matcher< SolveResult > HasSolution(PrimalSolution expected, const double tolerance)
void PrintTo(const Termination &termination, std::ostream *os)
testing::Matcher< SolveResult > TerminatesWithReasonNoSolutionFound(const Limit expected, const bool allow_limit_undetermined, const bool check_warnings)
std::ostream & operator<<(std::ostream &out, const E value)
Matcher< IncrementalSolver::UpdateResult > DidUpdate()
testing::Matcher< SolveResult > TerminatesWithReasonFeasible(const Limit expected, const bool allow_limit_undetermined, const bool check_warnings)
Matcher< SolveResult > IsConsistentWith(const SolveResult &expected, const SolveResultMatcherOptions &options)
Matcher< SolveResult > IsOptimal(const std::optional< double > expected_objective, const bool check_warnings, const double tolerance)
std::string MatcherToString(const Matcher< T > &matcher, bool negate)
Matcher< VariableMap< double > > IsNearlySubsetOf(VariableMap< double > expected, double tolerance)
std::string MatcherToStringImpl(const MatcherType &matcher, const bool negate)
MATCHER_P(SparseVectorMatcher, pairs, "")
Matcher< SolveResult > HasDualRay(DualRay expected, const double tolerance)
Matcher< SolveResult > IsOptimalWithSolution(const double expected_objective, const VariableMap< double > expected_variable_values, const bool check_warnings, const double tolerance)
Matcher< SolveResult > TerminatesWith(const TerminationReason expected, const bool check_warnings)
Matcher< SolveResult > HasPrimalRay(PrimalRay expected, const double tolerance)
testing::Matcher< SolveResult > TerminatesWithLimit(const Limit expected, const bool allow_limit_undetermined, const bool check_warnings)
Matcher< VariableMap< double > > IsNear(VariableMap< double > expected, const double tolerance)
Matcher< Basis > BasisIs(const Basis &expected)
Matcher< PrimalRay > PrimalRayIsNear(VariableMap< double > expected_var_values, const double tolerance)
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)
Matcher< SolveResult > TerminatesWithOneOf(const std::vector< TerminationReason > &allowed, const bool check_warnings)
Collection of objects used to extend the Constraint Solver library.
VariableMap< BasisStatus > variable_status
LinearConstraintMap< BasisStatus > constraint_status
SolutionStatus basic_dual_feasibility
LinearConstraintMap< double > dual_values
VariableMap< double > reduced_costs
SolutionStatus feasibility_status
LinearConstraintMap< double > dual_values
VariableMap< double > reduced_costs
std::optional< double > objective_value
VariableMap< double > variable_values
VariableMap< double > variable_values
SolutionStatus feasibility_status
std::optional< DualSolution > dual_solution
std::optional< PrimalSolution > primal_solution
std::optional< Basis > basis
std::vector< std::string > warnings
std::vector< PrimalRay > primal_rays
std::vector< Solution > solutions
std::vector< DualRay > dual_rays
bool inf_or_unb_soft_match
bool check_solutions_if_inf_or_unbounded
std::optional< Limit > limit