add typing info to model_builder/python; add numpy variable support; update samples

This commit is contained in:
Laurent Perron
2023-02-28 10:55:51 +04:00
parent 7174cf026e
commit d0f6b6db74
8 changed files with 1166 additions and 501 deletions

View File

@@ -13,7 +13,8 @@
set_property(SOURCE linear_solver.i PROPERTY CPLUSPLUS ON)
set_property(SOURCE linear_solver.i PROPERTY SWIG_MODULE_NAME pywraplp)
set_property(SOURCE linear_solver.i PROPERTY COMPILE_DEFINITIONS ${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
set_property(SOURCE linear_solver.i PROPERTY COMPILE_DEFINITIONS
${OR_TOOLS_COMPILE_DEFINITIONS} ABSL_MUST_USE_RESULT=)
swig_add_library(pywraplp
TYPE MODULE
LANGUAGE python

File diff suppressed because it is too large Load Diff

View File

@@ -13,43 +13,56 @@
"""helpers methods for the cp_model_builder module."""
import numbers
from typing import Any, Sequence, Union
import numpy as np
import numpy.typing as npt
# Custom types.
NumberT = Union[numbers.Number, np.number]
def is_integral(x):
def is_integral(x: Any) -> bool:
"""Checks if x has either a number.Integral or a np.integer type."""
return isinstance(x, numbers.Integral) or isinstance(x, np.integer)
def is_a_number(x):
def is_a_number(x: Any) -> bool:
"""Checks if x has either a number.Number or a np.double type."""
return isinstance(x, numbers.Number) or isinstance(
x, np.double) or isinstance(x, np.integer)
def is_zero(x):
def is_zero(x: Any) -> bool:
"""Checks if the x is 0 or 0.0."""
return (is_integral(x) and int(x) == 0) or (is_a_number(x) and
float(x) == 0.0)
def is_one(x):
def is_one(x: Any) -> bool:
"""Checks if x is 1 or 1.0."""
return (is_integral(x) and int(x) == 1) or (is_a_number(x) and
float(x) == 1.0)
def is_minus_one(x):
def is_minus_one(x: Any) -> bool:
"""Checks if x is -1 or -1.0."""
return (is_integral(x) and int(x) == -1) or (is_a_number(x) and
float(x) == -1.0)
def assert_is_a_number(x):
"""Asserts that x is a number and returns it."""
def assert_is_a_number(x: NumberT) -> np.double:
"""Asserts that x is a number and converts to a np.double."""
if not is_a_number(x):
raise TypeError('Not a number: %s' % x)
elif is_integral(x):
return int(x)
else:
return float(x)
return np.double(x)
def assert_is_a_number_array(x: Sequence[NumberT]) -> npt.NDArray[np.double]:
"""Asserts x is a list of numbers and converts it to np.array(np.double)."""
result = np.empty(len(x), dtype=np.double)
pos = 0
for c in x:
result[pos] = assert_is_a_number(c)
pos += 1
assert pos == len(x)
return result

View File

@@ -168,7 +168,7 @@ class PywrapModelBuilderHelperTest(unittest.TestCase):
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_')
var_array = model.add_var_array([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)

View File

@@ -11,13 +11,14 @@
# 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."""
"""Tests for ModelBuilder."""
import math
import numpy as np
import numpy.testing as np_testing
import os
from ortools.linear_solver.python import model_builder
from ortools.linear_solver.python import model_builder as mb
import unittest
@@ -29,7 +30,7 @@ class ModelBuilderTest(unittest.TestCase):
# pylint: disable=too-many-statements
def run_minimal_linear_example(self, solver_name):
"""Minimal Linear Example."""
model = model_builder.ModelBuilder()
model = mb.ModelBuilder()
model.name = 'minimal_linear_example'
x1 = model.new_num_var(0.0, math.inf, 'x1')
x2 = model.new_num_var(0.0, math.inf, 'x2')
@@ -54,13 +55,18 @@ class ModelBuilderTest(unittest.TestCase):
c2 = model.add(2.0 * x1 + 2.0 * x2 + 6.0 * x3 <= 300.0)
self.assertEqual(-math.inf, c2.lower_bound)
solver = model_builder.ModelSolver(solver_name)
self.assertEqual(model_builder.SolveStatus.OPTIMAL, solver.solve(model))
solver = mb.ModelSolver(solver_name)
self.assertEqual(mb.SolveStatus.OPTIMAL, solver.solve(model))
# The problem has an optimal solution.
self.assertAlmostEqual(733.333333 + model.objective_offset,
solver.objective_value,
places=self.NUM_PLACES)
self.assertAlmostEqual(
solver.value(10.0 * x1 + 6 * x2 + 4.0 * x3 - 5.5),
solver.objective_value,
places=self.NUM_PLACES,
)
self.assertAlmostEqual(33.333333,
solver.value(x1),
places=self.NUM_PLACES)
@@ -120,14 +126,14 @@ BOUNDS
UP BOUND X_ONE 4
ENDATA
"""
model = model_builder.ModelBuilder()
model = mb.ModelBuilder()
self.assertTrue(model.import_from_mps_string(mps_data))
self.assertEqual(model.name, 'SupportedMaximizationProblem')
def test_import_from_mps_file(self):
path = os.path.dirname(__file__)
mps_path = f'{path}/../testdata/maximization.mps'
model = model_builder.ModelBuilder()
model = mb.ModelBuilder()
self.assertTrue(model.import_from_mps_file(mps_path))
self.assertEqual(model.name, 'SupportedMaximizationProblem')
@@ -140,7 +146,7 @@ ENDATA
4 y + b2 - 3 b3 <= 2;
constraint_num2: -4 b1 + b2 - 3 z <= -2;
"""
model = model_builder.ModelBuilder()
model = mb.ModelBuilder()
self.assertTrue(model.import_from_lp_string(lp_data))
self.assertEqual(6, model.num_variables)
self.assertEqual(3, model.num_constraints)
@@ -151,7 +157,7 @@ ENDATA
def test_import_from_lp_file(self):
path = os.path.dirname(__file__)
lp_path = f'{path}/../testdata/small_model.lp'
model = model_builder.ModelBuilder()
model = mb.ModelBuilder()
self.assertTrue(model.import_from_lp_file(lp_path))
self.assertEqual(6, model.num_variables)
self.assertEqual(3, model.num_constraints)
@@ -159,8 +165,77 @@ ENDATA
self.assertEqual(42, model.var_from_index(0).upper_bound)
self.assertEqual('x', model.var_from_index(0).name)
def test_class_api(self):
model = mb.ModelBuilder()
x = model.new_int_var(0, 10, 'x')
y = model.new_int_var(1, 10, 'y')
z = model.new_int_var(2, 10, 'z')
t = model.new_int_var(3, 10, 't')
e1 = mb.LinearExpr.sum([x, y, z])
expected_vars = np.array([0, 1, 2], dtype=np.int32)
np_testing.assert_array_equal(expected_vars, e1.variable_indices)
np_testing.assert_array_equal(np.array([1, 1, 1], dtype=np.double),
e1.coefficients)
self.assertEqual(e1.constant, 0.0)
self.assertEqual(e1.pretty_string(model.helper), 'x + y + z')
e2 = mb.LinearExpr.sum([e1, 4.0])
np_testing.assert_array_equal(expected_vars, e2.variable_indices)
np_testing.assert_array_equal(np.array([1, 1, 1], dtype=np.double),
e2.coefficients)
self.assertEqual(e2.constant, 4.0)
self.assertEqual(e2.pretty_string(model.helper), 'x + y + z + 4.0')
e3 = mb.LinearExpr.term(e2, 2)
np_testing.assert_array_equal(expected_vars, e3.variable_indices)
np_testing.assert_array_equal(np.array([2, 2, 2], dtype=np.double),
e3.coefficients)
self.assertEqual(e3.constant, 8.0)
self.assertEqual(e3.pretty_string(model.helper),
'2.0 * x + 2.0 * y + 2.0 * z + 8.0')
e4 = mb.LinearExpr.weighted_sum([x, t], [-1, 1], constant=2)
np_testing.assert_array_equal(np.array([0, 3], dtype=np.int32),
e4.variable_indices)
np_testing.assert_array_equal(np.array([-1, 1], dtype=np.double),
e4.coefficients)
self.assertEqual(e4.constant, 2.0)
self.assertEqual(e4.pretty_string(model.helper), '-x + t + 2.0')
e4b = e4 * 3.0
np_testing.assert_array_equal(np.array([0, 3], dtype=np.int32),
e4b.variable_indices)
np_testing.assert_array_equal(np.array([-3, 3], dtype=np.double),
e4b.coefficients)
self.assertEqual(e4b.constant, 6.0)
self.assertEqual(e4b.pretty_string(model.helper),
'-3.0 * x + 3.0 * t + 6.0')
e5 = mb.LinearExpr.sum([e1, -3, e4])
np_testing.assert_array_equal(np.array([0, 1, 2, 0, 3], dtype=np.int32),
e5.variable_indices)
np_testing.assert_array_equal(
np.array([1, 1, 1, -1, 1], dtype=np.double), e5.coefficients)
self.assertEqual(e5.constant, -1.0)
self.assertEqual(e5.pretty_string(model.helper),
'x + y + z - x + t - 1.0')
e6 = mb.LinearExpr.term(x, 2.0, constant=1.0)
np_testing.assert_array_equal(np.array([0], dtype=np.int32),
e6.variable_indices)
np_testing.assert_array_equal(np.array([2], dtype=np.double),
e6.coefficients)
self.assertEqual(e6.constant, 1.0)
e7 = mb.LinearExpr.term(x, 1.0, constant=0.0)
self.assertEqual(x, e7)
e8 = mb.LinearExpr.term(2, 3, constant=4)
self.assertEqual(e8, 10)
def test_variables(self):
model = model_builder.ModelBuilder()
model = mb.ModelBuilder()
x = model.new_int_var(0.0, 4.0, 'x')
self.assertEqual(0, x.index)
self.assertEqual(0.0, x.lower_bound)
@@ -180,32 +255,59 @@ ENDATA
self.assertNotEqual(x, y)
# array
xs = model.new_int_var_ndarray(10, 0.0, 5.0, 'xs_')
xs = model.new_int_var_array(shape=10,
lower_bounds=0.0,
upper_bounds=5.0,
name='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_')
ubs = [3.0, 4.0, 5.0]
ys = model.new_int_var_array(lower_bounds=lbs,
upper_bounds=ubs,
name='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_')
zs = model.new_int_var_array(lower_bounds=[1.0, 2.0, 3],
upper_bounds=[4, 4, 4],
name='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]))
self.assertTrue(zs[2].is_integral)
bs = model.new_bool_var_ndarray([4, 5], 'bs_')
bs = model.new_bool_var_array([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)
self.assertTrue(bs[1, 1].is_integral)
# Slices are [lb, ub) closed - open.
self.assertEqual(5, bs[3, :].size)
self.assertEqual(6, bs[1:3, 2:5].size)
sum_bs = np.sum(bs)
self.assertEqual(20, sum_bs.variable_indices.size)
np_testing.assert_array_equal(sum_bs.variable_indices,
bs.variable_indices.flatten())
np_testing.assert_array_equal(sum_bs.coefficients, np.ones(20))
times_bs = np.multiply(bs, 4)
np_testing.assert_array_equal(times_bs.variable_indices,
bs.variable_indices.flatten())
np_testing.assert_array_equal(times_bs.coefficients, np.full(20, 4.0))
times_bs_rev = np.multiply(4, bs)
np_testing.assert_array_equal(times_bs_rev.variable_indices,
bs.variable_indices.flatten())
np_testing.assert_array_equal(times_bs_rev.coefficients,
np.full(20, 4.0))
dot_bs = np.dot(bs[2], np.array([1, 2, 3, 4, 5], dtype=np.double))
np_testing.assert_array_equal(dot_bs.variable_indices,
bs[2].variable_indices)
np_testing.assert_array_equal(dot_bs.coefficients, [1, 2, 3, 4, 5])
# Tests the hash method.
var_set = set()
var_set.add(x)
@@ -213,15 +315,187 @@ ENDATA
self.assertIn(x_copy, var_set)
self.assertNotIn(y, var_set)
def test_numpy_var_arrays(self):
model = mb.ModelBuilder()
x = model.new_var_array(
lower_bounds=0.0,
upper_bounds=4.0,
shape=[2, 3],
is_integral=False,
)
np_testing.assert_array_equal(x.shape, [2, 3])
y = model.new_var_array(
lower_bounds=[[0.0, 1.0, 2.0], [0.0, 0.0, 2.0]],
upper_bounds=4.0,
is_integral=False,
name='y',
)
np_testing.assert_array_equal(y.shape, [2, 3])
z = model.new_var_array(
lower_bounds=0.0,
upper_bounds=[[2.0, 1.0, 2.0], [3.0, 4.0, 2.0]],
is_integral=False,
name='z',
)
np_testing.assert_array_equal(z.shape, [2, 3])
with self.assertRaises(ValueError):
x = model.new_var_array(
lower_bounds=0.0,
upper_bounds=4.0,
is_integral=False,
)
with self.assertRaises(ValueError):
x = model.new_var_array(
lower_bounds=[0, 0],
upper_bounds=[1, 2, 3],
is_integral=False,
)
with self.assertRaises(ValueError):
x = model.new_var_array(
shape=[2, 3],
lower_bounds=0.0,
upper_bounds=[1, 2, 3],
is_integral=False,
)
with self.assertRaises(ValueError):
x = model.new_var_array(
shape=[2, 3],
lower_bounds=[1, 2],
upper_bounds=4.0,
is_integral=False,
)
with self.assertRaises(ValueError):
x = model.new_var_array(
shape=[2, 3],
lower_bounds=0.0,
upper_bounds=4.0,
is_integral=[False, True],
)
with self.assertRaises(ValueError):
x = model.new_var_array(
lower_bounds=[1, 2],
upper_bounds=4.0,
is_integral=[False, False, False],
)
def test_numpy_num_var_arrays(self):
model = mb.ModelBuilder()
x = model.new_num_var_array(
lower_bounds=0.0,
upper_bounds=4.0,
shape=[2, 3],
)
np_testing.assert_array_equal(x.shape, [2, 3])
y = model.new_num_var_array(
lower_bounds=[[0.0, 1.0, 2.0], [0.0, 0.0, 2.0]],
upper_bounds=4.0,
name='y',
)
np_testing.assert_array_equal(y.shape, [2, 3])
z = model.new_num_var_array(
lower_bounds=0.0,
upper_bounds=[[2.0, 1.0, 2.0], [3.0, 4.0, 2.0]],
name='z',
)
np_testing.assert_array_equal(z.shape, [2, 3])
with self.assertRaises(ValueError):
x = model.new_num_var_array(
lower_bounds=0.0,
upper_bounds=4.0,
)
with self.assertRaises(ValueError):
x = model.new_num_var_array(
lower_bounds=[0, 0],
upper_bounds=[1, 2, 3],
)
with self.assertRaises(ValueError):
x = model.new_num_var_array(
shape=[2, 3],
lower_bounds=0.0,
upper_bounds=[1, 2, 3],
)
with self.assertRaises(ValueError):
x = model.new_num_var_array(
shape=[2, 3],
lower_bounds=[1, 2],
upper_bounds=4.0,
)
def test_numpy_int_var_arrays(self):
model = mb.ModelBuilder()
x = model.new_int_var_array(
lower_bounds=0.0,
upper_bounds=4.0,
shape=[2, 3],
)
np_testing.assert_array_equal(x.shape, [2, 3])
y = model.new_int_var_array(
lower_bounds=[[0.0, 1.0, 2.0], [0.0, 0.0, 2.0]],
upper_bounds=4.0,
name='y',
)
np_testing.assert_array_equal(y.shape, [2, 3])
z = model.new_int_var_array(
lower_bounds=0.0,
upper_bounds=[[2.0, 1.0, 2.0], [3.0, 4.0, 2.0]],
name='z',
)
np_testing.assert_array_equal(z.shape, [2, 3])
with self.assertRaises(ValueError):
x = model.new_int_var_array(
lower_bounds=0.0,
upper_bounds=4.0,
)
with self.assertRaises(ValueError):
x = model.new_int_var_array(
lower_bounds=[0, 0],
upper_bounds=[1, 2, 3],
)
with self.assertRaises(ValueError):
x = model.new_int_var_array(
shape=[2, 3],
lower_bounds=0.0,
upper_bounds=[1, 2, 3],
)
with self.assertRaises(ValueError):
x = model.new_int_var_array(
shape=[2, 3],
lower_bounds=[1, 2],
upper_bounds=4.0,
)
def test_duplicate_variables(self):
model = model_builder.ModelBuilder()
model = mb.ModelBuilder()
x = model.new_int_var(0.0, 4.0, 'x')
y = model.new_int_var(0.0, 4.0, 'y')
z = model.new_int_var(0.0, 4.0, 'z')
model.add(x + 2 * y == x - z)
model.minimize(x + y + z)
solver = model_builder.ModelSolver('scip')
self.assertEqual(model_builder.SolveStatus.OPTIMAL, solver.solve(model))
solver = mb.ModelSolver('scip')
self.assertEqual(mb.SolveStatus.OPTIMAL, solver.solve(model))
def test_issue_3614(self):
total_number_of_choices = 5 + 1
@@ -235,7 +509,7 @@ ENDATA
bundle_start_idx = len(standalone_features)
# Model
model = model_builder.ModelBuilder()
model = mb.ModelBuilder()
y = {}
v = {}
for i in range(total_number_of_choices):
@@ -248,12 +522,12 @@ ENDATA
(feature_bundle_incidence_matrix[(i, 0)] *
y[bundle_start_idx])))
solver = model_builder.ModelSolver('scip')
solver = mb.ModelSolver('scip')
status = solver.solve(model)
self.assertEqual(model_builder.SolveStatus.OPTIMAL, status)
self.assertEqual(mb.SolveStatus.OPTIMAL, status)
def test_varcompvar(self):
model = model_builder.ModelBuilder()
model = mb.ModelBuilder()
x = model.new_int_var(0.0, 4.0, 'x')
y = model.new_int_var(0.0, 4.0, 'y')
ct = x == y

View File

@@ -13,13 +13,19 @@
// A pybind11 wrapper for model_builder_helper.
#include <algorithm>
#include <complex>
#include <limits>
#include <optional>
#include <stdexcept>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "Eigen/Core"
#include "Eigen/SparseCore"
#include "absl/log/check.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "ortools/linear_solver/linear_solver.pb.h"
@@ -106,6 +112,38 @@ void BuildModelFromSparseData(
}
}
std::vector<std::pair<int, double>> SortedGroupedTerms(
const std::vector<int>& indices, const std::vector<double>& coefficients) {
CHECK_EQ(indices.size(), coefficients.size());
std::vector<std::pair<int, double>> terms;
terms.reserve(indices.size());
for (int i = 0; i < indices.size(); ++i) {
terms.emplace_back(indices[i], coefficients[i]);
}
std::sort(
terms.begin(), terms.end(),
[](const std::pair<int, double>& a, const std::pair<int, double>& b) {
if (a.first != b.first) return a.first < b.first;
if (std::abs(a.second) != std::abs(b.second)) {
return std::abs(a.second) < std::abs(b.second);
}
return a.second < b.second;
});
int pos = 0;
for (int i = 0; i < terms.size(); ++i) {
if (i == 0 || terms[i].first != terms[i - 1].first) {
if (i != pos) {
terms[pos] = terms[i];
}
pos++;
} else {
terms[pos].second += terms[i].second;
}
}
terms.resize(pos);
return terms;
}
PYBIND11_MODULE(pywrap_model_builder_helper, m) {
py::class_<MPModelExportOptions>(m, "MPModelExportOptions")
.def(py::init<>())
@@ -151,7 +189,7 @@ 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",
.def("add_var_array",
[](ModelBuilderHelper* helper, std::vector<size_t> shape, double lb,
double ub, bool is_integral, absl::string_view name_prefix) {
int size = shape[0];
@@ -174,17 +212,13 @@ PYBIND11_MODULE(pywrap_model_builder_helper, m) {
}
return result;
})
.def("add_var_ndarray_with_bounds",
.def("add_var_array_with_bounds",
[](ModelBuilderHelper* helper, py::array_t<double> lbs,
py::array_t<double> ubs, py::array_t<bool> are_integral,
absl::string_view 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");
@@ -222,6 +256,14 @@ PYBIND11_MODULE(pywrap_model_builder_helper, m) {
.def("set_var_objective_coefficient",
&ModelBuilderHelper::SetVarObjectiveCoefficient, arg("var_index"),
arg("coeff"))
.def("set_objective_coefficients",
[](ModelBuilderHelper* helper, const std::vector<int>& indices,
const std::vector<double>& coefficients) {
for (const auto& [i, c] :
SortedGroupedTerms(indices, coefficients)) {
helper->SetVarObjectiveCoefficient(i, c);
}
})
.def("set_var_name", &ModelBuilderHelper::SetVarName, arg("var_index"),
arg("name"))
.def("add_linear_constraint", &ModelBuilderHelper::AddLinearConstraint)
@@ -233,6 +275,15 @@ PYBIND11_MODULE(pywrap_model_builder_helper, m) {
arg("ub"))
.def("add_term_to_constraint", &ModelBuilderHelper::AddConstraintTerm,
arg("ct_index"), arg("var_index"), arg("coeff"))
.def("add_terms_to_constraint",
[](ModelBuilderHelper* helper, int ct_index,
const std::vector<int>& indices,
const std::vector<double>& coefficients) {
for (const auto& [i, c] :
SortedGroupedTerms(indices, coefficients)) {
helper->AddConstraintTerm(ct_index, i, c);
}
})
.def("set_constraint_name", &ModelBuilderHelper::SetConstraintName,
arg("ct_index"), arg("name"))
.def("num_variables", &ModelBuilderHelper::num_variables)
@@ -339,7 +390,10 @@ PYBIND11_MODULE(pywrap_model_builder_helper, m) {
.def("dual_value", &ModelSolverHelper::dual_value, arg("ct_index"))
.def("variable_values",
[](const ModelSolverHelper& helper) {
if (!helper.has_response()) return Eigen::VectorXd();
if (!helper.has_response()) {
throw std::logic_error(
"Accessing a solution value when none has been found.");
}
const MPSolutionResponse& response = helper.response();
Eigen::VectorXd vec(response.variable_value_size());
for (int i = 0; i < response.variable_value_size(); ++i) {
@@ -347,9 +401,26 @@ PYBIND11_MODULE(pywrap_model_builder_helper, m) {
}
return vec;
})
.def("expression_value",
[](const ModelSolverHelper& helper, const std::vector<int>& indices,
const std::vector<double>& coefficients, double constant) {
if (!helper.has_response()) {
throw std::logic_error(
"Accessing a solution value when none has been found.");
}
const MPSolutionResponse& response = helper.response();
for (int i = 0; i < indices.size(); ++i) {
constant +=
response.variable_value(indices[i]) * coefficients[i];
}
return constant;
})
.def("reduced_costs",
[](const ModelSolverHelper& helper) {
if (!helper.has_response()) return Eigen::VectorXd();
if (!helper.has_response()) {
throw std::logic_error(
"Accessing a solution value when none has been found.");
}
const MPSolutionResponse& response = helper.response();
Eigen::VectorXd vec(response.reduced_cost_size());
for (int i = 0; i < response.reduced_cost_size(); ++i) {
@@ -358,7 +429,10 @@ PYBIND11_MODULE(pywrap_model_builder_helper, m) {
return vec;
})
.def("dual_values", [](const ModelSolverHelper& helper) {
if (!helper.has_response()) return Eigen::VectorXd();
if (!helper.has_response()) {
throw std::logic_error(
"Accessing a solution value when none has been found.");
}
const MPSolutionResponse& response = helper.response();
Eigen::VectorXd vec(response.dual_value_size());
for (int i = 0; i < response.dual_value_size(); ++i) {

View File

@@ -14,6 +14,8 @@
"""MIP example that solves an assignment problem."""
# [START program]
# [START import]
import numpy as np
from ortools.linear_solver.python import model_builder
# [END import]
@@ -41,30 +43,23 @@ def main():
# [START variables]
# x[i, j] is an array of 0-1 variables, which will be 1
# if worker i is assigned to task j.
x = {}
for i in range(num_workers):
for j in range(num_tasks):
x[i, j] = model.new_bool_var(f'x_{i}_{j}')
x = model.new_bool_var_array(shape=[num_workers, num_tasks], name='x')
# [END variables]
# Constraints
# [START constraints]
# Each worker is assigned to at most 1 task.
for i in range(num_workers):
model.add(sum(x[i, j] for j in range(num_tasks)) <= 1)
model.add(np.sum(x[i, :]) <= 1)
# Each task is assigned to exactly one worker.
for j in range(num_tasks):
model.add(sum(x[i, j] for i in range(num_workers)) == 1)
model.add(np.sum(x[:, j]) == 1)
# [END constraints]
# Objective
# [START objective]
objective_expr = 0
for i in range(num_workers):
for j in range(num_tasks):
objective_expr += costs[i][j] * x[i, j]
model.minimize(objective_expr)
model.minimize(np.multiply(x, costs))
# [END objective]
# [START solve]

View File

@@ -14,6 +14,8 @@
"""Solve a simple bin packing problem using a MIP solver."""
# [START program]
# [START import]
import numpy as np
from ortools.linear_solver.python import model_builder
# [END import]
@@ -36,6 +38,8 @@ def create_data_model():
def main():
# [START data]
data = create_data_model()
num_items = len(data['items'])
num_bins = len(data['bins'])
# [END data]
# [END program_part1]
@@ -48,33 +52,27 @@ def main():
# [START variables]
# Variables
# x[i, j] = 1 if item i is packed in bin j.
x = {}
for i in data['items']:
for j in data['bins']:
x[(i, j)] = model.new_bool_var(f'x_{i}_{j}')
x = model.new_bool_var_array(shape=[num_items, num_bins], name='x')
# y[j] = 1 if bin j is used.
y = {}
for j in data['bins']:
y[j] = model.new_bool_var(f'y_{j}')
y = model.new_bool_var_array(shape=[num_bins], name='y')
# [END variables]
# [START constraints]
# Constraints
# Each item must be in exactly one bin.
for i in data['items']:
model.add(sum(x[i, j] for j in data['bins']) == 1)
model.add(np.sum(x[i, :]) == 1)
# The amount packed in each bin cannot exceed its capacity.
for j in data['bins']:
model.add(
sum(x[(i, j)] * data['weights'][i] for i in data['items']) <= y[j] *
data['bin_capacity'])
np.dot(x[:, j], data['weights']) <= y[j] * data['bin_capacity'])
# [END constraints]
# [START objective]
# Objective: minimize the number of bins used.
model.minimize(model_builder.LinearExpr.sum([y[j] for j in data['bins']]))
model.minimize(np.sum(y))
# [END objective]
# [START solve]