17#include <initializer_list>
21#include "absl/log/check.h"
22#include "absl/status/statusor.h"
23#include "gtest/gtest.h"
33using ::testing::status::IsOkAndHolds;
36constexpr double kInf = std::numeric_limits<double>::infinity();
48 <<
", report_unboundness_correctly: "
60 : model_(
"incremental_solve_test"),
61 x_(model_.AddContinuousVariable(0.0, 1.0,
"x")),
62 y_(model_.AddIntegerVariable(0.0, 2.0,
"y")),
63 c_(model_.AddLinearConstraint(0 <= x_ + y_ <= 1.5,
"c")) {
64 model_.Maximize(3.0 * x_ + 2.0 * y_ + 0.1);
66 const SolveResult first_solve = solver_->Solve().value();
67 CHECK(first_solve.has_primal_feasible_solution());
68 CHECK_LE(std::abs(first_solve.objective_value() - 3.6),
kTolerance)
69 << first_solve.objective_value();
74TEST_P(SimpleMipTest, OneVarMax) {
79 Solve(model, GetParam().solver_type));
80 ASSERT_THAT(result, IsOptimal(8.0));
81 EXPECT_THAT(result.variable_values(), IsNear({{x, 4.0}}));
84TEST_P(SimpleMipTest, OneVarMin) {
86 const Variable
x = model.
AddVariable(-2.4, 4.0,
false,
"x");
89 Solve(model, GetParam().solver_type));
94TEST_P(SimpleMipTest, OneIntegerVar) {
96 const Variable
x = model.
AddVariable(0.0, 4.5,
true,
"x");
99 Solve(model, GetParam().solver_type));
104TEST_P(SimpleMipTest, SimpleLinearConstraint) {
106 const Variable
x = model.AddBinaryVariable(
"x");
107 const Variable y = model.AddBinaryVariable(
"y");
109 model.AddLinearConstraint(0.0 <= x + y <= 1.5,
"c");
111 Solve(model, GetParam().solver_type));
116TEST_P(SimpleMipTest, Unbounded) {
118 const Variable
x = model.
AddVariable(0.0, kInf,
true,
"x");
121 Solve(model, GetParam().solver_type));
122 if (GetParam().report_unboundness_correctly) {
124 {TerminationReason::kUnbounded,
125 TerminationReason::kInfeasibleOrUnbounded}));
127 ASSERT_THAT(result,
TerminatesWith(TerminationReason::kOtherError));
131TEST_P(SimpleMipTest, Infeasible) {
133 const Variable
x = model.
AddVariable(0.0, 3.0,
true,
"x");
135 model.AddLinearConstraint(x >= 4.0);
137 Solve(model, GetParam().solver_type));
138 ASSERT_THAT(result,
TerminatesWith(TerminationReason::kInfeasible));
141TEST_P(SimpleMipTest, FractionalBoundsContainNoInteger) {
142 if (GetParam().solver_type == SolverType::kGurobi) {
144 GTEST_SKIP() <<
"TODO(b/272298816): Gurobi bindings are broken here.";
146 if (GetParam().solver_type == SolverType::kXpress) {
150 GTEST_SKIP() <<
"Xpress does not support contradictory bounds.";
153 const Variable
x = model.AddIntegerVariable(0.5, 0.6,
"x");
159TEST_P(IncrementalMipTest, EmptyUpdate) {
166TEST_P(IncrementalMipTest, MakeContinuous) {
167 model_.set_continuous(y_);
176TEST_P(IncrementalMipTest, DISABLED_MakeContinuousWithNonIntegralBounds) {
180 Model model(
"bounds");
181 const Variable
x = model.AddIntegerVariable(0.5, 1.5,
"x");
191 model.set_continuous(x);
204 Model model(
"bounds");
222 model_.set_integer(x_);
230 model_.set_minimize();
238 model_.set_objective_offset(0.2);
246 model_.set_objective_coefficient(x_, 5.0);
254 model_.set_lower_bound(x_, 0.75);
262 model_.set_upper_bound(x_, 2.0);
270 model_.set_lower_bound(c_, 1.0);
285 model_.set_upper_bound(c_, 1.0);
293 model_.set_coefficient(c_, x_, 0.5);
301 Variable z = model_.AddVariable(0.0, 1.0,
true,
"z");
302 model_.set_objective_coefficient(z, 10.0);
303 model_.set_coefficient(c_, z, 1.0);
308 IsNear({{x_, 0.5}, {y_, 0.0}, {z, 1.0}}));
312 model_.AddLinearConstraint(0.0 <= x_ + 2.0 * y_ <= 2.0,
"d");
320 model_.DeleteVariable(x_);
328 model_.DeleteLinearConstraint(c_);
336 model_.set_lower_bound(x_, 3.0);
340 model_.set_upper_bound(x_, 5.0);
348 model_.set_lower_bound(c_, 4.0);
353 model_.set_upper_bound(c_, 5.5);
Variable * AddVariable(absl::string_view name, const Domain &domain, bool defined, bool set_is_fixed=false)
void Maximize(Variable *obj, std::vector< Annotation > search_annotations)
void Minimize(Variable *obj, std::vector< Annotation > search_annotations)
void Maximize(double objective)
void Minimize(double objective)
void set_integer(Variable variable)
Variable AddContinuousVariable(double lower_bound, double upper_bound, absl::string_view name="")
#define ASSERT_OK_AND_ASSIGN(lhs, rexpr)
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)
Matcher< SolveResult > TerminatesWithOneOf(const std::vector< TerminationReason > &allowed)
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
<=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.";} if(GetParam().solver_type==SolverType::kXpress) { GTEST_SKIP()<< "Xpress does not support contradictory bounds.";} 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->SolveWithoutUpdate(), IsOkAndHolds(IsOptimal(1.5)));model.Minimize(x);ASSERT_THAT(solver->Update(), IsOkAndHolds(DidUpdate()));ASSERT_THAT(solver-> IsOkAndHolds(IsOptimal(0.5)))
absl::StatusOr< std::unique_ptr< IncrementalSolver > > NewIncrementalSolver(Model *model, SolverType solver_type, SolverInitArguments arguments)
Matcher< SolveResult > TerminatesWith(const TerminationReason expected)
std::ostream & operator<<(std::ostream &ostr, const SecondOrderConeConstraint &constraint)
Matcher< VariableMap< double > > IsNear(VariableMap< double > expected, const double tolerance)
Matcher< UpdateResult > DidUpdate()
constexpr double kTolerance
Matcher< SolveResult > IsOptimal(const std::optional< double > expected_primal_objective, const double tolerance)
SimpleMipTestParameters(SolverType solver_type, bool report_unboundness_correctly=true)
bool report_unboundness_correctly
const VariableMap< double > & variable_values() const