Skip to content

Commit

Permalink
algorithms: Add python.set_cover
Browse files Browse the repository at this point in the history
  • Loading branch information
Mizux committed Aug 30, 2024
1 parent 8d60e85 commit 065d05c
Show file tree
Hide file tree
Showing 18 changed files with 1,287 additions and 43 deletions.
4 changes: 4 additions & 0 deletions cmake/python.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,8 @@ add_custom_command(
$<TARGET_FILE:init_pybind11> ${PYTHON_PROJECT}/init/python
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:knapsack_solver_pybind11> ${PYTHON_PROJECT}/algorithms/python
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:set_cover_pybind11> ${PYTHON_PROJECT}/algorithms/python
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:linear_sum_assignment_pybind11> ${PYTHON_PROJECT}/graph/python
COMMAND ${CMAKE_COMMAND} -E copy
Expand Down Expand Up @@ -480,6 +482,7 @@ add_custom_command(
DEPENDS
init_pybind11
knapsack_solver_pybind11
set_cover_pybind11
linear_sum_assignment_pybind11
max_flow_pybind11
min_cost_flow_pybind11
Expand Down Expand Up @@ -515,6 +518,7 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E remove -f stub_timestamp
COMMAND ${stubgen_EXECUTABLE} -p ortools.init.python.init --output .
COMMAND ${stubgen_EXECUTABLE} -p ortools.algorithms.python.knapsack_solver --output .
COMMAND ${stubgen_EXECUTABLE} -p ortools.algorithms.python.set_cover --output .
COMMAND ${stubgen_EXECUTABLE} -p ortools.graph.python.linear_sum_assignment --output .
COMMAND ${stubgen_EXECUTABLE} -p ortools.graph.python.max_flow --output .
COMMAND ${stubgen_EXECUTABLE} -p ortools.graph.python.min_cost_flow --output .
Expand Down
20 changes: 20 additions & 0 deletions ortools/algorithms/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_proto_library")
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@rules_python//python:proto.bzl", "py_proto_library")

package(default_visibility = ["//visibility:public"])

Expand Down Expand Up @@ -259,6 +260,24 @@ cc_proto_library(
deps = [":set_cover_proto"],
)

py_proto_library(
name = "set_cover_py_pb2",
deps = [":set_cover_proto"],
)

cc_library(
name = "set_cover_lagrangian",
srcs = ["set_cover_lagrangian.cc"],
hdrs = ["set_cover_lagrangian.h"],
deps = [
":adjustable_k_ary_heap",
":set_cover_invariant",
":set_cover_model",
"//ortools/base:threadpool",
"@com_google_absl//absl/log:check",
],
)

cc_library(
name = "set_cover_model",
srcs = ["set_cover_model.cc"],
Expand All @@ -284,6 +303,7 @@ cc_library(
"//ortools/base",
"@com_google_absl//absl/log",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/types:span",
],
)

Expand Down
27 changes: 27 additions & 0 deletions ortools/algorithms/python/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ config_setting(
},
)

# knapsack_solver
cc_library(
name = "knapsack_solver_doc",
hdrs = ["knapsack_solver_doc.h"],
Expand Down Expand Up @@ -77,3 +78,29 @@ py_test(
requirement("absl-py"),
],
)

# set_cover
pybind_extension(
name = "set_cover",
srcs = ["set_cover.cc"],
visibility = ["//visibility:public"],
deps = [
"//ortools/algorithms:knapsack_solver_lib",
"//ortools/algorithms:set_cover_heuristics",
"//ortools/algorithms:set_cover_invariant",
"//ortools/algorithms:set_cover_model",
"//ortools/algorithms:set_cover_reader",
"@com_google_absl//absl/strings",
"@pybind11_protobuf//pybind11_protobuf:native_proto_caster",
],
)

py_test(
name = "set_cover_test",
srcs = ["set_cover_test.py"],
python_version = "PY3",
deps = [
":set_cover",
requirement("absl-py"),
],
)
27 changes: 27 additions & 0 deletions ortools/algorithms/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# knapsack_solver
pybind11_add_module(knapsack_solver_pybind11 MODULE knapsack_solver.cc)
set_target_properties(knapsack_solver_pybind11 PROPERTIES
LIBRARY_OUTPUT_NAME "knapsack_solver")
Expand All @@ -33,6 +34,32 @@ endif()
target_link_libraries(knapsack_solver_pybind11 PRIVATE ${PROJECT_NAMESPACE}::ortools)
add_library(${PROJECT_NAMESPACE}::knapsack_solver_pybind11 ALIAS knapsack_solver_pybind11)

# set_cover
pybind11_add_module(set_cover_pybind11 MODULE set_cover.cc)
set_target_properties(set_cover_pybind11 PROPERTIES
LIBRARY_OUTPUT_NAME "set_cover")

# note: macOS is APPLE and also UNIX !
if(APPLE)
set_target_properties(set_cover_pybind11 PROPERTIES
SUFFIX ".so"
INSTALL_RPATH "@loader_path;@loader_path/../../../${PYTHON_PROJECT}/.libs"
)
set_property(TARGET set_cover_pybind11 APPEND PROPERTY
LINK_FLAGS "-flat_namespace -undefined suppress"
)
elseif(UNIX)
set_target_properties(set_cover_pybind11 PROPERTIES
INSTALL_RPATH "$ORIGIN:$ORIGIN/../../../${PYTHON_PROJECT}/.libs"
)
endif()

target_link_libraries(set_cover_pybind11 PRIVATE
${PROJECT_NAMESPACE}::ortools
pybind11_native_proto_caster
)
add_library(${PROJECT_NAMESPACE}::set_cover_pybind11 ALIAS set_cover_pybind11)

if(BUILD_TESTING)
file(GLOB PYTHON_SRCS "*_test.py")
foreach(FILE_NAME IN LISTS PYTHON_SRCS)
Expand Down
239 changes: 239 additions & 0 deletions ortools/algorithms/python/set_cover.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// Copyright 2010-2024 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.

// A pybind11 wrapper for set_cover_*.

#include <memory>

#include "absl/base/nullability.h"
#include "ortools/algorithms/set_cover_heuristics.h"
#include "ortools/algorithms/set_cover_invariant.h"
#include "ortools/algorithms/set_cover_model.h"
#include "ortools/algorithms/set_cover_reader.h"
#include "pybind11/pybind11.h"
#include "pybind11/pytypes.h"
#include "pybind11/stl.h"
#include "pybind11_protobuf/native_proto_caster.h"

using ::operations_research::ElementDegreeSolutionGenerator;
using ::operations_research::GreedySolutionGenerator;
using ::operations_research::GuidedLocalSearch;
using ::operations_research::Preprocessor;
using ::operations_research::RandomSolutionGenerator;
using ::operations_research::ReadBeasleySetCoverProblem;
using ::operations_research::ReadRailSetCoverProblem;
using ::operations_research::SetCoverInvariant;
using ::operations_research::SetCoverModel;
using ::operations_research::SteepestSearch;
using ::operations_research::SubsetIndex;
using ::operations_research::TrivialSolutionGenerator;

namespace py = pybind11;
using ::py::arg;

// General note about TODOs: the corresponding functions/classes/methods are
// more complex to wrap, as they use nonstandard types, and are less important,
// as they are not as useful to most users (mostly useful to write some custom
// Python heuristics).

PYBIND11_MODULE(set_cover, m) {
pybind11_protobuf::ImportNativeProtoCasters();

// set_cover_model.h
py::class_<SetCoverModel>(m, "SetCoverModel")
.def(py::init<>())
.def_property_readonly("num_elements", &SetCoverModel::num_elements)
.def_property_readonly("num_subsets", &SetCoverModel::num_subsets)
.def_property_readonly("num_nonzeros", &SetCoverModel::num_nonzeros)
.def_property_readonly("fill_rate", &SetCoverModel::FillRate)
.def("add_empty_subset", &SetCoverModel::AddEmptySubset, arg("cost"))
.def(
"add_element_to_last_subset",
[](SetCoverModel& model, int element) {
model.AddElementToLastSubset(element);
},
arg("element"))
.def(
"set_subset_cost",
[](SetCoverModel& model, int subset, double cost) {
model.SetSubsetCost(subset, cost);
},
arg("subset"), arg("cost"))
.def(
"add_element_to_subset",
[](SetCoverModel& model, int element, int subset) {
model.AddElementToSubset(element, subset);
},
arg("subset"), arg("cost"))
.def("compute_feasibility", &SetCoverModel::ComputeFeasibility)
.def(
"reserve_num_subsets",
[](SetCoverModel& model, int num_subsets) {
model.ReserveNumSubsets(num_subsets);
},
arg("num_subsets"))
.def(
"reserve_num_elements_in_subset",
[](SetCoverModel& model, int num_elements, int subset) {
model.ReserveNumElementsInSubset(num_elements, subset);
},
arg("num_elements"), arg("subset"))
.def("export_model_as_proto", &SetCoverModel::ExportModelAsProto)
.def("import_model_from_proto", &SetCoverModel::ImportModelFromProto);
// TODO(user): add support for subset_costs, columns, rows,
// row_view_is_valid, SubsetRange, ElementRange, all_subsets,
// CreateSparseRowView, ComputeCostStats, ComputeRowStats,
// ComputeColumnStats, ComputeRowDeciles, ComputeColumnDeciles.

// TODO(user): wrap IntersectingSubsetsIterator.

// set_cover_invariant.h
py::class_<SetCoverInvariant>(m, "SetCoverInvariant")
.def(py::init<SetCoverModel*>())
.def("initialize", &SetCoverInvariant::Initialize)
.def("clear", &SetCoverInvariant::Clear)
.def("recompute_invariant", &SetCoverInvariant::RecomputeInvariant)
.def("model", &SetCoverInvariant::model)
.def_property(
"model",
// Expected semantics: give a pointer to Python **while
// keeping ownership** in C++.
[](SetCoverInvariant& invariant) -> std::shared_ptr<SetCoverModel> {
// https://pybind11.readthedocs.io/en/stable/advanced/smart_ptrs.html#std-shared-ptr
std::shared_ptr<SetCoverModel> ptr(invariant.model());
return ptr;
},
[](SetCoverInvariant& invariant, const SetCoverModel& model) {
*invariant.model() = model;
})
.def("cost", &SetCoverInvariant::cost)
.def("num_uncovered_elements", &SetCoverInvariant::num_uncovered_elements)
.def("clear_trace", &SetCoverInvariant::ClearTrace)
.def("clear_removability_information",
&SetCoverInvariant::ClearRemovabilityInformation)
.def("compress_trace", &SetCoverInvariant::CompressTrace)
.def("check_consistency", &SetCoverInvariant::CheckConsistency)
.def(
"flip",
[](SetCoverInvariant& invariant, int subset) {
invariant.Flip(SubsetIndex(subset));
},
arg("subset"))
.def(
"flip_and_fully_update",
[](SetCoverInvariant& invariant, int subset) {
invariant.FlipAndFullyUpdate(SubsetIndex(subset));
},
arg("subset"))
.def(
"select",
[](SetCoverInvariant& invariant, int subset) {
invariant.Select(SubsetIndex(subset));
},
arg("subset"))
.def(
"select_and_fully_update",
[](SetCoverInvariant& invariant, int subset) {
invariant.SelectAndFullyUpdate(SubsetIndex(subset));
},
arg("subset"))
.def(
"deselect",
[](SetCoverInvariant& invariant, int subset) {
invariant.Deselect(SubsetIndex(subset));
},
arg("subset"))
.def(
"deselect_and_fully_update",
[](SetCoverInvariant& invariant, int subset) {
invariant.DeselectAndFullyUpdate(SubsetIndex(subset));
},
arg("subset"))
.def("export_solution_as_proto",
&SetCoverInvariant::ExportSolutionAsProto)
.def("import_solution_from_proto",
&SetCoverInvariant::ImportSolutionFromProto);
// TODO(user): add support for is_selected, num_free_elements,
// num_coverage_le_1_elements, coverage, ComputeCoverageInFocus,
// is_redundant, trace, new_removable_subsets, new_non_removable_subsets,
// LoadSolution, ComputeIsRedundant.

// set_cover_heuristics.h
py::class_<Preprocessor>(m, "Preprocessor")
.def(py::init<absl::Nonnull<SetCoverInvariant*>>())
.def("next_solution",
[](Preprocessor& heuristic) -> bool {
return heuristic.NextSolution();
})
.def("num_columns_fixed_by_singleton_row",
&Preprocessor::num_columns_fixed_by_singleton_row);
// TODO(user): add support for focus argument.

py::class_<TrivialSolutionGenerator>(m, "TrivialSolutionGenerator")
.def(py::init<SetCoverInvariant*>())
.def("next_solution", [](TrivialSolutionGenerator& heuristic) -> bool {
return heuristic.NextSolution();
});
// TODO(user): add support for focus argument.

py::class_<RandomSolutionGenerator>(m, "RandomSolutionGenerator")
.def(py::init<SetCoverInvariant*>())
.def("next_solution", [](RandomSolutionGenerator& heuristic) -> bool {
return heuristic.NextSolution();
});
// TODO(user): add support for focus argument.

py::class_<GreedySolutionGenerator>(m, "GreedySolutionGenerator")
.def(py::init<SetCoverInvariant*>())
.def("next_solution", [](GreedySolutionGenerator& heuristic) -> bool {
return heuristic.NextSolution();
});
// TODO(user): add support for focus and cost arguments.

py::class_<ElementDegreeSolutionGenerator>(m,
"ElementDegreeSolutionGenerator")
.def(py::init<SetCoverInvariant*>())
.def("next_solution",
[](ElementDegreeSolutionGenerator& heuristic) -> bool {
return heuristic.NextSolution();
});
// TODO(user): add support for focus and cost arguments.

py::class_<SteepestSearch>(m, "SteepestSearch")
.def(py::init<SetCoverInvariant*>())
.def("next_solution",
[](SteepestSearch& heuristic, int num_iterations) -> bool {
return heuristic.NextSolution(num_iterations);
});
// TODO(user): add support for focus and cost arguments.

py::class_<GuidedLocalSearch>(m, "GuidedLocalSearch")
.def(py::init<SetCoverInvariant*>())
.def("initialize", &GuidedLocalSearch::Initialize)
.def("next_solution",
[](GuidedLocalSearch& heuristic, int num_iterations) -> bool {
return heuristic.NextSolution(num_iterations);
});
// TODO(user): add support for focus and cost arguments.

// TODO(user): add support for ClearRandomSubsets, ClearRandomSubsets,
// ClearMostCoveredElements, ClearMostCoveredElements, TabuList,
// GuidedTabuSearch.

// set_cover_reader.h
m.def("read_beasly_set_cover_problem", &ReadBeasleySetCoverProblem);
m.def("read_rail_set_cover_problem", &ReadRailSetCoverProblem);

// set_cover_lagrangian.h
// TODO(user): add support for SetCoverLagrangian.
}
Loading

0 comments on commit 065d05c

Please sign in to comment.