Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
logical_constraint_tests.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 <memory>
17#include <ostream>
18#include <utility>
19
20#include "absl/status/status.h"
21#include "absl/strings/string_view.h"
22#include "gtest/gtest.h"
23#include "ortools/base/gmock.h"
26#include "ortools/math_opt/model_update.pb.h"
27#include "ortools/math_opt/result.pb.h"
29
31
33 const SolverType solver_type, SolveParameters parameters,
34 const bool supports_integer_variables, const bool supports_sos1,
35 const bool supports_sos2, const bool supports_indicator_constraints,
36 const bool supports_incremental_add_and_deletes,
37 const bool supports_incremental_variable_deletions,
38 const bool supports_deleting_indicator_variables,
39 const bool supports_updating_binary_variables)
40 : solver_type(solver_type),
42 supports_integer_variables(supports_integer_variables),
43 supports_sos1(supports_sos1),
44 supports_sos2(supports_sos2),
45 supports_indicator_constraints(supports_indicator_constraints),
46 supports_incremental_add_and_deletes(
47 supports_incremental_add_and_deletes),
48 supports_incremental_variable_deletions(
49 supports_incremental_variable_deletions),
50 supports_deleting_indicator_variables(
51 supports_deleting_indicator_variables),
52 supports_updating_binary_variables(supports_updating_binary_variables) {}
53
54std::ostream& operator<<(std::ostream& out,
55 const LogicalConstraintTestParameters& params) {
56 out << "{ solver_type: " << params.solver_type
57 << ", parameters: " << ProtobufShortDebugString(params.parameters.Proto())
58 << ", supports_integer_variables: "
59 << (params.supports_integer_variables ? "true" : "false")
60 << ", supports_sos1: " << (params.supports_sos1 ? "true" : "false")
61 << ", supports_sos2: " << (params.supports_sos2 ? "true" : "false")
62 << ", supports_indicator_constraints: "
63 << (params.supports_indicator_constraints ? "true" : "false")
64 << ", supports_incremental_add_and_deletes: "
65 << (params.supports_incremental_add_and_deletes ? "true" : "false")
66 << ", supports_incremental_variable_deletions: "
67 << (params.supports_incremental_variable_deletions ? "true" : "false")
68 << ", supports_deleting_indicator_variables: "
69 << (params.supports_deleting_indicator_variables ? "true" : "false")
70 << ", supports_updating_binary_variables: "
71 << (params.supports_updating_binary_variables ? "true" : "false") << " }";
72 return out;
73}
74
75namespace {
76
77using ::testing::AnyOf;
78using ::testing::HasSubstr;
79using ::testing::status::IsOkAndHolds;
80using ::testing::status::StatusIs;
81
82constexpr absl::string_view no_sos1_support_message =
83 "This test is disabled as the solver does not support sos1 constraints";
84constexpr absl::string_view no_sos2_support_message =
85 "This test is disabled as the solver does not support sos2 constraints";
86constexpr absl::string_view no_indicator_support_message =
87 "This test is disabled as the solver does not support indicator "
88 "constraints";
89
90// We test SOS1 constraints with both explicit weights and default weights.
91TEST_P(SimpleLogicalConstraintTest, CanBuildSos1Model) {
92 Model model;
93 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
94 model.AddSos1Constraint({3.0 * x + 2.0}, {3.0});
95 model.AddSos1Constraint({2.0 * x + 1.0}, {});
96 if (GetParam().supports_sos1) {
97 EXPECT_OK(NewIncrementalSolver(&model, GetParam().solver_type, {}));
98 } else {
99 EXPECT_THAT(NewIncrementalSolver(&model, GetParam().solver_type, {}),
100 StatusIs(AnyOf(absl::StatusCode::kInvalidArgument,
101 absl::StatusCode::kUnimplemented),
102 HasSubstr("sos1 constraints")));
103 }
104}
105
106// We test SOS2 constraints with both explicit weights and default weights.
107TEST_P(SimpleLogicalConstraintTest, CanBuildSos2Model) {
108 Model model;
109 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
110 model.AddSos2Constraint({3.0 * x + 2.0}, {3.0});
111 model.AddSos2Constraint({2.0 * x + 1.0}, {});
112 if (GetParam().supports_sos2) {
113 EXPECT_OK(NewIncrementalSolver(&model, GetParam().solver_type, {}));
114 } else {
115 EXPECT_THAT(NewIncrementalSolver(&model, GetParam().solver_type, {}),
116 StatusIs(AnyOf(absl::StatusCode::kInvalidArgument,
117 absl::StatusCode::kUnimplemented),
118 HasSubstr("sos2 constraints")));
119 }
120}
121
122// We solve
123//
124// max x + 2y
125// s.t. {x, y} is SOS-1
126// 0 <= x, y <= 1
127//
128// The optimal solution is (x*, y*) = (0, 1) with objective value 2.
129TEST_P(SimpleLogicalConstraintTest, SimpleSos1Instance) {
130 if (!GetParam().supports_sos1) {
131 GTEST_SKIP() << no_sos1_support_message;
132 }
133 Model model;
134 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
135 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
136 model.Maximize(x + 2.0 * y);
137 model.AddSos1Constraint({x, y}, {});
138
139 EXPECT_THAT(SimpleSolve(model),
140 IsOkAndHolds(IsOptimalWithSolution(2.0, {{x, 0.0}, {y, 1.0}})));
141}
142
143// We solve
144//
145// max 2x + y + 3z
146// s.t. {x, y, z} is SOS-2
147// 0 <= x, y, z <= 1
148//
149// The optimal solution is (x*, y*, z*) = (0, 1, 1) with objective value 4.
150TEST_P(SimpleLogicalConstraintTest, SimpleSos2Instance) {
151 if (!GetParam().supports_sos2) {
152 GTEST_SKIP() << no_sos2_support_message;
153 }
154 Model model;
155 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
156 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
157 const Variable z = model.AddContinuousVariable(0.0, 1.0, "z");
158 model.Maximize(2.0 * x + 1.0 * y + 3.0 * z);
159 model.AddSos2Constraint({x, y, z}, {});
160
162 4.0, {{x, 0.0}, {y, 1.0}, {z, 1.0}})));
163}
164
165// We solve
166//
167// max 2x + 1.5y + 3z
168// s.t. {y, z} is SOS-1
169// {x, y, z} is SOS-2
170// 0 <= x, y, z <= 1
171//
172// The optimal solution is (x*, y*, z*) = (1, 1, 0) with objective value 3.5.
173TEST_P(SimpleLogicalConstraintTest, InstanceWithSos1AndSos2) {
174 if (!GetParam().supports_sos1) {
175 GTEST_SKIP() << no_sos1_support_message;
176 }
177 if (!GetParam().supports_sos2) {
178 GTEST_SKIP() << no_sos2_support_message;
179 }
180 Model model;
181 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
182 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
183 const Variable z = model.AddContinuousVariable(0.0, 1.0, "z");
184 model.Maximize(2.0 * x + 1.5 * y + 3.0 * z);
185 model.AddSos1Constraint({y, z}, {});
186 model.AddSos2Constraint({x, y, z}, {});
187
189 3.5, {{x, 1.0}, {y, 1.0}, {z, 0.0}})));
190}
191
192// We solve
193//
194// min x + y
195// s.t. {2x - 1, y - 0.75} is SOS-1
196// 0 <= x, y <= 1
197//
198// The optimal solution is (x*, y*) = (0.5, 0) with objective value 0.5.
199TEST_P(SimpleLogicalConstraintTest, Sos1WithExpressions) {
200 if (!GetParam().supports_sos1) {
201 GTEST_SKIP() << no_sos1_support_message;
202 }
203 Model model;
204 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
205 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
206 model.Minimize(x + y);
207 model.AddSos1Constraint({2 * x - 1, y - 0.75}, {});
208
209 EXPECT_THAT(SimpleSolve(model),
210 IsOkAndHolds(IsOptimalWithSolution(0.5, {{x, 0.5}, {y, 0.0}})));
211}
212
213// We solve
214//
215// max x + y + z
216// s.t. {2x + 1, 8y + 1, 4z + 1} is SOS-2
217// -1 <= x, y, z <= 1
218//
219// The optimal solution is (x*, y*) = (1, 1, -0.25) with objective value 1.75.
220TEST_P(SimpleLogicalConstraintTest, Sos2WithExpressions) {
221 if (!GetParam().supports_sos2) {
222 GTEST_SKIP() << no_sos2_support_message;
223 }
224 Model model;
225 const Variable x = model.AddContinuousVariable(-1.0, 1.0, "x");
226 const Variable y = model.AddContinuousVariable(-1.0, 1.0, "y");
227 const Variable z = model.AddContinuousVariable(-1.0, 1.0, "z");
228 model.Maximize(x + y + z);
229 model.AddSos2Constraint({2 * x + 1, 8 * y + 1, 4 * z + 1}, {});
230
232 1.75, {{x, 1.0}, {y, 1.0}, {z, -0.25}})));
233}
234
235// We solve
236//
237// min x
238// s.t. {x, x} is SOS-1
239// -1 <= x <= 1
240//
241// The optimal solution is x* = 0 with objective value 0.
242TEST_P(SimpleLogicalConstraintTest, Sos1VariableInMultipleTerms) {
243 if (!GetParam().supports_sos1) {
244 GTEST_SKIP() << no_sos2_support_message;
245 }
246 Model model;
247 const Variable x = model.AddContinuousVariable(-1.0, 1.0, "x");
248 model.Minimize(x);
249 model.AddSos1Constraint({x, x});
250
251 EXPECT_THAT(SimpleSolve(model),
252 IsOkAndHolds(IsOptimalWithSolution(0.0, {{x, 0.0}})));
253}
254
255// We solve
256//
257// min x
258// s.t. {x, 0, x} is SOS-2
259// -1 <= x <= 1
260//
261// The optimal solution is x* = 0 with objective value 0.
262TEST_P(SimpleLogicalConstraintTest, Sos2VariableInMultipleTerms) {
263 if (!GetParam().supports_sos2) {
264 GTEST_SKIP() << no_sos2_support_message;
265 }
266 Model model;
267 const Variable x = model.AddContinuousVariable(-1.0, 1.0, "x");
268 model.Minimize(x);
269 model.AddSos2Constraint({x, 0.0, x});
270
271 EXPECT_THAT(SimpleSolve(model),
272 IsOkAndHolds(IsOptimalWithSolution(0.0, {{x, 0.0}})));
273}
274
275// We start with the LP
276//
277// max x + 2y
278// s.t. 0 <= x, y <= 1
279//
280// We then add the SOS1 constraint
281//
282// {x, y} is SOS-1
283//
284// The optimal solution for the modified problem is (x*, y*) = (0, 1) with
285// objective value 2.
286TEST_P(IncrementalLogicalConstraintTest, LinearToSos1Update) {
287 Model model;
288 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
289 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
290 model.Maximize(x + 2.0 * y);
291
293 const auto solver,
294 NewIncrementalSolver(&model, GetParam().solver_type, {}));
295 ASSERT_THAT(solver->Solve({.parameters = GetParam().parameters}),
296 IsOkAndHolds(IsOptimalWithSolution(3.0, {{x, 1.0}, {y, 1.0}})));
297
298 model.AddSos1Constraint({x, y}, {});
299
300 if (!GetParam().supports_sos1) {
301 // Here we test that solvers that don't support SOS1 constraints return
302 // false in SolverInterface::CanUpdate(). Thus they should fail in their
303 // factory function instead of failing in their SolverInterface::Update()
304 // function. To assert we rely on status annotations added by
305 // IncrementalSolver::Update() to the returned status of Solver::Update()
306 // and Solver::New().
308 solver->Update(),
309 StatusIs(AnyOf(absl::StatusCode::kInvalidArgument,
310 absl::StatusCode::kUnimplemented),
311 AllOf(HasSubstr("sos1 constraint"),
312 // Sub-string expected for Solver::Update() error.
313 Not(HasSubstr("update failed")),
314 // Sub-string expected for Solver::New() error.
315 HasSubstr("solver re-creation failed"))));
316 return;
317 }
318 ASSERT_THAT(solver->Update(),
319 IsOkAndHolds(GetParam().supports_incremental_add_and_deletes
320 ? DidUpdate()
321 : Not(DidUpdate())));
322 EXPECT_THAT(solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
323 IsOkAndHolds(IsOptimalWithSolution(2.0, {{x, 0.0}, {y, 1.0}})));
324}
325
326// We start with the LP
327//
328// max 2x + y + 3z
329// s.t. 0 <= x, y, z <= 1
330//
331// We then add the SOS2 constraint
332//
333// {x, y, z} is SOS-2
334//
335// The optimal solution for the modified problem is (x*, y*, z*) = (0, 1, 1)
336// with objective value 4.
337TEST_P(IncrementalLogicalConstraintTest, LinearToSos2Update) {
338 Model model;
339 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
340 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
341 const Variable z = model.AddContinuousVariable(0.0, 1.0, "z");
342 model.Maximize(2.0 * x + 1.0 * y + 3.0 * z);
343
345 const auto solver,
346 NewIncrementalSolver(&model, GetParam().solver_type, {}));
348 solver->Solve({.parameters = GetParam().parameters}),
349 IsOkAndHolds(IsOptimalWithSolution(6.0, {{x, 1.0}, {y, 1.0}, {z, 1.0}})));
350
351 model.AddSos2Constraint({x, y, z}, {});
352
353 if (!GetParam().supports_sos2) {
354 // Here we test that solvers that don't support SOS2 constraints return
355 // false in SolverInterface::CanUpdate(). Thus they should fail in their
356 // factory function instead of failing in their SolverInterface::Update()
357 // function. To assert we rely on status annotations added by
358 // IncrementalSolver::Update() to the returned status of Solver::Update()
359 // and Solver::New().
361 solver->Update(),
362 StatusIs(AnyOf(absl::StatusCode::kInvalidArgument,
363 absl::StatusCode::kUnimplemented),
364 AllOf(HasSubstr("sos2 constraint"),
365 // Sub-string expected for Solver::Update() error.
366 Not(HasSubstr("update failed")),
367 // Sub-string expected for Solver::New() error.
368 HasSubstr("solver re-creation failed"))));
369 return;
370 }
371 ASSERT_THAT(solver->Update(),
372 IsOkAndHolds(GetParam().supports_incremental_add_and_deletes
373 ? DidUpdate()
374 : Not(DidUpdate())));
376 solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
377 IsOkAndHolds(IsOptimalWithSolution(4.0, {{x, 0.0}, {y, 1.0}, {z, 1.0}})));
378}
379
380// We start with:
381//
382// max x + 3y
383// s.t. {2x - 1, 4y - 1} is SOS-1
384// x + y <= 1
385// 0 <= x, y <= 1
386//
387// The optimal solution is (x*, y*) = (0.25, 0.75) with objective value 2.5.
388//
389// Then we delete the SOS-1 constraint, leaving the LP:
390//
391// max x + 3y
392// s.t. x + y <= 1
393// 0 <= x, y <= 1
394//
395// The optimal solution is (x*, y*) = (0, 1) with objective value 3.
396TEST_P(IncrementalLogicalConstraintTest, UpdateDeletesSos1Constraint) {
397 if (!GetParam().supports_sos1) {
398 GTEST_SKIP() << no_sos1_support_message;
399 }
400 Model model;
401 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
402 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
403 model.Maximize(x + 3 * y);
404 model.AddLinearConstraint(x + y <= 1);
405 const Sos1Constraint c = model.AddSos1Constraint({2 * x - 1, 4 * y - 3}, {});
406
408 const auto solver,
409 NewIncrementalSolver(&model, GetParam().solver_type, {}));
410 ASSERT_THAT(solver->Solve({.parameters = GetParam().parameters}),
411 IsOkAndHolds(IsOptimalWithSolution(2.5, {{x, 0.25}, {y, 0.75}})));
412
413 model.DeleteSos1Constraint(c);
414
415 ASSERT_THAT(solver->Update(),
416 IsOkAndHolds(GetParam().supports_incremental_add_and_deletes
417 ? DidUpdate()
418 : Not(DidUpdate())));
419 EXPECT_THAT(solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
420 IsOkAndHolds(IsOptimalWithSolution(3.0, {{x, 0.0}, {y, 1.0}})));
421}
422
423// We start with:
424//
425// max x + 3y + 2z
426// s.t. {2x - 1, 8y - 1, 4z - 1} is SOS-2
427// x + y + z <= 2
428// 0 <= x, y, z <= 1
429//
430// The optimal solution is (x*, y*, z*) = (0.5, 1, 0.5) with objective value
431// 4.5.
432//
433// Then we delete the SOS-2 constraint, leaving the LP:
434//
435// max x + 3y + 2z
436// s.t. x + y + z <= 2
437// 0 <= x, y, z <= 1
438//
439// The optimal solution is (x*, y*, z*) = (0, 1, 1) with objective value 5.
440TEST_P(IncrementalLogicalConstraintTest, UpdateDeletesSos2Constraint) {
441 if (!GetParam().supports_sos2) {
442 GTEST_SKIP() << no_sos1_support_message;
443 }
444 Model model;
445 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
446 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
447 const Variable z = model.AddContinuousVariable(0.0, 1.0, "z");
448 model.Maximize(x + 3 * y + 2 * z);
449 model.AddLinearConstraint(x + y + z <= 2);
450 const Sos2Constraint c =
451 model.AddSos2Constraint({2 * x - 1, 8 * y - 1, 4 * z - 1}, {});
452
454 const auto solver,
455 NewIncrementalSolver(&model, GetParam().solver_type, {}));
457 solver->Solve({.parameters = GetParam().parameters}),
458 IsOkAndHolds(IsOptimalWithSolution(4.5, {{x, 0.5}, {y, 1.0}, {z, 0.5}})));
459
460 model.DeleteSos2Constraint(c);
461
462 ASSERT_THAT(solver->Update(),
463 IsOkAndHolds(GetParam().supports_incremental_add_and_deletes
464 ? DidUpdate()
465 : Not(DidUpdate())));
467 solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
468 IsOkAndHolds(IsOptimalWithSolution(5.0, {{x, 0.0}, {y, 1.0}, {z, 1.0}})));
469}
470
471// We start with:
472//
473// max 2x + 2y + z + w
474// s.t. {x, y + w, z} is SOS-1
475// 0 <= x, y, z, w <= 1
476//
477// The optimal solution is (x*, y*, z*, w) = (0, 1, 0, 1) with objective value
478// 3.
479//
480// We then delete the y variable, leaving the problem:
481//
482// max 2x + z + w
483// s.t. {x, w, z} is SOS-1
484// 0 <= x, z, w <= 1
485//
486// The optimal solution is (x*, z*, w*) = (1, 0, 0) with objective value 2.
487// TODO(b/237076465): With C++ API, also test deletion of single variable term.
488TEST_P(IncrementalLogicalConstraintTest,
489 UpdateDeletesVariableInSos1Constraint) {
490 if (!GetParam().supports_sos1) {
491 GTEST_SKIP() << no_sos1_support_message;
492 }
493 Model model;
494 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
495 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
496 const Variable z = model.AddContinuousVariable(0.0, 1.0, "z");
497 const Variable w = model.AddContinuousVariable(0.0, 1.0, "w");
498 model.Maximize(2.0 * x + 2.0 * y + z + w);
499 model.AddSos1Constraint({x, y + w, z}, {});
500
502 const auto solver,
503 NewIncrementalSolver(&model, GetParam().solver_type, {}));
504 ASSERT_THAT(solver->Solve({.parameters = GetParam().parameters}),
506 3.0, {{x, 0.0}, {y, 1.0}, {z, 0.0}, {w, 1.0}})));
507
508 model.DeleteVariable(y);
509
510 ASSERT_THAT(solver->Update(),
511 IsOkAndHolds(GetParam().supports_incremental_variable_deletions
512 ? DidUpdate()
513 : Not(DidUpdate())));
515 solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
516 IsOkAndHolds(IsOptimalWithSolution(2.0, {{x, 1.0}, {z, 0.0}, {w, 0.0}})));
517}
518
519// We start with:
520//
521// max 2x + 2y + 2z + w
522// s.t. {x, y, z + w} is SOS-2
523// 0 <= x, y, z, w <= 1
524//
525// The optimal solution is (x*, y*, z*, w*) = (0, 1, 1, 1) with objective value
526// 5.
527//
528// We then delete the z variable, leaving the problem:
529//
530// max 2x + 2y + w
531// s.t. {x, y, w} is SOS-2
532// 0 <= x, y, w <= 1
533//
534// The optimal solution is (x*, y*, w*) = (1, 1, 0) with objective value 4.
535// TODO(b/237076465): With C++ API, also test deletion of single variable term.
536TEST_P(IncrementalLogicalConstraintTest,
537 UpdateDeletesVariableInSos2Constraint) {
538 if (!GetParam().supports_sos2) {
539 GTEST_SKIP() << no_sos2_support_message;
540 }
541 Model model;
542 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
543 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
544 const Variable z = model.AddContinuousVariable(0.0, 1.0, "z");
545 const Variable w = model.AddContinuousVariable(0.0, 1.0, "w");
546 model.Maximize(2.0 * x + 2.0 * y + 2.0 * z + w);
547 model.AddSos2Constraint({x, y, z + w}, {});
548
550 const auto solver,
551 NewIncrementalSolver(&model, GetParam().solver_type, {}));
552 ASSERT_THAT(solver->Solve({.parameters = GetParam().parameters}),
554 5.0, {{x, 0.0}, {y, 1.0}, {z, 1.0}, {w, 1.0}})));
555
556 model.DeleteVariable(z);
557
558 ASSERT_THAT(solver->Update(),
559 IsOkAndHolds(GetParam().supports_incremental_variable_deletions
560 ? DidUpdate()
561 : Not(DidUpdate())));
563 solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
564 IsOkAndHolds(IsOptimalWithSolution(4.0, {{x, 1.0}, {y, 1.0}, {w, 0.0}})));
565}
566
567// We start with:
568//
569// max 2x + 1.5y + 3z
570// s.t. {y, z} is SOS-1
571// {x, y, z} is SOS-2
572// 0 <= x, y, z <= 1
573//
574// The optimal solution is (x*, y*, z*) = (1, 1, 0) with objective value 3.5.
575//
576// We then delete the SOS-1 constraint, leaving:
577//
578// max x + 1.5y + 3z
579// s.t. {x, y, z} is SOS-2
580// 0 <= x, y, z <= 1
581//
582// The optimal solution is (x*, y*, z*) = (0, 1, 1) with objective value 4.5.
583TEST_P(IncrementalLogicalConstraintTest, InstanceWithSos1AndSos2AndDeletion) {
584 if (!GetParam().supports_sos1) {
585 GTEST_SKIP() << no_sos1_support_message;
586 }
587 if (!GetParam().supports_sos2) {
588 GTEST_SKIP() << no_sos2_support_message;
589 }
590 Model model;
591 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
592 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
593 const Variable z = model.AddContinuousVariable(0.0, 1.0, "z");
594 model.Maximize(2.0 * x + 1.5 * y + 3.0 * z);
595 const Sos1Constraint c = model.AddSos1Constraint({y, z}, {});
596 model.AddSos2Constraint({x, y, z}, {});
597
599 const auto solver,
600 NewIncrementalSolver(&model, GetParam().solver_type, {}));
602 solver->Solve({.parameters = GetParam().parameters}),
603 IsOkAndHolds(IsOptimalWithSolution(3.5, {{x, 1.0}, {y, 1.0}, {z, 0.0}})));
604
605 model.DeleteSos1Constraint(c);
606
607 ASSERT_THAT(solver->Update(),
608 IsOkAndHolds(GetParam().supports_incremental_add_and_deletes
609 ? DidUpdate()
610 : Not(DidUpdate())));
612 solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
613 IsOkAndHolds(IsOptimalWithSolution(4.5, {{x, 0.0}, {y, 1.0}, {z, 1.0}})));
614}
615
616TEST_P(SimpleLogicalConstraintTest, CanBuildIndicatorModel) {
617 Model model;
618 // Technically `x` should be binary, but the validator will not enforce this.
619 // Instead, we expect that solvers will reject solving any models containing
620 // non-binary indicator variables (this is tested elsewhere). Therefore, here
621 // we want to test that solvers that do not support either indicator
622 // constraints or integer variables will reject indicator constraints with a
623 // useful message, regardless if the indicator is binary.
624 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
625 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
626 model.AddIndicatorConstraint(x, y <= 0.5);
627
628 if (GetParam().supports_indicator_constraints) {
629 EXPECT_OK(NewIncrementalSolver(&model, GetParam().solver_type, {}));
630 } else {
631 EXPECT_THAT(NewIncrementalSolver(&model, GetParam().solver_type, {}),
632 StatusIs(AnyOf(absl::StatusCode::kInvalidArgument,
633 absl::StatusCode::kUnimplemented),
634 HasSubstr("indicator constraints")));
635 }
636}
637
638// Here we test that each solver supporting indicator constraints will raise an
639// error when attempting to solve a model containing non-binary indicator
640// variables.
641TEST_P(SimpleLogicalConstraintTest, SolveFailsWithNonBinaryIndicatorVariable) {
642 if (!GetParam().supports_indicator_constraints) {
643 GTEST_SKIP() << no_indicator_support_message;
644 }
645 Model model;
646 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
647 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
648 model.AddIndicatorConstraint(x, y >= 0.5);
649
650 EXPECT_THAT(SimpleSolve(model),
651 StatusIs(absl::StatusCode::kInvalidArgument,
652 HasSubstr("indicator variable is not binary")));
653}
654
655// We solve
656//
657// min -x + y
658// s.t. x = 1 --> y >= 0.5
659// x in {0,1}
660// 0 <= y <= 1
661//
662// The optimal solution is (x*, y*) = (1, 0.5) with objective value -0.5.
663TEST_P(SimpleLogicalConstraintTest, SimpleIndicatorInstance) {
664 if (!GetParam().supports_indicator_constraints) {
665 GTEST_SKIP() << no_indicator_support_message;
666 }
667 Model model;
668 const Variable x = model.AddBinaryVariable("x");
669 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
670 model.Minimize(-x + y);
671 model.AddIndicatorConstraint(x, y >= 0.5);
672
673 EXPECT_THAT(SimpleSolve(model),
674 IsOkAndHolds(IsOptimalWithSolution(-0.5, {{x, 1.0}, {y, 0.5}})));
675}
676
677// We solve
678//
679// min x + y
680// s.t. x = 0 --> y >= 0.5
681// x in {0,1}
682// 0 <= y <= 1
683//
684// The optimal solution is (x*, y*) = (0, 0.5) with objective value 0.5.
685TEST_P(SimpleLogicalConstraintTest, ActivationOnZero) {
686 if (!GetParam().supports_indicator_constraints) {
687 GTEST_SKIP() << no_indicator_support_message;
688 }
689 Model model;
690 const Variable x = model.AddBinaryVariable("x");
691 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
692 model.Minimize(x + y);
693 model.AddIndicatorConstraint(x, y >= 0.5, /*activate_on_zero=*/true, "c");
694
695 EXPECT_THAT(SimpleSolve(model),
696 IsOkAndHolds(IsOptimalWithSolution(0.5, {{x, 0.0}, {y, 0.5}})));
697}
698
699// As of 2022-08-30, ModelProto supports indicator constraints with ranged
700// implied constraints, although no solver supports this functionality. If a
701// solver does add support in the future, this test should be updated and the
702// test parameters should be suitably modified to track this support.
703TEST_P(SimpleLogicalConstraintTest, IndicatorWithRangedImpliedConstraint) {
704 if (!GetParam().supports_indicator_constraints) {
705 GTEST_SKIP() << no_indicator_support_message;
706 }
707 Model model;
708 const Variable x = model.AddBinaryVariable("x");
709 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
710 model.AddIndicatorConstraint(x, 0.25 <= y <= 0.75);
711
712 EXPECT_THAT(NewIncrementalSolver(&model, GetParam().solver_type, {}),
713 StatusIs(AnyOf(absl::StatusCode::kInvalidArgument,
714 absl::StatusCode::kUnimplemented),
715 HasSubstr("ranged")));
716}
717
718// We write the model:
719//
720// max x
721// s.t. (unset variable id) = 1 --> x = 0.5
722// 0 <= x <= 1
723//
724// As the indicator variable is unset, the indicator constraint should be
725// ignored, and the optimal solution is x* = 1 with objective value 1.
726//
727// To get an unset indicator variable, we simply add an indicator variable, add
728// the constraint, and then delete the indicator variable.
729TEST_P(SimpleLogicalConstraintTest, UnsetIndicatorVariable) {
730 if (!GetParam().supports_indicator_constraints) {
731 GTEST_SKIP() << no_indicator_support_message;
732 }
733 Model model;
734 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
735 const Variable indicator = model.AddBinaryVariable("indicator");
736 model.Maximize(x);
737 model.AddIndicatorConstraint(indicator, x == 0.5);
738 model.DeleteVariable(indicator);
739
740 EXPECT_THAT(SimpleSolve(model),
741 IsOkAndHolds(IsOptimalWithSolution(1.0, {{x, 1.0}})));
742}
743
744// We test that indicator variables may have custom bounds set as long as the
745// variables are integer and those bounds are contained in [0, 1]. The model is
746//
747// max v + w
748// s.t. x = 1 --> w >= 1.5
749// y = 1 --> v <= 0.6
750// z = 1 --> w <= 0.4
751// x == 0
752// y == 1
753// 0.5 <= z <= 1
754// 0 <= v, w <= 1
755// x, y, z in {0, 1}.
756//
757// The unique optimal solution is (x, y, z, v, w) = (0, 1, 1, 0.6, 0.4) with
758// objective value 1.0.
759TEST_P(SimpleLogicalConstraintTest, IndicatorsWithOddButValidBounds) {
760 if (!GetParam().supports_indicator_constraints) {
761 GTEST_SKIP() << no_indicator_support_message;
762 }
763 Model model;
764 const Variable x = model.AddIntegerVariable(0.0, 0.0, "x");
765 const Variable y = model.AddIntegerVariable(1.0, 1.0, "y");
766 const Variable z = model.AddIntegerVariable(0.5, 1.0, "z");
767 const Variable v = model.AddContinuousVariable(0.0, 1.0, "v");
768 const Variable w = model.AddContinuousVariable(0.0, 1.0, "w");
769 model.Maximize(v + w);
770 model.AddIndicatorConstraint(x, w >= 1.5);
771 model.AddIndicatorConstraint(y, v <= 0.6);
772 model.AddIndicatorConstraint(z, w <= 0.4);
773
774 EXPECT_THAT(SimpleSolve(model),
776 1.0, {{x, 0.0}, {y, 1.0}, {z, 1.0}, {v, 0.6}, {w, 0.4}})));
777}
778
779// We start with the LP
780//
781// max x + y
782// s.t. x in {0,1}
783// 0 <= y <= 1
784//
785// The optimal solution is (x*, y*) = (1, 1) with objective value 2.
786//
787// We then add the indicator constraint
788//
789// x = 1 --> y <= 0.5
790//
791// The optimal solution for the modified problem is (x*, y*) = (1, 0.5) with
792// objective value 1.5.
793TEST_P(IncrementalLogicalConstraintTest, LinearToIndicatorUpdate) {
794 Model model;
795 // We want to test that, even for solvers that do not support either integer
796 // variables or indicator constraints, that we get a meaningful error message.
797 const Variable x = GetParam().supports_integer_variables
798 ? model.AddBinaryVariable("x")
799 : model.AddContinuousVariable(0.0, 1.0, "x");
800 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
801 model.Maximize(x + y);
802
804 const auto solver,
805 NewIncrementalSolver(&model, GetParam().solver_type, {}));
806 ASSERT_THAT(solver->Solve({.parameters = GetParam().parameters}),
807 IsOkAndHolds(IsOptimalWithSolution(2.0, {{x, 1.0}, {y, 1.0}})));
808
809 model.AddIndicatorConstraint(x, y <= 0.5);
810
811 if (!GetParam().supports_indicator_constraints) {
812 // Here we test that solvers that don't support indicator constraints return
813 // false in SolverInterface::CanUpdate(). Thus they should fail in their
814 // factory function instead of failing in their SolverInterface::Update()
815 // function. To assert we rely on status annotations added by
816 // IncrementalSolver::Update() to the returned status of Solver::Update()
817 // and Solver::New().
819 solver->Update(),
820 StatusIs(AnyOf(absl::StatusCode::kInvalidArgument,
821 absl::StatusCode::kUnimplemented),
822 AllOf(HasSubstr("indicator constraint"),
823 // Sub-string expected for Solver::Update() error.
824 Not(HasSubstr("update failed")),
825 // Sub-string expected for Solver::New() error.
826 HasSubstr("solver re-creation failed"))));
827 return;
828 }
829 ASSERT_THAT(solver->Update(),
830 IsOkAndHolds(GetParam().supports_incremental_add_and_deletes
831 ? DidUpdate()
832 : Not(DidUpdate())));
833 EXPECT_THAT(solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
834 IsOkAndHolds(IsOptimalWithSolution(1.5, {{x, 1.0}, {y, 0.5}})));
835}
836
837// We start with the problem:
838//
839// max x + y
840// s.t. x = 1 --> y <= 0.5
841// x in {0,1}
842// 0 <= y <= 1
843//
844// The optimal solution is (x*, y*) = (1, 0.5) with objective value 1.5.
845//
846// We then delete the indicator constraint, leaving the LP:
847//
848// max x + y
849// s.t. x in {0,1}
850// 0 <= y <= 1
851//
852// The optimal solution for the modified problem is (x*, y*) = (1, 1) with
853// objective value 2.
854TEST_P(IncrementalLogicalConstraintTest, UpdateDeletesIndicatorConstraint) {
855 if (!GetParam().supports_indicator_constraints) {
856 GTEST_SKIP() << no_indicator_support_message;
857 }
858 Model model;
859 const Variable x = model.AddBinaryVariable("x");
860 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
861 model.Maximize(x + y);
862 const IndicatorConstraint c = model.AddIndicatorConstraint(x, y <= 0.5);
864 const auto solver,
865 NewIncrementalSolver(&model, GetParam().solver_type, {}));
866 ASSERT_THAT(solver->Solve({.parameters = GetParam().parameters}),
867 IsOkAndHolds(IsOptimalWithSolution(1.5, {{x, 1.0}, {y, 0.5}})));
868
869 model.DeleteIndicatorConstraint(c);
870
871 ASSERT_THAT(solver->Update(),
872 IsOkAndHolds(GetParam().supports_incremental_add_and_deletes
873 ? DidUpdate()
874 : Not(DidUpdate())));
875 EXPECT_THAT(solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
876 IsOkAndHolds(IsOptimalWithSolution(2.0, {{x, 1.0}, {y, 1.0}})));
877}
878
879// We start with the problem:
880//
881// max x
882// s.t. (unset variable id) = 1 --> x <= 0.5
883// 0 <= x <= 1
884//
885// The optimal solution is x* = 1 with objective value 1. To write this model,
886// we add a placeholder indicator variable, add the indicator constraint, delete
887// that constraint, and only then initialize the solver.
888//
889// We then delete the indicator constraint, leaving the LP:
890//
891// max x
892// s.t. 0 <= x <= 1
893//
894// The optimal solution for the modified problem is also x* = 1 with objective
895// value 1.
896TEST_P(IncrementalLogicalConstraintTest,
897 UpdateDeletesIndicatorConstraintWithUnsetIndicatorVariable) {
898 if (!GetParam().supports_indicator_constraints) {
899 GTEST_SKIP() << no_indicator_support_message;
900 }
901 Model model;
902 const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
903 const Variable indicator = model.AddBinaryVariable("indicator");
904 model.Maximize(x);
905 const IndicatorConstraint c =
906 model.AddIndicatorConstraint(indicator, x <= 0.5);
907 model.DeleteVariable(indicator);
908
910 const auto solver,
911 NewIncrementalSolver(&model, GetParam().solver_type, {}));
912 ASSERT_THAT(solver->Solve({.parameters = GetParam().parameters}),
913 IsOkAndHolds(IsOptimalWithSolution(1.0, {{x, 1.0}})));
914
915 model.DeleteIndicatorConstraint(c);
916
917 ASSERT_THAT(solver->Update(),
918 IsOkAndHolds(GetParam().supports_incremental_add_and_deletes
919 ? DidUpdate()
920 : Not(DidUpdate())));
921 EXPECT_THAT(solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
922 IsOkAndHolds(IsOptimalWithSolution(1.0, {{x, 1.0}})));
923}
924
925// We start with the problem:
926//
927// max x + y
928// s.t. x = 1 --> y <= 0.5
929// x in {0,1}
930// 0 <= y <= 1
931//
932// The optimal solution is (x*, y*) = (1, 0.5) with objective value 1.5.
933//
934// We then delete the indicator variable x. If the solver supports this form of
935// update, we then solve the problem:
936//
937// max y
938// s.t. 0 <= y <= 1
939//
940// The optimal solution is y* = 1 with objective value 1.
941TEST_P(IncrementalLogicalConstraintTest, UpdateDeletesIndicatorVariable) {
942 if (!GetParam().supports_indicator_constraints) {
943 GTEST_SKIP() << no_indicator_support_message;
944 }
945 Model model;
946 const Variable x = model.AddBinaryVariable("x");
947 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
948 model.Maximize(x + y);
949 model.AddIndicatorConstraint(x, y <= 0.5);
950
952 const auto solver,
953 NewIncrementalSolver(&model, GetParam().solver_type, {}));
954 ASSERT_THAT(solver->Solve({.parameters = GetParam().parameters}),
955 IsOkAndHolds(IsOptimalWithSolution(1.5, {{x, 1.0}, {y, 0.5}})));
956
957 model.DeleteVariable(x);
958
959 ASSERT_THAT(solver->Update(),
960 IsOkAndHolds(GetParam().supports_deleting_indicator_variables
961 ? DidUpdate()
962 : Not(DidUpdate())));
963 EXPECT_THAT(solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
964 IsOkAndHolds(IsOptimalWithSolution(1.0, {{y, 1.0}})));
965}
966
967// We start with the problem:
968//
969// max x + 2y + z
970// s.t. x = 1 --> y <= 0.5
971// x = 1 --> z <= 0.5
972// x in {0,1}
973// 0 <= y, z <= 1
974//
975// The optimal solution is (x*, y*, z*) = (0, 1, 1) with objective value 3.
976//
977// We then delete the variable y, leaving the problem:
978//
979// max x + z
980// s.t. x = 1 --> 0 <= 0.5
981// x = 1 --> z <= 0.5
982// x in {0,1}
983// 0 <= z <= 1
984//
985// The optimal solution for the modified problem is (x*, z*) = (1, 0.5) with
986// objective value 1.5.
987TEST_P(IncrementalLogicalConstraintTest,
988 UpdateDeletesVariableInImpliedExpression) {
989 if (!GetParam().supports_indicator_constraints) {
990 GTEST_SKIP() << no_indicator_support_message;
991 }
992 Model model;
993 const Variable x = model.AddBinaryVariable("x");
994 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
995 const Variable z = model.AddContinuousVariable(0.0, 1.0, "z");
996 model.Maximize(x + 2.0 * y + z);
997 model.AddIndicatorConstraint(x, y <= 0.5);
998 model.AddIndicatorConstraint(x, z <= 0.5);
999
1001 const auto solver,
1002 NewIncrementalSolver(&model, GetParam().solver_type, {}));
1004 solver->Solve({.parameters = GetParam().parameters}),
1005 IsOkAndHolds(IsOptimalWithSolution(3.0, {{x, 0.0}, {y, 1.0}, {z, 1.0}})));
1006
1007 model.DeleteVariable(y);
1008
1009 ASSERT_THAT(solver->Update(),
1010 IsOkAndHolds(GetParam().supports_incremental_variable_deletions
1011 ? DidUpdate()
1012 : Not(DidUpdate())));
1013 EXPECT_THAT(solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
1014 IsOkAndHolds(IsOptimalWithSolution(1.5, {{x, 1.0}, {z, 0.5}})));
1015}
1016
1017// We start with a simple, valid indicator constraint with binary indicator
1018// variable. We then update the indicator variable to be continuous. The solver
1019// should permit the model update, but return an error when solving.
1020TEST_P(IncrementalLogicalConstraintTest,
1021 UpdateMakesIndicatorVariableTypeInvalid) {
1022 if (!GetParam().supports_indicator_constraints) {
1023 GTEST_SKIP() << no_indicator_support_message;
1024 }
1025 Model model;
1026 const Variable x = model.AddBinaryVariable("x");
1027 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
1028 model.AddIndicatorConstraint(x, y <= 0.5);
1029
1031 const auto solver,
1032 NewIncrementalSolver(&model, GetParam().solver_type, {}));
1033 ASSERT_OK(solver->Solve({.parameters = GetParam().parameters}));
1034
1035 model.set_continuous(x);
1036
1037 ASSERT_THAT(solver->Update(),
1038 IsOkAndHolds(GetParam().supports_updating_binary_variables
1039 ? DidUpdate()
1040 : Not(DidUpdate())));
1041 EXPECT_THAT(solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
1042 StatusIs(absl::StatusCode::kInvalidArgument,
1043 HasSubstr("indicator variable is not binary")));
1044}
1045
1046// We test that we can update indicator variable bounds as long as they are
1047// still contained in [0, 1]. The model is:
1048//
1049// max u + v
1050// s.t. x + y == 1
1051// x = 1 --> u <= 0.6
1052// y = 1 --> v <= 0.4
1053// x, y in {0, 1}
1054// 0 <= u, v <= 1
1055//
1056// The optimal solution is (x, y, u, v) = (1, 0, 0.6, 1.0) with objective value
1057// 1.6.
1058//
1059// If we update bounds to x == 0, the optimal solution is then (x, y, u, v) =
1060// (0, 1, 1, 0.4) with objective value 1.4.
1061//
1062// Alternatively, if we update bounds to 0.5 <= x <= 1 and 0 <= y <= 0.5, the
1063// optimal solution is then (x, y, u, v) = (1, 0, 0.6, 1.0) with objective
1064// value 1.6.
1065//
1066// Alternatively, if we update bounds to y == 1, the optimal solution is then
1067// (x, y, u, v) = (0, 1, 1, 0.4) with objective value 1.4.
1068TEST_P(IncrementalLogicalConstraintTest, UpdateChangesIndicatorVariableBound) {
1069 if (!GetParam().supports_indicator_constraints) {
1070 GTEST_SKIP() << no_indicator_support_message;
1071 }
1072 Model model;
1073 const Variable x = model.AddIntegerVariable(0.0, 1.0, "x");
1074 const Variable y = model.AddIntegerVariable(0.0, 1.0, "y");
1075 const Variable u = model.AddContinuousVariable(0.0, 1.0, "u");
1076 const Variable v = model.AddContinuousVariable(0.0, 1.0, "z");
1077 model.Maximize(u + v);
1078 model.AddLinearConstraint(x + y == 1.0);
1079 model.AddIndicatorConstraint(x, u <= 0.6);
1080 model.AddIndicatorConstraint(y, v <= 0.4);
1081
1083 const auto solver,
1084 NewIncrementalSolver(&model, GetParam().solver_type, {}));
1085 EXPECT_THAT(solver->Solve({.parameters = GetParam().parameters}),
1087 1.6, {{x, 1.0}, {y, 0.0}, {u, 0.6}, {v, 1.0}})));
1088
1089 model.set_lower_bound(x, 0.0);
1090 model.set_upper_bound(x, 0.0);
1091
1092 ASSERT_THAT(solver->Update(),
1093 IsOkAndHolds(GetParam().supports_updating_binary_variables
1094 ? DidUpdate()
1095 : Not(DidUpdate())));
1096 EXPECT_THAT(solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
1098 1.4, {{x, 0.0}, {y, 1.0}, {u, 1.0}, {v, 0.4}})));
1099
1100 model.set_lower_bound(x, 0.5);
1101 model.set_upper_bound(x, 1.0);
1102 model.set_lower_bound(y, 0.0);
1103 model.set_upper_bound(y, 0.5);
1104
1105 ASSERT_THAT(solver->Update(),
1106 IsOkAndHolds(GetParam().supports_updating_binary_variables
1107 ? DidUpdate()
1108 : Not(DidUpdate())));
1109 EXPECT_THAT(solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
1111 1.6, {{x, 1.0}, {y, 0.0}, {u, 0.6}, {v, 1.0}})));
1112
1113 model.set_lower_bound(x, 0.0);
1114 model.set_upper_bound(x, 1.0);
1115 model.set_lower_bound(y, 1.0);
1116 model.set_upper_bound(y, 1.0);
1117
1118 ASSERT_THAT(solver->Update(),
1119 IsOkAndHolds(GetParam().supports_updating_binary_variables
1120 ? DidUpdate()
1121 : Not(DidUpdate())));
1122 EXPECT_THAT(solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
1124 1.4, {{x, 0.0}, {y, 1.0}, {u, 1.0}, {v, 0.4}})));
1125}
1126
1127// We start with a simple, valid indicator constraint with binary indicator
1128// variable. We then update the indicator variable to have a larger upper bound,
1129// meaning it is integer but no longer binary. The solver should permit the
1130// model update, but return an error when solving.
1131TEST_P(IncrementalLogicalConstraintTest,
1132 UpdateMakesIndicatorVariableBoundsInvalid) {
1133 if (!GetParam().supports_indicator_constraints) {
1134 GTEST_SKIP() << no_indicator_support_message;
1135 }
1136 Model model;
1137 const Variable x = model.AddIntegerVariable(0.0, 1.0, "x");
1138 const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
1139 model.AddIndicatorConstraint(x, y <= 0.5);
1140
1142 const auto solver,
1143 NewIncrementalSolver(&model, GetParam().solver_type, {}));
1144 ASSERT_OK(solver->Solve({.parameters = GetParam().parameters}));
1145
1146 model.set_upper_bound(x, 2.0);
1147
1148 ASSERT_THAT(solver->Update(),
1149 IsOkAndHolds(GetParam().supports_updating_binary_variables
1150 ? DidUpdate()
1151 : Not(DidUpdate())));
1152 EXPECT_THAT(solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
1153 StatusIs(absl::StatusCode::kInvalidArgument,
1154 HasSubstr("indicator variable is not binary")));
1155}
1156
1157} // namespace
1158} // namespace operations_research::math_opt
IntegerValue y
SatParameters parameters
GRBmodel * model
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28
Matcher< SolveResult > IsOptimalWithSolution(const double expected_objective, const VariableMap< double > expected_variable_values, const double tolerance)
Definition matchers.cc:777
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)
SolverType
The solvers supported by MathOpt.
Definition parameters.h:42
<=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()))
std::ostream & operator<<(std::ostream &ostr, const IndicatorConstraint &constraint)
absl::StatusOr< std::unique_ptr< IncrementalSolver > > NewIncrementalSolver(Model *model, SolverType solver_type, SolverInitArguments arguments)
Definition solve.cc:82
Matcher< UpdateResult > DidUpdate()
Actual UpdateResult.did_update is true.
Definition matchers.cc:1027
BoolVar Not(BoolVar x)
Definition cp_model.cc:87
std::string ProtobufShortDebugString(const P &message)
Definition proto_utils.h:41
STL namespace.
internal::StatusIsMatcher StatusIs(CodeMatcher code_matcher, MessageMatcher message_matcher)
trees with all degrees equal w the current value of w
#define ASSERT_OK(expression)
#define EXPECT_OK(expression)
#define ASSERT_OK_AND_ASSIGN(lhs, rexpr)
bool supports_indicator_constraints
True if the solver supports indicator constraints.
bool supports_sos2
True if the solver supports SOS2 constraints.
bool supports_deleting_indicator_variables
True if the solver supports updates that delete indicator variables.
bool supports_integer_variables
True if the solver supports integer variables.
bool supports_incremental_variable_deletions
True if the solver supports updates that delete (non-indicator) variables.
LogicalConstraintTestParameters(SolverType solver_type, SolveParameters parameters, bool supports_integer_variables, bool supports_sos1, bool supports_sos2, bool supports_indicator_constraints, bool supports_incremental_add_and_deletes, bool supports_incremental_variable_deletions, bool supports_deleting_indicator_variables, bool supports_updating_binary_variables)
bool supports_sos1
True if the solver supports SOS1 constraints.