Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
lp_initial_basis_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 <optional>
19#include <vector>
20
21#include "absl/log/check.h"
22#include "absl/status/statusor.h"
23#include "gtest/gtest.h"
25#include "ortools/math_opt/solution.pb.h"
26
27namespace operations_research {
28namespace math_opt {
29
30constexpr double kInf = std::numeric_limits<double>::infinity();
31
32// Set threads=1 so that the underlying solver doesn't use an ensemble of LP
33// algorithms.
35 : model_("Box LP"),
36 params_({.threads = 1, .lp_algorithm = LPAlgorithm::kPrimalSimplex}) {}
37
39 const bool is_maximize, const bool starting_basis_max_opt) {
40 model_.set_is_maximize(is_maximize);
42 const SolveArguments args = {
43 .parameters = params_,
44 .model_parameters = {.initial_basis = starting_basis_max_opt
47 const SolveResult result = Solve(model_, TestedSolver(), args).value();
48 CHECK_OK(result.termination.EnsureIsOptimal());
49 return result.solve_stats;
50}
51
54 const std::unique_ptr<IncrementalSolver> solver =
56 const SolveResult max_result = solver->Solve({.parameters = params_}).value();
57 CHECK_OK(max_result.termination.EnsureIsOptimal());
58 ModelSolveParameters max_model_parameters;
59 max_model_parameters.initial_basis = max_result.solutions[0].basis;
60 const SolveResult min_result = solver->Solve({.parameters = params_}).value();
61 CHECK_OK(min_result.termination.EnsureIsOptimal());
62
64 const SolveResult max_result_second =
65 solver
66 ->Solve(
67 {.parameters = params_, .model_parameters = max_model_parameters})
68 .value();
69 CHECK_OK(max_result_second.termination.EnsureIsOptimal());
70 return max_result_second.solve_stats;
71}
72
74// Model Blocks:
75// * Each function builds a simple model with an objective that can be
76// minimized or maximized. In both cases the model has a unique solution.
77// The functions also creates the basis for these two unique optimal
78// solutions.
79// * All functions return the distance in pivots between the maximizing and
80// minimizing basis. Models are constructed specifically so this distance
81// is the same for any pivoting rule (Those with distance > 0 have feasible
83// * Models are composable: Because variables and constraints are pair-wise
84// disjoint, calling multiple functions maintains validity of models and
85// basis. The basis distance for combined models is the sum of the basis
86// distances for the models.
87// * For some models the unique basic optimal solution for maximization and
88// minimization are the same. These models need to be composed with another
89// one for testing.
91
92// Sets up the 2-variable/0-constraint optimization problem:
93// {min/max} x1 - x2
94// s.t. variable bounds:
95// 0 <= x1 <= 1
96// 0 <= x2 <= 1
97// s.t. constraints:
98// none
99//
100// Note that for maximizing, this problem has the unique optimal solution
101//
102// x1 = 1, x2 = 0
103//
104// and for minimizing, this problem has the unique optimal solution
105//
106// x1 = 0, x2 = 1
107//
108// Further, the optimal basis for maximizing and minimizing are unique as well,
109// and are:
110//
111// For maximizing:
112//
113// {x1, BasisStatus::kAtUpperBound},
114// {x2, BasisStatus::kAtLowerBound},
115//
116// For minimizing:
117//
118// {x1, BasisStatus::kAtLowerBound}
119// {x2, BasisStatus::kAtUpperBound}
120//
121// This model covers variables at bounds statuses.
123 Variable x1 = model_.AddContinuousVariable(0, 1, "x1_variable_box");
124 Variable x2 = model_.AddContinuousVariable(0, 1, "x2_variable_box");
126
129
132
133 return 2;
134}
135
136// Sets up the 2-variable/4-constraint optimization problem:
137// {min/max} x1 - x2
138// s.t. variable bounds:
139// -inf <= x1 <= inf
140// -inf <= x2 <= inf
141// s.t. constraints:
142// x1 >= 0 (c1)
143// x1 <= 1 (c2)
144// x1 >= 0 (c3)
145// x1 <= 1 (c4)
146//
147// Note that for maximizing, this problem has the unique optimal solution
148//
149// x1 = 1, x2 = 0
150//
151// and for minimizing, this problem has the unique optimal solution
152//
153// x1 = 0, x2 = 1
154//
155// Further, the optimal basis for maximizing and minimizing are unique as well,
156// and are:
157//
158// For maximizing:
159//
160// {x1, BasisStatus::kBasic},
161// {x2, BasisStatus::kBasic},
162// {c1, BasisStatus::kBasic},
163// {c2, BasisStatus::kAtUpperBound},
164// {c3, BasisStatus::kAtLowerBound},
165// {c4, BasisStatus::kBasic},
166//
167// For minimizing:
168//
169// {x1, BasisStatus::kBasic},
170// {x2, BasisStatus::kBasic},
171// {c1, BasisStatus::kAtLowerBound},
172// {c2, BasisStatus::kBasic},
173// {c3, BasisStatus::kBasic},
174// {c4, BasisStatus::kAtUpperBound},
175//
176// This model covers basic variables, basic non-ranged constraints and
177// non-ranged constraints at bounds statuses.
179 Variable x1 = model_.AddContinuousVariable(-kInf, kInf, "x1_constraint_box");
180 Variable x2 = model_.AddContinuousVariable(-kInf, kInf, "x2_constraint_box");
181 LinearConstraint c1 =
182 model_.AddLinearConstraint(x1 >= 0, "c1_constraint_box");
183 LinearConstraint c2 =
184 model_.AddLinearConstraint(x1 <= 1, "c2_constraint_box");
185 LinearConstraint c3 =
186 model_.AddLinearConstraint(x2 >= 0, "c3_constraint_box");
187 LinearConstraint c4 =
188 model_.AddLinearConstraint(x2 <= 1, "c4_constraint_box");
190
197
204
205 return 2;
206}
207
208// Sets up the 2-variable/2-constraint optimization problem:
209// {min/max} x1 - x2
210// s.t. variable bounds:
211// -inf <= x1 <= inf
212// -inf <= x2 <= inf
213// s.t. constraints:
214// 0 <= x2 <= 1 (c1)
215// 0 <= x2 <= 1 (c2)
216//
217// Note that for maximizing, this problem has the unique optimal solution
218//
219// x1 = 1, x2 = 0
220//
221// and for minimizing, this problem has the unique optimal solution
222//
223// x1 = 0, x2 = 1
224//
225// Further, the optimal basis for maximizing and minimizing are unique as well,
226// and are:
227//
228// For maximizing:
229//
230// {x1, BasisStatus::kBasic},
231// {x2, BasisStatus::kBasic},
232// {c1, BasisStatus::kAtUpperBound},
233// {c2, BasisStatus::kAtLowerBound},
234//
235// For minimizing:
236//
237// {x1, BasisStatus::kBasic},
238// {x2, BasisStatus::kBasic},
239// {c1, BasisStatus::kAtLowerBound},
240// {c2, BasisStatus::kAtUpperBound},
241//
242// This model covers basic variables and ranged constraints at bounds statuses.
244 Variable x1 =
245 model_.AddContinuousVariable(-kInf, kInf, "x1_ranged_constraint_box");
246 Variable x2 =
247 model_.AddContinuousVariable(-kInf, kInf, "x2_ranged_constraint_box");
248 LinearConstraint c1 =
249 model_.AddLinearConstraint(0 <= x1 <= 1, "c1_ranged_constraint_box");
250 LinearConstraint c2 =
251 model_.AddLinearConstraint(0 <= x2 <= 1, "c2_ranged_constraint_box");
253
258
263
264 return 2;
265}
266
267// Sets up the 2-variable/1-constraint optimization problem:
268// {min/max} x1 - x2
269// s.t. variable bounds:
270// 0 <= x1 <= 1
271// 0 <= x2 <= 1
272// s.t. constraints:
273// -1 <= x1 + x2 <= 3 (c1)
274//
275// Note that for maximizing, this problem has the unique optimal solution
276//
277// x1 = 1, x2 = 0
278//
279// and for minimizing, this problem has the unique optimal solution
280//
281// x1 = 0, x2 = 1
282//
283// Further, the optimal basis for maximizing and minimizing are unique as well,
284// and are:
285//
286// For maximizing:
287//
288// {x1, BasisStatus::kAtUpperBound},
289// {x2, BasisStatus::kAtLowerBound},
290// {c1, BasisStatus::kBasic},
291//
292// For minimizing:
293//
294// {x1, BasisStatus::kAtLowerBound},
295// {x2, BasisStatus::kAtUpperBound},
296// {c1, BasisStatus::kBasic},
297//
298// This model is used to cover basic ranged constraints.
300 Variable x1 = model_.AddContinuousVariable(0, 1, "x1_basic_ranged");
301 Variable x2 = model_.AddContinuousVariable(0, 1, "x2_basic_ranged");
302 LinearConstraint c1 =
303 model_.AddLinearConstraint(-1 <= x1 + x2 <= 3, "c1_basic_ranged");
305
309
313
314 return 2;
315}
316
317// Sets up the 2-variable/1-constraint optimization problem:
318// {min/max} 0
319// s.t. variable bounds:
320// -inf <= x1 <= inf
321// -inf <= x2 <= inf
322// -inf <= x3 <= inf
323// s.t. constraints:
324// -inf <= x1 + x2 <= inf (c1)
325// -inf <= x2 + x3 <= inf (c2)
326//
327// Note that the unique basic feasible solition for this problem is
328//
329// x1 = x2 = x3 = 0
330//
331// Further, this solution has multiple basis. We pick the following basis for
332// both directions to cover free and basic statuses for unbounded variables and
333// constraints.
334//
335// {x1, BasisStatus::kFree},
336// {x2, BasisStatus::kFree},
337// {x3, BasisStatus::kBasic},
338// {c1, BasisStatus::kBasic},
339// {c2, BasisStatus::kFree},
341 Variable x1 = model_.AddContinuousVariable(-kInf, kInf, "x1_unbounded");
342 Variable x2 = model_.AddContinuousVariable(-kInf, kInf, "x2_unbounded");
343 Variable x3 = model_.AddContinuousVariable(-kInf, kInf, "x3_unbounded");
344 LinearConstraint c1 =
345 model_.AddLinearConstraint(-kInf <= x1 + x2 <= kInf, "c1_unbounded");
346 LinearConstraint c2 =
347 model_.AddLinearConstraint(-kInf <= x2 + x3 <= kInf, "c2_unbounded");
348
354
360
361 return 0;
362}
363
364// Sets up the 2-variable/0-constraint optimization problem:
365// {min/max} 0
366// s.t. variable bounds:
367// 0 <= x1 <= 0
368// 0 <= x2 <= 0
369// 0 <= x3 <= 0
370// s.t. constraints:
371// none
372//
373// Note that the unique feasible solition for this problem is
374//
375// x1 = x2 = x3 = 0
376//
377// Further, this solution has multiple basis (we can pick FIXED, AT_LOWER_BOUND,
378// or AT_UPPER_BOUND for each variable). We pick the following basis for both
379// directions to cover all three possible status choices.
380//
381// {x1, BasisStatus::kFixedValue},
382// {x2, BasisStatus::kAtLowerBound},
383// {x2, BasisStatus::kAtUpperBound},
385 Variable x1 = model_.AddContinuousVariable(0, 0, "x1_fixed_variable");
386 Variable x2 = model_.AddContinuousVariable(0, 0, "x2_fixed_variable");
387 Variable x3 = model_.AddContinuousVariable(0, 0, "x3_fixed_variable");
388
395
396 return 0;
397}
398
399// Sets up the 2-variable/1-constraint optimization problem:
400// {min/max} 0
401// s.t. variable bounds:
402// -1 <= x1 <= 1
403// -1 <= x2 <= 1
404// -1 <= x3 <= 1
405// s.t. constraints:
406// x1 + x2 == 0 (c1)
407// x2 + x3 == 0 (c2)
408// x3 + x1 == 0 (c3)
409// x1 + x2 + x3 == 0 (c4)
410//
411// Note that the unique feasible solition for this problem is
412//
413// x1 = x2 = x3 = 0
414//
415// Further, this solution has multiple basis (e.g. note that c4 is a redundant
416// constraint). We pick the following basis for both directions to cover all
417// four possible status choices for equality constraints
418//
419// {x1, BasisStatus::kBasic},
420// {x2, BasisStatus::kBasic},
421// {x3, BasisStatus::kBasic},
422// {c1, BasisStatus::kFixedValue},
423// {c1, BasisStatus::kAtLowerBound},
424// {c1, BasisStatus::kAtUpperBound},
425// {c1, BasisStatus::kBasic},
427 Variable x1 = model_.AddContinuousVariable(-1, 1, "x1_equality");
428 Variable x2 = model_.AddContinuousVariable(-1, 1, "x2_equality");
429 Variable x3 = model_.AddContinuousVariable(-1, 1, "x3_equality");
430 LinearConstraint c1 = model_.AddLinearConstraint(x1 + x2 == 0, "c1_equality");
431 LinearConstraint c2 = model_.AddLinearConstraint(x2 + x3 == 0, "c2_equality");
432 LinearConstraint c3 = model_.AddLinearConstraint(x3 + x1 == 0, "c3_equality");
433 LinearConstraint c4 =
434 model_.AddLinearConstraint(x1 + x2 + x3 == 0, "c4_equality");
435
443
451
452 return 0;
453}
454
455namespace {
456TEST_P(LpBasisStartTest, EmptyModelAndBasis) {
457 model_.Maximize(objective_expression_);
458 const SolveStats stats = SolveWithWarmStart(
459 /*is_maximize=*/true, /*starting_basis_max_opt=*/false);
460 EXPECT_EQ(stats.simplex_iterations, 0);
461}
462
463TEST_P(LpBasisStartTest, ModelWithoutVariables) {
464 LinearConstraint c = model_.AddLinearConstraint("trivial equality");
465 min_optimal_basis_.constraint_status.emplace(c, BasisStatus::kBasic);
466 const SolveStats stats = SolveWithWarmStart(
467 /*is_maximize=*/true, /*starting_basis_max_opt=*/false);
468 EXPECT_EQ(stats.simplex_iterations, 0);
469}
470
472// Basis distance test for individual models and full combined model:
473// * Set minimize basis
474// * Solve maximize problem
475// * Check that the number of simplex iterations is equal to the distance
476// between the maximize and minimize basis
478
479TEST_P(LpBasisStartTest, VariableBoundBoxModel) {
480 const int basis_distance = SetUpVariableBoundBoxModel();
481 const SolveStats stats = SolveWithWarmStart(
482 /*is_maximize=*/true, /*starting_basis_max_opt=*/false);
483 EXPECT_EQ(stats.simplex_iterations, basis_distance);
484}
485
486TEST_P(LpBasisStartTest, ConstraintBoxModel) {
487 const int basis_distance = SetUpConstraintBoxModel();
488 const SolveStats stats = SolveWithWarmStart(
489 /*is_maximize=*/true, /*starting_basis_max_opt=*/false);
490 EXPECT_EQ(stats.simplex_iterations, basis_distance);
491}
492
493TEST_P(LpBasisStartTest, RangedConstraintBoxModel) {
494 const int basis_distance = SetUpRangedConstraintBoxModel();
495 const SolveStats stats = SolveWithWarmStart(
496 /*is_maximize=*/true, /*starting_basis_max_opt=*/false);
497 EXPECT_EQ(stats.simplex_iterations, basis_distance);
498}
499
500TEST_P(LpBasisStartTest, BasicRangedConstraintModel) {
501 const int basis_distance = SetUpBasicRangedConstraintModel();
502 const SolveStats stats = SolveWithWarmStart(
503 /*is_maximize=*/true, /*starting_basis_max_opt=*/false);
504 EXPECT_EQ(stats.simplex_iterations, basis_distance);
505}
506
507TEST_P(LpBasisStartTest, UnboundedVariablesAndConstraintsModel) {
508 // UnboundedVariablesAndConstraintsModel has the same optimal basic solution
509 // for max and min so we compose it with SingleVariableModel.
510 int basis_distance = SetUpVariableBoundBoxModel();
511 basis_distance += SetUpUnboundedVariablesAndConstraintsModel();
512 const SolveStats stats = SolveWithWarmStart(
513 /*is_maximize=*/true, /*starting_basis_max_opt=*/false);
514 EXPECT_EQ(stats.simplex_iterations, basis_distance);
515}
516
517TEST_P(LpBasisStartTest, FixedVariablesModel) {
518 // FixedVariablesModel has the same optimal basic solution for max and min
519 // so we compose it with SingleVariableModel.
520 int basis_distance = SetUpVariableBoundBoxModel();
521 basis_distance += SetUpFixedVariablesModel();
522 const SolveStats stats = SolveWithWarmStart(
523 /*is_maximize=*/true, /*starting_basis_max_opt=*/false);
524 EXPECT_EQ(stats.simplex_iterations, basis_distance);
525}
526
527TEST_P(LpBasisStartTest, EqualitiesModel) {
528 // EqualitiesModel has the same optimal basic solution for max and min
529 // so we compose it with SingleVariableModel.
530 int basis_distance = SetUpVariableBoundBoxModel();
531 basis_distance += SetUpEqualitiesModel();
532 const SolveStats stats = SolveWithWarmStart(
533 /*is_maximize=*/true, /*starting_basis_max_opt=*/false);
534 EXPECT_EQ(stats.simplex_iterations, basis_distance);
535}
536
537TEST_P(LpBasisStartTest, CombinedModels) {
538 // EqualitiesModel has the same optimal basic solution for max and min
539 // so we compose it with SingleVariableModel.
540 int basis_distance = SetUpVariableBoundBoxModel();
541 basis_distance += SetUpConstraintBoxModel();
542 basis_distance += SetUpRangedConstraintBoxModel();
543 basis_distance += SetUpBasicRangedConstraintModel();
544 basis_distance += SetUpUnboundedVariablesAndConstraintsModel();
545 basis_distance += SetUpFixedVariablesModel();
546 basis_distance += SetUpEqualitiesModel();
547 const SolveStats stats = SolveWithWarmStart(
548 /*is_maximize=*/true, /*starting_basis_max_opt=*/false);
549 EXPECT_EQ(stats.simplex_iterations, basis_distance);
550}
551
553// Roundtrip for individual models and full combined model:
554// * Solve maximize problem
555// * Save optimal basis
556// * Solve minimize problem
557// * Set saved basis
558// * Solve maximize problem
559// * Check that simplex takes zero iterations
560//
561// Note: The minimization solve in the middle aims to leave the solver's
562// internal status at the minimization basis before setting the basis for the
563// last maximization solve. If setting this basis fails (i.e. the solver
564// rejects the basis), then the solver "should" start that last maximization
565// solve from the minimization basis, hence taking at least one pivot and
566// failing the test (this assumes the solver does not re-run preprocessing
567// for this last maximization problem if the basis is rejected).
569
570TEST_P(LpBasisStartTest, VariableBoundBoxModelOptimalRoundtrip) {
571 SetUpVariableBoundBoxModel();
572 const SolveStats stats = RoundTripSolve();
573 EXPECT_EQ(stats.simplex_iterations, 0);
574}
575
576TEST_P(LpBasisStartTest, ConstraintBoxModelOptimalRoundtrip) {
577 SetUpConstraintBoxModel();
578 const SolveStats stats = RoundTripSolve();
579 EXPECT_EQ(stats.simplex_iterations, 0);
580}
581
582TEST_P(LpBasisStartTest, RangedConstraintBoxModelOptimalRoundtrip) {
583 SetUpRangedConstraintBoxModel();
584 const SolveStats stats = RoundTripSolve();
585 EXPECT_EQ(stats.simplex_iterations, 0);
586}
587
588TEST_P(LpBasisStartTest, BasicRangedConstraintModelOptimalRoundtrip) {
589 SetUpBasicRangedConstraintModel();
590 const SolveStats stats = RoundTripSolve();
591 EXPECT_EQ(stats.simplex_iterations, 0);
592}
593
594TEST_P(LpBasisStartTest,
595 UnboundedVariablesAndConstraintsModelOptimalRoundtrip) {
596 // UnboundedVariablesAndConstraintsModel has the same optimal basic solution
597 // for max and min so we compose it with SingleVariableModel.
598 SetUpVariableBoundBoxModel();
599 SetUpUnboundedVariablesAndConstraintsModel();
600 const SolveStats stats = RoundTripSolve();
601 EXPECT_EQ(stats.simplex_iterations, 0);
602}
603
604TEST_P(LpBasisStartTest, FixedVariablesModelOptimalRoundtrip) {
605 // FixedVariablesModel has the same optimal basic solution for max and min
606 // so we compose it with SingleVariableModel.
607 SetUpVariableBoundBoxModel();
608 SetUpFixedVariablesModel();
609 const SolveStats stats = RoundTripSolve();
610 EXPECT_EQ(stats.simplex_iterations, 0);
611}
612
613TEST_P(LpBasisStartTest, EqualitiesModelOptimalRoundtrip) {
614 // EqualitiesModel has the same optimal basic solution for max and min
615 // so we compose it with SingleVariableModel.
616 SetUpVariableBoundBoxModel();
617 SetUpEqualitiesModel();
618 const SolveStats stats = RoundTripSolve();
619 EXPECT_EQ(stats.simplex_iterations, 0);
620}
621
622TEST_P(LpBasisStartTest, CombinedModelsOptimalRoundtrip) {
623 // EqualitiesModel has the same optimal basic solution for max and min
624 // so we compose it with SingleVariableModel.
625 SetUpVariableBoundBoxModel();
626 SetUpConstraintBoxModel();
627 SetUpRangedConstraintBoxModel();
628 SetUpBasicRangedConstraintModel();
629 SetUpUnboundedVariablesAndConstraintsModel();
630 SetUpFixedVariablesModel();
631 SetUpEqualitiesModel();
632 const SolveStats stats = RoundTripSolve();
633 EXPECT_EQ(stats.simplex_iterations, 0);
634}
635
636} // namespace
637} // namespace math_opt
638} // namespace operations_research
SolveStats SolveWithWarmStart(bool is_maximize, bool starting_basis_max_opt)
void Maximize(double objective)
Sets the objective to maximize the provided expression.
Definition model.h:1389
void set_is_maximize(bool is_maximize)
Prefer set_maximize() and set_minimize() above for more readable code.
Definition model.h:1512
void AddToObjective(double objective)
Adds the provided expression terms to the objective.
Definition model.h:1431
Variable AddContinuousVariable(double lower_bound, double upper_bound, absl::string_view name="")
Adds a variable to the model with domain [lower_bound, upper_bound].
Definition model.h:963
LinearConstraint AddLinearConstraint(absl::string_view name="")
Adds a linear constraint to the model with bounds [-inf, +inf].
Definition model.h:1064
int64_t value
const Variable x2
const Variable x1
TEST_P(InfeasibleSubsystemTest, CanComputeInfeasibleSubsystem)
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
Definition solve.cc:62
absl::StatusOr< std::unique_ptr< IncrementalSolver > > NewIncrementalSolver(Model *model, SolverType solver_type, SolverInitArguments arguments)
Definition solve.cc:82
@ kFixedValue
The variable/constraint has identical finite lower and upper bounds.
@ kBasic
The variable/constraint is basic.
@ kFree
The variable/constraint is free (it has no finite bounds).
@ kAtLowerBound
The variable/constraint is at its lower bound (which must be finite).
@ kAtUpperBound
The variable/constraint is at its upper bound (which must be finite).
In SWIG mode, we don't want anything besides these top-level includes.
LinearConstraintMap< BasisStatus > constraint_status
Definition solution.h:234
VariableMap< BasisStatus > variable_status
Definition solution.h:235