23 #include <type_traits>
27 #include "absl/strings/str_cat.h"
28 #include "absl/types/span.h"
29 #include "gmock/gmock.h"
30 #include "gtest/gtest.h"
40 using ::testing::AllOf;
41 using ::testing::AllOfArray;
42 using ::testing::AnyOf;
43 using ::testing::AnyOfArray;
44 using ::testing::Contains;
45 using ::testing::DoubleNear;
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;
66 explicit Printer(
const T& t) :
value(t) {}
70 friend std::ostream&
operator<<(std::ostream& os,
const Printer& printer) {
71 os << PrintToString(printer.value);
77 Printer<T> Print(
const T& t) {
84 *os <<
"{reason: " << termination.
reason;
85 if (termination.
limit.has_value()) {
86 *os <<
", limit: " << *termination.
limit;
88 *os <<
", detail: " << Print(termination.
detail) <<
"}";
99 *os <<
"{dual_values: " << Print(dual_solution.
dual_values)
107 *os <<
"{variable_values: " << Print(primal_ray.
variable_values) <<
"}";
111 *os <<
"{dual_values: " << Print(dual_ray.
dual_values)
112 <<
", reduced_costs: " << Print(dual_ray.
reduced_costs) <<
"}";
125 <<
", basis: " << Print(solution.
basis) <<
"}";
129 *os <<
"{termination: " << Print(result.
termination)
131 <<
", solutions: " << Print(result.
solutions)
133 <<
", dual_rays: " << Print(result.
dual_rays) <<
"}";
142 template <
typename K>
143 class IdMapMatcher :
public MatcherInterface<IdMap<K, double>> {
145 IdMapMatcher(IdMap<K, double> expected,
const bool all_keys,
146 const double tolerance)
147 : expected_(std::move(expected)),
149 tolerance_(tolerance) {
150 for (
const auto [k, v] : expected_) {
151 CHECK(!std::isnan(v)) <<
"Illegal NaN for key: " << k;
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";
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);
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";
180 LOG(FATAL) <<
"unreachable";
185 void DescribeTo(std::ostream*
const os)
const override {
187 *os <<
"has identical keys to ";
189 *os <<
"keys are contained in ";
192 *os <<
" and values within " << tolerance_;
195 void DescribeNegationTo(std::ostream*
const os)
const override {
197 *os <<
"either keys differ from ";
199 *os <<
"either has a key not in ";
202 *os <<
" or a value differs by more than " << tolerance_;
206 const IdMap<K, double> expected_;
207 const bool all_keys_;
208 const double tolerance_;
215 return Matcher<VariableMap<double>>(
new IdMapMatcher<Variable>(
216 std::move(expected),
false, tolerance));
220 const double tolerance) {
221 return Matcher<VariableMap<double>>(
new IdMapMatcher<Variable>(
222 std::move(expected),
true, tolerance));
227 return Matcher<LinearConstraintMap<double>>(
228 new IdMapMatcher<LinearConstraint>(std::move(expected),
232 Matcher<LinearConstraintMap<double>>
IsNear(
234 return Matcher<LinearConstraintMap<double>>(
235 new IdMapMatcher<LinearConstraint>(std::move(expected),
true,
239 template <
typename K>
241 const double tolerance) {
242 return Matcher<IdMap<K, double>>(
243 new IdMapMatcher<K>(std::move(expected),
true, tolerance));
246 template <
typename K>
248 const double tolerance) {
249 return Matcher<IdMap<K, double>>(
250 new IdMapMatcher<K>(std::move(expected),
false, tolerance));
263 CHECK(!std::isnan(expected.
offset())) <<
"Illegal NaN-valued offset";
267 testing::DoubleNear(expected.
offset(), tolerance)),
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(),
279 Property(
"lower_bound_minus_offset",
281 testing::DoubleNear(expected.lower_bound_minus_offset(),
285 IsNear(expected.expression.terms(), tolerance))));
294 return AnyOf(IsNearForSign(expected, tolerance),
295 IsNearForSign(expected_negation, tolerance));
300 CHECK(!std::isnan(expected.
offset())) <<
"Illegal NaN-valued offset";
305 testing::Eq(expected.
offset())),
318 template <
typename RayType>
319 class RayMatcher :
public MatcherInterface<RayType> {
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_
328 void DescribeNegationTo(std::ostream*
const os)
const final {
329 *os <<
"after L_inf normalization, is not within tolerance: " << tolerance_
335 const RayType expected_;
336 const double tolerance_;
340 Matcher<double>
IsNear(
double expected,
const double tolerance) {
341 return DoubleNear(expected, tolerance);
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));
350 return testing::Eq(std::nullopt);
354 Matcher<std::optional<Basis>>
BasisIs(
const std::optional<Basis>& expected) {
355 if (expected.has_value()) {
356 return Optional(
BasisIs(*expected));
358 return testing::Eq(std::nullopt);
361 testing::Matcher<std::vector<Solution>>
IsNear(
362 const std::vector<Solution>& expected_solutions,
363 const SolutionMatcherOptions options) {
364 if (expected_solutions.empty()) {
367 std::vector<Matcher<Solution>> matchers;
368 for (
const Solution& sol : expected_solutions) {
369 matchers.push_back(
IsNear(sol, options));
371 return ::testing::ElementsAreArray(matchers);
381 const double tolerance) {
412 std::vector<Matcher<Solution>> to_check;
427 return AllOfArray(to_check);
436 template <
typename K>
438 double infinity_norm = 0.0;
439 for (
auto [
id,
value] : vector) {
442 return infinity_norm;
452 PrimalRay NormalizePrimalRay(PrimalRay ray) {
455 for (
auto entry : ray.variable_values) {
456 entry.second /= norm;
462 class PrimalRayMatcher :
public RayMatcher<PrimalRay> {
464 PrimalRayMatcher(PrimalRay expected,
const double tolerance)
465 : RayMatcher(std::move(expected), tolerance) {}
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);
475 return ExplainMatchResult(
476 IsNear(normalized_expected.variable_values, tolerance_),
477 normalized_actual.variable_values, os);
484 return Matcher<PrimalRay>(
485 new PrimalRayMatcher(std::move(expected), tolerance));
489 const double tolerance) {
492 return IsNear(expected, tolerance);
508 DualRay NormalizeDualRay(DualRay ray) {
512 for (
auto entry : ray.dual_values) {
513 entry.second /= norm;
515 for (
auto entry : ray.reduced_costs) {
516 entry.second /= norm;
522 class DualRayMatcher :
public RayMatcher<DualRay> {
524 DualRayMatcher(DualRay expected,
const double tolerance)
525 : RayMatcher(std::move(expected), tolerance) {}
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);
534 return ExplainMatchResult(
535 IsNear(normalized_expected.dual_values, tolerance_),
536 normalized_actual.dual_values, os) &&
538 IsNear(normalized_expected.reduced_costs, tolerance_),
539 normalized_actual.reduced_costs, os);
546 return Matcher<DualRay>(
new DualRayMatcher(std::move(expected), tolerance));
554 const std::vector<TerminationReason>& allowed) {
565 testing::Matcher<SolveResult> LimitIs(
const Limit expected,
566 const bool allow_limit_undetermined) {
567 if (allow_limit_undetermined) {
579 const Limit expected,
const bool allow_limit_undetermined) {
580 std::vector<Matcher<SolveResult>> matchers;
581 matchers.push_back(LimitIs(expected, allow_limit_undetermined));
584 return ::testing::AllOfArray(matchers);
588 const Limit expected,
const bool allow_limit_undetermined) {
589 std::vector<Matcher<SolveResult>> matchers;
590 matchers.push_back(LimitIs(expected, allow_limit_undetermined));
592 return ::testing::AllOfArray(matchers);
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);
603 template <
typename MatcherType>
605 std::ostringstream os;
607 matcher.DescribeNegationTo(&os);
609 matcher.DescribeTo(&os);
614 template <
typename T>
623 template <
typename T>
631 ? absl::StrCat(
"is empty or first element ",
633 : absl::StrCat(
"has at least one element and first element ",
635 return ExplainMatchResult(UnorderedElementsAre(first_element_matcher),
636 absl::MakeSpan(arg).subspan(0, 1), result_listener);
647 Matcher<SolveResult>
IsOptimal(
const std::optional<double> expected_objective,
648 const double tolerance) {
649 std::vector<Matcher<SolveResult>> matchers;
652 if (expected_objective.has_value()) {
653 matchers.push_back(Field(
655 FirstElementIs(Field(
658 IsNear(*expected_objective, tolerance)))))));
660 return ::testing::AllOfArray(matchers);
664 const double expected_objective,
666 const double tolerance) {
668 IsOptimal(std::make_optional(expected_objective), tolerance),
671 .objective_value = expected_objective,
677 const double expected_objective,
681 IsOptimal(std::make_optional(expected_objective), tolerance),
685 .reduced_costs = expected_reduced_costs,
686 .objective_value = std::make_optional(expected_objective),
692 const double tolerance) {
693 return ::testing::Field(
696 Optional(
IsNear(std::move(expected), tolerance)))));
700 const double tolerance) {
701 return ::testing::Field(
704 Optional(
IsNear(std::move(expected), tolerance)))));
709 Contains(
IsNear(std::move(expected), tolerance)));
713 const double tolerance) {
721 Contains(
IsNear(std::move(expected), tolerance)));
737 std::vector<TerminationReason> CompatibleReasons(
739 if (!inf_or_unb_soft_match) {
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}));
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});
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));
782 return ::testing::Contains(::testing::AnyOfArray(matchers));
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));
792 return ::testing::UnorderedElementsAreArray(matchers);
795 template <
typename RayType>
796 Matcher<std::vector<RayType>> CheckRays(
797 const std::vector<RayType>& expected_rays,
const double tolerance,
799 if (expected_rays.empty()) {
800 return ::testing::IsEmpty();
803 return AllRaysNear(expected_rays, tolerance);
805 return AnyRayNear(expected_rays, tolerance);
812 std::vector<Matcher<SolveResult>> to_check;
815 const bool skip_solution =
818 if (!skip_solution) {
820 CheckSolutions(expected.
solutions, options)));
831 return AllOfArray(to_check);
840 ::testing::IsTrue());
const VariableMap< double > & terms() const
const ModelStorage * storage() const
const QuadraticTermMap< double > & quadratic_terms() const
const VariableMap< double > & linear_terms() const
const ModelStorage * storage() const
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)
Matcher< Termination > ReasonIsOptimal()
testing::Matcher< LinearExpression > IsIdentical(LinearExpression expected)
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< Termination > ReasonIs(TerminationReason reason)
testing::Matcher< LinearExpression > LinearExpressionIsNear(const LinearExpression expected, const double tolerance)
Matcher< VariableMap< double > > IsNearlySubsetOf(VariableMap< double > expected, double tolerance)
Matcher< UpdateResult > DidUpdate()
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)
testing::Matcher< BoundedLinearExpression > IsNearlyEquivalent(const BoundedLinearExpression &expected, 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)
std::ostream & operator<<(std::ostream &ostr, const IndicatorConstraint &constraint)
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
double upper_bound_minus_offset() const
LinearExpression expression
double lower_bound_minus_offset() const
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