Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
qp_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 <limits>
17#include <memory>
18#include <ostream>
19#include <string>
20#include <string_view>
21#include <utility>
22
23#include "absl/status/status.h"
24#include "gtest/gtest.h"
25#include "ortools/base/gmock.h"
30
31// TODO(b/178702980): this should not be needed
32// IWYU pragma: no_include <type_traits>
33
35
36using ::testing::AnyOf;
37using ::testing::HasSubstr;
38using ::testing::status::IsOkAndHolds;
39using ::testing::status::StatusIs;
40
41constexpr std::string_view kNoQpSupportMessage =
42 "This test is disabled as the solver does not support quadratic objectives";
43
44constexpr std::string_view kNoNonDiagonalQpSupportMessage =
45 "This test is disabled as the solver does not support non-diagonal "
46 "quadratic objectives";
47
48constexpr double kInf = std::numeric_limits<double>::infinity();
49constexpr double kTolerance = 1.0e-3;
50
52 const SolverType solver_type, SolveParameters parameters,
53 const QpSupportType qp_support,
54 const bool supports_incrementalism_not_modifying_qp,
55 const bool supports_qp_incrementalism, const bool use_integer_variables)
56 : solver_type(solver_type),
58 qp_support(qp_support),
59 supports_incrementalism_not_modifying_qp(
60 supports_incrementalism_not_modifying_qp),
61 supports_qp_incrementalism(supports_qp_incrementalism),
62 use_integer_variables(use_integer_variables) {}
63
64std::string ToString(QpSupportType qp_support) {
65 switch (qp_support) {
67 return "No QP support";
69 return "Diagonal QP only";
71 return "Convex QP";
72 }
73 LOG(FATAL) << "Invalid QpSupportType";
74 return "";
75}
76
77std::ostream& operator<<(std::ostream& out, const QpTestParameters& params) {
78 out << "{ solver_type: " << params.solver_type
79 << ", parameters: " << ProtobufShortDebugString(params.parameters.Proto())
80 << ", qp_support: " << ToString(params.qp_support)
81 << ", supports_incrementalism_not_modifying_qp: "
82 << (params.supports_incrementalism_not_modifying_qp ? "true" : "false")
83 << ", supports_qp_incrementalism: "
84 << (params.supports_qp_incrementalism ? "true" : "false")
85 << ", use_integer_variables: "
86 << (params.use_integer_variables ? "true" : "false") << " }";
87 return out;
88}
89
90namespace {
91
92TEST_P(SimpleQpTest, CanBuildQpModel) {
93 Model model;
94 const Variable x =
95 model.AddVariable(0, 1, GetParam().use_integer_variables, "x");
96 model.Minimize(x * x - 0.5 * x + 0.0625);
97
98 if (GetParam().qp_support == QpSupportType::kDiagonalQpOnly ||
99 GetParam().qp_support == QpSupportType::kConvexQp) {
100 EXPECT_THAT(SimpleSolve(model),
102 GetParam().use_integer_variables ? 0.0625 : 0.0)));
103 } else {
104 EXPECT_THAT(SimpleSolve(model),
105 StatusIs(AnyOf(absl::StatusCode::kInvalidArgument,
106 absl::StatusCode::kUnimplemented),
107 HasSubstr("quadratic objective")));
108 }
109}
110
111// Models the following problem:
112// min_x (x - 0.25)^2 = x^2 - 0.5x + 0.0625
113// s.t. 0 <= x <= 1
114//
115// along with, if use_integer_variables = true, integrality on x.
116//
117// The unique optimal solution is attained at x = 0.25 with objective value 0.
118// If in addition you impose integrality on x, the unique optimal solution is
119// x = 0 with objective value 0.0625.
120struct UnivariateQpProblem {
121 explicit UnivariateQpProblem(bool use_integer_variables)
122 : model(), x(model.AddVariable(0, 1, use_integer_variables, "x")) {
123 model.Minimize(x * x - 0.5 * x + 0.0625);
124 }
125
126 Model model;
127 const Variable x;
128};
129
130// Models the following problem:
131// min_(x,y} Q(x,y) = (x-0.2)^2 + (y-0.8)^2 + (x-0.2)(y-0.8)
132// = x^2 + xy - 1.2x + y^2 - 1.8y + 0.84
133// s.t. x + y = 1
134// 0 <= x, y <= 1
135//
136// along with, if use_integer_variables = true, integrality on x and y.
137//
138// The unique optimal solution is attained at (x,y) = (0.2, 0.8) with objective
139// value 0. To see this, observe that our quadratic objective Q has:
140// - Jacobian = [2x + y - 1.2] and Hessian = [2 1]
141// [x + 2y - 1.8] [1 2].
142// The Hessian shows that the Q is convex. Setting the Jacobian equal to zero
143// and solving the linear system, we derive that (x,y) = (0.2, 0.8) is the
144// unique global minimum of Q. It is also feasible for our constrained problem
145// above, yielding the result.
146//
147// If integrality is imposed on x and y, the unique optimal solution is
148// (x,y) = (0,1) with objective value 0.04.
149struct SimplexConstrainedQpProblem {
150 explicit SimplexConstrainedQpProblem(bool use_integer_variables)
151 : model(),
152 x(model.AddVariable(0, 1, use_integer_variables, "x")),
153 y(model.AddVariable(0, 1, use_integer_variables, "y")) {
154 model.Minimize(x * x + x * y - 1.2 * x + y * y - 1.8 * y + 0.84);
155 model.AddLinearConstraint(x + y == 1);
156 }
157
158 Model model;
159 const Variable x;
160 const Variable y;
161};
162
163TEST_P(SimpleQpTest, SolveUnivariateQp) {
164 if (GetParam().qp_support == QpSupportType::kNoQpSupport) {
165 GTEST_SKIP() << kNoQpSupportMessage;
166 }
167 const UnivariateQpProblem qp_problem(GetParam().use_integer_variables);
168 ASSERT_OK_AND_ASSIGN(const SolveResult result, SimpleSolve(qp_problem.model));
169 if (GetParam().use_integer_variables) {
170 EXPECT_THAT(result, IsOptimalWithSolution(0.0625, {{qp_problem.x, 0.0}},
171 kTolerance));
172 } else {
173 EXPECT_THAT(result,
174 IsOptimalWithSolution(0.0, {{qp_problem.x, 0.25}}, kTolerance));
175 }
176}
177
178TEST_P(SimpleQpTest, SolveSimplexConstrainedQp) {
179 if (GetParam().qp_support != QpSupportType::kConvexQp) {
180 GTEST_SKIP() << kNoNonDiagonalQpSupportMessage;
181 }
182
183 const SimplexConstrainedQpProblem qp_problem(
184 GetParam().use_integer_variables);
185
186 ASSERT_OK_AND_ASSIGN(const SolveResult result, SimpleSolve(qp_problem.model));
187 if (GetParam().use_integer_variables) {
189 0.04, {{qp_problem.x, 0.0}, {qp_problem.y, 1.0}},
190 kTolerance));
191 } else {
193 0.0, {{qp_problem.x, 0.2}, {qp_problem.y, 0.8}},
194 kTolerance));
195 }
196}
197
198TEST_P(IncrementalQpTest, EmptyUpdate) {
199 if (GetParam().qp_support == QpSupportType::kNoQpSupport) {
200 GTEST_SKIP() << kNoQpSupportMessage;
201 }
202
203 UnivariateQpProblem qp_problem(GetParam().use_integer_variables);
204
205 ASSERT_OK_AND_ASSIGN(const std::unique_ptr<IncrementalSolver> solver,
206 NewIncrementalSolver(&qp_problem.model, TestedSolver()));
207 ASSERT_OK_AND_ASSIGN(const SolveResult first_result,
208 solver->Solve({.parameters = GetParam().parameters}));
209 ASSERT_THAT(first_result,
210 IsOptimal(GetParam().use_integer_variables ? 0.0625 : 0.0));
211
212 // NOTE: This should work even for a solver with no incrementalism support.
213 ASSERT_THAT(solver->Update(), IsOkAndHolds(DidUpdate()));
214 ASSERT_OK_AND_ASSIGN(const SolveResult second_result,
215 solver->SolveWithoutUpdate());
216
217 if (GetParam().use_integer_variables) {
218 EXPECT_THAT(second_result, IsOptimalWithSolution(
219 0.0625, {{qp_problem.x, 0.0}}, kTolerance));
220 } else {
221 EXPECT_THAT(second_result,
222 IsOptimalWithSolution(0.0, {{qp_problem.x, 0.25}}, kTolerance));
223 if (GetParam().supports_incrementalism_not_modifying_qp &&
224 GetParam().solver_type != SolverType::kGscip) {
225 EXPECT_EQ(second_result.solve_stats.barrier_iterations, 0);
226 EXPECT_EQ(second_result.solve_stats.simplex_iterations, 0);
227 EXPECT_EQ(second_result.solve_stats.first_order_iterations, 0);
228 }
229 }
230}
231
232TEST_P(IncrementalQpTest, LinearToQuadraticUpdate) {
233 // We remove the quadratic coefficient x * x from the objective, leaving an LP
234 UnivariateQpProblem qp_problem(GetParam().use_integer_variables);
235 qp_problem.model.set_objective_coefficient(qp_problem.x, qp_problem.x, 0);
236 ASSERT_OK_AND_ASSIGN(const std::unique_ptr<IncrementalSolver> solver,
237 NewIncrementalSolver(&qp_problem.model, TestedSolver()));
238 ASSERT_OK_AND_ASSIGN(const SolveResult first_result,
239 solver->Solve({.parameters = GetParam().parameters}));
240 ASSERT_THAT(first_result, IsOptimal(0.0625 - 0.5));
241
242 // We now reset the objective with the "missing" objective term to its
243 // previous value, leaving a QP.
244 qp_problem.model.set_objective_coefficient(qp_problem.x, qp_problem.x, 1);
245
246 if (GetParam().qp_support == QpSupportType::kNoQpSupport) {
247 // Here we test that solvers that don't support quadratic objective return
248 // false in SolverInterface::CanUpdate(). Thus they should fail in their
249 // factory function instead of failing in their SolverInterface::Update()
250 // function. To assert we rely on status annotations added by
251 // IncrementalSolver::Update() to the returned status of Solver::Update()
252 // and Solver::New().
254 solver->Update(),
255 StatusIs(AnyOf(absl::StatusCode::kInvalidArgument,
256 absl::StatusCode::kUnimplemented),
257 AllOf(HasSubstr("quadratic objective"),
258 // Sub-string expected for Solver::Update() error.
259 Not(HasSubstr("update failed")),
260 // Sub-string expected for Solver::New() error.
261 HasSubstr("solver re-creation failed"))));
262 return;
263 }
264
265 ASSERT_THAT(solver->Update(),
266 IsOkAndHolds(GetParam().supports_incrementalism_not_modifying_qp
267 ? DidUpdate()
268 : Not(DidUpdate())));
269
270 if (GetParam().use_integer_variables) {
272 solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
274 IsOptimalWithSolution(0.0625, {{qp_problem.x, 0.0}}, kTolerance)));
275 } else {
277 solver->SolveWithoutUpdate({.parameters = GetParam().parameters}),
279 IsOptimalWithSolution(0.0, {{qp_problem.x, 0.25}}, kTolerance)));
280 }
281}
282
283TEST_P(IncrementalQpTest, ModifyQuadraticObjective) {
284 if (GetParam().qp_support == QpSupportType::kNoQpSupport) {
285 GTEST_SKIP() << kNoQpSupportMessage;
286 }
287
288 UnivariateQpProblem qp_problem(GetParam().use_integer_variables);
289
290 ASSERT_OK_AND_ASSIGN(const std::unique_ptr<IncrementalSolver> solver,
291 NewIncrementalSolver(&qp_problem.model, TestedSolver()));
292 ASSERT_OK_AND_ASSIGN(const SolveResult first_result,
293 solver->Solve({.parameters = GetParam().parameters}));
294 ASSERT_THAT(first_result,
295 IsOptimal(GetParam().use_integer_variables ? 0.0625 : 0.0));
296
297 // Now we change the objective to (x-0.75)^2 = x^2 - 1.5x + 0.5625. The new
298 // optimal solution for the continuous problem is x=0.75 with objective value
299 // 0; for the integer problem it is x=1 with objective value 0.0625.
300 const Variable x = qp_problem.x;
301 qp_problem.model.Minimize(x * x - 1.5 * x + 0.5625);
302
303 if (!GetParam().supports_qp_incrementalism) {
304 EXPECT_THAT(solver->Update(), IsOkAndHolds(Not(DidUpdate())));
305 return;
306 }
307 ASSERT_THAT(solver->Update(), IsOkAndHolds(DidUpdate()));
308 ASSERT_OK_AND_ASSIGN(const SolveResult second_result,
309 solver->SolveWithoutUpdate());
310
311 if (GetParam().use_integer_variables) {
312 EXPECT_THAT(second_result,
313 IsOptimalWithSolution(0.0625, {{x, 1.0}}, kTolerance));
314 } else {
315 EXPECT_THAT(second_result,
316 IsOptimalWithSolution(0.0, {{x, 0.75}}, kTolerance));
317 }
318}
319
320TEST_P(IncrementalQpTest, DeleteVariable) {
321 if (GetParam().qp_support != QpSupportType::kConvexQp) {
322 GTEST_SKIP() << kNoNonDiagonalQpSupportMessage;
323 }
324
325 SimplexConstrainedQpProblem qp_problem(GetParam().use_integer_variables);
326
327 ASSERT_OK_AND_ASSIGN(const std::unique_ptr<IncrementalSolver> solver,
328 NewIncrementalSolver(&qp_problem.model, TestedSolver()));
329 ASSERT_OK_AND_ASSIGN(const SolveResult first_result,
330 solver->Solve({.parameters = GetParam().parameters}));
331 ASSERT_THAT(first_result,
332 IsOptimal(GetParam().use_integer_variables ? 0.04 : 0.0));
333
334 // After deleting x, the only feasible solution is y=1 with objective
335 // value 0.04.
336 qp_problem.model.DeleteVariable(qp_problem.x);
337
338 if (!GetParam().supports_qp_incrementalism) {
339 EXPECT_THAT(solver->Update(), IsOkAndHolds(Not(DidUpdate())));
340 return;
341 }
342
343 ASSERT_THAT(solver->Update(), IsOkAndHolds(DidUpdate()));
344 ASSERT_OK_AND_ASSIGN(const SolveResult second_result,
345 solver->SolveWithoutUpdate());
346
347 EXPECT_THAT(second_result,
348 IsOptimalWithSolution(0.04, {{qp_problem.y, 1.0}}));
349}
350
351// Primal:
352// min 2x_0^2 + 0.5x_1^2 - x_0 - x_1 + 5
353// s.t. -inf <= x_0 + x_1 <= 1
354// 1 <= x_0 <= 2
355// -2 <= x_1 <= 4
356//
357// Optimal solution: x* = (1, 0).
358//
359// Dual (go/mathopt-qp-dual):
360// max -2x_0^2 - 0.5x_1^2 + y_0 + min{r_0, 2r_0} + min{-2r_1, 4r_1} + 5
361// s.t. y_0 + r_0 = 4x_0 - 1
362// y_0 + r_1 = x_1 - 1
363// y_0 <= 0
364//
365// Optimal solution: x* = (1, 0), y* = (-1), r* = (4, 0).
366// TODO(b/225196547): Show unique optimality of the primal/dual solutions.
367TEST_P(QpDualsTest, DiagonalQp1) {
368 if (GetParam().qp_support == QpSupportType::kNoQpSupport) {
369 GTEST_SKIP() << kNoQpSupportMessage;
370 }
371 Model model;
372 const Variable x0 = model.AddContinuousVariable(1, 2);
373 const Variable x1 = model.AddContinuousVariable(-2, 4);
374 const LinearConstraint y0 = model.AddLinearConstraint(x0 + x1 <= 1);
375 model.Minimize(2 * x0 * x0 + 0.5 * x1 * x1 - x0 - x1 + 5);
376
377 ASSERT_OK_AND_ASSIGN(const SolveResult solve_result, SimpleSolve(model));
378 const double expected_objective_value = 6.0;
379 EXPECT_THAT(solve_result, IsOptimalWithSolution(expected_objective_value,
380 {{x0, 1.0}, {x1, 0.0}}));
381 EXPECT_THAT(solve_result,
382 IsOptimalWithDualSolution(expected_objective_value, {{y0, -1.0}},
383 {{x0, 4.0}, {x1, 0.0}}));
384}
385
386// Primal:
387// min 0.5x_0^2 + 0.5x_1^2 - 3x_0 - x_1
388// s.t. 2 <= x_0 - x_1 <= 2
389// 0 <= x_0 <= inf
390// 0 <= x_1 <= inf
391//
392// Optimal solution: x* = (3, 1).
393//
394// Dual (go/mathopt-qp-dual):
395// max -0.5x_0^2 - 0.5x_1^2 + 2y_0
396// s.t. y_0 + r_0 = x_0 - 3
397// -y_0 + r_1 = x_1 - 1
398// r_0 >= 0
399// r_1 >= 0
400//
401// Optimal solution: x* = (3, 1), y* = (0), r* = (0, 0).
402// TODO(b/225196547): Show unique optimality of the primal/dual solutions.
403TEST_P(QpDualsTest, DiagonalQp2) {
404 if (GetParam().qp_support == QpSupportType::kNoQpSupport) {
405 GTEST_SKIP() << kNoQpSupportMessage;
406 }
407 Model model;
408 const Variable x0 = model.AddContinuousVariable(0, kInf);
409 const Variable x1 = model.AddContinuousVariable(0, kInf);
410 const LinearConstraint y0 = model.AddLinearConstraint(x0 - x1 == 2);
411 model.Minimize(0.5 * x0 * x0 + 0.5 * x1 * x1 - 3 * x0 - x1);
412
413 ASSERT_OK_AND_ASSIGN(const SolveResult solve_result, SimpleSolve(model));
414 const double expected_objective_value = -5.0;
415 EXPECT_THAT(solve_result, IsOptimalWithSolution(expected_objective_value,
416 {{x0, 3.0}, {x1, 1.0}}));
417 EXPECT_THAT(solve_result,
418 IsOptimalWithDualSolution(expected_objective_value, {{y0, 0.0}},
419 {{x0, 0.0}, {x1, 0.0}}));
420}
421
422// Primal:
423// min 0.5x_1^2 + x_2^2 + x_0 - x_2
424// s.t. 1 <= x_0 - x_2 <= 1
425// 4 <= 2x_0 <= 4
426// 0 <= x_0 <= inf
427// 0 <= x_1 <= inf
428// 0 <= x_2 <= inf
429//
430// Optimal solution: x* = (2, 0, 1).
431//
432// Dual (go/mathopt-qp-dual):
433// max -0.5x_1^2 - x_2^2 + y_0 + 4y_1
434// s.t. y_0 + 2y_1 + r_0 = 1
435// r_1 = x_1
436// -y_0 + r_2 = 2x_2 - 1
437// r_0 >= 0
438// r_1 >= 0
439// r_2 >= 0
440//
441// Optimal solution: x* = (2, 0, 1), y* = (-1, 1), r* = (0, 0, 0).
442// TODO(b/225196547): Show unique optimality of the primal/dual solutions.
443TEST_P(QpDualsTest, DiagonalQp3) {
444 if (GetParam().qp_support == QpSupportType::kNoQpSupport) {
445 GTEST_SKIP() << kNoQpSupportMessage;
446 }
447 Model model;
448 const Variable x0 = model.AddContinuousVariable(0, kInf);
449 const Variable x1 = model.AddContinuousVariable(0, kInf);
450 const Variable x2 = model.AddContinuousVariable(0, kInf);
451 const LinearConstraint y0 = model.AddLinearConstraint(x0 - x2 == 1);
452 const LinearConstraint y1 = model.AddLinearConstraint(2 * x0 == 4);
453 model.Minimize(0.5 * x1 * x1 + x2 * x2 + x0 - x2);
454
455 ASSERT_OK_AND_ASSIGN(const SolveResult solve_result, SimpleSolve(model));
456 const double expected_objective_value = 2.0;
457 EXPECT_THAT(solve_result,
458 IsOptimalWithSolution(expected_objective_value,
459 {{x0, 2.0}, {x1, 0.0}, {x2, 1.0}}));
460 EXPECT_THAT(solve_result,
461 IsOptimalWithDualSolution(expected_objective_value,
462 {{y0, -1.0}, {y1, 1.0}},
463 {{x0, 0.0}, {x1, 0.0}, {x2, 0.0}}));
464}
465
466// Primal:
467// min x_0^2 + x_0x_1 + 3x_1^2 - 2x_0
468// s.t. 2 <= x_0 + 2x_1 <= inf
469// 0 <= x_0 <= inf
470// 0 <= x_1 <= inf
471//
472// Optimal solution: x* = (1.6, 0.2).
473//
474// Dual (go/mathopt-qp-dual):
475// max -x_0^2 - x_0x_1 - 3x_1^2 + 2y_0
476// s.t. y_0 + r_0 = 2x_0 + x_1 - 2
477// 2y_0 + r_1 = x_0 + 6x_1
478// y_0 >= 0
479// r_0 >= 0
480// r_1 >= 0
481//
482// Optimal solution: x* = (1.6, 0.2), y* = (1.4), r* = (0, 0).
483TEST_P(QpDualsTest, GeneralQp1) {
484 if (GetParam().qp_support != QpSupportType::kConvexQp) {
485 GTEST_SKIP() << kNoNonDiagonalQpSupportMessage;
486 }
487 Model model;
488 const Variable x0 = model.AddContinuousVariable(0, kInf);
489 const Variable x1 = model.AddContinuousVariable(0, kInf);
490 const LinearConstraint y0 = model.AddLinearConstraint(x0 + 2 * x1 >= 2);
491 model.Minimize(x0 * x0 + x0 * x1 + 3 * x1 * x1 - 2 * x0);
492
493 ASSERT_OK_AND_ASSIGN(const SolveResult solve_result, SimpleSolve(model));
494 const double expected_objective_value = -0.2;
495 EXPECT_THAT(solve_result, IsOptimalWithSolution(expected_objective_value,
496 {{x0, 1.6}, {x1, 0.2}}));
497 EXPECT_THAT(solve_result,
498 IsOptimalWithDualSolution(expected_objective_value, {{y0, 1.4}},
499 {{x0, 0.0}, {x1, 0.0}}));
500}
501
502// Primal:
503// min x_0^2 + x_0x_1 + 3x_1^2 - 2x_0
504// s.t. 2 <= x_0 + 2x_1 <= inf
505// 0 <= x_0 <= 1
506// 1 <= x_1 <= 2
507//
508// Optimal solution: x* = (0.5, 1).
509//
510// Dual (go/mathopt-qp-dual):
511// max -x_0^2 - x_0x_1 - 3x_1^2 + min{0, r_0} + min{r_1, 2r_1} + 2y_0
512// s.t. y_0 + r_0 = 2x_0 + x_1 - 2
513// 2y_0 + r_1 = x_0 + 6x_1
514// y_0 >= 0
515//
516// Optimal solution: x* = (0.5, 1), y* = (0), r* = (0, 6.5).
517TEST_P(QpDualsTest, GeneralQp2) {
518 if (GetParam().qp_support != QpSupportType::kConvexQp) {
519 GTEST_SKIP() << kNoNonDiagonalQpSupportMessage;
520 }
521 Model model;
522 const Variable x0 = model.AddContinuousVariable(0, 1);
523 const Variable x1 = model.AddContinuousVariable(1, 2);
524 const LinearConstraint y0 = model.AddLinearConstraint(x0 + 2 * x1 >= 2);
525 model.Minimize(x0 * x0 + x0 * x1 + 3 * x1 * x1 - 2 * x0);
526
527 ASSERT_OK_AND_ASSIGN(const SolveResult solve_result, SimpleSolve(model));
528 const double expected_objective_value = 2.75;
529 EXPECT_THAT(solve_result, IsOptimalWithSolution(expected_objective_value,
530 {{x0, 0.5}, {x1, 1}}));
531 EXPECT_THAT(solve_result,
532 IsOptimalWithDualSolution(expected_objective_value, {{y0, 0.0}},
533 {{x0, 0.0}, {x1, 6.5}}));
534}
535
536} // namespace
537} // namespace operations_research::math_opt
IntegerValue y
SatParameters parameters
GRBmodel * model
const Variable x2
const LinearConstraint y1
const Variable x1
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}}}})))
constexpr std::string_view kNoQpSupportMessage
Definition qp_tests.cc:41
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::string ToString(QpSupportType qp_support)
Definition qp_tests.cc:64
constexpr std::string_view kNoNonDiagonalQpSupportMessage
Definition qp_tests.cc:44
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
Matcher< SolveResult > IsOptimalWithDualSolution(const double expected_objective, const LinearConstraintMap< double > expected_dual_values, const VariableMap< double > expected_reduced_costs, const double tolerance)
Definition matchers.cc:790
Matcher< SolveResult > IsOptimal(const std::optional< double > expected_primal_objective, const double tolerance)
Definition matchers.cc:762
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)
const Variable x
Definition qp_tests.cc:127
#define ASSERT_OK_AND_ASSIGN(lhs, rexpr)
bool use_integer_variables
True if the solver supports integer variables.
Definition qp_tests.h:50
QpTestParameters(SolverType solver_type, SolveParameters parameters, QpSupportType qp_support, bool supports_incrementalism_not_modifying_qp, bool supports_qp_incrementalism, bool use_integer_variables)
Definition qp_tests.cc:51
SolverType solver_type
The tested solver.
Definition qp_tests.h:35