diff --git a/bazel/python_deps.txt b/bazel/python_deps.txt index 1cd3dd668a..879d7b0900 100644 --- a/bazel/python_deps.txt +++ b/bazel/python_deps.txt @@ -1,3 +1,4 @@ absl-py >= 0.13 numpy >= 1.13.3 protobuf >= 4.21.10 +scipy >= 1.10.0 diff --git a/ortools/linear_solver/python/BUILD.bazel b/ortools/linear_solver/python/BUILD.bazel index 952a290900..b0011890fb 100644 --- a/ortools/linear_solver/python/BUILD.bazel +++ b/ortools/linear_solver/python/BUILD.bazel @@ -14,7 +14,7 @@ # Python wrapper for model_builder. load("@ortools_deps//:requirements.bzl", "requirement") load("@pybind11_bazel//:build_defs.bzl", "pybind_extension") -load("@rules_python//python:defs.bzl", "py_library") +load("@rules_python//python:defs.bzl", "py_library", "py_test") pybind_extension( name = "pywrap_model_builder_helper", @@ -42,6 +42,22 @@ py_library( ], ) +py_test( + name = "model_builder_helper_test", + srcs = ["model_builder_helper_test.py"], + data = [ + ":pywrap_model_builder_helper.so", + "//ortools/linear_solver/testdata:large_model.mps.gz", + "//ortools/linear_solver/testdata:maximization.mps", + ], + python_version = "PY3", + deps = [ + ":model_builder_helper", + requirement("numpy"), + requirement("scipy"), + ], +) + py_library( name = "model_builder", srcs = ["model_builder.py"], @@ -51,6 +67,23 @@ py_library( visibility = ["//visibility:public"], deps = [ ":model_builder_helper", + requirement("numpy"), "//ortools/linear_solver:linear_solver_py_pb2", ], ) + +py_test( + name = "model_builder_test", + srcs = ["model_builder_test.py"], + data = [ + ":pywrap_model_builder_helper.so", + "//ortools/linear_solver/testdata:maximization.mps", + "//ortools/linear_solver/testdata:small_model.lp", + ], + python_version = "PY3", + deps = [ + ":model_builder", + ":model_builder_helper", + requirement("numpy"), + ], +) diff --git a/ortools/linear_solver/python/model_builder.py b/ortools/linear_solver/python/model_builder.py index cd82fef764..5e8599f52c 100644 --- a/ortools/linear_solver/python/model_builder.py +++ b/ortools/linear_solver/python/model_builder.py @@ -32,6 +32,7 @@ rather than for solving specific optimization problems. """ import math +import numpy as np from ortools.linear_solver.python import model_builder_helper as mbh from ortools.linear_solver.python import pywrap_model_builder_helper as pwmb @@ -40,7 +41,7 @@ from ortools.linear_solver.python import pywrap_model_builder_helper as pwmb SolveStatus = pwmb.SolveStatus -class LinearExpr(object): +class LinearExpr: """Holds an linear expression. A linear expression is built from constants and variables. @@ -425,11 +426,11 @@ class Variable(LinearExpr): # helper is a ModelBuilderHelper, lb is an index (int), ub is None, # is_integral is None, and name is None. if mbh.is_integral(lb) and ub is None and is_integral is None: - self.__index = int(lb) + self.__index = np.int32(lb) self.__helper = helper else: index = helper.add_var() - self.__index = index + self.__index = np.int32(index) self.__helper = helper helper.set_var_lower_bound(index, lb) helper.set_var_upper_bound(index, ub) @@ -464,10 +465,10 @@ class Variable(LinearExpr): def __repr__(self): index = self.__index - name = self.__helper.VarName(index) - lb = self.__helper.VarLowerBound(index) - ub = self.__helper.VarUpperBound(index) - is_integer = self.__helper.VarIsInteger(index) + name = self.__helper.var_name(index) + lb = self.__helper.var_lower_bound(index) + ub = self.__helper.var_upper_bound(index) + is_integer = self.__helper.var_is_integral(index) if name: if is_integer: return f'{name}(index={index}, lb={lb}, ub={ub}, integer)' @@ -549,7 +550,61 @@ class Variable(LinearExpr): return hash((self.__helper, self.__index)) -class VarCompVar(object): +class VariableContainer: + """Variable container.""" + + def __init__(self, helper, indices): + self.__helper = helper + self.__indices = indices + + def __getitem__(self, pos): + index_or_slice = self.__indices[pos] + if mbh.is_integral(index_or_slice): + return Variable(self.__helper, self.__indices[pos], None, None, + None) + else: + return VariableContainer(self.__helper, index_or_slice) + + def index_at(self, pos): + """Returns the index of the variable at the position 'pos'.""" + return self.__indices[pos] + + # pylint: disable=invalid-name + @property + def T(self): + """Returns a view upon the transposed numpy array of variables.""" + return VariableContainer(self.__helper, self.__indices.T) + + # pylint: enable=invalid-name + + @property + def shape(self): + """Returns the shape of the numpy array.""" + return self.__indices.shape + + @property + def size(self): + """Returns the number of variables in the numpy array.""" + return self.__indices.size + + @property + def ravel(self): + """returns the flattened array of variables.""" + return VariableContainer(self.__helper, self.__indices.ravel()) + + @property + def flatten(self): + """returns the flattened array of variables.""" + return VariableContainer(self.__helper, self.__indices.flatten()) + + def __str__(self): + return f'VariableContainer({self.__indices})' + + def __repr__(self): + return f'VariableContainer({self.__helper}, {repr(self.__indices)})' + + +class VarCompVar: """Represents var == /!= var.""" def __init__(self, left, right, is_equality): @@ -579,10 +634,11 @@ class VarCompVar(object): return self.__is_equality def __bool__(self): - return (self.__left.index == self.__right.index) == self.__is_equality + return bool( + self.__left.index == self.__right.index) == self.__is_equality -class BoundedLinearExpression(object): +class BoundedLinearExpression: """Represents a linear constraint: `lb <= linear expression <= ub`. The only use of this class is to be added to the ModelBuilder through @@ -627,7 +683,7 @@ class BoundedLinearExpression(object): f'Cannot use a BoundedLinearExpression {self} as a Boolean value') -class LinearConstraint(object): +class LinearConstraint: """Stores a linear equation. Example: @@ -680,7 +736,7 @@ class LinearConstraint(object): self.__helper.add_term_to_constraint(self.__index, var.index, coeff) -class ModelBuilder(object): +class ModelBuilder: """Methods for building a linear model. Methods beginning with: @@ -745,6 +801,53 @@ class ModelBuilder(object): """Declares a constant variable.""" return self.new_var(value, value, False, '') + def new_var_ndarray_with_bounds(self, lbs, ubs, ints, name): + """Creates a vector of continuous variables from two vector of bounds.""" + if np.shape(lbs) != np.shape(ubs): + raise ValueError('The lbs and ubs vectors must have the same size') + if np.shape(lbs) != np.shape(ints): + raise ValueError('The lbs and ints vectors must have the same size') + var_indices = self.__helper.add_var_ndarray_with_bounds( + lbs, ubs, ints, name) + return VariableContainer(self.__helper, var_indices) + + def new_num_var_ndarray(self, shape, lb, ub, name): + """Creates a vector of continuous variables with the same bounds.""" + if mbh.is_integral(shape): + shape = [shape] + var_indices = self.__helper.add_var_ndarray(shape, lb, ub, False, name) + return VariableContainer(self.__helper, var_indices) + + def new_num_var_ndarray_with_bounds(self, lbs, ubs, name): + """Creates a vector of continuous variables from two vector of bounds.""" + if np.shape(lbs) != np.shape(ubs): + raise ValueError('The lbs and ubs vectors must have the same size') + var_indices = self.__helper.add_var_ndarray_with_bounds( + lbs, ubs, np.zeros(len(lbs), dtype=bool), name) + return VariableContainer(self.__helper, var_indices) + + def new_int_var_ndarray(self, shape, lb, ub, name): + """Creates a vector of integer variables with the same bounds.""" + if mbh.is_integral(shape): + shape = [shape] + var_indices = self.__helper.add_var_ndarray(shape, lb, ub, True, name) + return VariableContainer(self.__helper, var_indices) + + def new_int_var_ndarray_with_bounds(self, lbs, ubs, name): + """Creates a vector of integer variables from two vector of bounds.""" + if np.shape(lbs) != np.shape(ubs): + raise ValueError('The lbs and ubs vectors must have the same size') + var_indices = self.__helper.add_var_ndarray_with_bounds( + lbs, ubs, np.ones(len(lbs), dtype=bool), name) + return VariableContainer(self.__helper, var_indices) + + def new_bool_var_ndarray(self, shape, name): + """Creates a vector of Boolean variables.""" + if mbh.is_integral(shape): + shape = [shape] + var_indices = self.__helper.add_var_ndarray(shape, 0.0, 1.0, True, name) + return VariableContainer(self.__helper, var_indices) + def var_from_index(self, index): """Rebuilds a variable object from the model and its index.""" return Variable(self.__helper, index, None, None, None) @@ -896,7 +999,7 @@ class ModelBuilder(object): return self.__helper -class ModelSolver(object): +class ModelSolver: """Main solver class. The purpose of this class is to search for a solution to the model provided diff --git a/ortools/linear_solver/python/model_builder_helper_test.py b/ortools/linear_solver/python/model_builder_helper_test.py new file mode 100644 index 0000000000..fc0585cbcb --- /dev/null +++ b/ortools/linear_solver/python/model_builder_helper_test.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# Copyright 2010-2022 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. +"""Tests for model_builder_helper.""" + +import gzip +import os +import threading +import numpy as np +from scipy import sparse + +import unittest +from ortools.linear_solver import linear_solver_pb2 +from ortools.linear_solver.python import pywrap_model_builder_helper + + +class PywrapModelBuilderHelperTest(unittest.TestCase): + + def test_export_model_proto_to_mps_string(self): + model = pywrap_model_builder_helper.ModelBuilderHelper() + model.set_name('testmodel') + result = model.export_to_mps_string() + self.assertIn('testmodel', result) + self.assertIn('ENDATA', result) + + def test_export_model_proto_to_lp_string(self): + model = pywrap_model_builder_helper.ModelBuilderHelper() + model.set_maximize(True) + lp_string = model.export_to_lp_string() + self.assertIn('Maximize', lp_string) + + def test_import_from_mps_string(self): + model = pywrap_model_builder_helper.ModelBuilderHelper() + self.assertTrue(model.import_from_mps_string('NAME testmodel')) + self.assertEqual(model.name(), 'testmodel') + + # ImportFromMpsFile doesn't read from files yet + def test_import_from_mps_file(self): + path = os.path.dirname(__file__) + mps_path = f'{path}/../testdata/maximization.mps' + model = pywrap_model_builder_helper.ModelBuilderHelper() + self.assertTrue(model.import_from_mps_file(mps_path)) + self.assertEqual(model.name(), 'SupportedMaximizationProblem') + + def test_import_from_lp_string(self): + model = pywrap_model_builder_helper.ModelBuilderHelper() + model.import_from_lp_string('max:') + self.assertTrue(model.maximize()) + + # TODO(user): Add test_import_from_lp_file after the implementation is fixed + + def test_solve_with_glop(self): + model = linear_solver_pb2.MPModelProto() + model.variable.append( + linear_solver_pb2.MPVariableProto(lower_bound=0.0, + upper_bound=1.0, + objective_coefficient=1.0)) + model.maximize = True + request = linear_solver_pb2.MPModelRequest( + model=model, + solver_type=linear_solver_pb2.MPModelRequest.GLOP_LINEAR_PROGRAMMING + ) + solver_helper = pywrap_model_builder_helper.ModelSolverHelper('') + result = solver_helper.solve_serialized_request( + request.SerializeToString()) + response = linear_solver_pb2.MPSolutionResponse().FromString(result) + self.assertEqual( + response.status, + linear_solver_pb2.MPSolverResponseStatus.MPSOLVER_OPTIMAL) + self.assertAlmostEqual(response.objective_value, 1.0) + + def test_solve_with_glop_direct(self): + model = pywrap_model_builder_helper.ModelBuilderHelper() + self.assertEqual(0, model.add_var()) + model.set_var_lower_bound(0, 0.0) + model.set_var_upper_bound(0, 1.0) + model.set_var_objective_coefficient(0, 1.0) + model.set_maximize(True) + + solver = pywrap_model_builder_helper.ModelSolverHelper('glop') + solver.solve(model) + self.assertEqual( + solver.status(), + linear_solver_pb2.MPSolverResponseStatus.MPSOLVER_OPTIMAL) + self.assertAlmostEqual(solver.objective_value(), 1.0) + self.assertAlmostEqual(solver.var_value(0), 1.0) + values = solver.variable_values() + self.assertEqual(1, len(values)) + self.assertAlmostEqual(1.0, values[0]) + + def test_solve_with_pdlp(self): + model = linear_solver_pb2.MPModelProto() + model.variable.append( + linear_solver_pb2.MPVariableProto(lower_bound=0.0, + upper_bound=1.0, + objective_coefficient=1.0)) + model.maximize = True + request = linear_solver_pb2.MPModelRequest( + model=model, + solver_type=linear_solver_pb2.MPModelRequest.PDLP_LINEAR_PROGRAMMING + ) + solver_helper = pywrap_model_builder_helper.ModelSolverHelper('') + result = solver_helper.solve_serialized_request( + request.SerializeToString()) + response = linear_solver_pb2.MPSolutionResponse().FromString(result) + self.assertEqual( + response.status, + linear_solver_pb2.MPSolverResponseStatus.MPSOLVER_OPTIMAL) + self.assertAlmostEqual(response.objective_value, 1.0) + + # TODO(user): Test the log callback after the implementation is completed. + + def test_interrupt_solve(self): + # This is an instance that we know Glop won't solve quickly. + path = os.path.dirname(__file__) + mps_path = f'{path}/../testdata/large_model.mps.gz' + with gzip.open(mps_path, 'r') as f: + mps_data = f.read() + model_helper = pywrap_model_builder_helper.ModelBuilderHelper() + self.assertTrue(model_helper.import_from_mps_string(mps_data)) + solver_helper = pywrap_model_builder_helper.ModelSolverHelper('glop') + + result = [] + solve_thread = threading.Thread( + target=lambda: result.append(solver_helper.solve(model_helper))) + solve_thread.start() + self.assertTrue(solver_helper.interrupt_solve()) + solve_thread.join(timeout=30.0) + self.assertTrue(solver_helper.has_response()) + self.assertEqual( + solver_helper.status(), + pywrap_model_builder_helper.SolveStatus.CANCELLED_BY_USER) + + def test_build_model(self): + var_lb = np.array([-1.0]) + var_ub = np.array([np.inf]) + obj = np.array([10.0]) + con_lb = np.array([-5.0, -6.0]) + con_ub = np.array([5.0, 6.0]) + constraint_matrix = sparse.csr_matrix(np.array([[1.0], [2.0]])) + + model = pywrap_model_builder_helper.ModelBuilderHelper() + model.fill_model_from_sparse_data(var_lb, var_ub, obj, con_lb, con_ub, + constraint_matrix) + self.assertEqual(1, model.num_variables()) + self.assertEqual(-1.0, model.var_lower_bound(0)) + self.assertEqual(np.inf, model.var_upper_bound(0)) + self.assertEqual(10.0, model.var_objective_coefficient(0)) + + self.assertEqual(2, model.num_constraints()) + self.assertEqual(-5.0, model.constraint_lower_bound(0)) + self.assertEqual(5.0, model.constraint_upper_bound(0)) + self.assertEqual([0], model.constraint_var_indices(0)) + self.assertEqual([1.0], model.constraint_coefficients(0)) + + self.assertEqual(-6.0, model.constraint_lower_bound(1)) + self.assertEqual(6.0, model.constraint_upper_bound(1)) + self.assertEqual([0], model.constraint_var_indices(1)) + self.assertEqual([2.0], model.constraint_coefficients(1)) + + var_array = model.add_var_ndarray([10], 1.0, 5.0, True, 'var_') + self.assertEqual(1, var_array.ndim) + self.assertEqual(10, var_array.size) + self.assertEqual((10,), var_array.shape) + self.assertEqual(model.var_name(var_array[3]), 'var_3') + + +if __name__ == '__main__': + unittest.main() diff --git a/ortools/linear_solver/python/model_builder_test.py b/ortools/linear_solver/python/model_builder_test.py index 8e23773070..efbd095e50 100644 --- a/ortools/linear_solver/python/model_builder_test.py +++ b/ortools/linear_solver/python/model_builder_test.py @@ -14,9 +14,11 @@ """Tests for model_builder.""" import math +import numpy as np +import os + from ortools.linear_solver.python import model_builder import unittest -import os class ModelBuilderTest(unittest.TestCase): @@ -124,7 +126,7 @@ ENDATA def test_import_from_mps_file(self): path = os.path.dirname(__file__) - mps_path = f'{path}/maximization.mps' + mps_path = f'{path}/../testdata/maximization.mps' model = model_builder.ModelBuilder() self.assertTrue(model.import_from_mps_file(mps_path)) self.assertEqual(model.name, 'SupportedMaximizationProblem') @@ -148,7 +150,7 @@ ENDATA def test_import_from_lp_file(self): path = os.path.dirname(__file__) - lp_path = f'{path}/small_model.lp' + lp_path = f'{path}/../testdata/small_model.lp' model = model_builder.ModelBuilder() self.assertTrue(model.import_from_lp_file(lp_path)) self.assertEqual(6, model.num_variables) @@ -177,6 +179,33 @@ ENDATA self.assertEqual(x, x_copy) self.assertNotEqual(x, y) + # array + xs = model.new_int_var_ndarray(10, 0.0, 5.0, 'xs_') + self.assertEqual(10, xs.size) + self.assertEqual('xs_4', str(xs[4])) + lbs = np.array([1.0, 2.0, 3.0]) + ubs = np.array([3.0, 4.0, 5.0]) + ys = model.new_int_var_ndarray_with_bounds(lbs, ubs, 'ys_') + self.assertEqual('VariableContainer([12 13 14])', str(ys)) + zs = model.new_int_var_ndarray_with_bounds([1.0, 2.0, 3], [4, 4, 4], + 'zs_') + self.assertEqual(3, zs.size) + self.assertEqual((3,), zs.shape) + self.assertEqual('zs_1', str(zs[1])) + self.assertEqual('zs_2(index=17, lb=3.0, ub=4.0, integer)', repr(zs[2])) + + bs = model.new_bool_var_ndarray([4, 5], 'bs_') + self.assertEqual((4, 5), bs.shape) + self.assertEqual((5, 4), bs.T.shape) + self.assertEqual(31, bs.index_at((2, 3))) + self.assertEqual(20, bs.size) + self.assertEqual((20,), bs.flatten.shape) + self.assertEqual((20,), bs.ravel.shape) + + # Slices are [lb, ub) closed - open. + self.assertEqual(5, bs[3, :].size) + self.assertEqual(6, bs[1:3, 2:5].size) + # Tests the hash method. var_set = set() var_set.add(x) diff --git a/ortools/linear_solver/python/pywrap_model_builder_helper.cc b/ortools/linear_solver/python/pywrap_model_builder_helper.cc index db088027fa..b1d7a7f8d9 100644 --- a/ortools/linear_solver/python/pywrap_model_builder_helper.cc +++ b/ortools/linear_solver/python/pywrap_model_builder_helper.cc @@ -16,6 +16,7 @@ #include #include #include +#include #include "Eigen/Core" #include "Eigen/SparseCore" @@ -39,7 +40,9 @@ using ::operations_research::MPModelRequest; using ::operations_research::MPSolutionResponse; using ::operations_research::MPVariableProto; using ::operations_research::SolveStatus; -using ::pybind11::arg; + +namespace py = pybind11; +using ::py::arg; // TODO(user): The interface uses serialized protos because of issues building // pybind11_protobuf. See @@ -103,8 +106,8 @@ void BuildModelFromSparseData( } PYBIND11_MODULE(pywrap_model_builder_helper, m) { - pybind11::class_(m, "MPModelExportOptions") - .def(pybind11::init<>()) + py::class_(m, "MPModelExportOptions") + .def(py::init<>()) .def_readwrite("obfuscate", &MPModelExportOptions::obfuscate) .def_readwrite("log_invalid_names", &MPModelExportOptions::log_invalid_names) @@ -112,8 +115,8 @@ PYBIND11_MODULE(pywrap_model_builder_helper, m) { &MPModelExportOptions::show_unused_variables) .def_readwrite("max_line_length", &MPModelExportOptions::max_line_length); - pybind11::class_(m, "ModelBuilderHelper") - .def(pybind11::init<>()) + py::class_(m, "ModelBuilderHelper") + .def(py::init<>()) .def("export_to_mps_string", &ModelBuilderHelper::ExportToMpsString, arg("options") = MPModelExportOptions()) .def("export_to_lp_string", &ModelBuilderHelper::ExportToLpString, @@ -147,6 +150,68 @@ PYBIND11_MODULE(pywrap_model_builder_helper, m) { arg("objective_coefficients"), arg("constraint_lower_bounds"), arg("constraint_upper_bounds"), arg("constraint_matrix")) .def("add_var", &ModelBuilderHelper::AddVar) + .def("add_var_ndarray", + [](ModelBuilderHelper* helper, std::vector shape, double lb, + double ub, bool is_integral, const std::string& name_prefix) { + int size = shape[0]; + for (int i = 1; i < shape.size(); ++i) { + size *= shape[i]; + } + py::array_t result(size); + py::buffer_info info = result.request(); + result.resize(shape); + auto ptr = static_cast(info.ptr); + for (int i = 0; i < size; ++i) { + const int index = helper->AddVar(); + ptr[i] = index; + helper->SetVarLowerBound(index, lb); + helper->SetVarUpperBound(index, ub); + helper->SetVarIntegrality(index, is_integral); + if (!name_prefix.empty()) { + helper->SetVarName(index, absl::StrCat(name_prefix, i)); + } + } + return result; + }) + .def("add_var_ndarray_with_bounds", + [](ModelBuilderHelper* helper, py::array_t lbs, + py::array_t ubs, py::array_t are_integral, + const std::string& name_prefix) { + py::buffer_info buf_lbs = lbs.request(); + py::buffer_info buf_ubs = ubs.request(); + py::buffer_info buf_are_integral = are_integral.request(); + if (buf_lbs.ndim != 1 || buf_ubs.ndim != 1 || + buf_are_integral.ndim != 1) { + throw std::runtime_error("Number of dimensions must be one"); + } + const int size = buf_lbs.size; + if (size != buf_ubs.size || size != buf_are_integral.size) { + throw std::runtime_error("Input sizes must match"); + } + const auto shape = buf_lbs.shape; + if (shape != buf_ubs.shape || shape != buf_are_integral.shape) { + throw std::runtime_error("Input shapes must match"); + } + + auto lower_bounds = static_cast(buf_lbs.ptr); + auto upper_bounds = static_cast(buf_ubs.ptr); + auto integers = static_cast(buf_are_integral.ptr); + py::array_t result(size); + result.resize(shape); + py::buffer_info result_info = result.request(); + auto ptr = static_cast(result_info.ptr); + for (int i = 0; i < size; ++i) { + const int index = helper->AddVar(); + ptr[i] = index; + helper->SetVarLowerBound(index, lower_bounds[i]); + helper->SetVarUpperBound(index, upper_bounds[i]); + helper->SetVarIntegrality(index, integers[i]); + if (!name_prefix.empty()) { + helper->SetVarName(index, absl::StrCat(name_prefix, i)); + } + } + return result; + }) .def("set_var_lower_bound", &ModelBuilderHelper::SetVarLowerBound, arg("var_index"), arg("lb")) .def("set_var_upper_bound", &ModelBuilderHelper::SetVarUpperBound, @@ -199,7 +264,7 @@ PYBIND11_MODULE(pywrap_model_builder_helper, m) { arg("offset")) .def("objective_offset", &ModelBuilderHelper::ObjectiveOffset); - pybind11::enum_(m, "SolveStatus") + py::enum_(m, "SolveStatus") .value("OPTIMAL", SolveStatus::OPTIMAL) .value("FEASIBLE", SolveStatus::FEASIBLE) .value("INFEASIBLE", SolveStatus::INFEASIBLE) @@ -216,20 +281,20 @@ PYBIND11_MODULE(pywrap_model_builder_helper, m) { .value("INCOMPATIBLE_OPTIONS", SolveStatus::INCOMPATIBLE_OPTIONS) .export_values(); - pybind11::class_(m, "ModelSolverHelper") - .def(pybind11::init()) + py::class_(m, "ModelSolverHelper") + .def(py::init()) .def("solver_is_supported", &ModelSolverHelper::SolverIsSupported) .def("solve", &ModelSolverHelper::Solve, arg("model"), // The GIL is released during the solve to allow Python threads to do // other things in parallel, e.g., log and interrupt. - pybind11::call_guard()) + py::call_guard()) .def("solve_serialized_request", [](ModelSolverHelper* solver, const std::string& request_str) { std::string result; { // The GIL is released during the solve to allow Python threads // to do other things in parallel, e.g., log and interrupt. - pybind11::gil_scoped_release release; + py::gil_scoped_release release; MPModelRequest request; if (!request.ParseFromString(request_str)) { @@ -242,7 +307,7 @@ PYBIND11_MODULE(pywrap_model_builder_helper, m) { result = solution.value().SerializeAsString(); } } - return pybind11::bytes(result); + return py::bytes(result); }) .def("interrupt_solve", &ModelSolverHelper::InterruptSolve, "Returns true if the interrupt signal was correctly sent, that is, " diff --git a/ortools/linear_solver/python/small_model.lp b/ortools/linear_solver/python/small_model.lp deleted file mode 100644 index 26c486218a..0000000000 --- a/ortools/linear_solver/python/small_model.lp +++ /dev/null @@ -1,7 +0,0 @@ -min: x + y; -bin: b1, b2, b3; -1 <= x <= 42; -constraint_num1: 5 b1 + 3b2 + x <= 7; -4 y + b2 - 3 b3 <= 2; -constraint_num2: -4 b1 + b2 - 3 z <= -2; - diff --git a/ortools/linear_solver/testdata/BUILD.bazel b/ortools/linear_solver/testdata/BUILD.bazel new file mode 100644 index 0000000000..744c0f7ffb --- /dev/null +++ b/ortools/linear_solver/testdata/BUILD.bazel @@ -0,0 +1,20 @@ +# Copyright 2010-2022 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. + +# Test files for the linear_solver packages. + +exports_files([ + "maximization.mps", + "small_model.lp", + "large_model.mps.gz", +]) diff --git a/ortools/linear_solver/testdata/large_model.mps.gz b/ortools/linear_solver/testdata/large_model.mps.gz new file mode 100644 index 0000000000..a238376ee5 Binary files /dev/null and b/ortools/linear_solver/testdata/large_model.mps.gz differ diff --git a/ortools/linear_solver/python/maximization.mps b/ortools/linear_solver/testdata/maximization.mps similarity index 99% rename from ortools/linear_solver/python/maximization.mps rename to ortools/linear_solver/testdata/maximization.mps index c1fe29c12b..6aa8764cf9 100644 --- a/ortools/linear_solver/python/maximization.mps +++ b/ortools/linear_solver/testdata/maximization.mps @@ -16,4 +16,3 @@ COLUMNS BOUNDS UP BOUND X_ONE 4 ENDATA -