Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
ip_model_solve_parameters_tests.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <optional>
17#include <ostream>
18#include <sstream>
19#include <string>
20#include <vector>
21
22#include "absl/status/statusor.h"
23#include "gtest/gtest.h"
24#include "ortools/base/gmock.h"
30
31namespace operations_research {
32namespace math_opt {
33namespace {
34
35using ::testing::DoubleNear;
36using ::testing::status::IsOkAndHolds;
37
38std::string PrintParams(const std::optional<SolveParameters>& params) {
39 return params.has_value() ? ProtobufShortDebugString(params->Proto())
40 : "nullopt";
41}
42
43} // namespace
44
45std::ostream& operator<<(std::ostream& out,
46 const SolutionHintTestParams& params) {
47 out << "{ solver_type: " << params.solver_type
48 << " single_hint_params: " << PrintParams(params.single_hint_params)
49 << " two_hint_params: " << PrintParams(params.two_hint_params)
50 << ", hint_message_regex: " << params.hint_accepted_message_regex << " }";
51 return out;
52}
53
54std::ostream& operator<<(std::ostream& out,
55 const BranchPrioritiesTestParams& params) {
56 out << "{ solver_type: " << params.solver_type << " solve_params: "
57 << ProtobufShortDebugString(params.solve_params.Proto()) << " }";
58 return out;
59}
60
61std::ostream& operator<<(std::ostream& out,
62 const LazyConstraintsTestParams& params) {
63 out << "{ solver_type: " << params.solver_type << " solve_params: "
65 return out;
66}
67
68namespace {
69
70TEST_P(IpModelSolveParametersTest, SolutionFilterSkipZeros) {
71 Model model;
72 const Variable x = model.AddBinaryVariable("x");
73 const Variable y = model.AddBinaryVariable("y");
74 model.Maximize(2.0 * x + y);
75 model.AddLinearConstraint(0.0 <= x + y <= 1.5, "c");
77 const SolveResult result,
78 Solve(model, TestedSolver(),
79 {.model_parameters = {
80 .variable_values_filter = {.skip_zero_values = true}}}));
81 ASSERT_THAT(result, IsOptimal(2.0));
82 EXPECT_THAT(result.variable_values(), IsNear({{x, 1.0}}));
83}
84
85TEST_P(IpModelSolveParametersTest, SolutionFilterByKey) {
86 Model model;
87 const Variable x = model.AddBinaryVariable("x");
88 const Variable y = model.AddBinaryVariable("y");
89 model.Maximize(2.0 * x + y);
90 model.AddLinearConstraint(0.0 <= x + y <= 1.5, "c");
91
93 const SolveResult result,
94 Solve(model, TestedSolver(),
95 {.model_parameters =
97 ASSERT_THAT(result, IsOptimal(2.0));
98 EXPECT_THAT(result.variable_values(), IsNear({{y, 0.0}}));
99}
100
101TEST_P(MipSolutionHintTest, SingleHintTest) {
102 if (!SingleHintParams().has_value()) {
103 GTEST_SKIP() << "Single hints not supported. Ignoring this test.";
104 }
105
106 ModelSolveParameters model_parameters;
107
108 Model model("Solution Hint MIP");
109
110 Variable x1 = model.AddBinaryVariable("x1");
111 Variable x2 = model.AddBinaryVariable("x2");
112 model.AddLinearConstraint(x1 + x2 == 1);
113
114 Variable x3 = model.AddBinaryVariable("x3");
115 Variable x4 = model.AddBinaryVariable("x4");
116 model.AddLinearConstraint(x3 + x4 == 1);
117
118 model.Maximize(x1 + 3 * x2 + 2 * x3 + 4 * x4);
119
120 // Only feasible completion of this hint has (x1, x2, x3, x4) = (1, 0, 1, 0)
121 // with objective value equal to 3.
123 hint.variable_values = {{x1, 1.0}, {x4, 0.0}};
124 model_parameters.solution_hints.emplace_back(hint);
125
126 std::ostringstream log;
127 const SolveArguments args = {
128 // SingleHintParams() is expected to set (possibly solver-specific)
129 // parameters to ensure the optimization stops after the first feasible
130 // solution (e.g. solution limit of 1) and that this solution is the one
131 // associated to the hint and not the optimal solution with objective
132 // value 7.
133 .parameters = *SingleHintParams(),
134 .model_parameters = model_parameters,
135 .message_callback = PrinterMessageCallback(log)};
137 Solve(model, TestedSolver(), args));
138 EXPECT_THAT(result,
140 /*allow_limit_undetermined=*/true));
142 result,
144 .variable_values = {{x1, 1.0}, {x2, 0.0}, {x3, 1.0}, {x4, 0.0}},
145 .objective_value = 3.0,
146 .feasibility_status = SolutionStatus::kFeasible}));
147 EXPECT_THAT(log.str(), testing::ContainsRegex(HintAcceptedMessageRegex()));
148}
149
150TEST_P(MipSolutionHintTest, TwoHintTest) {
151 if (!TwoHintParams().has_value()) {
152 GTEST_SKIP() << "Multiple hints not supported. Ignoring this test.";
153 }
154 if (GetParam().solver_type == SolverType::kXpress) {
155 // Xpress has no configuration options to "just complete" a partial
156 // solution hint. For an incomplete solution it will always run simple
157 // heuristics to find a solution. The effort of this heuristic can be
158 // controlled via the USERSOLHEURISTIC control, but both values
159 // 0 (off) and 1 (light) make the test fail: with off no heuristic
160 // is applied on the provided solution and hence the expected solutions
161 // are not found. With light the heuristic finds the optimal solution
162 // from the solution hint.
163 GTEST_SKIP() << "Xpress cannot be forced to only complete a solution.";
164 }
165
166 ModelSolveParameters model_parameters;
167
168 Model model("Solution Hint MIP");
169
170 Variable x1 = model.AddBinaryVariable("x1");
171 Variable x2 = model.AddBinaryVariable("x2");
172 model.AddLinearConstraint(x1 + x2 == 1);
173
174 Variable x3 = model.AddBinaryVariable("x3");
175 Variable x4 = model.AddBinaryVariable("x4");
176 model.AddLinearConstraint(x3 + x4 == 1);
177
178 Variable x5 = model.AddBinaryVariable("x5");
179 Variable x6 = model.AddBinaryVariable("x6");
180 model.AddLinearConstraint(x5 + x6 == 1);
181
182 model.Maximize(x1 + 3 * x2 + 2 * x3 + 4 * x4 + x5 + 2 * x6);
183
184 // Only feasible completion of this hint has
185 // (x1, x2, x3, x4, x5, x6) = (1, 0, 1, 0, 1, 0)
186 // with objective value equal to 4.
188 first_hint.variable_values = {{x1, 1.0}, {x4, 0.0}, {x5, 1.0}};
189 model_parameters.solution_hints.emplace_back(first_hint);
190 const Solution first_solution{
191 .primal_solution = {{.variable_values = {{x1, 1.0},
192 {x2, 0.0},
193 {x3, 1.0},
194 {x4, 0.0},
195 {x5, 1.0},
196 {x6, 0.0}},
197 .objective_value = 4,
198 .feasibility_status = SolutionStatus::kFeasible}}};
199
200 // Only feasible completion of this hint has
201 // (x1, x2, x3, x4, x5, x6) = (1, 0, 1, 0, 0, 1)
202 // with objective value equal to 5.
204 second_hint.variable_values = {{x1, 1.0}, {x4, 0.0}, {x6, 1.0}};
205 model_parameters.solution_hints.emplace_back(second_hint);
206 const Solution second_solution{
207 .primal_solution =
208 PrimalSolution{.variable_values = {{x1, 1.0},
209 {x2, 0.0},
210 {x3, 1.0},
211 {x4, 0.0},
212 {x5, 0.0},
213 {x6, 1.0}},
214 .objective_value = 5,
215 .feasibility_status = SolutionStatus::kFeasible}};
216 std::ostringstream log;
217 const SolveArguments args = {
218 // TwoHintParams() is expected to set (possibly solver-specific)
219 // parameters to ensure the optimization stops after the second feasible
220 // solution (e.g. solution limit of 2) and that these solutions are the
221 // ones associated to the hints and not the optimal solution with
222 // objective value 9.
223 .parameters = *TwoHintParams(),
224 .model_parameters = model_parameters,
225 .message_callback = PrinterMessageCallback(log)};
227 Solve(model, TestedSolver(), args));
229 // Solutions should be objective-ordered and not hint-ordered.
230 // Gurobi does not guarantee that all solution pool entries are feasible, so
231 // we also accept undetermined feasibility status.
233 result.solutions,
234 ElementsAre(IsNear(second_solution),
235 IsNear(first_solution, {.allow_undetermined = true})));
236 EXPECT_THAT(log.str(), testing::ContainsRegex(HintAcceptedMessageRegex()));
237}
238
239TEST_P(BranchPrioritiesTest, PrioritiesAreSetProperly) {
240 // We solve min{ |x| : x in {-2, -1, 1}} = 1 through the following simple
241 // MIP formulation.
242 Model model("Solution Hint MIP");
243 Variable x = model.AddContinuousVariable(-3.0, 1.0, "x");
244 Variable y = model.AddContinuousVariable(0.0, 3.0, "y");
245 Variable zminus2 = model.AddBinaryVariable("zminus2");
246 Variable zminus1 = model.AddBinaryVariable("zminus1");
247 Variable zplus1 = model.AddBinaryVariable("zplus1");
248 model.AddLinearConstraint(zminus2 + zminus1 + zplus1 == 1);
249 model.AddLinearConstraint(-2 * zminus2 - zminus1 + zplus1 == x);
250 model.AddLinearConstraint(x <= y);
251 model.AddLinearConstraint(-x <= y);
252 model.Minimize(y);
253 // The optimal value of the LP relaxation of this formulation is zero and (in
254 // the absence of cuts and preprocessing) the best bound will remain at zero
255 // after branching on variables zminus3 or zminus2. The problem can be solved
256 // by branching on zminus3 and zminus2. However, it can also be solved by
257 // just branching on zplus1. Hence, adding higher branch priority to zplus1
258 // should result in fewer branch-and-bound nodes than adding higher priorities
259 // to zminus3 and zminus2.
260
261 // SolveParams() is expected to set (possibly solver-specific) parameters
262 // to ensure the solver behaves as close as possible to a pure
263 // branch-and-bound solver (e.g. turn presolve, heuristics and cuts off).
264 // Major deviations from this could cause the test to fail.
265 const SolveParameters solve_params = SolveParams();
266
267 // We first solve giving higher branch priority to zplus1
268 // Note: we only store the node count instead of testing its value as this
269 // could be brittle (solvers often differ by one unit on the meaning of node
270 // count).
271 ModelSolveParameters model_parameters;
272 model_parameters.branching_priorities = {
273 {zminus2, 1}, {zminus1, 1}, {zplus1, 2}};
274 const SolveArguments good_args = {.parameters = solve_params,
275 .model_parameters = model_parameters};
276 ASSERT_OK_AND_ASSIGN(const SolveResult good_result,
277 Solve(model, TestedSolver(), good_args));
278 ASSERT_THAT(good_result, IsOptimal());
279 const int good_node_count = good_result.solve_stats.node_count;
280
281 // We then give higher priorities to zminus2 and zminus1 and check it takes
282 // more nodes to solve.
283 model_parameters.branching_priorities = {
284 {zminus2, 2}, {zminus1, 2}, {zplus1, 1}};
285 const SolveArguments bad_args = {.parameters = solve_params,
286 .model_parameters = model_parameters};
287 ASSERT_OK_AND_ASSIGN(const SolveResult bad_result,
288 Solve(model, TestedSolver(), bad_args));
289 ASSERT_THAT(bad_result, IsOptimal());
290 EXPECT_GT(bad_result.solve_stats.node_count, good_node_count);
291}
292
293// See PrioritiesAreSetProperly for details on the model and solve parameters.
294TEST_P(BranchPrioritiesTest, PrioritiesClearedAfterIncrementalSolve) {
295 if (GetParam().solver_type == SolverType::kXpress) {
296 // This test does not work with Xpress since Xpress does not clear/reset
297 // model parameters after a solve. See the comment in XpressSolver::Solve
298 // in xpress_solver.cc.
299 GTEST_SKIP() << "Xpress does not clear model parameters in Solve().";
300 }
301 Model model;
302 Variable x = model.AddContinuousVariable(-3.0, 1.0, "x");
303 Variable y = model.AddContinuousVariable(0.0, 3.0, "y");
304 Variable zminus2 = model.AddBinaryVariable("zminus2");
305 Variable zminus1 = model.AddBinaryVariable("zminus1");
306 Variable zplus1 = model.AddBinaryVariable("zplus1");
307 model.AddLinearConstraint(zminus2 + zminus1 + zplus1 == 1);
308 model.AddLinearConstraint(-2 * zminus2 - zminus1 + zplus1 == x);
309 model.AddLinearConstraint(x <= y);
310 model.AddLinearConstraint(-x <= y);
311 model.Minimize(y);
312
313 // First, we do a static solve with "good" branching priorities as a baseline.
315 const int node_count_good_priorities, ([&]() -> absl::StatusOr<int> {
316 const SolveArguments args = {
317 .parameters = SolveParams(),
318 .model_parameters = {.branching_priorities = {
319 {zminus1, 1}, {zminus2, 1}, {zplus1, 3}}}};
320 ASSIGN_OR_RETURN(const SolveResult result,
321 Solve(model, TestedSolver(), args));
322 RETURN_IF_ERROR(result.termination.EnsureIsOptimal());
323 return result.solve_stats.node_count;
324 }()));
325
326 // Next, we solve incrementally with "good" branching priorities, but a very
327 // tight node limit. We expect the solver to load the priorities, but not to
328 // make any progress towards the optimal solution.
329 ASSERT_OK_AND_ASSIGN(const auto solver,
330 NewIncrementalSolver(&model, TestedSolver()));
331 {
332 SolveParameters params = SolveParams();
333 params.node_limit = 0;
334 const SolveArguments args = {
335 .parameters = params,
336 .model_parameters = {
337 .branching_priorities = {{zminus1, 1}, {zminus2, 1}, {zplus1, 3}}}};
338 ASSERT_OK_AND_ASSIGN(const SolveResult good_result, solver->Solve(args));
339 ASSERT_THAT(good_result, TerminatesWithLimit(Limit::kNode));
340 }
341
342 // Finally, using the same incremental solver we solve with partial branching
343 // priorities, and record the node count. If the previously set branching
344 // priorities are overwritten, these are "good" priorities (zplus1 will be
345 // highest priority); if they were cleared previously, then these are "bad"
346 // priorities (zplus has the lowest priority with a default value of 0).
348 const int node_count_no_priorities, ([&]() -> absl::StatusOr<int> {
349 const SolveArguments args{
350 .parameters = SolveParams(),
351 .model_parameters = {
352 .branching_priorities = {{zminus1, 2}, {zminus2, 2}}}};
353 ASSIGN_OR_RETURN(const SolveResult result, solver->Solve(args));
354 RETURN_IF_ERROR(result.termination.EnsureIsOptimal());
355 return result.solve_stats.node_count;
356 }()));
357
358 // If priorities were properly cleared for the second incremental solve, it
359 // should take more nodes to solve than with the "good" branching priorities.
360 EXPECT_GT(node_count_no_priorities, node_count_good_priorities);
361}
362
363// The problem is:
364// min x
365// s.t. x >= 1 (c)
366// 0 <= x <= 2
367// x integer
368//
369// We mark (c) as a lazy constraint, solve, and verify that the optimal solution
370// returned respects it (i.e., x^* = 1).
371TEST_P(LazyConstraintsTest, LazyConstraintsImposedOnModel) {
372 Model model;
373 Variable x = model.AddIntegerVariable(0, 2, "x");
374 const LinearConstraint c = model.AddLinearConstraint(x >= 1);
375 model.Minimize(x);
376
377 // We intentionally do not use NerfedSolveParams() here: Gurobi produces the
378 // wrong solution with presolve disabled (!), and we only want to test that
379 // the lazy constraint is respected.
380 SolveArguments args = {.model_parameters = {.lazy_linear_constraints = {c}}};
381 args.parameters.enable_output = true;
382 EXPECT_THAT(Solve(model, TestedSolver(), args),
384}
385
386// The problem is:
387// min y
388// s.t. y >= x (c)
389// y >= -x (d)
390// -1 <= x, y <= 1
391// x, y integer
392//
393// With a node limit of 0 and solver parameters set to disable presolve, we
394// expect a dual bound equal to the LP relaxation bound (which is 0). However,
395// if c and d are lazy constraints, they are not included in the LP relaxation,
396// and the bound instead is -1.
397TEST_P(LazyConstraintsTest, AnnotationsAreSetProperly) {
398 Model model;
399 Variable x = model.AddIntegerVariable(-1, 1, "x");
400 Variable y = model.AddIntegerVariable(-1, 1, "y");
401 const LinearConstraint c = model.AddLinearConstraint(y >= x);
402 const LinearConstraint d = model.AddLinearConstraint(y >= -x);
403 model.Minimize(y);
404
405 SolveArguments args = {
406 .parameters = NerfedSolveParams(),
407 .model_parameters = {.lazy_linear_constraints = {c, d}}};
408 args.parameters.node_limit = 0;
410 Solve(model, TestedSolver(), args));
412 EXPECT_THAT(result.best_objective_bound(), DoubleNear(-1, 1.0e-5));
413}
414
415// Same setting as in AnnotationsAreSetProperly above, but we solve twice with
416// an incremental solver: first with the lazy constraint annotations, and then
417// without. If the annotations are cleared after the first, then we expect the
418// second to solve the entire LP (including c and d), giving a dual bound of 0.
419TEST_P(LazyConstraintsTest, AnnotationsAreClearedAfterSolve) {
420 if (GetParam().solver_type == SolverType::kXpress) {
421 // For the AnnotationsAreSetProperly we set STOP_AFTER_LP=1 which stops
422 // Xpress right after the relaxation. Since the same parameters are
423 // also used for the test here, this settings kills the test.
424 GTEST_SKIP() << "Xpress stops too early with shared parameter settings.";
425 }
426 Model model;
427 Variable x = model.AddIntegerVariable(-1, 1, "x");
428 Variable y = model.AddIntegerVariable(-1, 1, "y");
429 const LinearConstraint c = model.AddLinearConstraint(y >= x);
430 const LinearConstraint d = model.AddLinearConstraint(y >= -x);
431 model.Minimize(y);
432 ASSERT_OK_AND_ASSIGN(const auto solver,
433 NewIncrementalSolver(&model, TestedSolver()));
434
435 SolveArguments args = {
436 .parameters = NerfedSolveParams(),
437 .model_parameters = {.lazy_linear_constraints = {c, d}}};
438 args.parameters.node_limit = 0;
439 ASSERT_OK_AND_ASSIGN(const SolveResult bad_result, solver->Solve(args));
440 ASSERT_THAT(bad_result, TerminatesWithReasonNoSolutionFound(Limit::kNode));
441 ASSERT_THAT(bad_result.best_objective_bound(), DoubleNear(-1, 1.0e-5));
442
443 args.model_parameters.lazy_linear_constraints.clear();
444 ASSERT_OK_AND_ASSIGN(const SolveResult good_result, solver->Solve(args));
445 ASSERT_THAT(good_result, TerminatesWithReasonNoSolutionFound(Limit::kNode));
446 EXPECT_THAT(good_result.best_objective_bound(), DoubleNear(0, 1.0e-5));
447}
448
449} // namespace
450} // namespace math_opt
451} // namespace operations_research
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
void Maximize(Variable *obj, std::vector< Annotation > search_annotations)
Definition model.cc:1074
void Minimize(Variable *obj, std::vector< Annotation > search_annotations)
Definition model.cc:1067
void Maximize(double objective)
Definition model.h:1409
Variable AddBinaryVariable(absl::string_view name="")
Definition model.h:975
LinearConstraint AddLinearConstraint(absl::string_view name="")
Definition model.h:1080
#define ASSERT_OK_AND_ASSIGN(lhs, rexpr)
Definition gmock.h:53
Matcher< SolveResult > IsOptimalWithSolution(const double expected_objective, const VariableMap< double > expected_variable_values, const double tolerance)
Definition matchers.cc:778
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 > HasSolution(PrimalSolution expected, const double tolerance)
Definition matchers.cc:824
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
Definition solve.cc:62
<=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)
Definition solve.cc:82
testing::Matcher< SolveResult > TerminatesWithReasonFeasible(const Limit expected, const bool allow_limit_undetermined)
Definition matchers.cc:658
MessageCallback PrinterMessageCallback(std::ostream &output_stream, const absl::string_view prefix)
testing::Matcher< SolveResult > TerminatesWithLimit(const Limit expected, const bool allow_limit_undetermined)
Definition matchers.cc:649
std::ostream & operator<<(std::ostream &ostr, const SecondOrderConeConstraint &constraint)
Matcher< VariableMap< double > > IsNear(VariableMap< double > expected, const double tolerance)
Definition matchers.cc:222
Matcher< SolveResult > IsOptimal(const std::optional< double > expected_primal_objective, const double tolerance)
Definition matchers.cc:763
testing::Matcher< SolveResult > TerminatesWithReasonNoSolutionFound(const Limit expected, const bool allow_limit_undetermined)
Definition matchers.cc:666
OR-Tools root namespace.
std::string ProtobufShortDebugString(const P &message)
Definition proto_utils.h:46
BranchPrioritiesTestParams(SolverType solver_type, SolveParameters solve_params)
LazyConstraintsTestParams(SolverType solver_type, SolveParameters solve_params)
static ModelSolveParameters OnlySomePrimalVariables(const Collection &variables)
SolutionHintTestParams(SolverType solver_type, std::optional< SolveParameters > single_hint_params, std::optional< SolveParameters > two_hint_params, std::string hint_accepted_message_regex)
const VariableMap< double > & variable_values() const