Skip to content

Commit b093e98

Browse files
committed
algorithms: Add python.set_cover
1 parent 8d60e85 commit b093e98

18 files changed

+1288
-43
lines changed

cmake/python.cmake

+4
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,8 @@ add_custom_command(
449449
$<TARGET_FILE:init_pybind11> ${PYTHON_PROJECT}/init/python
450450
COMMAND ${CMAKE_COMMAND} -E copy
451451
$<TARGET_FILE:knapsack_solver_pybind11> ${PYTHON_PROJECT}/algorithms/python
452+
COMMAND ${CMAKE_COMMAND} -E copy
453+
$<TARGET_FILE:set_cover_pybind11> ${PYTHON_PROJECT}/algorithms/python
452454
COMMAND ${CMAKE_COMMAND} -E copy
453455
$<TARGET_FILE:linear_sum_assignment_pybind11> ${PYTHON_PROJECT}/graph/python
454456
COMMAND ${CMAKE_COMMAND} -E copy
@@ -480,6 +482,7 @@ add_custom_command(
480482
DEPENDS
481483
init_pybind11
482484
knapsack_solver_pybind11
485+
set_cover_pybind11
483486
linear_sum_assignment_pybind11
484487
max_flow_pybind11
485488
min_cost_flow_pybind11
@@ -515,6 +518,7 @@ add_custom_command(
515518
COMMAND ${CMAKE_COMMAND} -E remove -f stub_timestamp
516519
COMMAND ${stubgen_EXECUTABLE} -p ortools.init.python.init --output .
517520
COMMAND ${stubgen_EXECUTABLE} -p ortools.algorithms.python.knapsack_solver --output .
521+
COMMAND ${stubgen_EXECUTABLE} -p ortools.algorithms.python.set_cover --output .
518522
COMMAND ${stubgen_EXECUTABLE} -p ortools.graph.python.linear_sum_assignment --output .
519523
COMMAND ${stubgen_EXECUTABLE} -p ortools.graph.python.max_flow --output .
520524
COMMAND ${stubgen_EXECUTABLE} -p ortools.graph.python.min_cost_flow --output .

ortools/algorithms/BUILD.bazel

+20
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
1515
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_proto_library")
1616
load("@rules_proto//proto:defs.bzl", "proto_library")
17+
load("@rules_python//python:proto.bzl", "py_proto_library")
1718

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

@@ -259,6 +260,24 @@ cc_proto_library(
259260
deps = [":set_cover_proto"],
260261
)
261262

263+
py_proto_library(
264+
name = "set_cover_py_pb2",
265+
deps = [":set_cover_proto"],
266+
)
267+
268+
cc_library(
269+
name = "set_cover_lagrangian",
270+
srcs = ["set_cover_lagrangian.cc"],
271+
hdrs = ["set_cover_lagrangian.h"],
272+
deps = [
273+
":adjustable_k_ary_heap",
274+
":set_cover_invariant",
275+
":set_cover_model",
276+
"//ortools/base:threadpool",
277+
"@com_google_absl//absl/log:check",
278+
],
279+
)
280+
262281
cc_library(
263282
name = "set_cover_model",
264283
srcs = ["set_cover_model.cc"],
@@ -284,6 +303,7 @@ cc_library(
284303
"//ortools/base",
285304
"@com_google_absl//absl/log",
286305
"@com_google_absl//absl/log:check",
306+
"@com_google_absl//absl/types:span",
287307
],
288308
)
289309

ortools/algorithms/python/BUILD.bazel

+28
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ config_setting(
4444
},
4545
)
4646

47+
# knapsack_solver
4748
cc_library(
4849
name = "knapsack_solver_doc",
4950
hdrs = ["knapsack_solver_doc.h"],
@@ -77,3 +78,30 @@ py_test(
7778
requirement("absl-py"),
7879
],
7980
)
81+
82+
# set_cover
83+
pybind_extension(
84+
name = "set_cover",
85+
srcs = ["set_cover.cc"],
86+
visibility = ["//visibility:public"],
87+
deps = [
88+
"//ortools/algorithms:set_cover_cc_proto",
89+
"//ortools/algorithms:set_cover_heuristics",
90+
"//ortools/algorithms:set_cover_invariant",
91+
"//ortools/algorithms:set_cover_model",
92+
"//ortools/algorithms:set_cover_reader",
93+
"@com_google_absl//absl/strings",
94+
"@pybind11_protobuf//pybind11_protobuf:native_proto_caster",
95+
],
96+
)
97+
98+
py_test(
99+
name = "set_cover_test",
100+
srcs = ["set_cover_test.py"],
101+
python_version = "PY3",
102+
deps = [
103+
":set_cover",
104+
"//ortools/algorithms:set_cover_py_pb2",
105+
requirement("absl-py"),
106+
],
107+
)

ortools/algorithms/python/CMakeLists.txt

+27
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# See the License for the specific language governing permissions and
1212
# limitations under the License.
1313

14+
# knapsack_solver
1415
pybind11_add_module(knapsack_solver_pybind11 MODULE knapsack_solver.cc)
1516
set_target_properties(knapsack_solver_pybind11 PROPERTIES
1617
LIBRARY_OUTPUT_NAME "knapsack_solver")
@@ -33,6 +34,32 @@ endif()
3334
target_link_libraries(knapsack_solver_pybind11 PRIVATE ${PROJECT_NAMESPACE}::ortools)
3435
add_library(${PROJECT_NAMESPACE}::knapsack_solver_pybind11 ALIAS knapsack_solver_pybind11)
3536

37+
# set_cover
38+
pybind11_add_module(set_cover_pybind11 MODULE set_cover.cc)
39+
set_target_properties(set_cover_pybind11 PROPERTIES
40+
LIBRARY_OUTPUT_NAME "set_cover")
41+
42+
# note: macOS is APPLE and also UNIX !
43+
if(APPLE)
44+
set_target_properties(set_cover_pybind11 PROPERTIES
45+
SUFFIX ".so"
46+
INSTALL_RPATH "@loader_path;@loader_path/../../../${PYTHON_PROJECT}/.libs"
47+
)
48+
set_property(TARGET set_cover_pybind11 APPEND PROPERTY
49+
LINK_FLAGS "-flat_namespace -undefined suppress"
50+
)
51+
elseif(UNIX)
52+
set_target_properties(set_cover_pybind11 PROPERTIES
53+
INSTALL_RPATH "$ORIGIN:$ORIGIN/../../../${PYTHON_PROJECT}/.libs"
54+
)
55+
endif()
56+
57+
target_link_libraries(set_cover_pybind11 PRIVATE
58+
${PROJECT_NAMESPACE}::ortools
59+
pybind11_native_proto_caster
60+
)
61+
add_library(${PROJECT_NAMESPACE}::set_cover_pybind11 ALIAS set_cover_pybind11)
62+
3663
if(BUILD_TESTING)
3764
file(GLOB PYTHON_SRCS "*_test.py")
3865
foreach(FILE_NAME IN LISTS PYTHON_SRCS)
+239
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
// Copyright 2010-2024 Google LLC
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
// A pybind11 wrapper for set_cover_*.
15+
16+
#include <memory>
17+
18+
#include "absl/base/nullability.h"
19+
#include "ortools/algorithms/set_cover_heuristics.h"
20+
#include "ortools/algorithms/set_cover_invariant.h"
21+
#include "ortools/algorithms/set_cover_model.h"
22+
#include "ortools/algorithms/set_cover_reader.h"
23+
#include "pybind11/pybind11.h"
24+
#include "pybind11/pytypes.h"
25+
#include "pybind11/stl.h"
26+
#include "pybind11_protobuf/native_proto_caster.h"
27+
28+
using ::operations_research::ElementDegreeSolutionGenerator;
29+
using ::operations_research::GreedySolutionGenerator;
30+
using ::operations_research::GuidedLocalSearch;
31+
using ::operations_research::Preprocessor;
32+
using ::operations_research::RandomSolutionGenerator;
33+
using ::operations_research::ReadBeasleySetCoverProblem;
34+
using ::operations_research::ReadRailSetCoverProblem;
35+
using ::operations_research::SetCoverInvariant;
36+
using ::operations_research::SetCoverModel;
37+
using ::operations_research::SteepestSearch;
38+
using ::operations_research::SubsetIndex;
39+
using ::operations_research::TrivialSolutionGenerator;
40+
41+
namespace py = pybind11;
42+
using ::py::arg;
43+
44+
// General note about TODOs: the corresponding functions/classes/methods are
45+
// more complex to wrap, as they use nonstandard types, and are less important,
46+
// as they are not as useful to most users (mostly useful to write some custom
47+
// Python heuristics).
48+
49+
PYBIND11_MODULE(set_cover, m) {
50+
pybind11_protobuf::ImportNativeProtoCasters();
51+
52+
// set_cover_model.h
53+
py::class_<SetCoverModel>(m, "SetCoverModel")
54+
.def(py::init<>())
55+
.def_property_readonly("num_elements", &SetCoverModel::num_elements)
56+
.def_property_readonly("num_subsets", &SetCoverModel::num_subsets)
57+
.def_property_readonly("num_nonzeros", &SetCoverModel::num_nonzeros)
58+
.def_property_readonly("fill_rate", &SetCoverModel::FillRate)
59+
.def("add_empty_subset", &SetCoverModel::AddEmptySubset, arg("cost"))
60+
.def(
61+
"add_element_to_last_subset",
62+
[](SetCoverModel& model, int element) {
63+
model.AddElementToLastSubset(element);
64+
},
65+
arg("element"))
66+
.def(
67+
"set_subset_cost",
68+
[](SetCoverModel& model, int subset, double cost) {
69+
model.SetSubsetCost(subset, cost);
70+
},
71+
arg("subset"), arg("cost"))
72+
.def(
73+
"add_element_to_subset",
74+
[](SetCoverModel& model, int element, int subset) {
75+
model.AddElementToSubset(element, subset);
76+
},
77+
arg("subset"), arg("cost"))
78+
.def("compute_feasibility", &SetCoverModel::ComputeFeasibility)
79+
.def(
80+
"reserve_num_subsets",
81+
[](SetCoverModel& model, int num_subsets) {
82+
model.ReserveNumSubsets(num_subsets);
83+
},
84+
arg("num_subsets"))
85+
.def(
86+
"reserve_num_elements_in_subset",
87+
[](SetCoverModel& model, int num_elements, int subset) {
88+
model.ReserveNumElementsInSubset(num_elements, subset);
89+
},
90+
arg("num_elements"), arg("subset"))
91+
.def("export_model_as_proto", &SetCoverModel::ExportModelAsProto)
92+
.def("import_model_from_proto", &SetCoverModel::ImportModelFromProto);
93+
// TODO(user): add support for subset_costs, columns, rows,
94+
// row_view_is_valid, SubsetRange, ElementRange, all_subsets,
95+
// CreateSparseRowView, ComputeCostStats, ComputeRowStats,
96+
// ComputeColumnStats, ComputeRowDeciles, ComputeColumnDeciles.
97+
98+
// TODO(user): wrap IntersectingSubsetsIterator.
99+
100+
// set_cover_invariant.h
101+
py::class_<SetCoverInvariant>(m, "SetCoverInvariant")
102+
.def(py::init<SetCoverModel*>())
103+
.def("initialize", &SetCoverInvariant::Initialize)
104+
.def("clear", &SetCoverInvariant::Clear)
105+
.def("recompute_invariant", &SetCoverInvariant::RecomputeInvariant)
106+
.def("model", &SetCoverInvariant::model)
107+
.def_property(
108+
"model",
109+
// Expected semantics: give a pointer to Python **while
110+
// keeping ownership** in C++.
111+
[](SetCoverInvariant& invariant) -> std::shared_ptr<SetCoverModel> {
112+
// https://pybind11.readthedocs.io/en/stable/advanced/smart_ptrs.html#std-shared-ptr
113+
std::shared_ptr<SetCoverModel> ptr(invariant.model());
114+
return ptr;
115+
},
116+
[](SetCoverInvariant& invariant, const SetCoverModel& model) {
117+
*invariant.model() = model;
118+
})
119+
.def("cost", &SetCoverInvariant::cost)
120+
.def("num_uncovered_elements", &SetCoverInvariant::num_uncovered_elements)
121+
.def("clear_trace", &SetCoverInvariant::ClearTrace)
122+
.def("clear_removability_information",
123+
&SetCoverInvariant::ClearRemovabilityInformation)
124+
.def("compress_trace", &SetCoverInvariant::CompressTrace)
125+
.def("check_consistency", &SetCoverInvariant::CheckConsistency)
126+
.def(
127+
"flip",
128+
[](SetCoverInvariant& invariant, int subset) {
129+
invariant.Flip(SubsetIndex(subset));
130+
},
131+
arg("subset"))
132+
.def(
133+
"flip_and_fully_update",
134+
[](SetCoverInvariant& invariant, int subset) {
135+
invariant.FlipAndFullyUpdate(SubsetIndex(subset));
136+
},
137+
arg("subset"))
138+
.def(
139+
"select",
140+
[](SetCoverInvariant& invariant, int subset) {
141+
invariant.Select(SubsetIndex(subset));
142+
},
143+
arg("subset"))
144+
.def(
145+
"select_and_fully_update",
146+
[](SetCoverInvariant& invariant, int subset) {
147+
invariant.SelectAndFullyUpdate(SubsetIndex(subset));
148+
},
149+
arg("subset"))
150+
.def(
151+
"deselect",
152+
[](SetCoverInvariant& invariant, int subset) {
153+
invariant.Deselect(SubsetIndex(subset));
154+
},
155+
arg("subset"))
156+
.def(
157+
"deselect_and_fully_update",
158+
[](SetCoverInvariant& invariant, int subset) {
159+
invariant.DeselectAndFullyUpdate(SubsetIndex(subset));
160+
},
161+
arg("subset"))
162+
.def("export_solution_as_proto",
163+
&SetCoverInvariant::ExportSolutionAsProto)
164+
.def("import_solution_from_proto",
165+
&SetCoverInvariant::ImportSolutionFromProto);
166+
// TODO(user): add support for is_selected, num_free_elements,
167+
// num_coverage_le_1_elements, coverage, ComputeCoverageInFocus,
168+
// is_redundant, trace, new_removable_subsets, new_non_removable_subsets,
169+
// LoadSolution, ComputeIsRedundant.
170+
171+
// set_cover_heuristics.h
172+
py::class_<Preprocessor>(m, "Preprocessor")
173+
.def(py::init<absl::Nonnull<SetCoverInvariant*>>())
174+
.def("next_solution",
175+
[](Preprocessor& heuristic) -> bool {
176+
return heuristic.NextSolution();
177+
})
178+
.def("num_columns_fixed_by_singleton_row",
179+
&Preprocessor::num_columns_fixed_by_singleton_row);
180+
// TODO(user): add support for focus argument.
181+
182+
py::class_<TrivialSolutionGenerator>(m, "TrivialSolutionGenerator")
183+
.def(py::init<SetCoverInvariant*>())
184+
.def("next_solution", [](TrivialSolutionGenerator& heuristic) -> bool {
185+
return heuristic.NextSolution();
186+
});
187+
// TODO(user): add support for focus argument.
188+
189+
py::class_<RandomSolutionGenerator>(m, "RandomSolutionGenerator")
190+
.def(py::init<SetCoverInvariant*>())
191+
.def("next_solution", [](RandomSolutionGenerator& heuristic) -> bool {
192+
return heuristic.NextSolution();
193+
});
194+
// TODO(user): add support for focus argument.
195+
196+
py::class_<GreedySolutionGenerator>(m, "GreedySolutionGenerator")
197+
.def(py::init<SetCoverInvariant*>())
198+
.def("next_solution", [](GreedySolutionGenerator& heuristic) -> bool {
199+
return heuristic.NextSolution();
200+
});
201+
// TODO(user): add support for focus and cost arguments.
202+
203+
py::class_<ElementDegreeSolutionGenerator>(m,
204+
"ElementDegreeSolutionGenerator")
205+
.def(py::init<SetCoverInvariant*>())
206+
.def("next_solution",
207+
[](ElementDegreeSolutionGenerator& heuristic) -> bool {
208+
return heuristic.NextSolution();
209+
});
210+
// TODO(user): add support for focus and cost arguments.
211+
212+
py::class_<SteepestSearch>(m, "SteepestSearch")
213+
.def(py::init<SetCoverInvariant*>())
214+
.def("next_solution",
215+
[](SteepestSearch& heuristic, int num_iterations) -> bool {
216+
return heuristic.NextSolution(num_iterations);
217+
});
218+
// TODO(user): add support for focus and cost arguments.
219+
220+
py::class_<GuidedLocalSearch>(m, "GuidedLocalSearch")
221+
.def(py::init<SetCoverInvariant*>())
222+
.def("initialize", &GuidedLocalSearch::Initialize)
223+
.def("next_solution",
224+
[](GuidedLocalSearch& heuristic, int num_iterations) -> bool {
225+
return heuristic.NextSolution(num_iterations);
226+
});
227+
// TODO(user): add support for focus and cost arguments.
228+
229+
// TODO(user): add support for ClearRandomSubsets, ClearRandomSubsets,
230+
// ClearMostCoveredElements, ClearMostCoveredElements, TabuList,
231+
// GuidedTabuSearch.
232+
233+
// set_cover_reader.h
234+
m.def("read_beasly_set_cover_problem", &ReadBeasleySetCoverProblem);
235+
m.def("read_rail_set_cover_problem", &ReadRailSetCoverProblem);
236+
237+
// set_cover_lagrangian.h
238+
// TODO(user): add support for SetCoverLagrangian.
239+
}

0 commit comments

Comments
 (0)