run model_builder python tests, rearrange linear solver testdata
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
absl-py >= 0.13
|
||||
numpy >= 1.13.3
|
||||
protobuf >= 4.21.10
|
||||
scipy >= 1.10.0
|
||||
|
||||
@@ -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"),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
179
ortools/linear_solver/python/model_builder_helper_test.py
Normal file
179
ortools/linear_solver/python/model_builder_helper_test.py
Normal file
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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_<MPModelExportOptions>(m, "MPModelExportOptions")
|
||||
.def(pybind11::init<>())
|
||||
py::class_<MPModelExportOptions>(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_<ModelBuilderHelper>(m, "ModelBuilderHelper")
|
||||
.def(pybind11::init<>())
|
||||
py::class_<ModelBuilderHelper>(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<size_t> 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<int> result(size);
|
||||
py::buffer_info info = result.request();
|
||||
result.resize(shape);
|
||||
auto ptr = static_cast<int*>(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<double> lbs,
|
||||
py::array_t<double> ubs, py::array_t<bool> 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<double*>(buf_lbs.ptr);
|
||||
auto upper_bounds = static_cast<double*>(buf_ubs.ptr);
|
||||
auto integers = static_cast<bool*>(buf_are_integral.ptr);
|
||||
py::array_t<int> result(size);
|
||||
result.resize(shape);
|
||||
py::buffer_info result_info = result.request();
|
||||
auto ptr = static_cast<int*>(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_<SolveStatus>(m, "SolveStatus")
|
||||
py::enum_<SolveStatus>(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_<ModelSolverHelper>(m, "ModelSolverHelper")
|
||||
.def(pybind11::init<const std::string&>())
|
||||
py::class_<ModelSolverHelper>(m, "ModelSolverHelper")
|
||||
.def(py::init<const std::string&>())
|
||||
.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<pybind11::gil_scoped_release>())
|
||||
py::call_guard<py::gil_scoped_release>())
|
||||
.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, "
|
||||
|
||||
@@ -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;
|
||||
|
||||
20
ortools/linear_solver/testdata/BUILD.bazel
vendored
Normal file
20
ortools/linear_solver/testdata/BUILD.bazel
vendored
Normal file
@@ -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",
|
||||
])
|
||||
BIN
ortools/linear_solver/testdata/large_model.mps.gz
vendored
Normal file
BIN
ortools/linear_solver/testdata/large_model.mps.gz
vendored
Normal file
Binary file not shown.
@@ -16,4 +16,3 @@ COLUMNS
|
||||
BOUNDS
|
||||
UP BOUND X_ONE 4
|
||||
ENDATA
|
||||
|
||||
Reference in New Issue
Block a user