From ca6f7afdcf35578a63b42554e78df760f5f902ff Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Tue, 11 Jul 2023 10:33:55 -0700 Subject: [PATCH] move pywrap_pdlp to pdlp --- cmake/python.cmake | 4 +- ortools/pdlp/python/BUILD.bazel | 10 ++-- ortools/pdlp/python/CMakeLists.txt | 14 ++--- .../pdlp/python/{pywrap_pdlp.cc => pdlp.cc} | 39 +++++++------- .../{pywrap_pdlp_test.py => pdlp_test.py} | 52 ++++++++----------- ortools/pdlp/samples/simple_pdlp_program.py | 10 ++-- ortools/python/setup.py.in | 2 +- 7 files changed, 60 insertions(+), 71 deletions(-) rename ortools/pdlp/python/{pywrap_pdlp.cc => pdlp.cc} (86%) rename ortools/pdlp/python/{pywrap_pdlp_test.py => pdlp_test.py} (86%) diff --git a/cmake/python.cmake b/cmake/python.cmake index bafe78bab2b..e3e32c169e0 100644 --- a/cmake/python.cmake +++ b/cmake/python.cmake @@ -325,7 +325,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E copy $ ${PYTHON_PROJECT}/constraint_solver COMMAND ${CMAKE_COMMAND} -E copy $ ${PYTHON_PROJECT}/linear_solver COMMAND ${CMAKE_COMMAND} -E copy $ ${PYTHON_PROJECT}/linear_solver/python - COMMAND ${CMAKE_COMMAND} -E copy $ ${PYTHON_PROJECT}/pdlp/python + COMMAND ${CMAKE_COMMAND} -E copy $ ${PYTHON_PROJECT}/pdlp/python COMMAND ${CMAKE_COMMAND} -E copy $ ${PYTHON_PROJECT}/sat/python COMMAND ${CMAKE_COMMAND} -E copy $ ${PYTHON_PROJECT}/scheduling/python COMMAND ${CMAKE_COMMAND} -E copy $ ${PYTHON_PROJECT}/util/python @@ -346,7 +346,7 @@ add_custom_command( pywrapcp pywraplp model_builder_helper_pybind11 - pywrap_pdlp_pybind11 + pdlp_pybind11 swig_helper_pybind11 rcpsp_pybind11 sorted_interval_list_pybind11 diff --git a/ortools/pdlp/python/BUILD.bazel b/ortools/pdlp/python/BUILD.bazel index e1e3b4bceec..7e2b450b48d 100644 --- a/ortools/pdlp/python/BUILD.bazel +++ b/ortools/pdlp/python/BUILD.bazel @@ -18,8 +18,8 @@ load("@rules_python//python:defs.bzl", "py_test") load("@pip_deps//:requirements.bzl", "requirement") pybind_extension( - name = "pywrap_pdlp", - srcs = ["pywrap_pdlp.cc"], + name = "pdlp", + srcs = ["pdlp.cc"], visibility = ["//visibility:public"], deps = [ "//ortools/linear_solver:linear_solver_cc_proto", @@ -35,11 +35,11 @@ pybind_extension( ) py_test( - name = "pywrap_pdlp_test", + name = "pdlp_test", size = "small", - srcs = ["pywrap_pdlp_test.py"], + srcs = ["pdlp_test.py"], data = [ - ":pywrap_pdlp.so", + ":pdlp.so", ], python_version = "PY3", srcs_version = "PY3", diff --git a/ortools/pdlp/python/CMakeLists.txt b/ortools/pdlp/python/CMakeLists.txt index 5eeaca39c74..29f3493ae96 100644 --- a/ortools/pdlp/python/CMakeLists.txt +++ b/ortools/pdlp/python/CMakeLists.txt @@ -11,23 +11,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -pybind11_add_module(pywrap_pdlp_pybind11 MODULE pywrap_pdlp.cc) +pybind11_add_module(pdlp_pybind11 MODULE pdlp.cc) # note: macOS is APPLE and also UNIX ! -set_target_properties(pywrap_pdlp_pybind11 PROPERTIES - LIBRARY_OUTPUT_NAME "pywrap_pdlp") +set_target_properties(pdlp_pybind11 PROPERTIES + LIBRARY_OUTPUT_NAME "pdlp") if(APPLE) - set_target_properties(pywrap_pdlp_pybind11 PROPERTIES + set_target_properties(pdlp_pybind11 PROPERTIES SUFFIX ".so" INSTALL_RPATH "@loader_path;@loader_path/../../../${PYTHON_PROJECT}/.libs" LINK_FLAGS "-flat_namespace -undefined suppress" ) elseif(UNIX) - set_target_properties(pywrap_pdlp_pybind11 PROPERTIES + set_target_properties(pdlp_pybind11 PROPERTIES INSTALL_RPATH "$ORIGIN:$ORIGIN/../../../${PYTHON_PROJECT}/.libs" ) endif() -target_link_libraries(pywrap_pdlp_pybind11 PRIVATE ${PROJECT_NAMESPACE}::ortools) -add_library(${PROJECT_NAMESPACE}::pywrap_pdlp_pybind11 ALIAS pywrap_pdlp_pybind11) +target_link_libraries(pdlp_pybind11 PRIVATE ${PROJECT_NAMESPACE}::ortools) +add_library(${PROJECT_NAMESPACE}::pdlp_pybind11 ALIAS pdlp_pybind11) if(BUILD_TESTING) file(GLOB PYTHON_SRCS "*_test.py") diff --git a/ortools/pdlp/python/pywrap_pdlp.cc b/ortools/pdlp/python/pdlp.cc similarity index 86% rename from ortools/pdlp/python/pywrap_pdlp.cc rename to ortools/pdlp/python/pdlp.cc index 8e905ec0a10..bffb7979ac7 100644 --- a/ortools/pdlp/python/pywrap_pdlp.cc +++ b/ortools/pdlp/python/pdlp.cc @@ -33,9 +33,9 @@ #include "pybind11/pytypes.h" #include "pybind11/stl.h" -namespace pdlp = ::operations_research::pdlp; -using ::operations_research::MPModelProto; -using ::operations_research::pdlp::QuadraticProgram; +namespace operations_research::pdlp { +namespace { + using ::pybind11::arg; // TODO(user): The interface uses serialized protos because of issues building @@ -51,7 +51,9 @@ struct PywrapSolverResult { pybind11::bytes solve_log_str; }; -PYBIND11_MODULE(pywrap_pdlp, m) { +} // namespace + +PYBIND11_MODULE(pdlp, m) { // --------------------------------------------------------------------------- // quadratic_program.h // --------------------------------------------------------------------------- @@ -96,8 +98,7 @@ PYBIND11_MODULE(pywrap_pdlp, m) { m.def("validate_quadratic_program_dimensions", [](const QuadraticProgram& qp) { - const absl::Status status = - pdlp::ValidateQuadraticProgramDimensions(qp); + const absl::Status status = ValidateQuadraticProgramDimensions(qp); if (status.ok()) { return; } else { @@ -105,7 +106,7 @@ PYBIND11_MODULE(pywrap_pdlp, m) { } }); - m.def("is_linear_program", &pdlp::IsLinearProgram); + m.def("is_linear_program", &IsLinearProgram); m.def( "qp_from_mpmodel_proto", @@ -115,8 +116,8 @@ PYBIND11_MODULE(pywrap_pdlp, m) { if (!proto.ParseFromString(std::string(proto_str))) { throw std::invalid_argument("Unable to parse input proto"); } - absl::StatusOr qp = pdlp::QpFromMpModelProto( - proto, relax_integer_variables, include_names); + absl::StatusOr qp = + QpFromMpModelProto(proto, relax_integer_variables, include_names); if (qp.ok()) { return *qp; } else { @@ -127,7 +128,7 @@ PYBIND11_MODULE(pywrap_pdlp, m) { arg("include_names") = false); m.def("qp_to_mpmodel_proto", [](const QuadraticProgram& qp) { - absl::StatusOr proto = pdlp::QpToMpModelProto(qp); + absl::StatusOr proto = QpToMpModelProto(qp); if (proto.ok()) { return pybind11::bytes(proto->SerializeAsString()); } else { @@ -139,19 +140,17 @@ PYBIND11_MODULE(pywrap_pdlp, m) { // quadratic_program_io.h // --------------------------------------------------------------------------- - m.def("read_quadratic_program_or_die", &pdlp::ReadQuadraticProgramOrDie, + m.def("read_quadratic_program_or_die", &ReadQuadraticProgramOrDie, arg("filename"), arg("include_names") = false); // --------------------------------------------------------------------------- // primal_dual_hybrid_gradient.h // --------------------------------------------------------------------------- - pybind11::class_(m, "PrimalAndDualSolution") + pybind11::class_(m, "PrimalAndDualSolution") .def(pybind11::init<>()) - .def_readwrite("primal_solution", - &pdlp::PrimalAndDualSolution::primal_solution) - .def_readwrite("dual_solution", - &pdlp::PrimalAndDualSolution::dual_solution); + .def_readwrite("primal_solution", &PrimalAndDualSolution::primal_solution) + .def_readwrite("dual_solution", &PrimalAndDualSolution::dual_solution); pybind11::class_(m, "SolverResult") .def(pybind11::init<>()) @@ -164,12 +163,12 @@ PYBIND11_MODULE(pywrap_pdlp, m) { m.def( "primal_dual_hybrid_gradient", [](QuadraticProgram qp, absl::string_view params_str, - std::optional initial_solution) { - pdlp::PrimalDualHybridGradientParams params; + std::optional initial_solution) { + PrimalDualHybridGradientParams params; if (!params.ParseFromString(std::string(params_str))) { throw std::invalid_argument("Unable to parse input params"); } - pdlp::SolverResult result = pdlp::PrimalDualHybridGradient( + SolverResult result = PrimalDualHybridGradient( std::move(qp), params, std::move(initial_solution)); return PywrapSolverResult{ .primal_solution = std::move(result.primal_solution), @@ -179,3 +178,5 @@ PYBIND11_MODULE(pywrap_pdlp, m) { }, arg("qp"), arg("params"), arg("initial_solution") = std::nullopt); } + +} // namespace operations_research::pdlp diff --git a/ortools/pdlp/python/pywrap_pdlp_test.py b/ortools/pdlp/python/pdlp_test.py similarity index 86% rename from ortools/pdlp/python/pywrap_pdlp_test.py rename to ortools/pdlp/python/pdlp_test.py index c1fad74e088..c19a2709627 100644 --- a/ortools/pdlp/python/pywrap_pdlp_test.py +++ b/ortools/pdlp/python/pdlp_test.py @@ -20,7 +20,7 @@ from ortools.pdlp import solve_log_pb2 from ortools.pdlp import solvers_pb2 -from ortools.pdlp.python import pywrap_pdlp +from ortools.pdlp.python import pdlp from ortools.linear_solver import linear_solver_pb2 @@ -77,31 +77,31 @@ def small_proto_qp(): class QuadraticProgramTest(absltest.TestCase): def test_validate_quadratic_program_dimensions_for_empty_qp(self): - qp = pywrap_pdlp.QuadraticProgram() + qp = pdlp.QuadraticProgram() qp.resize_and_initialize(3, 2) - pywrap_pdlp.validate_quadratic_program_dimensions(qp) - self.assertTrue(pywrap_pdlp.is_linear_program(qp)) + pdlp.validate_quadratic_program_dimensions(qp) + self.assertTrue(pdlp.is_linear_program(qp)) def test_converts_from_tiny_mpmodel_lp(self): lp_proto = small_proto_lp() - qp = pywrap_pdlp.qp_from_mpmodel_proto( + qp = pdlp.qp_from_mpmodel_proto( lp_proto.SerializeToString(), relax_integer_variables=False ) - pywrap_pdlp.validate_quadratic_program_dimensions(qp) - self.assertTrue(pywrap_pdlp.is_linear_program(qp)) + pdlp.validate_quadratic_program_dimensions(qp) + self.assertTrue(pdlp.is_linear_program(qp)) self.assertSameElements(qp.objective_vector, [0, -2]) def test_converts_from_tiny_mpmodel_qp(self): qp_proto = small_proto_qp() - qp = pywrap_pdlp.qp_from_mpmodel_proto( + qp = pdlp.qp_from_mpmodel_proto( qp_proto.SerializeToString(), relax_integer_variables=False ) - pywrap_pdlp.validate_quadratic_program_dimensions(qp) - self.assertFalse(pywrap_pdlp.is_linear_program(qp)) + pdlp.validate_quadratic_program_dimensions(qp) + self.assertFalse(pdlp.is_linear_program(qp)) self.assertSameElements(qp.objective_vector, [0, 0]) def test_build_lp(self): - qp = pywrap_pdlp.QuadraticProgram() + qp = pdlp.QuadraticProgram() qp.objective_vector = [0, -2] qp.constraint_matrix = scipy.sparse.csr_matrix(np.array([[1.0, 1.0]])) qp.constraint_lower_bounds = [-np.inf] @@ -110,14 +110,12 @@ def test_build_lp(self): qp.variable_upper_bounds = [np.inf, np.inf] qp.variable_names = ["x", "y"] self.assertEqual( - linear_solver_pb2.MPModelProto.FromString( - pywrap_pdlp.qp_to_mpmodel_proto(qp) - ), + linear_solver_pb2.MPModelProto.FromString(pdlp.qp_to_mpmodel_proto(qp)), small_proto_lp(), ) def test_build_qp(self): - qp = pywrap_pdlp.QuadraticProgram() + qp = pdlp.QuadraticProgram() qp.objective_vector = [0, 0] qp.constraint_matrix = scipy.sparse.csr_matrix(np.array([[1.0, 1.0]])) qp.set_objective_matrix_diagonal([4.0]) @@ -127,9 +125,7 @@ def test_build_qp(self): qp.variable_upper_bounds = [np.inf, np.inf] qp.variable_names = ["x", "y"] self.assertEqual( - linear_solver_pb2.MPModelProto.FromString( - pywrap_pdlp.qp_to_mpmodel_proto(qp) - ), + linear_solver_pb2.MPModelProto.FromString(pdlp.qp_to_mpmodel_proto(qp)), small_proto_qp(), ) @@ -152,7 +148,7 @@ def tiny_lp(): Dual: [0.5, 4.0, 0.0] Value: 6 + 28 - 3.5*6 - 14 = -1 Reduced costs: [0.0, 1.5, -3.5, 0.0] """ - qp = pywrap_pdlp.QuadraticProgram() + qp = pdlp.QuadraticProgram() qp.objective_offset = -14 qp.objective_vector = [5, 2, 1, 1] qp.constraint_lower_bounds = [12, 7, 1] @@ -182,7 +178,7 @@ def test_lp(): Dual: [-2, 0, 2.375, 2.0/3] Value: -5.5 - 16 -1 + 2.5 - 14 = -34 """ - qp = pywrap_pdlp.QuadraticProgram() + qp = pdlp.QuadraticProgram() qp.objective_offset = -14 qp.objective_vector = [5.5, -2, -1, 1] qp.constraint_lower_bounds = [12, -np.inf, -4, -1] @@ -201,9 +197,7 @@ def test_iteration_limit(self): params = solvers_pb2.PrimalDualHybridGradientParams() params.termination_criteria.iteration_limit = 1 params.termination_check_frequency = 1 - result = pywrap_pdlp.primal_dual_hybrid_gradient( - tiny_lp(), params.SerializeToString() - ) + result = pdlp.primal_dual_hybrid_gradient(tiny_lp(), params.SerializeToString()) solve_log = solve_log_pb2.SolveLog.FromString(result.solve_log_str) self.assertLessEqual(solve_log.iteration_count, 1) self.assertEqual( @@ -216,9 +210,7 @@ def test_solution(self): opt_criteria = params.termination_criteria.simple_optimality_criteria opt_criteria.eps_optimal_relative = 0.0 opt_criteria.eps_optimal_absolute = 1.0e-10 - result = pywrap_pdlp.primal_dual_hybrid_gradient( - tiny_lp(), params.SerializeToString() - ) + result = pdlp.primal_dual_hybrid_gradient(tiny_lp(), params.SerializeToString()) solve_log = solve_log_pb2.SolveLog.FromString(result.solve_log_str) self.assertEqual( solve_log.termination_reason, solve_log_pb2.TERMINATION_REASON_OPTIMAL @@ -232,9 +224,7 @@ def test_solution_2(self): opt_criteria = params.termination_criteria.simple_optimality_criteria opt_criteria.eps_optimal_relative = 0.0 opt_criteria.eps_optimal_absolute = 1.0e-10 - result = pywrap_pdlp.primal_dual_hybrid_gradient( - test_lp(), params.SerializeToString() - ) + result = pdlp.primal_dual_hybrid_gradient(test_lp(), params.SerializeToString()) solve_log = solve_log_pb2.SolveLog.FromString(result.solve_log_str) self.assertEqual( solve_log.termination_reason, solve_log_pb2.TERMINATION_REASON_OPTIMAL @@ -250,10 +240,10 @@ def test_starting_point(self): params.l_inf_ruiz_iterations = 0 params.l2_norm_rescaling = False - start = pywrap_pdlp.PrimalAndDualSolution() + start = pdlp.PrimalAndDualSolution() start.primal_solution = [1.0, 0.0, 6.0, 2.0] start.dual_solution = [0.5, 4.0, 0.0] - result = pywrap_pdlp.primal_dual_hybrid_gradient( + result = pdlp.primal_dual_hybrid_gradient( tiny_lp(), params.SerializeToString(), initial_solution=start ) solve_log = solve_log_pb2.SolveLog.FromString(result.solve_log_str) diff --git a/ortools/pdlp/samples/simple_pdlp_program.py b/ortools/pdlp/samples/simple_pdlp_program.py index 80e8d5332f7..2afabfb3a0d 100644 --- a/ortools/pdlp/samples/simple_pdlp_program.py +++ b/ortools/pdlp/samples/simple_pdlp_program.py @@ -25,11 +25,11 @@ from ortools.pdlp import solve_log_pb2 from ortools.pdlp import solvers_pb2 -from ortools.pdlp.python import pywrap_pdlp +from ortools.pdlp.python import pdlp from ortools.init.python import init -def simple_lp() -> pywrap_pdlp.QuadraticProgram: +def simple_lp() -> pdlp.QuadraticProgram: """Returns a small LP. min 5.5 x_0 - 2 x_1 - x_2 + x_3 - 14 s.t. @@ -42,7 +42,7 @@ def simple_lp() -> pywrap_pdlp.QuadraticProgram: -infinity <= x_2 <= 6 2.5 <= x_3 <= 3.5 """ - lp = pywrap_pdlp.QuadraticProgram() + lp = pdlp.QuadraticProgram() lp.objective_offset = -14 lp.objective_vector = [5.5, -2, -1, 1] lp.constraint_lower_bounds = [12, -np.inf, -4, -1] @@ -73,9 +73,7 @@ def main() -> None: # Call the main solve function. Note that a quirk of the pywrap11 API forces # us to serialize the `params` and deserialize the `solve_log` proto messages. - result = pywrap_pdlp.primal_dual_hybrid_gradient( - simple_lp(), params.SerializeToString() - ) + result = pdlp.primal_dual_hybrid_gradient(simple_lp(), params.SerializeToString()) solve_log = solve_log_pb2.SolveLog.FromString(result.solve_log_str) if solve_log.termination_reason == solve_log_pb2.TERMINATION_REASON_OPTIMAL: diff --git a/ortools/python/setup.py.in b/ortools/python/setup.py.in index 1555ce8273f..3280e5300a1 100644 --- a/ortools/python/setup.py.in +++ b/ortools/python/setup.py.in @@ -63,7 +63,7 @@ setup( '@PYTHON_PROJECT@.linear_solver.python':['$', '*.pyi'], '@PYTHON_PROJECT@.packing':['*.pyi'], '@PYTHON_PROJECT@.pdlp':['*.pyi'], - '@PYTHON_PROJECT@.pdlp.python':['$'], + '@PYTHON_PROJECT@.pdlp.python':['$'], '@PYTHON_PROJECT@.sat':['*.pyi'], '@PYTHON_PROJECT@.sat.python':['$', '*.pyi'], '@PYTHON_PROJECT@.scheduling.python':['$', '*.pyi'],