work on model_builder python; support building and testing it in python on bazel
This commit is contained in:
32
WORKSPACE
32
WORKSPACE
@@ -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")
|
||||
|
||||
@@ -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
3
bazel/python_deps.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
absl-py >= 0.13
|
||||
numpy >= 1.13.3
|
||||
protobuf >= 3.19.4
|
||||
@@ -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.
|
||||
#
|
||||
|
||||
@@ -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,
|
||||
|
||||
44
ortools/model_builder/python/BUILD.bazel
Normal file
44
ortools/model_builder/python/BUILD.bazel
Normal 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",
|
||||
],
|
||||
)
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
9
ortools/model_builder/samples/code_samples_py_test.sh
Executable file
9
ortools/model_builder/samples/code_samples_py_test.sh
Executable 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
|
||||
31
ortools/model_builder/wrappers/BUILD.bazel
Normal file
31
ortools/model_builder/wrappers/BUILD.bazel
Normal 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",
|
||||
],
|
||||
)
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user