25#include "absl/strings/str_cat.h"
26#include "absl/types/span.h"
27#include "gmock/gmock.h"
28#include "gtest/gtest.h"
37using ::testing::AllOf;
38using ::testing::AllOfArray;
39using ::testing::AnyOf;
40using ::testing::AnyOfArray;
41using ::testing::Contains;
42using ::testing::DoubleNear;
43using ::testing::ExplainMatchResult;
44using ::testing::Field;
45using ::testing::IsEmpty;
46using ::testing::Matcher;
47using ::testing::MatcherInterface;
48using ::testing::MatchResultListener;
49using ::testing::Optional;
50using ::testing::PrintToString;
61 explicit Printer(
const T& t) :
value(t) {}
65 friend std::ostream&
operator<<(std::ostream& os,
const Printer& printer) {
66 os << PrintToString(printer.value);
72Printer<T> Print(
const T& t) {
79 *os <<
"{reason: " << termination.
reason;
80 if (termination.
limit.has_value()) {
81 *os <<
", limit: " << *termination.
limit;
83 *os <<
", detail: " << Print(termination.
detail) <<
"}";
94 *os <<
"{dual_values: " << Print(dual_solution.
dual_values)
102 *os <<
"{variable_values: " << Print(primal_ray.
variable_values) <<
"}";
106 *os <<
"{dual_values: " << Print(dual_ray.
dual_values)
107 <<
", reduced_costs: " << Print(dual_ray.
reduced_costs) <<
"}";
120 <<
", basis: " << Print(solution.
basis) <<
"}";
124 *os <<
"{termination: " << Print(result.
termination)
126 <<
", solutions: " << Print(result.
solutions)
128 <<
", dual_rays: " << Print(result.
dual_rays) <<
"}";
138class IdMapMatcher :
public MatcherInterface<IdMap<K, double>> {
140 IdMapMatcher(IdMap<K, double> expected,
const bool all_keys,
141 const double tolerance)
142 : expected_(
std::move(expected)),
144 tolerance_(tolerance) {
145 for (
const auto [k, v] : expected_) {
146 CHECK(!std::isnan(v)) <<
"Illegal NaN for key: " << k;
150 bool MatchAndExplain(IdMap<K, double> actual,
151 MatchResultListener*
const os)
const override {
152 for (
const auto& [key,
value] : expected_) {
153 if (!actual.contains(key)) {
154 *os <<
"expected key " << key <<
" not found";
157 if (!(std::abs(
value - actual.at(key)) <= tolerance_)) {
158 *os <<
"value for key " << key
159 <<
" not within tolerance, expected: " <<
value
160 <<
" but found: " << actual.at(key);
165 if (all_keys_ && expected_.size() != actual.size()) {
166 for (
const auto& [key,
value] : actual) {
167 if (!expected_.contains(key)) {
168 *os <<
"found unexpected key " << key <<
" in actual";
180 void DescribeTo(std::ostream*
const os)
const override {
182 *os <<
"has identical keys to ";
184 *os <<
"keys are contained in ";
187 *os <<
" and values within " << tolerance_;
190 void DescribeNegationTo(std::ostream*
const os)
const override {
192 *os <<
"either keys differ from ";
194 *os <<
"either has a key not in ";
197 *os <<
" or a value differs by more than " << tolerance_;
201 const IdMap<K, double> expected_;
202 const bool all_keys_;
203 const double tolerance_;
210 return Matcher<VariableMap<double>>(
new IdMapMatcher<Variable>(
211 std::move(expected),
false, tolerance));
215 const double tolerance) {
216 return Matcher<VariableMap<double>>(
new IdMapMatcher<Variable>(
217 std::move(expected),
true, tolerance));
222 return Matcher<LinearConstraintMap<double>>(
223 new IdMapMatcher<LinearConstraint>(std::move(expected),
227Matcher<LinearConstraintMap<double>>
IsNear(
229 return Matcher<LinearConstraintMap<double>>(
230 new IdMapMatcher<LinearConstraint>(std::move(expected),
true,
240template <
typename RayType>
241class RayMatcher :
public MatcherInterface<RayType> {
243 RayMatcher(RayType expected,
const double tolerance)
244 : expected_(
std::move(expected)), tolerance_(tolerance) {}
245 void DescribeTo(std::ostream* os)
const final {
246 *os <<
"after L_inf normalization, is within tolerance: " << tolerance_
250 void DescribeNegationTo(std::ostream*
const os)
const final {
251 *os <<
"after L_inf normalization, is not within tolerance: " << tolerance_
257 const RayType expected_;
258 const double tolerance_;
262Matcher<double>
IsNear(
double expected,
const double tolerance) {
263 return DoubleNear(expected, tolerance);
266template <
typename Type>
267Matcher<std::optional<Type>>
IsNear(std::optional<Type> expected,
268 const double tolerance) {
269 if (expected.has_value()) {
270 return Optional(
IsNear(*expected, tolerance));
272 return testing::Eq(std::nullopt);
276Matcher<std::optional<Basis>>
BasisIs(
const std::optional<Basis>& expected) {
277 if (expected.has_value()) {
278 return Optional(
BasisIs(*expected));
280 return testing::Eq(std::nullopt);
283testing::Matcher<std::vector<Solution>>
IsNear(
284 const std::vector<Solution>& expected_solutions,
285 const SolutionMatcherOptions options) {
286 if (expected_solutions.empty()) {
289 std::vector<Matcher<Solution>> matchers;
290 for (
const Solution& sol : expected_solutions) {
291 matchers.push_back(
IsNear(sol, options));
293 return ::testing::ElementsAreArray(matchers);
303 const double tolerance) {
334 std::vector<Matcher<Solution>> to_check;
349 return AllOfArray(to_check);
360 double infinity_norm = 0.0;
361 for (
auto [
id,
value] : vector) {
364 return infinity_norm;
374PrimalRay NormalizePrimalRay(PrimalRay ray) {
377 for (
auto entry : ray.variable_values) {
378 entry.second /= norm;
384class PrimalRayMatcher :
public RayMatcher<PrimalRay> {
386 PrimalRayMatcher(PrimalRay expected,
const double tolerance)
387 : RayMatcher(
std::move(expected), tolerance) {}
389 bool MatchAndExplain(PrimalRay actual,
390 MatchResultListener*
const os)
const override {
391 auto normalized_actual = NormalizePrimalRay(actual);
392 auto normalized_expected = NormalizePrimalRay(expected_);
393 if (os->IsInterested()) {
394 *os <<
"actual normalized: " << PrintToString(normalized_actual)
395 <<
", expected normalized: " << PrintToString(normalized_expected);
397 return ExplainMatchResult(
398 IsNear(normalized_expected.variable_values, tolerance_),
399 normalized_actual.variable_values, os);
406 return Matcher<PrimalRay>(
407 new PrimalRayMatcher(std::move(expected), tolerance));
411 const double tolerance) {
414 return IsNear(expected, tolerance);
430DualRay NormalizeDualRay(DualRay ray) {
434 for (
auto entry : ray.dual_values) {
435 entry.second /= norm;
437 for (
auto entry : ray.reduced_costs) {
438 entry.second /= norm;
444class DualRayMatcher :
public RayMatcher<DualRay> {
446 DualRayMatcher(DualRay expected,
const double tolerance)
447 : RayMatcher(
std::move(expected), tolerance) {}
449 bool MatchAndExplain(DualRay actual, MatchResultListener* os)
const override {
450 auto normalized_actual = NormalizeDualRay(actual);
451 auto normalized_expected = NormalizeDualRay(expected_);
452 if (os->IsInterested()) {
453 *os <<
"actual normalized: " << PrintToString(normalized_actual)
454 <<
", expected normalized: " << PrintToString(normalized_expected);
456 return ExplainMatchResult(
457 IsNear(normalized_expected.dual_values, tolerance_),
458 normalized_actual.dual_values, os) &&
460 IsNear(normalized_expected.reduced_costs, tolerance_),
461 normalized_actual.reduced_costs, os);
468 return Matcher<DualRay>(
new DualRayMatcher(std::move(expected), tolerance));
476 const std::vector<TerminationReason>& allowed) {
487testing::Matcher<SolveResult> LimitIs(
const Limit expected,
488 const bool allow_limit_undetermined) {
489 if (allow_limit_undetermined) {
501 const Limit expected,
const bool allow_limit_undetermined) {
502 std::vector<Matcher<SolveResult>> matchers;
503 matchers.push_back(LimitIs(expected, allow_limit_undetermined));
506 return ::testing::AllOfArray(matchers);
510 const Limit expected,
const bool allow_limit_undetermined) {
511 std::vector<Matcher<SolveResult>> matchers;
512 matchers.push_back(LimitIs(expected, allow_limit_undetermined));
514 return ::testing::AllOfArray(matchers);
518 const Limit expected,
const bool allow_limit_undetermined) {
519 std::vector<Matcher<SolveResult>> matchers;
520 matchers.push_back(LimitIs(expected, allow_limit_undetermined));
522 return ::testing::AllOfArray(matchers);
525template <
typename MatcherType>
527 std::ostringstream os;
529 matcher.DescribeNegationTo(&os);
531 matcher.DescribeTo(&os);
551 ? absl::StrCat(
"is empty or first element ",
553 : absl::StrCat(
"has at least one element and first element ",
555 return ExplainMatchResult(UnorderedElementsAre(first_element_matcher),
556 absl::MakeSpan(arg).subspan(0, 1), result_listener);
559Matcher<SolveResult>
IsOptimal(
const std::optional<double> expected_objective,
560 const double tolerance) {
561 std::vector<Matcher<SolveResult>> matchers;
562 matchers.push_back(Field(
565 if (expected_objective.has_value()) {
566 matchers.push_back(Field(
568 FirstElementIs(Field(
571 IsNear(*expected_objective, tolerance)))))));
573 return ::testing::AllOfArray(matchers);
577 const double expected_objective,
579 const double tolerance) {
581 IsOptimal(std::make_optional(expected_objective), tolerance),
584 .objective_value = expected_objective,
590 const double expected_objective,
594 IsOptimal(std::make_optional(expected_objective), tolerance),
598 .reduced_costs = expected_reduced_costs,
599 .objective_value = std::make_optional(expected_objective),
605 const double tolerance) {
606 return ::testing::Field(
609 Optional(
IsNear(std::move(expected), tolerance)))));
613 const double tolerance) {
614 return ::testing::Field(
617 Optional(
IsNear(std::move(expected), tolerance)))));
622 Contains(
IsNear(std::move(expected), tolerance)));
626 const double tolerance) {
634 Contains(
IsNear(std::move(expected), tolerance)));
650std::vector<TerminationReason> CompatibleReasons(
652 if (!inf_or_unb_soft_match) {
670Matcher<std::vector<Solution>> CheckSolutions(
671 const std::vector<Solution>& expected_solutions,
672 const SolveResultMatcherOptions& options) {
673 if (options.first_solution_only && !expected_solutions.empty()) {
674 return FirstElementIs(
675 IsNear(expected_solutions[0],
676 SolutionMatcherOptions{.tolerance = options.tolerance,
677 .check_primal =
true,
678 .check_dual = options.check_dual,
679 .check_basis = options.check_basis}));
681 return IsNear(expected_solutions,
682 SolutionMatcherOptions{.tolerance = options.tolerance,
683 .check_primal =
true,
684 .check_dual = options.check_dual,
685 .check_basis = options.check_basis});
688template <
typename RayType>
689Matcher<std::vector<RayType>> AnyRayNear(
690 const std::vector<RayType>& expected_rays,
const double tolerance) {
691 std::vector<Matcher<RayType>> matchers;
692 for (
const RayType& ray : expected_rays) {
693 matchers.push_back(
IsNear(ray, tolerance));
695 return ::testing::Contains(::testing::AnyOfArray(matchers));
698template <
typename RayType>
699Matcher<std::vector<RayType>> AllRaysNear(
700 const std::vector<RayType>& expected_rays,
const double tolerance) {
701 std::vector<Matcher<RayType>> matchers;
702 for (
const RayType& ray : expected_rays) {
703 matchers.push_back(
IsNear(ray, tolerance));
705 return ::testing::UnorderedElementsAreArray(matchers);
708template <
typename RayType>
709Matcher<std::vector<RayType>> CheckRays(
710 const std::vector<RayType>& expected_rays,
const double tolerance,
712 if (expected_rays.empty()) {
713 return ::testing::IsEmpty();
716 return AllRaysNear(expected_rays, tolerance);
718 return AnyRayNear(expected_rays, tolerance);
725 std::vector<Matcher<SolveResult>> to_check;
728 const bool skip_solution =
731 if (!skip_solution) {
733 CheckSolutions(expected.
solutions, options)));
744 return AllOfArray(to_check);
752 return ::testing::Field(
"did_update",
754 ::testing::IsTrue());
Fractional InfinityNorm(const DenseColumn &v)
Matcher< SolveResult > HasDualSolution(DualSolution expected, const double tolerance)
Matcher< SolveResult > HasSolution(PrimalSolution expected, const double tolerance)
testing::Matcher< SolveResult > TerminatesWithReasonNoSolutionFound(const Limit expected, const bool allow_limit_undetermined)
Matcher< SolveResult > IsOptimal(const std::optional< double > expected_objective, const double tolerance)
void PrintTo(const Termination &termination, std::ostream *os)
std::ostream & operator<<(std::ostream &out, const E value)
Matcher< IncrementalSolver::UpdateResult > DidUpdate()
Matcher< SolveResult > IsOptimalWithSolution(const double expected_objective, const VariableMap< double > expected_variable_values, const double tolerance)
Matcher< SolveResult > TerminatesWithOneOf(const std::vector< TerminationReason > &allowed)
Matcher< SolveResult > IsConsistentWith(const SolveResult &expected, const SolveResultMatcherOptions &options)
std::string MatcherToString(const Matcher< T > &matcher, bool negate)
testing::Matcher< SolveResult > TerminatesWithReasonFeasible(const Limit expected, const bool allow_limit_undetermined)
Matcher< VariableMap< double > > IsNearlySubsetOf(VariableMap< double > expected, double tolerance)
std::string MatcherToStringImpl(const MatcherType &matcher, const bool negate)
MATCHER_P(SparseVectorMatcher, pairs, "")
testing::Matcher< SolveResult > TerminatesWithLimit(const Limit expected, const bool allow_limit_undetermined)
Matcher< SolveResult > HasDualRay(DualRay expected, const double tolerance)
Matcher< SolveResult > IsOptimalWithDualSolution(const double expected_objective, const LinearConstraintMap< double > expected_dual_values, const VariableMap< double > expected_reduced_costs, const double tolerance)
Matcher< SolveResult > TerminatesWith(const TerminationReason expected)
Matcher< SolveResult > HasPrimalRay(PrimalRay expected, const double tolerance)
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)
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< 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