work on model_builder python; support building and testing it in python on bazel

This commit is contained in:
Laurent Perron
2022-03-29 17:59:36 +02:00
parent cdfce146ff
commit c84dc88872
12 changed files with 206 additions and 11 deletions

View File

@@ -106,3 +106,35 @@ cc_library(
)
"""
)
http_archive(
name = "rules_python",
sha256 = "9fcf91dbcc31fde6d1edb15f117246d912c33c36f44cf681976bd886538deba6",
strip_prefix = "rules_python-0.8.0",
url = "https://github.com/bazelbuild/rules_python/archive/refs/tags/0.8.0.tar.gz",
)
load("@rules_python//python:pip.bzl", "pip_install")
# Create a central external repo, @ortools_deps, that contains Bazel targets for all the
# third-party packages specified in the python_deps.txt file.
pip_install(
name = "ortools_deps",
requirements = "//bazel:python_deps.txt",
)
git_repository(
name = "pybind11_bazel",
commit = "72cbbf1fbc830e487e3012862b7b720001b70672",
remote = "https://github.com/pybind/pybind11_bazel.git",
)
new_git_repository(
name = "pybind11",
build_file = "@pybind11_bazel//:pybind11.BUILD",
tag = "v2.9.1",
remote = "https://github.com/pybind/pybind11.git",
)
load("@pybind11_bazel//:python_configure.bzl", "python_configure")
python_configure(name = "local_config_python", python_version = "3")

View File

@@ -9,4 +9,5 @@ exports_files([
"bliss-0.73.patch",
# "zlib.BUILD",
"archive_helper.bzl",
"python_deps.txt",
])

3
bazel/python_deps.txt Normal file
View File

@@ -0,0 +1,3 @@
absl-py >= 0.13
numpy >= 1.13.3
protobuf >= 3.19.4

View File

@@ -1,5 +1,6 @@
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
load("@com_google_protobuf//:protobuf.bzl", "py_proto_library")
package(default_visibility = ["//visibility:public"])
@@ -41,6 +42,13 @@ cc_proto_library(
deps = [":linear_solver_proto"],
)
py_proto_library(
name = "linear_solver_py_pb2",
srcs = ["linear_solver.proto"],
deps = ["//ortools/util:optional_boolean_py_pb2"],
visibility = ["//visibility:public"],
)
# You can include the interfaces to different solvers by invoking '--define'
# flags. By default GLOP, BOP, SCIP, GUROBI, and CP-SAT interface are included.
#

View File

@@ -24,6 +24,7 @@
#include "ortools/sat/cp_model.pb.h"
#include "ortools/sat/cp_model_solver.h"
#include "ortools/sat/lp_utils.h"
#include "ortools/sat/parameters_validation.h"
#include "ortools/sat/sat_parameters.pb.h"
#include "ortools/util/logging.h"
#include "ortools/util/time_limit.h"
@@ -80,13 +81,17 @@ sat::CpSolverStatus FromMPSolverResponseStatus(MPSolverResponseStatus status) {
MPSolutionResponse InfeasibleResponse(SolverLogger& logger,
std::string message) {
SOLVER_LOG(&logger, "Infeasible model detected in sat_solve_proto.\n",
message);
// This is needed for our benchmark scripts.
MPSolutionResponse response;
if (logger.LoggingIsEnabled()) {
sat::CpSolverResponse cp_response;
cp_response.set_status(sat::CpSolverStatus::INFEASIBLE);
SOLVER_LOG(&logger, CpSolverResponseStats(cp_response));
}
MPSolutionResponse response;
response.set_status(MPSolverResponseStatus::MPSOLVER_INFEASIBLE);
response.set_status_str(message);
return response;
@@ -94,13 +99,16 @@ MPSolutionResponse InfeasibleResponse(SolverLogger& logger,
MPSolutionResponse ModelInvalidResponse(SolverLogger& logger,
std::string message) {
SOLVER_LOG(&logger, "Invalid input detected in sat_solve_proto.\n", message);
// This is needed for our benchmark scripts.
MPSolutionResponse response;
if (logger.LoggingIsEnabled()) {
sat::CpSolverResponse cp_response;
cp_response.set_status(sat::CpSolverStatus::MODEL_INVALID);
SOLVER_LOG(&logger, CpSolverResponseStats(cp_response));
}
MPSolutionResponse response;
response.set_status(MPSolverResponseStatus::MPSOLVER_MODEL_INVALID);
response.set_status_str(message);
return response;
@@ -175,6 +183,14 @@ absl::StatusOr<MPSolutionResponse> SatSolveProto(
return ModelInvalidResponse(logger, "Extra CP-SAT validation failed.");
}
{
const std::string error = sat::ValidateParameters(params);
if (!error.empty()) {
return ModelInvalidResponse(
logger, absl::StrCat("Invalid CP-SAT parameters: ", error));
}
}
// This is good to do before any presolve.
if (!sat::MakeBoundsOfIntegerVariablesInteger(params, mp_model, &logger)) {
return InfeasibleResponse(logger,

View File

@@ -0,0 +1,44 @@
# 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")
pybind_extension(
name = "pywrap_model_builder_helper",
srcs = ["pywrap_model_builder_helper.cc"],
visibility = ["//visibility:public"],
deps = [
"//ortools/linear_solver:linear_solver_cc_proto",
"//ortools/linear_solver:model_exporter",
"//ortools/model_builder/wrappers:model_builder_helper",
"@com_google_absl//absl/strings",
"@eigen//:eigen3",
],
)
py_library(
name = "model_builder_helper",
srcs = ["model_builder_helper.py"],
data = [
":pywrap_model_builder_helper.so",
],
visibility = ["//visibility:public"],
deps = [
requirement("numpy"),
"//ortools/linear_solver:linear_solver_py_pb2",
],
)
py_library(
name = "model_builder",
srcs = ["model_builder.py"],
data = [
":pywrap_model_builder_helper.so",
],
visibility = ["//visibility:public"],
deps = [
":model_builder_helper",
"//ortools/linear_solver:linear_solver_py_pb2",
],
)

View File

@@ -157,7 +157,7 @@ class LinearExpr(object):
return _Sum(self, arg)
def __radd__(self, arg):
return self.__add(arg)
return self.__add__(arg)
def __sub__(self, arg):
if mbh.is_zero(arg):
@@ -219,8 +219,6 @@ class LinearExpr(object):
def __eq__(self, arg):
if arg is None:
return False
if isinstance(self, Variable) and isinstance(arg, Variable):
return VarCompVar(self, arg, True)
if mbh.is_a_number(arg):
arg = mbh.assert_is_a_number(arg)
return BoundedLinearExpression(self, arg, arg)
@@ -242,8 +240,6 @@ class LinearExpr(object):
return BoundedLinearExpression(self - arg, -math.inf, 0)
def __ne__(self, arg):
if isinstance(self, Variable) and isinstance(arg, Variable):
return VarCompVar(self, arg, False)
return NotImplemented
def __lt__(self, arg):
@@ -540,6 +536,25 @@ class Variable(LinearExpr):
def objective_coefficient(self, coeff):
return self.__helper.set_var_objective_coefficient(self.__index, coeff)
def __eq__(self, arg):
if arg is None:
return False
if isinstance(arg, Variable):
return VarCompVar(self, arg, True)
else:
if mbh.is_a_number(arg):
arg = mbh.assert_is_a_number(arg)
return BoundedLinearExpression(self, arg, arg)
else:
return BoundedLinearExpression(self - arg, 0, 0)
def __ne__(self, arg):
if arg is None:
return True
if isinstance(arg, Variable):
return VarCompVar(self, arg, False)
return NotImplemented
def __hash__(self):
return hash((self.__helper, self.__index))
@@ -559,7 +574,7 @@ class VarCompVar(object):
return f'{self.__left} == {self.__right}'
def __repr__(self):
return f'VarEqVar({self.__left}, {self.__right}, {self.__is_equality})'
return f'VarCompVar({self.__left}, {self.__right}, {self.__is_equality})'
@property
def left(self):
@@ -574,7 +589,7 @@ class VarCompVar(object):
return self.__is_equality
def __bool__(self):
return (self.__left == self.__right) == self.__is_equality
return (self.__left.index == self.__right.index) == self.__is_equality
class BoundedLinearExpression(object):

View File

@@ -1 +1,11 @@
# Samples code the model builder library.
load(":code_samples.bzl", "code_sample_py")
code_sample_py(name = "assignment_mb")
code_sample_py(name = "bin_packing_mb")
code_sample_py(name = "simple_lp_program_mb")
code_sample_py(name = "simple_mip_program_mb")

View File

@@ -1,5 +1,8 @@
"""Helper macro to compile and test code samples."""
load("@ortools_deps//:requirements.bzl", "requirement")
load("@rules_python//python:defs.bzl", "py_binary")
def code_sample_cc(name):
native.cc_binary(
name = name,
@@ -23,3 +26,26 @@ def code_sample_cc(name):
],
)
def code_sample_py(name):
py_binary(
name = name + "_py3",
srcs = [name + ".py"],
main = name + ".py",
deps = [
requirement("absl-py"),
"//ortools/model_builder/python:model_builder",
],
python_version = "PY3",
srcs_version = "PY3",
)
native.sh_test(
name = name + "_py_test",
size = "small",
srcs = ["code_samples_py_test.sh"],
args = [name],
data = [
":" + name + "_py3",
],
)

View File

@@ -0,0 +1,9 @@
#!/bin/bash
declare -r DIR="${TEST_SRCDIR}/com_google_ortools/ortools/model_builder/samples"
function test::ortools::code_samples_model_builder_py() {
"${DIR}/$1_py3"
}
test::ortools::code_samples_model_builder_py $1

View File

@@ -0,0 +1,31 @@
# ModelBuilder: a lightweight implementation of the linear_solver API
# Public exports.
exports_files(
[
"README.md",
"BUILD.bazel",
"CMakeLists.txt",
] + glob([
"*.cc",
"*.h",
]),
)
cc_library(
name = "model_builder_helper",
srcs = ["model_builder_helper.cc"],
hdrs = ["model_builder_helper.h"],
visibility = ["//visibility:public"],
copts = [
"-DUSE_SCIP",
],
deps = [
"//ortools/linear_solver",
"//ortools/linear_solver:linear_solver_cc_proto",
"//ortools/linear_solver:model_exporter",
"//ortools/lp_data:lp_parser",
"//ortools/lp_data:mps_reader",
"//ortools/util:logging",
],
)

View File

@@ -269,7 +269,7 @@ void ModelSolverHelper::Solve(const ModelBuilderHelper& model) {
}
break;
}
#if defined(USE_SCIP)
#if defined(USE_SCIP)
case MPModelRequest::SCIP_MIXED_INTEGER_PROGRAMMING: {
// TODO(user): Enable log_callback support.
// TODO(user): Enable interrupt_solve.
@@ -279,7 +279,7 @@ void ModelSolverHelper::Solve(const ModelBuilderHelper& model) {
}
break;
}
#endif // defined(USE_SCIP)
#endif // defined(USE_SCIP)
default: {
response_->set_status(
MPSolverResponseStatus::MPSOLVER_SOLVER_TYPE_UNAVAILABLE);