Skip to content

Commit fee3467

Browse files
committed
routing: migrate to pybind11 (code, tests, examples)
1 parent 339ea69 commit fee3467

39 files changed

+476
-419
lines changed

ortools/constraint_solver/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ cc_library(
221221
"@abseil-cpp//absl/base:nullability",
222222
"@abseil-cpp//absl/container:flat_hash_map",
223223
"@abseil-cpp//absl/container:flat_hash_set",
224+
"@abseil-cpp//absl/container:btree",
224225
"@abseil-cpp//absl/flags:flag",
225226
"@abseil-cpp//absl/hash",
226227
"@abseil-cpp//absl/log",

ortools/constraint_solver/constraint_solver.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ Solver(name = "pheasant",
8383

8484
#include "absl/base/attributes.h"
8585
#include "absl/base/log_severity.h"
86+
#include "absl/container/btree_map.h"
8687
#include "absl/container/flat_hash_map.h"
8788
#include "absl/container/flat_hash_set.h"
8889
#include "absl/flags/declare.h"
@@ -5330,6 +5331,8 @@ class SequenceVarElement : public AssignmentElement {
53305331
template <class V, class E>
53315332
class AssignmentContainer {
53325333
public:
5334+
using LocalMap = absl::btree_map<const V*, int>;
5335+
53335336
AssignmentContainer() {}
53345337
E* Add(V* var) {
53355338
CHECK(var != nullptr);
@@ -5473,8 +5476,7 @@ class AssignmentContainer {
54735476

54745477
private:
54755478
void EnsureMapIsUpToDate() const {
5476-
absl::flat_hash_map<const V*, int>* map =
5477-
const_cast<absl::flat_hash_map<const V*, int>*>(&elements_map_);
5479+
LocalMap* map = const_cast<LocalMap*>(&elements_map_);
54785480
for (int i = map->size(); i < elements_.size(); ++i) {
54795481
(*map)[elements_[i].Var()] = i;
54805482
}
@@ -5501,7 +5503,7 @@ class AssignmentContainer {
55015503
}
55025504

55035505
std::vector<E> elements_;
5504-
absl::flat_hash_map<const V*, int> elements_map_;
5506+
LocalMap elements_map_;
55055507
};
55065508

55075509
/// An Assignment is a variable -> domains mapping, used

ortools/routing/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ java_proto_library(
349349

350350
cc_proto_library(
351351
name = "heuristic_parameters_cc_proto",
352+
visibility = ["//visibility:public"],
352353
deps = [":heuristic_parameters_proto"],
353354
)
354355

ortools/routing/docs/ROUTING.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ int main(int argc, char* argv[]) {
102102
# Snippet from ortools/routing/samples/simple_routing_program.py
103103
"""Vehicle Routing example."""
104104
105-
from ortools.routing import enums_pb2
106-
from ortools.routing import parameters_pb2
105+
107106
from ortools.routing.python import routing
108107
109108
@@ -136,11 +135,9 @@ def main():
136135
routing_model.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)
137136
138137
# Setting first solution heuristic.
139-
search_parameters: parameters_pb2.RoutingSearchParameters = (
140-
routing.default_routing_search_parameters()
141-
)
138+
search_parameters = routing.default_routing_search_parameters()
142139
search_parameters.first_solution_strategy = (
143-
enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
140+
routing.FirstSolutionStrategy.PATH_CHEAPEST_ARC
144141
) # pylint: disable=no-member
145142
146143
# Solve the problem.

ortools/routing/python/BUILD.bazel

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
load("@pip_deps//:requirements.bzl", "requirement")
1717
load("@pybind11_bazel//:build_defs.bzl", "pybind_extension")
18+
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
1819
load("@rules_cc//cc:cc_library.bzl", "cc_library")
1920
load("@rules_python//python:py_test.bzl", "py_test")
2021

@@ -27,31 +28,71 @@ cc_library(
2728
],
2829
)
2930

31+
cc_binary(
32+
name = "gen_proto_builder_pybind11",
33+
srcs = ["gen_proto_builder_pybind11.cc"],
34+
deps = [
35+
"//ortools/base",
36+
"//ortools/constraint_solver:search_stats_cc_proto",
37+
"//ortools/constraint_solver:solver_parameters_cc_proto",
38+
"//ortools/routing:enums_cc_proto",
39+
"//ortools/routing:heuristic_parameters_cc_proto",
40+
"//ortools/routing:ils_cc_proto",
41+
"//ortools/routing:parameters_cc_proto",
42+
"//ortools/sat/python:wrappers",
43+
"@abseil-cpp//absl/flags:parse",
44+
"@abseil-cpp//absl/flags:usage",
45+
"@abseil-cpp//absl/log:die_if_null",
46+
"@abseil-cpp//absl/log:initialize",
47+
"@abseil-cpp//absl/strings:str_format",
48+
],
49+
)
50+
51+
genrule(
52+
name = "run_gen_proto_builder_pybind11",
53+
outs = ["proto_builder_pybind11.h"],
54+
cmd = "$(location :gen_proto_builder_pybind11) > $@",
55+
tools = [":gen_proto_builder_pybind11"],
56+
)
57+
58+
cc_library(
59+
name = "proto_builder_pybind11",
60+
hdrs = ["proto_builder_pybind11.h"],
61+
)
62+
3063
pybind_extension(
3164
name = "routing",
3265
srcs = ["routing.cc"],
3366
visibility = ["//visibility:public"],
3467
deps = [
3568
":doc",
69+
":proto_builder_pybind11",
3670
"//ortools/constraint_solver:cp",
71+
"//ortools/constraint_solver:search_stats_cc_proto",
3772
"//ortools/constraint_solver:solver_parameters_cc_proto",
3873
"//ortools/constraint_solver/python:constraint_solver",
74+
"//ortools/port:proto_utils",
3975
"//ortools/routing",
76+
"//ortools/routing:enums_cc_proto",
77+
"//ortools/routing:heuristic_parameters_cc_proto",
78+
"//ortools/routing:ils_cc_proto",
4079
"//ortools/routing:index_manager",
4180
"//ortools/routing:parameters",
4281
"//ortools/routing:types",
82+
"//ortools/sat:sat_parameters_cc_proto",
83+
"//ortools/util:optional_boolean_cc_proto",
4384
"//ortools/util:sorted_interval_list",
4485
"//ortools/util/python:sorted_interval_list",
4586
"@abseil-cpp//absl/algorithm:container",
4687
"@abseil-cpp//absl/container:flat_hash_set",
88+
"@protobuf//:duration_cc_proto",
4789
"@pybind11_abseil//pybind11_abseil:absl_casters",
48-
"@pybind11_protobuf//pybind11_protobuf:native_proto_caster",
4990
],
5091
)
5192

5293
py_test(
5394
name = "routing_test",
54-
size = "small",
95+
size = "medium",
5596
srcs = ["routing_test.py"],
5697
deps = [
5798
":routing",
@@ -85,7 +126,7 @@ py_test(
85126

86127
py_test(
87128
name = "sat_test",
88-
size = "small",
129+
size = "medium",
89130
srcs = ["sat_test.py"],
90131
deps = [
91132
":routing",

ortools/routing/python/CMakeLists.txt

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,43 @@
1111
# See the License for the specific language governing permissions and
1212
# limitations under the License.
1313

14+
# routing_gen_proto_builder_pybind11 code generator.
15+
add_executable(routing_gen_proto_builder_pybind11)
16+
target_sources(routing_gen_proto_builder_pybind11 PRIVATE "gen_proto_builder_pybind11.cc")
17+
target_include_directories(routing_gen_proto_builder_pybind11 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
18+
target_compile_features(routing_gen_proto_builder_pybind11 PRIVATE cxx_std_17)
19+
target_link_libraries(routing_gen_proto_builder_pybind11 PRIVATE
20+
absl::flags_commandlineflag
21+
absl::flags_parse
22+
absl::flags_usage
23+
absl::die_if_null
24+
absl::str_format
25+
protobuf::libprotobuf
26+
${PROJECT_NAMESPACE}::ortools_proto
27+
${PROJECT_NAMESPACE}::sat_python_wrappers)
28+
29+
include(GNUInstallDirs)
30+
if(APPLE)
31+
set_target_properties(routing_gen_proto_builder_pybind11 PROPERTIES INSTALL_RPATH
32+
"@loader_path/../${CMAKE_INSTALL_LIBDIR};@loader_path")
33+
elseif(UNIX)
34+
cmake_path(RELATIVE_PATH CMAKE_INSTALL_FULL_LIBDIR
35+
BASE_DIRECTORY ${CMAKE_INSTALL_FULL_BINDIR}
36+
OUTPUT_VARIABLE libdir_relative_path)
37+
set_target_properties(routing_gen_proto_builder_pybind11 PROPERTIES
38+
INSTALL_RPATH "$ORIGIN/${libdir_relative_path}")
39+
endif()
40+
41+
install(TARGETS routing_gen_proto_builder_pybind11)
42+
43+
add_custom_command(
44+
OUTPUT proto_builder_pybind11.h
45+
COMMAND routing_gen_proto_builder_pybind11 > proto_builder_pybind11.h
46+
COMMENT "Generate C++ proto_builder_pybind11.h"
47+
VERBATIM)
48+
1449
# routing
15-
pybind11_add_module(routing_pybind11 MODULE routing.cc)
50+
pybind11_add_module(routing_pybind11 MODULE routing.cc proto_builder_pybind11.h)
1651
set_target_properties(routing_pybind11 PROPERTIES
1752
LIBRARY_OUTPUT_NAME "routing")
1853

@@ -28,7 +63,7 @@ endif()
2863

2964
target_link_libraries(routing_pybind11 PRIVATE
3065
${PROJECT_NAMESPACE}::ortools
31-
pybind11_native_proto_caster
66+
${PROJECT_NAMESPACE}::ortools_proto
3267
pybind11_abseil::absl_casters
3368
)
3469
add_library(${PROJECT_NAMESPACE}::routing_pybind11 ALIAS routing_pybind11)

ortools/routing/python/dimension_test.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@
1818

1919
from absl.testing import absltest
2020

21-
from ortools.routing import enums_pb2
2221
from ortools.routing.python import routing
2322

24-
FirstSolutionStrategy = enums_pb2.FirstSolutionStrategy
25-
RoutingSearchStatus = enums_pb2.RoutingSearchStatus
23+
FirstSolutionStrategy = routing.FirstSolutionStrategy
24+
RoutingSearchStatus = routing.RoutingSearchStatus
2625

2726

2827
def distance(node_i: int, node_j: int) -> int:
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2010-2025 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+
#include "absl/flags/parse.h"
15+
#include "absl/flags/usage.h"
16+
#include "absl/log/die_if_null.h"
17+
#include "absl/log/initialize.h"
18+
#include "absl/strings/str_format.h"
19+
#include "ortools/constraint_solver/search_stats.pb.h"
20+
#include "ortools/constraint_solver/solver_parameters.pb.h"
21+
#include "ortools/routing/enums.pb.h"
22+
#include "ortools/routing/heuristic_parameters.pb.h"
23+
#include "ortools/routing/ils.pb.h"
24+
#include "ortools/routing/parameters.pb.h"
25+
#include "ortools/sat/python/wrappers.h"
26+
27+
namespace operations_research::routing::python {
28+
29+
void ParseAndGenerate() {
30+
absl::PrintF(
31+
R"(
32+
33+
// This is a generated file, do not edit.
34+
#if defined(IMPORT_PROTO_WRAPPER_CODE)
35+
%s
36+
#endif // defined(IMPORT_PROTO_WRAPPER_CODE)
37+
)",
38+
sat::python::GeneratePybindCode(
39+
{ABSL_DIE_IF_NULL(RoutingModelParameters::descriptor()),
40+
ABSL_DIE_IF_NULL(RoutingSearchParameters::descriptor()),
41+
ABSL_DIE_IF_NULL(FirstSolutionStrategy::descriptor()),
42+
ABSL_DIE_IF_NULL(LocalSearchMetaheuristic::descriptor()),
43+
ABSL_DIE_IF_NULL(RoutingSearchStatus::descriptor()),
44+
ABSL_DIE_IF_NULL(PerturbationStrategy::descriptor()),
45+
ABSL_DIE_IF_NULL(CoolingScheduleStrategy::descriptor()),
46+
ABSL_DIE_IF_NULL(RuinCompositionStrategy::descriptor()),
47+
ABSL_DIE_IF_NULL(LocalCheapestInsertionParameters::descriptor()),
48+
ABSL_DIE_IF_NULL(SavingsParameters::descriptor()),
49+
ABSL_DIE_IF_NULL(GlobalCheapestInsertionParameters::descriptor()),
50+
ABSL_DIE_IF_NULL(SubSolverStatistics::descriptor())}));
51+
}
52+
53+
} // namespace operations_research::routing::python
54+
55+
int main(int argc, char* argv[]) {
56+
// We do not use InitGoogle() to avoid linking with or-tools as this would
57+
// create a circular dependency.
58+
absl::InitializeLog();
59+
absl::SetProgramUsageMessage(argv[0]);
60+
absl::ParseCommandLine(argc, argv);
61+
operations_research::routing::python::ParseAndGenerate();
62+
return 0;
63+
}

ortools/routing/python/routing.cc

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,30 @@
2222

2323
#include "absl/algorithm/container.h"
2424
#include "absl/container/flat_hash_set.h"
25+
#include "google/protobuf/duration.pb.h"
2526
#include "ortools/constraint_solver/constraint_solver.h"
27+
#include "ortools/constraint_solver/search_stats.pb.h"
2628
#include "ortools/constraint_solver/solver_parameters.pb.h"
29+
#include "ortools/port/proto_utils.h" // IWYU: keep
30+
#include "ortools/routing/enums.pb.h"
31+
#include "ortools/routing/heuristic_parameters.pb.h"
32+
#include "ortools/routing/ils.pb.h"
2733
#include "ortools/routing/index_manager.h"
2834
#include "ortools/routing/parameters.h"
2935
#include "ortools/routing/python/doc.h" // IWYU pragma: keep
3036
#include "ortools/routing/python/index_manager_doc.h" // IWYU pragma: keep. NOLINT
3137
#include "ortools/routing/python/parameters_doc.h" // IWYU pragma: keep
38+
#include "ortools/routing/python/proto_builder_pybind11.h"
3239
#include "ortools/routing/types.h"
40+
#include "ortools/sat/sat_parameters.pb.h"
41+
#include "ortools/util/optional_boolean.pb.h"
3342
#include "ortools/util/sorted_interval_list.h"
3443
#include "pybind11/cast.h"
3544
#include "pybind11/functional.h"
3645
#include "pybind11/gil.h"
3746
#include "pybind11/pybind11.h"
3847
#include "pybind11/stl.h"
3948
#include "pybind11_abseil/absl_casters.h"
40-
#include "pybind11_protobuf/native_proto_caster.h"
4149

4250
namespace py = ::pybind11;
4351

@@ -52,12 +60,14 @@ using ::operations_research::routing::RoutingModelParameters;
5260
using ::operations_research::routing::RoutingSearchParameters;
5361

5462
PYBIND11_MODULE(routing, m) {
55-
pybind11_protobuf::ImportNativeProtoCasters();
56-
5763
pybind11::module::import(
5864
"ortools.constraint_solver.python.constraint_solver");
5965
pybind11::module::import("ortools.util.python.sorted_interval_list");
6066

67+
#define IMPORT_PROTO_WRAPPER_CODE
68+
#include "ortools/routing/python/proto_builder_pybind11.h"
69+
#undef IMPORT_PROTO_WRAPPER_CODE
70+
6171
m.def("default_routing_model_parameters", &DefaultRoutingModelParameters,
6272
DOC(operations_research, routing, DefaultRoutingModelParameters));
6373

ortools/routing/python/routing_test.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,11 @@
1919
from absl.testing import absltest
2020

2121
from ortools.constraint_solver.python import constraint_solver
22-
from ortools.routing import enums_pb2
23-
from ortools.routing import parameters_pb2
2422
from ortools.routing.python import routing
2523

26-
FirstSolutionStrategy = enums_pb2.FirstSolutionStrategy
27-
RoutingSearchStatus = enums_pb2.RoutingSearchStatus
28-
RoutingSearchParameters = parameters_pb2.RoutingSearchParameters
24+
FirstSolutionStrategy = routing.FirstSolutionStrategy
25+
RoutingSearchStatus = routing.RoutingSearchStatus
26+
RoutingSearchParameters = routing.RoutingSearchParameters
2927

3028

3129
def distance(node_i: int, node_j: int) -> int:
@@ -356,8 +354,8 @@ def test_disjunction_penalty_tsp(self) -> None:
356354
def test_routing_model_parameters(self) -> None:
357355
# Create routing model with parameters
358356
parameters = routing.default_routing_model_parameters()
359-
parameters.solver_parameters.CopyFrom(
360-
constraint_solver.Solver.default_solver_parameters()
357+
parameters.solver_parameters.parse_text_format(
358+
str(constraint_solver.Solver.default_solver_parameters())
361359
)
362360
parameters.solver_parameters.trace_propagation = True
363361
manager = routing.IndexManager(10, 1, 0)

0 commit comments

Comments
 (0)