20#include "absl/base/optimization.h"
21#include "absl/status/status.h"
22#include "absl/status/statusor.h"
23#include "absl/strings/string_view.h"
24#include "absl/time/time.h"
25#include "gtest/gtest.h"
38using ::testing::AnyOf;
39using ::testing::DoubleNear;
40using ::testing::HasSubstr;
41using ::testing::status::IsOkAndHolds;
42using ::testing::status::StatusIs;
45 "This test is disabled as the solver does not support multiple objective "
48 "This test is disabled as the solver does not support integer variables";
71 <<
", supports_auxiliary_objectives: "
73 <<
", supports_incremental_objective_add_and_delete: "
76 <<
", supports_incremental_objective_modification: "
78 <<
", supports_integer_variables: "
85TEST_P(SimpleMultiObjectiveTest, CanBuildMultiObjectiveModel) {
87 const Variable x = model.AddContinuousVariable(0.0, 1.0,
"x");
88 model.AddMaximizationObjective(
x, 2);
89 model.AddMinimizationObjective(-3.0 *
x + 2.0, 1);
91 if (GetParam().supports_auxiliary_objectives) {
95 StatusIs(AnyOf(absl::StatusCode::kInvalidArgument,
96 absl::StatusCode::kUnimplemented),
97 HasSubstr(
"multiple objectives")));
109 if (!GetParam().supports_auxiliary_objectives) {
113 const Variable x = model.AddContinuousVariable(0.0, 1.0,
"x");
114 const Variable y = model.AddContinuousVariable(0.0, 1.0,
"y");
115 model.AddLinearConstraint(
x + y <= 1.5);
118 model.AddMaximizationObjective(
x + 3.0 * y + 2.0, 1);
122 EXPECT_EQ(result.objective_value(o), 4.5);
133 if (!GetParam().supports_auxiliary_objectives) {
137 const Variable x = model.AddContinuousVariable(0.0, 1.0,
"x");
138 const Variable y = model.AddContinuousVariable(0.0, 1.0,
"y");
139 model.AddLinearConstraint(
x + y <= 1.5);
142 model.AddMaximizationObjective(
x + 3.0 * y + 2.0, 1);
146 EXPECT_EQ(result.objective_value(o), 4.5);
150 if (!GetParam().supports_auxiliary_objectives) {
154 model.set_objective_priority(model.primary_objective(), 1);
155 model.AddAuxiliaryObjective(1);
157 StatusIs(absl::StatusCode::kInvalidArgument,
158 HasSubstr(
"repeated objective priority: 1")));
162 if (!GetParam().supports_auxiliary_objectives) {
166 model.AddAuxiliaryObjective(1);
167 model.AddAuxiliaryObjective(1);
169 StatusIs(absl::StatusCode::kInvalidArgument,
170 HasSubstr(
"repeated objective priority: 1")));
174struct SimpleMultiObjectiveSolveResult {
176 double solution = 0.0;
177 double priority_0_objective_value = 0.0;
178 double priority_1_objective_value = 0.0;
181enum class ObjectiveType { kPrimary, kAuxiliary };
182enum class ToleranceType { kAbsolute, kRelative };
195absl::StatusOr<SimpleMultiObjectiveSolveResult> SolveWithObjectiveDegradation(
197 const ObjectiveType priority_0_type,
const ObjectiveType priority_1_type,
198 const ToleranceType tolerance_type) {
199 if (priority_0_type == ObjectiveType::kPrimary &&
200 priority_1_type == ObjectiveType::kPrimary) {
201 return absl::InvalidArgumentError(
"Both objectives cannot both be primary");
204 const Variable x = model.AddIntegerVariable(0.0, 2.0,
"x");
206 switch (priority_0_type) {
207 case ObjectiveType::kPrimary:
209 model.set_objective_priority(model.primary_objective(), 0);
210 return model.primary_objective();
211 case ObjectiveType::kAuxiliary:
212 return model.AddMaximizationObjective(
x, 0);
217 switch (priority_1_type) {
218 case ObjectiveType::kPrimary:
220 model.set_objective_priority(model.primary_objective(), 1);
221 return model.primary_objective();
222 case ObjectiveType::kAuxiliary:
223 return model.AddMinimizationObjective(
x, 1);
228 switch (tolerance_type) {
229 case ToleranceType::kAbsolute:
230 model_parameters.objective_parameters[priority_0]
231 .objective_degradation_absolute_tolerance = 1.0;
233 case ToleranceType::kRelative:
234 model_parameters.objective_parameters[priority_0]
235 .objective_degradation_relative_tolerance = 0.5;
239 Solve(model, solver_type,
240 {.parameters = parameters, .model_parameters = model_parameters}));
241 if (!result.has_primal_feasible_solution()) {
242 return absl::InternalError(
"No feasible solution found");
244 return SimpleMultiObjectiveSolveResult{
245 .termination = result.termination.reason,
246 .solution = result.best_primal_solution().variable_values.at(
x),
247 .priority_0_objective_value = result.objective_value(priority_0),
248 .priority_1_objective_value = result.objective_value(priority_1)};
252 if (!GetParam().supports_auxiliary_objectives) {
256 SolveWithObjectiveDegradation(
257 GetParam().solver_type, GetParam().parameters,
258 ObjectiveType::kPrimary,
259 ObjectiveType::kAuxiliary,
260 ToleranceType::kAbsolute));
268 AuxiliaryObjectiveDegradationAbsoluteTolerance) {
269 if (!GetParam().supports_auxiliary_objectives) {
273 SolveWithObjectiveDegradation(
274 GetParam().solver_type, GetParam().parameters,
275 ObjectiveType::kAuxiliary,
276 ObjectiveType::kPrimary,
277 ToleranceType::kAbsolute));
285 if (!GetParam().supports_auxiliary_objectives) {
289 SolveWithObjectiveDegradation(
290 GetParam().solver_type, GetParam().parameters,
291 ObjectiveType::kPrimary,
292 ObjectiveType::kAuxiliary,
293 ToleranceType::kRelative));
303 SingleObjectiveModelWithObjectiveDegradationAbsoluteTolerance) {
304 if (!GetParam().supports_auxiliary_objectives) {
308 const Variable x = model.AddIntegerVariable(0.0, 1.0,
"x");
312 .objective_degradation_absolute_tolerance = 0.5;
314 Solve(model, GetParam().solver_type,
315 {.parameters = GetParam().parameters,
316 .model_parameters = model_parameters}));
323 SingleObjectiveModelWithObjectiveDegradationRelativeTolerance) {
324 if (!GetParam().supports_auxiliary_objectives) {
328 const Variable x = model.AddIntegerVariable(0.0, 1.0,
"x");
332 .objective_degradation_relative_tolerance = 0.5;
334 Solve(model, GetParam().solver_type,
335 {.parameters = GetParam().parameters,
336 .model_parameters = model_parameters}));
341 AuxiliaryObjectiveDegradationRelativeTolerance) {
342 if (!GetParam().supports_auxiliary_objectives) {
346 SolveWithObjectiveDegradation(
347 GetParam().solver_type, GetParam().parameters,
348 ObjectiveType::kAuxiliary,
349 ObjectiveType::kPrimary,
350 ToleranceType::kRelative));
362absl::StatusOr<std::unique_ptr<Model>> Load23588MiplibInstance() {
365 ReadMpsFile(absl::StrCat(
"ortools/math_opt/solver_tests/testdata/"
373 MultiObjectiveModelWithAuxiliaryObjectiveTimeLimit) {
374 if (!GetParam().supports_integer_variables) {
378 GTEST_SKIP() <<
"Ignoring this test because Xpress does not support per "
379 "objective time limits at the moment";
382 Load23588MiplibInstance());
383 const Objective aux_obj = model->AddMaximizationObjective(
384 model->primary_objective().AsLinearExpression(), 1);
385 model->clear_objective();
387 .parameters = GetParam().parameters,
388 .model_parameters = {
389 .objective_parameters = {
390 {aux_obj, {.time_limit = absl::Milliseconds(1)}}}}};
391 const auto result =
Solve(*model, GetParam().solver_type, args);
392 if (!GetParam().supports_auxiliary_objectives) {
393 EXPECT_THAT(result, StatusIs(absl::StatusCode::kInvalidArgument,
394 HasSubstr(
"multiple objectives")));
402 EXPECT_LE(result->solve_stats.solve_time, absl::Seconds(1));
408 MultiObjectiveModelWithPrimaryObjectiveTimeLimit) {
409 if (!GetParam().supports_integer_variables) {
413 GTEST_SKIP() <<
"Ignoring this test because Xpress does not support per "
414 "objective time limits at the moment";
417 Load23588MiplibInstance());
418 model->AddMaximizationObjective(0, 1);
420 .parameters = GetParam().parameters,
421 .model_parameters = {
422 .objective_parameters = {{model->primary_objective(),
423 {.time_limit = absl::Milliseconds(1)}}}}};
424 const auto result =
Solve(*model, GetParam().solver_type, args);
425 if (!GetParam().supports_auxiliary_objectives) {
426 EXPECT_THAT(result, StatusIs(absl::StatusCode::kInvalidArgument,
427 HasSubstr(
"multiple objectives")));
435 EXPECT_LE(result->solve_stats.solve_time, absl::Seconds(1));
442 SingleObjectiveModelWithPrimaryObjectiveTimeLimit) {
443 if (!GetParam().supports_auxiliary_objectives) {
446 if (!GetParam().supports_integer_variables) {
450 Load23588MiplibInstance());
452 .parameters = GetParam().parameters,
453 .model_parameters = {
454 .objective_parameters = {{model->primary_objective(),
455 {.time_limit = absl::Milliseconds(1)}}}}};
458 Solve(*model, GetParam().solver_type, args));
463 EXPECT_LE(result.solve_stats.solve_time, absl::Seconds(1));
470 SingleObjectiveModelTakesMininumFromPrimaryAndGlobalTimeLimits) {
471 if (!GetParam().supports_auxiliary_objectives) {
474 if (!GetParam().supports_integer_variables) {
478 GTEST_SKIP() <<
"Ignoring this test because Xpress does not support per "
479 "objective time limits at the moment";
482 Load23588MiplibInstance());
484 .parameters = GetParam().parameters,
485 .model_parameters = {
486 .objective_parameters = {{model->primary_objective(),
487 {.time_limit = absl::Seconds(10)}}}}};
490 Solve(*model, GetParam().solver_type, args));
495 EXPECT_LE(result.solve_stats.solve_time, absl::Seconds(1));
501 SolverWithoutSupportErrorsWithPrimaryObjectiveParameters) {
502 if (GetParam().supports_auxiliary_objectives) {
507 .parameters = GetParam().parameters,
508 .model_parameters = {
509 .objective_parameters = {
510 {model.primary_objective(), {.time_limit = absl::Seconds(10)}}}}};
512 StatusIs(absl::StatusCode::kInvalidArgument,
513 HasSubstr(
"primary_objective_parameters")));
534 const Variable x = model.AddContinuousVariable(0.0, 1.0,
"x");
535 const Variable y = model.AddContinuousVariable(0.0, 1.0,
"y");
536 model.AddLinearConstraint(
x + y <= 1.5);
544 ASSERT_THAT(solver->Solve({.parameters = GetParam().parameters}),
548 model.AddMaximizationObjective(
x + 3.0 * y + 2.0, 1);
550 if (!GetParam().supports_auxiliary_objectives) {
559 StatusIs(AnyOf(absl::StatusCode::kInvalidArgument,
560 absl::StatusCode::kUnimplemented),
561 AllOf(HasSubstr(
"multiple objective"),
563 Not(HasSubstr(
"update failed")),
565 HasSubstr(
"solver re-creation failed"))));
571 IsOkAndHolds(GetParam().supports_incremental_objective_add_and_delete
576 solver->SolveWithoutUpdate({.parameters = GetParam().parameters}));
578 EXPECT_EQ(result.objective_value(o), 4.5);
598 if (!GetParam().supports_auxiliary_objectives) {
601 if (!GetParam().supports_incremental_objective_add_and_delete) {
603 <<
"Ignoring this test as it requires support for incremental solve";
606 const Variable x = model.AddContinuousVariable(0.0, 1.0,
"x");
607 const Variable y = model.AddContinuousVariable(0.0, 1.0,
"y");
608 model.AddLinearConstraint(
x + y <= 1.5);
610 const Objective o = model.AddMaximizationObjective(3.0, 2);
617 solver->Solve({.parameters = GetParam().parameters}));
619 EXPECT_EQ(result.objective_value(o), 3.0);
623 model.AddMaximizationObjective(
x + 3.0 * y + 2.0, 5);
627 IsOkAndHolds(GetParam().supports_incremental_objective_add_and_delete
632 solver->SolveWithoutUpdate({.parameters = GetParam().parameters}));
634 EXPECT_EQ(result.objective_value(o), 3.0);
635 EXPECT_EQ(result.objective_value(o2), 4.5);
655 if (!GetParam().supports_auxiliary_objectives) {
658 if (!GetParam().supports_incremental_objective_add_and_delete) {
660 <<
"Ignoring this test as it requires support for incremental solve";
663 const Variable x = model.AddContinuousVariable(0.0, 1.0,
"x");
664 const Variable y = model.AddContinuousVariable(0.0, 1.0,
"y");
665 model.AddLinearConstraint(
x + y <= 1.5);
667 const Objective o = model.AddMaximizationObjective(3.0, 2);
669 model.AddMaximizationObjective(
x + 3.0 * y + 2.0, 3);
676 solver->Solve({.parameters = GetParam().parameters}));
678 EXPECT_EQ(result.objective_value(o), 3.0);
679 EXPECT_EQ(result.objective_value(o2), 4.5);
682 model.DeleteAuxiliaryObjective(o);
686 IsOkAndHolds(GetParam().supports_incremental_objective_add_and_delete
691 solver->SolveWithoutUpdate({.parameters = GetParam().parameters}));
693 EXPECT_EQ(result.objective_value(o2), 4.5);
713 ModifyPrimaryObjectiveSenseInMultiObjectiveModel) {
714 if (!GetParam().supports_auxiliary_objectives) {
718 const Variable x = model.AddContinuousVariable(0.0, 1.0,
"x");
719 const Variable y = model.AddContinuousVariable(0.0, 1.0,
"y");
720 model.AddLinearConstraint(
x + y <= 1.5);
723 model.AddMaximizationObjective(
x + 3.0 * y + 2.0, 2);
730 solver->Solve({.parameters = GetParam().parameters}));
733 EXPECT_EQ(result.objective_value(o), 4.5);
736 model.set_minimize();
740 IsOkAndHolds(GetParam().supports_incremental_objective_modification
745 solver->SolveWithoutUpdate({.parameters = GetParam().parameters}));
747 EXPECT_EQ(result.objective_value(o), 5.0);
753 ModifyAuxiliaryObjectiveSenseInMultiObjectiveModel) {
754 if (!GetParam().supports_auxiliary_objectives) {
758 const Variable x = model.AddContinuousVariable(0.0, 1.0,
"x");
759 const Variable y = model.AddContinuousVariable(0.0, 1.0,
"y");
760 model.AddLinearConstraint(
x + y <= 1.5);
762 model.set_objective_priority(model.primary_objective(), 2);
763 const Objective o = model.AddMaximizationObjective(
x, 0);
770 solver->Solve({.parameters = GetParam().parameters}));
772 EXPECT_EQ(result.objective_value(o), 1.0);
775 model.set_minimize(o);
779 IsOkAndHolds(GetParam().supports_incremental_objective_modification
784 solver->SolveWithoutUpdate({.parameters = GetParam().parameters}));
786 EXPECT_EQ(result.objective_value(o), 0.0);
#define ASSIGN_OR_RETURN(lhs, rexpr)
void Maximize(Variable *obj, std::vector< Annotation > search_annotations)
void Minimize(Variable *obj, std::vector< Annotation > search_annotations)
static absl::StatusOr< std::unique_ptr< Model > > FromModelProto(const ModelProto &model_proto)
#define ASSERT_OK(expression)
#define EXPECT_OK(expression)
#define ASSERT_OK_AND_ASSIGN(lhs, rexpr)
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)
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)
absl::StatusOr< ModelProto > ReadMpsFile(const absl::string_view filename)
testing::Matcher< SolveResult > TerminatesWithLimit(const Limit expected, const bool allow_limit_undetermined)
std::ostream & operator<<(std::ostream &ostr, const SecondOrderConeConstraint &constraint)
constexpr absl::string_view kNoMultiObjectiveSupportMessage
Matcher< UpdateResult > DidUpdate()
constexpr double kTolerance
constexpr absl::string_view kNoIntegerVariableSupportMessage
Matcher< SolveResult > IsOptimal(const std::optional< double > expected_primal_objective, const double tolerance)
std::string ProtobufShortDebugString(const P &message)
ObjectiveMap< ObjectiveParameters > objective_parameters
bool supports_integer_variables
MultiObjectiveTestParameters(SolverType solver_type, SolveParameters parameters, bool supports_auxiliary_objectives, bool supports_incremental_objective_add_and_delete, bool supports_incremental_objective_modification, bool supports_integer_variables)
bool supports_auxiliary_objectives
SolveParameters parameters
bool supports_incremental_objective_modification
bool supports_incremental_objective_add_and_delete
SolveParameters parameters
absl::Duration time_limit
SolveParametersProto Proto() const