24#include "absl/container/flat_hash_set.h"
25#include "absl/log/check.h"
26#include "absl/status/status.h"
27#include "absl/status/statusor.h"
28#include "absl/strings/escaping.h"
29#include "absl/strings/str_cat.h"
30#include "absl/strings/str_join.h"
31#include "absl/strings/string_view.h"
32#include "absl/synchronization/mutex.h"
33#include "absl/types/span.h"
34#include "gtest/gtest.h"
40#include "ortools/math_opt/callback.pb.h"
52 const SolverType solver_type,
const bool support_message_callback,
53 const bool support_interrupter,
const bool integer_variables,
55 : solver_type(solver_type),
56 support_message_callback(support_message_callback),
57 support_interrupter(support_interrupter),
58 integer_variables(integer_variables),
59 ending_substring(
std::move(ending_substring)),
60 solve_parameters(
std::move(solve_parameters)) {}
69 <<
"\", solve_parameters: "
75 const SolverType solver_type,
const bool integer_variables,
76 const bool add_lazy_constraints,
const bool add_cuts,
77 absl::flat_hash_set<CallbackEvent> supported_events,
78 std::optional<SolveParameters> all_solutions,
79 std::optional<SolveParameters> reaches_cut_callback)
80 : solver_type(solver_type),
81 integer_variables(integer_variables),
82 add_lazy_constraints(add_lazy_constraints),
84 supported_events(
std::move(supported_events)),
85 all_solutions(
std::move(all_solutions)),
86 reaches_cut_callback(
std::move(reaches_cut_callback)) {}
92 void operator()(std::string*
const out,
const T
value) {
97absl::StatusOr<std::unique_ptr<Model>> LoadMiplibInstance(
98 const absl::string_view
name) {
100 const ModelProto model_proto,
101 ReadMpsFile(absl::StrCat(
"ortools/math_opt/solver_tests/testdata/",
name,
112 <<
", add_cuts: " << params.
add_cuts <<
", supported_events: "
114 EnumFormatter<CallbackEvent>())
115 <<
", all_solutions: "
119 <<
", reaches_cut_callback: "
130using ::testing::AllOf;
131using ::testing::AnyOf;
132using ::testing::Each;
133using ::testing::HasSubstr;
134using ::testing::IsEmpty;
135using ::testing::Pair;
136using ::testing::UnorderedElementsAre;
137using ::testing::status::IsOkAndHolds;
138using ::testing::status::StatusIs;
140constexpr double kInf = std::numeric_limits<double>::infinity();
147 std::vector<std::string> callback_messages;
148 const auto callback = [&](absl::Span<const std::string> messages) {
149 const absl::MutexLock lock(&mutex);
150 for (
const auto&
message : messages) {
151 callback_messages.push_back(
message);
158 if (!GetParam().support_message_callback) {
163TEST_P(MessageCallbackTest, ObjectiveValueAndEndingSubstring) {
164 Model
model(
"model");
166 model.AddVariable(0, 21.0, GetParam().integer_variables,
"x");
170 std::vector<std::string> callback_messages;
172 SolveArguments args = {
173 .parameters = GetParam().solve_parameters,
175 [&](absl::Span<const std::string> messages) {
176 const absl::MutexLock lock(&mutex);
177 for (
const auto&
message : messages) {
178 callback_messages.push_back(
message);
184 args.parameters.enable_output =
false;
186#ifdef OPERATIONS_RESEARCH_OUTPUT_CAPTURE_SUPPORTED
187 ScopedStdStreamCapture stdout_capture(CapturedStream::kStdout);
191#ifdef OPERATIONS_RESEARCH_OUTPUT_CAPTURE_SUPPORTED
193 std::move(stdout_capture).StopCaptureAndReturnContents(),
199 if (GetParam().support_message_callback) {
200 EXPECT_THAT(absl::StrJoin(callback_messages,
"\n"),
201 AllOf(AnyOf(HasSubstr(
"42"), HasSubstr(
"4.2")),
202 HasSubstr(GetParam().ending_substring)));
209 callback_messages.clear();
210 args.parameters.enable_output =
true;
212#ifdef OPERATIONS_RESEARCH_OUTPUT_CAPTURE_SUPPORTED
213 ScopedStdStreamCapture stdout_capture(CapturedStream::kStdout);
217#ifdef OPERATIONS_RESEARCH_OUTPUT_CAPTURE_SUPPORTED
219 std::move(stdout_capture).StopCaptureAndReturnContents(),
225 if (GetParam().support_message_callback) {
226 EXPECT_THAT(absl::StrJoin(callback_messages,
"\n"),
227 AllOf(AnyOf(HasSubstr(
"42"), HasSubstr(
"4.2")),
228 HasSubstr(GetParam().ending_substring)));
235 args.parameters.enable_output =
true;
236 args.message_callback =
nullptr;
237 callback_messages.clear();
239#ifdef OPERATIONS_RESEARCH_OUTPUT_CAPTURE_SUPPORTED
240 ScopedStdStreamCapture stdout_capture(CapturedStream::kStdout);
244#ifdef OPERATIONS_RESEARCH_OUTPUT_CAPTURE_SUPPORTED
245 EXPECT_THAT(std::move(stdout_capture).StopCaptureAndReturnContents(),
246 AllOf(AnyOf(HasSubstr(
"42"), HasSubstr(
"4.2")),
247 HasSubstr(GetParam().ending_substring)));
253TEST_P(MessageCallbackTest, InterruptAtFirstMessage) {
254 if (!GetParam().support_message_callback) {
255 GTEST_SKIP() <<
"Message callback not supported. Ignoring this test.";
257 if (!GetParam().support_interrupter) {
258 GTEST_SKIP() <<
"Solve interrupter not supported. Ignoring this test.";
260 const std::unique_ptr<const Model>
model =
264 std::vector<std::string> callback_messages;
268 SolveInterrupter interrupter;
269 args.interrupter = &interrupter;
270 args.message_callback = [&](absl::Span<const std::string> messages) {
271 const absl::MutexLock lock(&mutex);
272 for (
const auto&
message : messages) {
273 callback_messages.push_back(
message);
277 interrupter.Interrupt();
284 EXPECT_THAT(absl::StrJoin(callback_messages,
"\n"),
285 Not(HasSubstr(GetParam().ending_substring)));
290TEST_P(CallbackTest, EventPresolve) {
293 <<
"Test skipped because this solver does not support this event.";
296 Model
model(
"model");
297 Variable
x =
model.AddVariable(0, 2.0, GetParam().integer_variables,
"x");
298 Variable
y =
model.AddVariable(0, 3.0, GetParam().integer_variables,
"y");
299 model.AddLinearConstraint(
y <= 1.0);
301 SolveArguments args = {
304 std::optional<CallbackData> last_presolve_data;
305 args.callback = [&](
const CallbackData& callback_data) {
306 const absl::MutexLock lock(&mutex);
307 last_presolve_data = callback_data;
308 return CallbackResult();
314 ASSERT_TRUE(last_presolve_data.has_value());
315 EXPECT_EQ(last_presolve_data->presolve_stats.removed_variables(), 2);
316 EXPECT_EQ(last_presolve_data->presolve_stats.removed_constraints(), 1);
319TEST_P(CallbackTest, EventSimplex) {
322 <<
"Test skipped because this solver does not support this event.";
325 Model
model(
"model");
326 Variable
x1 =
model.AddVariable(0, 2.0,
false,
"x1");
327 Variable
x2 =
model.AddVariable(0, 3.0,
false,
"x2");
328 Variable x3 =
model.AddVariable(0, 4.0,
false,
"x3");
349 std::vector<CallbackDataProto::SimplexStats> stats;
350 args.callback = [&](
const CallbackData& callback_data) {
351 const absl::MutexLock lock(&mutex);
352 stats.push_back(callback_data.simplex_stats);
353 return CallbackResult();
358 ASSERT_GE(stats.size(), 3);
359 for (
const CallbackDataProto::SimplexStats& s : stats) {
362 EXPECT_NEAR(s.primal_infeasibility(), 0,
kTolerance);
365 EXPECT_EQ(stats[0].iteration_count(), 0);
366 EXPECT_GT(stats[0].dual_infeasibility(), 0.0);
369 EXPECT_GE(stats.back().iteration_count(), 3);
373TEST_P(CallbackTest, EventBarrier) {
376 <<
"Test skipped because this solver does not support this event.";
380 const std::unique_ptr<const Model>
model =
388 std::vector<CallbackDataProto::BarrierStats> stats;
389 args.callback = [&](
const CallbackData& callback_data) {
390 const absl::MutexLock lock(&mutex);
391 stats.push_back(callback_data.barrier_stats);
392 return CallbackResult();
399 ASSERT_GE(stats.size(), 1);
400 EXPECT_GE(stats.back().iteration_count(), 3);
403TEST_P(CallbackTest, EventSolutionAlwaysCalled) {
405 GTEST_SKIP() <<
"Test skipped because this solver does not support "
406 "CallbackEvent::kMipSolution.";
410 ASSERT_TRUE(GetParam().integer_variables);
412 Model
model(
"model");
413 const Variable
x =
model.AddBinaryVariable(
"x");
414 const Variable
y =
model.AddBinaryVariable(
"y");
415 model.AddLinearConstraint(
x +
y <= 1);
418 SolveArguments args = {
421 bool cb_called =
false;
422 bool cb_called_on_optimal =
false;
423 args.callback = [&](
const CallbackData& callback_data) {
424 const absl::MutexLock lock(&mutex);
427 if (!callback_data.solution.has_value()) {
428 ADD_FAILURE() <<
"callback_data.solution should always be set at event "
429 "MIP_SOLUTION but was empty";
430 return CallbackResult();
437 cb_called_on_optimal =
true;
439 return CallbackResult();
443 EXPECT_TRUE(cb_called);
444 EXPECT_TRUE(cb_called_on_optimal);
447TEST_P(CallbackTest, EventSolutionInterrupt) {
449 GTEST_SKIP() <<
"Test skipped because this solver does not support "
450 "CallbackEvent::kMipSolution.";
454 ASSERT_TRUE(GetParam().integer_variables);
457 const std::unique_ptr<const Model>
model =
459 const SolveArguments args = {
463 .callback = [&](
const CallbackData& ) {
464 return CallbackResult{.terminate =
true};
469 EXPECT_TRUE(result.has_primal_feasible_solution());
472TEST_P(CallbackTest, EventSolutionCalledMoreThanOnce) {
474 GTEST_SKIP() <<
"Test skipped because this solver does not support "
475 "CallbackEvent::kMipSolution.";
477 if (!GetParam().all_solutions.has_value()) {
478 GTEST_SKIP() <<
"Test skipped because this solver does not support "
479 "getting all solutions.";
483 ASSERT_TRUE(GetParam().integer_variables);
485 Model
model(
"model");
486 const Variable
x =
model.AddBinaryVariable(
"x");
487 const Variable
y =
model.AddBinaryVariable(
"y");
488 const Variable z =
model.AddBinaryVariable(
"z");
489 model.AddLinearConstraint(
x +
y + z <= 1);
491 SolveArguments args = {
492 .parameters = *GetParam().all_solutions,
496 bool cb_called_on_zero =
false;
497 bool cb_called_on_x =
false;
498 bool cb_called_on_y =
false;
499 bool cb_called_on_z =
false;
500 args.callback = [&](
const CallbackData& callback_data) {
501 const absl::MutexLock lock(&mutex);
503 if (!callback_data.solution.has_value()) {
504 ADD_FAILURE() <<
"callback_data.solution should always be set at event "
505 "MIP_SOLUTION but was empty";
506 return CallbackResult();
510 IsNear({{
x, 1.0}, {
y, 0.0}, {z, 0.0}}),
511 IsNear({{
x, 0.0}, {
y, 1.0}, {z, 0.0}}),
512 IsNear({{
x, 0.0}, {
y, 0.0}, {z, 1.0}})));
514 cb_called_on_x =
true;
516 cb_called_on_y =
true;
518 cb_called_on_z =
true;
520 cb_called_on_zero =
true;
522 return CallbackResult();
526 EXPECT_TRUE(cb_called_on_zero);
527 EXPECT_TRUE(cb_called_on_x);
528 EXPECT_TRUE(cb_called_on_y);
529 EXPECT_TRUE(cb_called_on_z);
532TEST_P(CallbackTest, EventSolutionLazyConstraint) {
534 GTEST_SKIP() <<
"Test skipped because this solver does not support "
535 "CallbackEvent::kMipSolution.";
537 if (!GetParam().add_lazy_constraints) {
538 GTEST_SKIP() <<
"Test skipped because this solver does not support adding "
543 ASSERT_TRUE(GetParam().integer_variables);
545 Model
model(
"model");
546 const Variable
x =
model.AddBinaryVariable(
"x");
547 const Variable
y =
model.AddBinaryVariable(
"y");
550 SolveArguments args = {
552 .add_lazy_constraints =
true}};
554 args.callback = [&](
const CallbackData& callback_data) -> CallbackResult {
555 if (!callback_data.solution.has_value()) {
556 ADD_FAILURE() <<
"callback_data.solution should always be set at event "
557 "MIP_SOLUTION but was empty";
561 if (sol.size() != 2) {
563 <<
"callback_data.solution should have two elements but found: "
567 const double x_val = sol.at(
x);
568 const double y_val = sol.at(
y);
569 CallbackResult result;
570 if (x_val + y_val >= 1.0 + 1e-5) {
571 result.AddLazyConstraint(
x +
y <= 1.0);
580TEST_P(CallbackTest, EventSolutionLazyConstraintWithLinearConstraints) {
582 GTEST_SKIP() <<
"Test skipped because this solver does not support "
583 "CallbackEvent::kMipSolution.";
585 if (!GetParam().add_lazy_constraints) {
586 GTEST_SKIP() <<
"Test skipped because this solver does not support adding "
591 ASSERT_TRUE(GetParam().integer_variables);
593 Model
model(
"model");
594 const Variable
x =
model.AddBinaryVariable(
"x");
595 const Variable
y =
model.AddBinaryVariable(
"y");
596 const Variable z =
model.AddBinaryVariable(
"z");
598 model.AddLinearConstraint(
x +
y + z >= 1.0);
600 SolveArguments args = {
602 .add_lazy_constraints =
true}};
604 args.callback = [&](
const CallbackData& callback_data) -> CallbackResult {
605 if (!callback_data.solution.has_value()) {
606 ADD_FAILURE() <<
"callback_data.solution should always be set at event "
607 "MIP_SOLUTION but was empty";
611 if (sol.size() != 3) {
613 <<
"callback_data.solution should have two elements but found: "
617 const double x_val = sol.at(
x);
618 const double y_val = sol.at(
y);
619 CallbackResult result;
620 if (x_val + y_val >= 1.0 + 1e-5) {
621 result.AddLazyConstraint(
x +
y <= 1.0);
630TEST_P(CallbackTest, EventSolutionFilter) {
632 GTEST_SKIP() <<
"Test skipped because this solver does not support "
633 "CallbackEvent::kMipSolution.";
637 ASSERT_TRUE(GetParam().integer_variables);
639 Model
model(
"model");
640 const Variable
x =
model.AddBinaryVariable(
"x");
641 const Variable
y =
model.AddBinaryVariable(
"y");
642 model.AddLinearConstraint(
x +
y <= 1);
645 SolveArguments args = {.callback_registration = {
649 bool cb_called =
false;
650 bool cb_called_on_optimal =
false;
651 args.callback = [&](
const CallbackData& callback_data) {
652 const absl::MutexLock lock(&mutex);
655 if (!callback_data.solution.has_value()) {
656 ADD_FAILURE() <<
"callback_data.solution should always be set at event "
657 "MIP_SOLUTION but was empty";
658 return CallbackResult();
663 cb_called_on_optimal =
true;
665 return CallbackResult();
669 EXPECT_TRUE(cb_called);
670 EXPECT_TRUE(cb_called_on_optimal);
673TEST_P(CallbackTest, EventNodeCut) {
675 GTEST_SKIP() <<
"Test skipped because this solver does not support "
676 "CallbackEvent::kMipNode.";
678 if (!GetParam().add_cuts) {
679 GTEST_SKIP() <<
"Test skipped because this solver does not support adding "
682 if (!GetParam().reaches_cut_callback.has_value()) {
683 GTEST_SKIP() <<
"Test skipped, needs reaches_cut_callback to be set.";
687 ASSERT_TRUE(GetParam().integer_variables);
702 Model
model(
"model");
703 constexpr int n = 10;
704 std::vector<Variable>
x;
705 for (
int i = 0;
i < n; ++
i) {
706 x.push_back(
model.AddBinaryVariable(absl::StrCat(
"x", i)));
708 for (
int i = 0;
i < n; ++
i) {
709 for (
int j = i + 1; j < n; ++j) {
710 for (
int k = j + 1; k < n; ++k) {
711 model.AddLinearConstraint(
x[i] +
x[j] +
x[k] <= 2.0);
717 for (
const bool use_cut : {
false,
true}) {
718 SCOPED_TRACE(absl::StrCat(
"use_cut:", use_cut));
719 SolveArguments args = {.parameters =
720 GetParam().reaches_cut_callback.value()};
721 args.parameters.node_limit = 1;
726 args.callback = [&](
const CallbackData& callback_data) -> CallbackResult {
727 if (!callback_data.solution.has_value()) {
731 CallbackResult result;
732 if (
Sum(
x).Evaluate(sol) > 2.0 + 1.0e-5) {
733 result.AddUserCut(
Sum(
x) <= 2.0);
753TEST_P(CallbackTest, EventNodeFilter) {
755 GTEST_SKIP() <<
"Test skipped because this solver does not support "
756 "CallbackEvent::kMipNode.";
760 ASSERT_TRUE(GetParam().integer_variables);
766 LoadMiplibInstance(
"23588"));
767 const std::vector<Variable> variables =
model->SortedVariables();
768 CHECK_GE(variables.size(), 3);
769 const Variable x0 = variables[0];
770 const Variable
x2 = variables[2];
772 SolveArguments args = {.callback_registration = {
776 std::vector<VariableMap<double>> solutions;
777 int empty_solution_count = 0;
778 args.callback = [&](
const CallbackData& callback_data) {
780 const absl::MutexLock lock(&mutex);
781 if (!callback_data.solution.has_value()) {
782 empty_solution_count++;
784 solutions.push_back(callback_data.solution.value());
786 return CallbackResult();
790 LOG(INFO) <<
"callback_data.solution was not set " << empty_solution_count
792 EXPECT_THAT(solutions, Each(UnorderedElementsAre(Pair(x0, _), Pair(
x2, _))));
795TEST_P(CallbackTest, StatusPropagation) {
797 GTEST_SKIP() <<
"Test skipped because this solver does not support "
798 "CallbackEvent::kMipSolution.";
800 if (!GetParam().add_lazy_constraints) {
801 GTEST_SKIP() <<
"Test skipped because this solver does not support adding "
806 ASSERT_TRUE(GetParam().integer_variables);
809 Model
model(
"model");
810 const Variable
x =
model.AddBinaryVariable(
"x");
811 const Variable
y =
model.AddBinaryVariable(
"y");
814 SolveArguments args = {
816 .add_lazy_constraints =
true}};
818 bool added_cut =
false;
819 args.callback = [&](
const CallbackData& ) {
820 const absl::MutexLock lock(&mutex);
821 CallbackResult result;
823 result.AddLazyConstraint(
x +
y <= -
kInf);
829 StatusIs(absl::StatusCode::kInvalidArgument,
830 HasSubstr(
"Invalid negative infinite value; for "
831 "GeneratedLinearConstraint.upper_bound")));
834TEST_P(CallbackTest, UnsupportedEvents) {
835 Model
model(
"model");
836 model.AddVariable(0, 1.0, GetParam().integer_variables,
"x");
839 if (GetParam().supported_events.contains(event)) {
842 SCOPED_TRACE(absl::StrCat(
"event: ",
EnumToString(event)));
844 const SolveArguments args = {
845 .callback_registration = {.events = {
event}},
846 .callback = [](
const CallbackData&) {
return CallbackResult{}; }};
849 StatusIs(absl::StatusCode::kInvalidArgument,
#define ASSIGN_OR_RETURN(lhs, rexpr)
static absl::StatusOr< std::unique_ptr< Model > > FromModelProto(const ModelProto &model_proto)
const std::string name
A name for logging purposes.
const MapUtilMappedT< Collection > & FindWithDefault(const Collection &collection, const KeyType &key, const MapUtilMappedT< Collection > &value)
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)
absl::flat_hash_map< Variable, V > VariableMap
SolverType
The solvers supported by MathOpt.
<=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()))
LinearExpression Sum(const Iterable &items)
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
Matcher< Termination > LimitIs(math_opt::Limit limit, const Matcher< std::string > detail_matcher)
std::ostream & operator<<(std::ostream &ostr, const IndicatorConstraint &constraint)
std::unique_ptr< Model > SmallModel(const bool integer)
absl::StatusOr< std::unique_ptr< IncrementalSolver > > NewIncrementalSolver(Model *model, SolverType solver_type, SolverInitArguments arguments)
testing::Matcher< SolveResult > TerminatesWithReasonFeasible(const Limit expected, const bool allow_limit_undetermined)
absl::StatusOr< ModelProto > ReadMpsFile(const absl::string_view filename)
testing::Matcher< SolveResult > TerminatesWithLimit(const Limit expected, const bool allow_limit_undetermined)
CallbackEvent
The supported events during a solve for callbacks.
Matcher< VariableMap< double > > IsNear(VariableMap< double > expected, const double tolerance)
MapFilter< ValueType > MakeKeepKeysFilter(const Collection &keys)
Enum< E >::Proto EnumToProto(std::optional< E > value)
constexpr double kTolerance
std::unique_ptr< Model > DenseIndependentSet(const bool integer, const int n)
absl::string_view EnumToString(E value)
Matcher< SolveResult > IsOptimal(const std::optional< double > expected_primal_objective, const double tolerance)
In SWIG mode, we don't want anything besides these top-level includes.
std::string ProtoEnumToString(ProtoEnumType enum_value)
std::string ProtobufShortDebugString(const P &message)
testing::Matcher< std::string > EmptyOrGurobiLicenseWarningIfGurobi(const bool is_gurobi)
internal::StatusIsMatcher StatusIs(CodeMatcher code_matcher, MessageMatcher message_matcher)
#define ASSERT_OK_AND_ASSIGN(lhs, rexpr)
Parameters for CallbackTest.
std::optional< SolveParameters > all_solutions
CallbackTestParams(SolverType solver_type, bool integer_variables, bool add_lazy_constraints, bool add_cuts, absl::flat_hash_set< CallbackEvent > supported_events, std::optional< SolveParameters > all_solutions, std::optional< SolveParameters > reaches_cut_callback)
absl::flat_hash_set< CallbackEvent > supported_events
The events that should be supported by the solver.
bool add_lazy_constraints
If the solver supports adding lazy constraints at the MIP_SOLUTION event.
bool integer_variables
True if the tests should be performed with integer variables.
bool add_cuts
If the solver supports adding cuts at the event MIP_NODE.
SolverType solver_type
The solver to test.
std::optional< SolveParameters > reaches_cut_callback
static absl::Span< const E > AllValues()
Returns all possible values of the enum.
Parameters for the MessageCallbackTest suite below.
SolveParameters solve_parameters
Additional parameters to control the solve.
bool support_interrupter
True if the solver supports SolveInterrupter.
SolverType solver_type
The tested solver.
bool support_message_callback
std::string ending_substring
A sub-string expected to be found on the last log lines.
MessageCallbackTestParams(SolverType solver_type, bool support_message_callback, bool support_interrupter, bool integer_variables, std::string ending_substring, SolveParameters solve_parameters={})
bool integer_variables
True if the tests should be performed with integer variables.
SolveParametersProto Proto() const
double objective_value
The value objective_vector^T * (solution - center_point).