22#include "absl/status/statusor.h"
23#include "gtest/gtest.h"
28#include "ortools/math_opt/parameters.pb.h"
35using ::testing::DoubleNear;
36using ::testing::status::IsOkAndHolds;
38std::string PrintParams(
const std::optional<SolveParameters>& params) {
56 out <<
"{ solver_type: " << params.
solver_type <<
" solve_params: "
63 out <<
"{ solver_type: " << params.
solver_type <<
" solve_params: "
75 model.AddLinearConstraint(0.0 <=
x +
y <= 1.5,
"c");
77 const SolveResult result,
79 {.model_parameters = {
80 .variable_values_filter = {.skip_zero_values =
true}}}));
85TEST_P(IpModelSolveParametersTest, SolutionFilterByKey) {
87 const Variable
x =
model.AddBinaryVariable(
"x");
88 const Variable
y =
model.AddBinaryVariable(
"y");
90 model.AddLinearConstraint(0.0 <=
x +
y <= 1.5,
"c");
93 const SolveResult result,
101TEST_P(MipSolutionHintTest, SingleHintTest) {
102 if (!SingleHintParams().has_value()) {
103 GTEST_SKIP() <<
"Single hints not supported. Ignoring this test.";
106 ModelSolveParameters model_parameters;
108 Model
model(
"Solution Hint MIP");
110 Variable
x1 =
model.AddBinaryVariable(
"x1");
111 Variable
x2 =
model.AddBinaryVariable(
"x2");
114 Variable x3 =
model.AddBinaryVariable(
"x3");
115 Variable x4 =
model.AddBinaryVariable(
"x4");
116 model.AddLinearConstraint(x3 + x4 == 1);
118 model.Maximize(
x1 + 3 *
x2 + 2 * x3 + 4 * x4);
122 ModelSolveParameters::SolutionHint
hint;
123 hint.variable_values = {{
x1, 1.0}, {x4, 0.0}};
124 model_parameters.solution_hints.emplace_back(
hint);
126 std::ostringstream log;
127 const SolveArguments args = {
133 .parameters = *SingleHintParams(),
134 .model_parameters = model_parameters,
144 .variable_values = {{
x1, 1.0}, {
x2, 0.0}, {x3, 1.0}, {x4, 0.0}},
145 .objective_value = 3.0,
147 EXPECT_THAT(log.str(), testing::ContainsRegex(HintAcceptedMessageRegex()));
150TEST_P(MipSolutionHintTest, TwoHintTest) {
151 if (!TwoHintParams().has_value()) {
152 GTEST_SKIP() <<
"Multiple hints not supported. Ignoring this test.";
155 ModelSolveParameters model_parameters;
157 Model
model(
"Solution Hint MIP");
159 Variable
x1 =
model.AddBinaryVariable(
"x1");
160 Variable
x2 =
model.AddBinaryVariable(
"x2");
163 Variable x3 =
model.AddBinaryVariable(
"x3");
164 Variable x4 =
model.AddBinaryVariable(
"x4");
165 model.AddLinearConstraint(x3 + x4 == 1);
167 Variable x5 =
model.AddBinaryVariable(
"x5");
168 Variable x6 =
model.AddBinaryVariable(
"x6");
169 model.AddLinearConstraint(x5 + x6 == 1);
171 model.Maximize(
x1 + 3 *
x2 + 2 * x3 + 4 * x4 + x5 + 2 * x6);
176 ModelSolveParameters::SolutionHint first_hint;
177 first_hint.variable_values = {{
x1, 1.0}, {x4, 0.0}, {x5, 1.0}};
178 model_parameters.solution_hints.emplace_back(first_hint);
179 const Solution first_solution{
180 .primal_solution = {{.variable_values = {{
x1, 1.0},
186 .objective_value = 4,
192 ModelSolveParameters::SolutionHint second_hint;
193 second_hint.variable_values = {{
x1, 1.0}, {x4, 0.0}, {x6, 1.0}};
194 model_parameters.solution_hints.emplace_back(second_hint);
195 const Solution second_solution{
197 PrimalSolution{.variable_values = {{
x1, 1.0},
203 .objective_value = 5,
205 std::ostringstream log;
206 const SolveArguments args = {
212 .parameters = *TwoHintParams(),
213 .model_parameters = model_parameters,
223 ElementsAre(
IsNear(second_solution),
224 IsNear(first_solution, {.allow_undetermined =
true})));
225 EXPECT_THAT(log.str(), testing::ContainsRegex(HintAcceptedMessageRegex()));
228TEST_P(BranchPrioritiesTest, PrioritiesAreSetProperly) {
231 Model
model(
"Solution Hint MIP");
232 Variable
x =
model.AddContinuousVariable(-3.0, 1.0,
"x");
233 Variable
y =
model.AddContinuousVariable(0.0, 3.0,
"y");
234 Variable zminus2 =
model.AddBinaryVariable(
"zminus2");
235 Variable zminus1 =
model.AddBinaryVariable(
"zminus1");
236 Variable zplus1 =
model.AddBinaryVariable(
"zplus1");
237 model.AddLinearConstraint(zminus2 + zminus1 + zplus1 == 1);
238 model.AddLinearConstraint(-2 * zminus2 - zminus1 + zplus1 ==
x);
239 model.AddLinearConstraint(
x <=
y);
240 model.AddLinearConstraint(-
x <=
y);
254 const SolveParameters solve_params = SolveParams();
260 ModelSolveParameters model_parameters;
261 model_parameters.branching_priorities = {
262 {zminus2, 1}, {zminus1, 1}, {zplus1, 2}};
263 const SolveArguments good_args = {.parameters = solve_params,
264 .model_parameters = model_parameters};
268 const int good_node_count = good_result.solve_stats.node_count;
272 model_parameters.branching_priorities = {
273 {zminus2, 2}, {zminus1, 2}, {zplus1, 1}};
274 const SolveArguments bad_args = {.parameters = solve_params,
275 .model_parameters = model_parameters};
279 EXPECT_GT(bad_result.solve_stats.node_count, good_node_count);
283TEST_P(BranchPrioritiesTest, PrioritiesClearedAfterIncrementalSolve) {
285 Variable
x =
model.AddContinuousVariable(-3.0, 1.0,
"x");
286 Variable
y =
model.AddContinuousVariable(0.0, 3.0,
"y");
287 Variable zminus2 =
model.AddBinaryVariable(
"zminus2");
288 Variable zminus1 =
model.AddBinaryVariable(
"zminus1");
289 Variable zplus1 =
model.AddBinaryVariable(
"zplus1");
290 model.AddLinearConstraint(zminus2 + zminus1 + zplus1 == 1);
291 model.AddLinearConstraint(-2 * zminus2 - zminus1 + zplus1 ==
x);
292 model.AddLinearConstraint(
x <=
y);
293 model.AddLinearConstraint(-
x <=
y);
298 const int node_count_good_priorities, ([&]() -> absl::StatusOr<int> {
299 const SolveArguments args = {
300 .parameters = SolveParams(),
301 .model_parameters = {.branching_priorities = {
302 {zminus1, 1}, {zminus2, 1}, {zplus1, 3}}}};
306 return result.solve_stats.node_count;
315 SolveParameters params = SolveParams();
316 params.node_limit = 0;
317 const SolveArguments args = {
318 .parameters = params,
319 .model_parameters = {
320 .branching_priorities = {{zminus1, 1}, {zminus2, 1}, {zplus1, 3}}}};
331 const int node_count_no_priorities, ([&]() -> absl::StatusOr<int> {
332 const SolveArguments args{
333 .parameters = SolveParams(),
334 .model_parameters = {
335 .branching_priorities = {{zminus1, 2}, {zminus2, 2}}}};
338 return result.solve_stats.node_count;
343 EXPECT_GT(node_count_no_priorities, node_count_good_priorities);
354TEST_P(LazyConstraintsTest, LazyConstraintsImposedOnModel) {
356 Variable
x =
model.AddIntegerVariable(0, 2,
"x");
357 const LinearConstraint
c =
model.AddLinearConstraint(
x >= 1);
363 SolveArguments args = {.model_parameters = {.lazy_linear_constraints = {
c}}};
364 args.parameters.enable_output =
true;
380TEST_P(LazyConstraintsTest, AnnotationsAreSetProperly) {
382 Variable
x =
model.AddIntegerVariable(-1, 1,
"x");
383 Variable
y =
model.AddIntegerVariable(-1, 1,
"y");
384 const LinearConstraint
c =
model.AddLinearConstraint(
y >=
x);
385 const LinearConstraint d =
model.AddLinearConstraint(
y >= -
x);
388 SolveArguments args = {
389 .parameters = NerfedSolveParams(),
390 .model_parameters = {.lazy_linear_constraints = {
c, d}}};
391 args.parameters.node_limit = 0;
395 EXPECT_THAT(result.best_objective_bound(), DoubleNear(-1, 1.0e-5));
402TEST_P(LazyConstraintsTest, AnnotationsAreClearedAfterSolve) {
404 Variable
x =
model.AddIntegerVariable(-1, 1,
"x");
405 Variable
y =
model.AddIntegerVariable(-1, 1,
"y");
406 const LinearConstraint
c =
model.AddLinearConstraint(
y >=
x);
407 const LinearConstraint d =
model.AddLinearConstraint(
y >= -
x);
412 SolveArguments args = {
413 .parameters = NerfedSolveParams(),
414 .model_parameters = {.lazy_linear_constraints = {
c, d}}};
415 args.parameters.node_limit = 0;
418 ASSERT_THAT(bad_result.best_objective_bound(), DoubleNear(-1, 1.0e-5));
420 args.model_parameters.lazy_linear_constraints.clear();
423 EXPECT_THAT(good_result.best_objective_bound(), DoubleNear(0, 1.0e-5));
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
std::optional< ModelSolveParameters::SolutionHint > hint
Matcher< SolveResult > IsOptimalWithSolution(const double expected_objective, const VariableMap< double > expected_variable_values, const double tolerance)
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<=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)))
ASSERT_THAT(solver->Update(), IsOkAndHolds(DidUpdate()))
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
std::ostream & operator<<(std::ostream &ostr, const IndicatorConstraint &constraint)
absl::StatusOr< std::unique_ptr< IncrementalSolver > > NewIncrementalSolver(Model *model, SolverType solver_type, SolverInitArguments arguments)
Matcher< SolveResult > HasSolution(PrimalSolution expected, const double tolerance)
SolveResult has a primal solution matching expected within tolerance.
@ kFeasible
Solver claims the solution is feasible.
testing::Matcher< SolveResult > TerminatesWithReasonFeasible(const Limit expected, const bool allow_limit_undetermined)
MessageCallback PrinterMessageCallback(std::ostream &output_stream, const absl::string_view prefix)
testing::Matcher< SolveResult > TerminatesWithLimit(const Limit expected, const bool allow_limit_undetermined)
Matcher< VariableMap< double > > IsNear(VariableMap< double > expected, const double tolerance)
Matcher< SolveResult > IsOptimal(const std::optional< double > expected_primal_objective, const double tolerance)
testing::Matcher< SolveResult > TerminatesWithReasonNoSolutionFound(const Limit expected, const bool allow_limit_undetermined)
In SWIG mode, we don't want anything besides these top-level includes.
std::string ProtobufShortDebugString(const P &message)
#define ASSERT_OK_AND_ASSIGN(lhs, rexpr)
Parameters for the BranchPrioritiesTest suite below.
SolveParameters solve_params
SolverType solver_type
The tested solver.
Parameters for the LazyConstraintsTest suite below.
SolveParameters nerfed_solve_params
SolverType solver_type
The tested solver.
static ModelSolveParameters OnlySomePrimalVariables(const Collection &variables)
Parameters for the MipSolutionHintTest suite below.
SolverType solver_type
The tested solver.
std::optional< SolveParameters > single_hint_params
std::optional< SolveParameters > two_hint_params
std::string hint_accepted_message_regex
SolveParametersProto Proto() const