Skip to content

Commit

Permalink
Merge pull request #4188 from rte-france/python_lp_basis
Browse files Browse the repository at this point in the history
Allow usage of MPSolver.SetStartingLPBasis in Python API
  • Loading branch information
lperron authored Sep 21, 2024
2 parents 9f41024 + 0cb3ceb commit 6099295
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 0 deletions.
27 changes: 27 additions & 0 deletions ortools/linear_solver/python/linear_solver.i
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>& variable_statuses,
const std::vector<int>& constraint_statuses) {
std::vector<operations_research::MPSolver::BasisStatus> variable_statuses_enum(variable_statuses.size());
std::vector<operations_research::MPSolver::BasisStatus> constraint_statuses_enum(constraint_statuses.size());
for (int i = 0; i < variable_statuses.size(); ++i) {
variable_statuses_enum[i] = static_cast<operations_research::MPSolver::BasisStatus>(variable_statuses[i]);
}
for (int i = 0; i < constraint_statuses.size(); ++i) {
constraint_statuses_enum[i] = static_cast<operations_research::MPSolver::BasisStatus>(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();
Expand Down
66 changes: 66 additions & 0 deletions ortools/linear_solver/python/set_starting_basis_test.py
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit 6099295

Please sign in to comment.