diff --git a/ortools/linear_solver/python/linear_solver.i b/ortools/linear_solver/python/linear_solver.i index c73d11807c2..6956dd70cbb 100644 --- a/ortools/linear_solver/python/linear_solver.i +++ b/ortools/linear_solver/python/linear_solver.i @@ -168,6 +168,33 @@ from ortools.linear_solver.python.linear_solver_natural_api import VariableExpr $self->SetHint(hint); } + // We have to define this custom method to handle conversion between int & enum + // from Python to C++ + /// Advanced usage: Incrementality. + /// + /// This function takes a starting basis to be used in the next LP Solve() + /// call. The statuses of a current solution can be retrieved via the + /// basis_status() function of a MPVariable or a MPConstraint (int between + /// 0 and 4: FREE = 0, AT_LOWER_BOUND = 1, AT_UPPER_BOUND = 2, FIXED_VALUE = 3, + // BASIC = 4) + /// + /// WARNING: With Glop, you should disable presolve when using this because + /// this information will not be modified in sync with the presolve and will + /// likely not mean much on the presolved problem. + void SetStartingLpBasis( + const std::vector& variable_statuses, + const std::vector& constraint_statuses) { + std::vector variable_statuses_enum(variable_statuses.size()); + std::vector constraint_statuses_enum(constraint_statuses.size()); + for (int i = 0; i < variable_statuses.size(); ++i) { + variable_statuses_enum[i] = static_cast(variable_statuses[i]); + } + for (int i = 0; i < constraint_statuses.size(); ++i) { + constraint_statuses_enum[i] = static_cast(constraint_statuses[i]); + } + $self->SetStartingLpBasis(variable_statuses_enum, constraint_statuses_enum); + } + /// Sets the number of threads to be used by the solver. bool SetNumThreads(int num_theads) { return $self->SetNumThreads(num_theads).ok(); diff --git a/ortools/linear_solver/python/set_starting_basis_test.py b/ortools/linear_solver/python/set_starting_basis_test.py new file mode 100644 index 00000000000..f49a1762af8 --- /dev/null +++ b/ortools/linear_solver/python/set_starting_basis_test.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# Copyright 2010-2024 Google LLC +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Simple unit tests for python LP Basis API.""" + +import unittest +import random +from ortools.linear_solver import pywraplp + +class TestSetStartingBasis(unittest.TestCase): + def build_large_lp(self, solver): + n_vars = 10 + if not solver: + return + random.seed(123) + objective = solver.Objective() + objective.SetMaximization() + for i in range(0, n_vars): + x = solver.IntVar(-random.random() * 200, random.random() * 200, 'x_' + str(i)) + objective.SetCoefficient(x, random.random() * 200 - 100) + if i == 0: + continue + rand1 = -random.random() * 2000 + rand2 = random.random() * 2000 + c = solver.Constraint(min(rand1, rand2), max(rand1, rand2)) + c.SetCoefficient(x, random.random() * 200 - 100) + for j in range(0, i): + c.SetCoefficient(solver.variable(j), random.random() * 200 - 100) + + def test_xpress(self): + # Build an LP and solve it, then fetch LP basis + solver = pywraplp.Solver.CreateSolver("XPRESS_LP") + self.build_large_lp(solver) + solver.Solve() + assert solver.iterations() >= 1 + + var_basis = [] + con_basis = [] + for var in solver.variables(): + var_basis.append(var.basis_status()) + for con in solver.constraints(): + con_basis.append(con.basis_status()) + + # Re-build the same optimization problem in another MPSolver + solver_with_basis = pywraplp.Solver.CreateSolver("XPRESS_LP") + self.build_large_lp(solver_with_basis) + # Set same basis as previous Solver + solver_with_basis.SetStartingLpBasis(var_basis, con_basis) + # Solve and check that it finds the same solution with no iterations at all + solver_with_basis.Solve() + self.assertAlmostEqual(solver.Objective().Value(), solver_with_basis.Objective().Value(), delta=1) + assert solver_with_basis.iterations() == 0 + +if __name__ == "__main__": + unittest.main()