22#include "absl/status/status.h"
23#include "absl/status/statusor.h"
24#include "absl/strings/str_join.h"
25#include "absl/time/time.h"
26#include "gtest/gtest.h"
31#include "ortools/math_opt/infeasible_subsystem.pb.h"
36using ::testing::AnyOf;
37using ::testing::HasSubstr;
38using ::testing::status::IsOkAndHolds;
39using ::testing::status::StatusIs;
41constexpr double kInf = std::numeric_limits<double>::infinity();
45 ostr <<
"{ infeasible_subsystem_support: "
54 ostr <<
", support_menu: " << params.
support_menu <<
" }";
60 if (GetParam().support_menu.supports_infeasible_subsystems) {
65 StatusIs(AnyOf(absl::StatusCode::kInvalidArgument,
66 absl::StatusCode::kUnimplemented),
67 HasSubstr(
"infeasible subsystem")));
77 if (!GetParam().support_menu.supports_infeasible_subsystems) {
97 if (!GetParam().support_menu.supports_infeasible_subsystems) {
109 .variable_integrality = {
x}})));
119 if (!GetParam().support_menu.supports_infeasible_subsystems) {
130 .upper =
true}}}})));
140 if (!GetParam().support_menu.supports_infeasible_subsystems) {
151 .upper =
false}}}})));
160 if (!GetParam().support_menu.supports_infeasible_subsystems) {
171 .upper =
true}}}})));
180 if (!GetParam().support_menu.supports_infeasible_subsystems) {
191 .upper =
true}}}})));
201 if (!GetParam().support_menu.supports_infeasible_subsystems) {
214 .linear_constraints = {
225 if (!GetParam().support_menu.supports_infeasible_subsystems) {
236 .upper =
true}}}})));
246 if (!GetParam().support_menu.supports_infeasible_subsystems) {
257 .upper =
false}}}})));
266 if (!GetParam().support_menu.supports_infeasible_subsystems) {
277 .upper =
true}}}})));
288TEST_P(InfeasibleSubsystemTest,
290 if (!GetParam().support_menu.supports_infeasible_subsystems) {
303 .quadratic_constraints = {
316 if (!GetParam().support_menu.supports_infeasible_subsystems) {
322 model.AddSecondOrderConeConstraint({
x}, 1.0);
330 .second_order_cone_constraints = {
c}})));
341TEST_P(InfeasibleSubsystemTest,
343 if (!GetParam().support_menu.supports_infeasible_subsystems) {
349 model.AddSecondOrderConeConstraint({2.0 *
x}, 1.0);
357 .second_order_cone_constraints = {
c}})));
368TEST_P(InfeasibleSubsystemTest,
370 if (!GetParam().support_menu.supports_infeasible_subsystems) {
376 model.AddSecondOrderConeConstraint({
x}, 2.0 *
x - 2.0);
384 .second_order_cone_constraints = {
c}})));
396 if (!GetParam().support_menu.supports_infeasible_subsystems) {
411 .sos1_constraints = {
c}})));
420 if (!GetParam().support_menu.supports_infeasible_subsystems) {
441 if (!GetParam().support_menu.supports_infeasible_subsystems) {
457 .sos2_constraints = {
c}})));
466 if (!GetParam().support_menu.supports_infeasible_subsystems) {
487 if (!GetParam().support_menu.supports_infeasible_subsystems) {
501 .variable_integrality = {
x},
502 .indicator_constraints = {
c}})));
511 IndicatorConstraintOkInconsistentImpliedNullIndicator) {
512 if (!GetParam().support_menu.supports_infeasible_subsystems) {
546 x(
model.AddBinaryVariable(
"x")),
547 y(
model.AddBinaryVariable(
"y")),
548 z(
model.AddBinaryVariable(
"z")),
549 a(
model.AddLinearConstraint(2.0 *
x + 2.0 *
y + 2.0 *
z >= 3.0)),
550 b(
model.AddLinearConstraint(
x +
y <= 1.0)),
551 c(
model.AddLinearConstraint(
y +
z <= 1.0)),
552 d(
model.AddLinearConstraint(
x +
z <= 1.0)) {}
556 NontrivialInfeasibleIpSolveWithoutLimitsFindsIIS) {
557 if (!GetParam().support_menu.supports_infeasible_subsystems) {
562 .variable_integrality = {ip.
x, ip.
y, ip.
z},
563 .linear_constraints = {{ip.
a, {.lower =
true}},
564 {ip.
b, {.upper =
true}},
565 {ip.
c, {.upper =
true}},
566 {ip.
d, {.upper =
true}}}};
573 NontrivialInfeasibleIpSolveTimeLimitZeroIsUndetermined) {
574 if (!GetParam().support_menu.supports_infeasible_subsystems) {
579 ip.
model, GetParam().solver_type,
580 {.parameters = {.time_limit = absl::Seconds(0)}}),
585 NontrivialInfeasibleIpSolveInterruptedBeforeStartIsUndetermined) {
586 if (!GetParam().support_menu.supports_infeasible_subsystems) {
593 {.interrupter = &interrupter}),
598 NontrivialInfeasibleIpSolveWithMessageCallbackIsInvoked) {
599 if (!GetParam().support_menu.supports_infeasible_subsystems) {
603 std::vector<std::string> logs;
606 ip.
model, GetParam().solver_type,
609 EXPECT_THAT(absl::StrJoin(logs,
"\n"), HasSubstr(
"IIS computed"));
612#ifdef OPERATIONS_RESEARCH_OUTPUT_CAPTURE_SUPPORTED
613TEST_P(InfeasibleSubsystemTest, NoStdoutOutputByDefault) {
614 if (!GetParam().support_menu.supports_infeasible_subsystems) {
617 const NontrivialInfeasibleIp ip;
618 absl::StatusOr<ComputeInfeasibleSubsystemResult> result;
620 ScopedStdStreamCapture stdout_capture(CapturedStream::kStdout);
622 const std::string standard_output =
623 std::move(stdout_capture).StopCaptureAndReturnContents();
631TEST_P(InfeasibleSubsystemTest, EnableOutputPrintsToStdOut) {
632 if (!GetParam().support_menu.supports_infeasible_subsystems) {
635 const NontrivialInfeasibleIp ip;
636 const SolveParameters params = {.enable_output =
true};
638 absl::StatusOr<ComputeInfeasibleSubsystemResult> result;
640 ScopedStdStreamCapture stdout_capture(CapturedStream::kStdout);
642 {.parameters = params});
643 const std::string standard_output =
644 std::move(stdout_capture).StopCaptureAndReturnContents();
646 EXPECT_THAT(standard_output, HasSubstr(
"IIS computed"));
650TEST_P(InfeasibleSubsystemTest, EnableOutputIgnoredWithMessageCallback) {
651 if (!GetParam().support_menu.supports_infeasible_subsystems) {
654 const NontrivialInfeasibleIp ip;
655 const SolveParameters params = {.enable_output =
true};
656 std::vector<std::string> logs;
658 absl::StatusOr<ComputeInfeasibleSubsystemResult> result;
660 ScopedStdStreamCapture stdout_capture(CapturedStream::kStdout);
662 ip.model, GetParam().solver_type,
664 const std::string standard_output =
665 std::move(stdout_capture).StopCaptureAndReturnContents();
671 EXPECT_THAT(absl::StrJoin(logs,
"\n"), HasSubstr(
"IIS computed"));
An object oriented wrapper for quadratic constraints in ModelStorage.
EXPECT_THAT(ComputeInfeasibleSubsystem(model, GetParam().solver_type), IsOkAndHolds(IsInfeasible(true, ModelSubset{ .variable_bounds={{x, ModelSubset::Bounds{.lower=false,.upper=true}}},.linear_constraints={ {c, ModelSubset::Bounds{.lower=true,.upper=false}}}})))
TEST_P(InfeasibleSubsystemTest, CanComputeInfeasibleSubsystem)
≤ x InconsistentSecondOrderConeConstraintWithExpressionInUpperBound
<=x<=1 IncrementalMipTest::IncrementalMipTest() :model_("incremental_solve_test"), x_(model_.AddContinuousVariable(0.0, 1.0, "x")), y_(model_.AddIntegerVariable(0.0, 2.0, "y")), c_(model_.AddLinearConstraint(0<=x_+y_<=1.5, "c")) { model_.Maximize(3.0 *x_+2.0 *y_+0.1);solver_=NewIncrementalSolver(&model_, TestedSolver()).value();const SolveResult first_solve=solver_->Solve().value();CHECK(first_solve.has_primal_feasible_solution());CHECK_LE(std::abs(first_solve.objective_value() - 3.6), kTolerance)<< first_solve.objective_value();} namespace { TEST_P(SimpleMipTest, OneVarMax) { Model model;const Variable x=model.AddVariable(0.0, 4.0, false, "x");model.Maximize(2.0 *x);ASSERT_OK_AND_ASSIGN(const SolveResult result, Solve(model, GetParam().solver_type));ASSERT_THAT(result, IsOptimal(8.0));EXPECT_THAT(result.variable_values(), IsNear({{x, 4.0}}));} TEST_P(SimpleMipTest, OneVarMin) { Model model;const Variable x=model.AddVariable(-2.4, 4.0, false, "x");model.Minimize(2.0 *x);ASSERT_OK_AND_ASSIGN(const SolveResult result, Solve(model, GetParam().solver_type));ASSERT_THAT(result, IsOptimal(-4.8));EXPECT_THAT(result.variable_values(), IsNear({{x, -2.4}}));} TEST_P(SimpleMipTest, OneIntegerVar) { Model model;const Variable x=model.AddVariable(0.0, 4.5, true, "x");model.Maximize(2.0 *x);ASSERT_OK_AND_ASSIGN(const SolveResult result, Solve(model, GetParam().solver_type));ASSERT_THAT(result, IsOptimal(8.0));EXPECT_THAT(result.variable_values(), IsNear({{x, 4.0}}));} TEST_P(SimpleMipTest, SimpleLinearConstraint) { Model model;const Variable x=model.AddBinaryVariable("x");const Variable y=model.AddBinaryVariable("y");model.Maximize(2.0 *x+y);model.AddLinearConstraint(0.0<=x+y<=1.5, "c");ASSERT_OK_AND_ASSIGN(const SolveResult result, Solve(model, GetParam().solver_type));ASSERT_THAT(result, IsOptimal(2.0));EXPECT_THAT(result.variable_values(), IsNear({{x, 1}, {y, 0}}));} TEST_P(SimpleMipTest, Unbounded) { Model model;const Variable x=model.AddVariable(0.0, kInf, true, "x");model.Maximize(2.0 *x);ASSERT_OK_AND_ASSIGN(const SolveResult result, Solve(model, GetParam().solver_type));if(GetParam().report_unboundness_correctly) { ASSERT_THAT(result, TerminatesWithOneOf({TerminationReason::kUnbounded, TerminationReason::kInfeasibleOrUnbounded}));} else { ASSERT_THAT(result, TerminatesWith(TerminationReason::kOtherError));} } TEST_P(SimpleMipTest, Infeasible) { Model model;const Variable x=model.AddVariable(0.0, 3.0, true, "x");model.Maximize(2.0 *x);model.AddLinearConstraint(x >=4.0);ASSERT_OK_AND_ASSIGN(const SolveResult result, Solve(model, GetParam().solver_type));ASSERT_THAT(result, TerminatesWith(TerminationReason::kInfeasible));} TEST_P(SimpleMipTest, FractionalBoundsContainNoInteger) { if(GetParam().solver_type==SolverType::kGurobi) { GTEST_SKIP()<< "TODO(b/272298816): Gurobi bindings are broken here.";} Model model;const Variable x=model.AddIntegerVariable(0.5, 0.6, "x");model.Maximize(x);EXPECT_THAT(Solve(model, GetParam().solver_type), IsOkAndHolds(TerminatesWith(TerminationReason::kInfeasible)));} TEST_P(IncrementalMipTest, EmptyUpdate) { ASSERT_THAT(solver_->Update(), IsOkAndHolds(DidUpdate()));ASSERT_OK_AND_ASSIGN(const SolveResult result, solver_->SolveWithoutUpdate());ASSERT_THAT(result, IsOptimal(3.6));EXPECT_THAT(result.variable_values(), IsNear({{x_, 0.5}, {y_, 1.0}}));} TEST_P(IncrementalMipTest, MakeContinuous) { model_.set_continuous(y_);ASSERT_THAT(solver_->Update(), IsOkAndHolds(DidUpdate()));ASSERT_OK_AND_ASSIGN(const SolveResult result, solver_->SolveWithoutUpdate());ASSERT_THAT(result, IsOptimal(4.1));EXPECT_THAT(result.variable_values(), IsNear({{x_, 1.0}, {y_, 0.5}}));} TEST_P(IncrementalMipTest, DISABLED_MakeContinuousWithNonIntegralBounds) { solver_.reset();Model model("bounds");const Variable x=model.AddIntegerVariable(0.5, 1.5, "x");model.Maximize(x);ASSERT_OK_AND_ASSIGN(const auto solver, NewIncrementalSolver(&model, TestedSolver()));ASSERT_THAT(solver->Solve(), IsOkAndHolds(IsOptimal(1.0)));model.set_continuous(x);ASSERT_THAT(solver->Update(), IsOkAndHolds(DidUpdate()));ASSERT_THAT(solver-> IsOkAndHolds(IsOptimal(1.5)))
testing::Matcher< ComputeInfeasibleSubsystemResult > IsInfeasible(const std::optional< bool > expected_is_minimal, const std::optional< ModelSubset > expected_infeasible_subsystem)
std::ostream & operator<<(std::ostream &ostr, const IndicatorConstraint &constraint)
∞ ≤ x InconsistentVariableBoundsAndLinearConstraint
≤ x InconsistentSecondOrderConeConstraintWithExpressionUnderNorm
absl::StatusOr< ComputeInfeasibleSubsystemResult > ComputeInfeasibleSubsystem(const Model &model, const SolverType solver_type, const ComputeInfeasibleSubsystemArguments &compute_args, const SolverInitArguments &init_args)
MessageCallback VectorMessageCallback(std::vector< std::string > *sink)
≤ x InconsistentSecondOrderConeConstraint
≤ x InconsistentIndicatorConstraint
absl::string_view EnumToString(E value)
testing::Matcher< ComputeInfeasibleSubsystemResult > IsUndetermined()
@ kInfeasible
Solver claims the problem is infeasible.
≤ x InconsistentVariableBoundsAndQuadraticConstraint
testing::Matcher< std::string > EmptyOrGurobiLicenseWarningIfGurobi(const bool is_gurobi)
#define ASSERT_OK(expression)
#define ASSERT_OK_AND_ASSIGN(lhs, rexpr)
A LinearExpression with upper and lower bounds.
A QuadraticExpression with upper and lower bounds.
FeasibilityStatus feasibility
The primal feasibility status of the model, as determined by the solver.
bool supports_infeasible_subsystems
SolverType solver_type
The tested solver.
InfeasibleSubsystemSupport support_menu