pyroutingkit 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/.github/workflows/publish.yml +1 -1
  2. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/CMakeLists.txt +10 -0
  3. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/PKG-INFO +1 -1
  4. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/pyproject.toml +1 -1
  5. pyroutingkit-0.2.0/src/pyroutingkit/__init__.py +31 -0
  6. pyroutingkit-0.2.0/src/routingkit_module.cpp +586 -0
  7. pyroutingkit-0.2.0/tests/test_topology_metric.py +228 -0
  8. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/uv.lock +1 -1
  9. pyroutingkit-0.1.0/src/pyroutingkit/__init__.py +0 -17
  10. pyroutingkit-0.1.0/src/routingkit_module.cpp +0 -263
  11. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/.gitignore +0 -0
  12. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/.gitmodules +0 -0
  13. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/README.md +0 -0
  14. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/tests/test_cch.py +0 -0
  15. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/.gitignore +0 -0
  16. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/.travis.yml +0 -0
  17. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/LICENSE +0 -0
  18. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/Makefile +0 -0
  19. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/README.md +0 -0
  20. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/doc/ContractionHierarchy.md +0 -0
  21. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/doc/CoordinatesToNodeID.md +0 -0
  22. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/doc/CustomizableContractionHierarchy.md +0 -0
  23. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/doc/OpenStreetMap.md +0 -0
  24. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/doc/Setup.md +0 -0
  25. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/doc/SupportFunctions.md +0 -0
  26. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/generate_make_file +0 -0
  27. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/all.h +0 -0
  28. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/bit_vector.h +0 -0
  29. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/constants.h +0 -0
  30. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/contraction_hierarchy.h +0 -0
  31. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/customizable_contraction_hierarchy.h +0 -0
  32. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/dijkstra.h +0 -0
  33. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/filter.h +0 -0
  34. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/geo_dist.h +0 -0
  35. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/geo_position_to_node.h +0 -0
  36. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/google_polyline.h +0 -0
  37. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/graph_util.h +0 -0
  38. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/id_mapper.h +0 -0
  39. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/id_queue.h +0 -0
  40. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/id_set_queue.h +0 -0
  41. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/inverse_vector.h +0 -0
  42. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/min_max.h +0 -0
  43. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/nested_dissection.h +0 -0
  44. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/osm_decoder.h +0 -0
  45. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/osm_graph_builder.h +0 -0
  46. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/osm_profile.h +0 -0
  47. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/osm_simple.h +0 -0
  48. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/permutation.h +0 -0
  49. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/sort.h +0 -0
  50. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/strongly_connected_component.h +0 -0
  51. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/tag_map.h +0 -0
  52. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/timer.h +0 -0
  53. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/timestamp_flag.h +0 -0
  54. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/include/routingkit/vector_io.h +0 -0
  55. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/bit_select.cpp +0 -0
  56. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/bit_select.h +0 -0
  57. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/bit_vector.cpp +0 -0
  58. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/buffered_asynchronous_reader.cpp +0 -0
  59. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/buffered_asynchronous_reader.h +0 -0
  60. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/compare_vector.cpp +0 -0
  61. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/compute_contraction_hierarchy.cpp +0 -0
  62. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/compute_geographic_distance_weights.cpp +0 -0
  63. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/compute_nested_dissection_order.cpp +0 -0
  64. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/contraction_hierarchy.cpp +0 -0
  65. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/convert_road_dimacs_coordinates.cpp +0 -0
  66. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/convert_road_dimacs_graph.cpp +0 -0
  67. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/customizable_contraction_hierarchy.cpp +0 -0
  68. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/decode_vector.cpp +0 -0
  69. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/emulate_gcc_builtin.h +0 -0
  70. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/encode_vector.cpp +0 -0
  71. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/examine_ch.cpp +0 -0
  72. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/expect.cpp +0 -0
  73. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/expect.h +0 -0
  74. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/export_road_dimacs_graph.cpp +0 -0
  75. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/file_data_source.cpp +0 -0
  76. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/file_data_source.h +0 -0
  77. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/generate_constant_vector.cpp +0 -0
  78. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/generate_dijkstra_rank_test_queries.cpp +0 -0
  79. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/generate_random_node_list.cpp +0 -0
  80. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/generate_random_source_times.cpp +0 -0
  81. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/generate_test_queries.cpp +0 -0
  82. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/geo_position_to_node.cpp +0 -0
  83. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/google_polyline.cpp +0 -0
  84. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/graph_to_dot.cpp +0 -0
  85. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/graph_to_svg.cpp +0 -0
  86. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/graph_util.cpp +0 -0
  87. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/id_mapper.cpp +0 -0
  88. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/nested_dissection.cpp +0 -0
  89. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/osm_decoder.cpp +0 -0
  90. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/osm_extract.cpp +0 -0
  91. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/osm_graph_builder.cpp +0 -0
  92. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/osm_profile.cpp +0 -0
  93. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/osm_simple.cpp +0 -0
  94. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/osmpbfformat.proto +0 -0
  95. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/protobuf.cpp +0 -0
  96. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/protobuf.h +0 -0
  97. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/randomly_permute_nodes.cpp +0 -0
  98. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/run_contraction_hierarchy_query.cpp +0 -0
  99. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/run_dijkstra.cpp +0 -0
  100. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/show_path.cpp +0 -0
  101. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/strongly_connected_component.cpp +0 -0
  102. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_basic_features.cpp +0 -0
  103. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_bit_vector.cpp +0 -0
  104. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_buffered_asynchronous_reader.cpp +0 -0
  105. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_contraction_hierarchy_extra_weight.cpp +0 -0
  106. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_contraction_hierarchy_path_query.cpp +0 -0
  107. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_contraction_hierarchy_pinned_query.cpp +0 -0
  108. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_customizable_contraction_hierarchy.cpp +0 -0
  109. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_customizable_contraction_hierarchy_customization.cpp +0 -0
  110. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_customizable_contraction_hierarchy_path_query.cpp +0 -0
  111. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_customizable_contraction_hierarchy_perfect_customization.cpp +0 -0
  112. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_customizable_contraction_hierarchy_pinned_query.cpp +0 -0
  113. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_customizable_contraction_hierarchy_reset.cpp +0 -0
  114. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_dijkstra.cpp +0 -0
  115. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_geo_dist.cpp +0 -0
  116. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_google_polyline.cpp +0 -0
  117. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_id_mapper.cpp +0 -0
  118. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_id_set_queue.cpp +0 -0
  119. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_inverse_vector.cpp +0 -0
  120. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_nearest_neighbor.cpp +0 -0
  121. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_nested_dissection.cpp +0 -0
  122. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_osm_simple.cpp +0 -0
  123. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_permutation.cpp +0 -0
  124. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_protobuf.cpp +0 -0
  125. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_sort.cpp +0 -0
  126. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_strongly_connected_component.cpp +0 -0
  127. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/test_tag_map.cpp +0 -0
  128. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/timer.cpp +0 -0
  129. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/vector_io.cpp +0 -0
  130. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/verify.cpp +0 -0
  131. {pyroutingkit-0.1.0 → pyroutingkit-0.2.0}/vendor/RoutingKit/src/verify.h +0 -0
@@ -24,7 +24,7 @@ jobs:
24
24
  CIBW_BUILD: cp310-* cp311-* cp312-* cp313-*
25
25
  CIBW_SKIP: "*-win32 *-manylinux_i686 *musllinux* pp*"
26
26
  CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
27
- CIBW_TEST_COMMAND: python -c "from pyroutingkit import CCH, INF_WEIGHT; print('OK')"
27
+ CIBW_TEST_COMMAND: python -c "from pyroutingkit import CCH, CCHMetric, CCHTopology, HAS_OPENMP, INF_WEIGHT; print('OK, OpenMP:', HAS_OPENMP)"
28
28
 
29
29
  - uses: actions/upload-artifact@v4
30
30
  with:
@@ -28,6 +28,16 @@ add_library(routingkit_static STATIC ${ROUTINGKIT_SOURCES})
28
28
  target_include_directories(routingkit_static PUBLIC ${ROUTINGKIT_DIR}/include)
29
29
  target_compile_options(routingkit_static PRIVATE -O3)
30
30
 
31
+ # OpenMP enables parallel CCH customization (RoutingKit guards with #ifdef
32
+ # _OPENMP, so building without it just makes customization serial).
33
+ find_package(OpenMP QUIET)
34
+ if(OpenMP_CXX_FOUND)
35
+ target_link_libraries(routingkit_static PUBLIC OpenMP::OpenMP_CXX)
36
+ message(STATUS "OpenMP found: parallel CCH customization enabled")
37
+ else()
38
+ message(STATUS "OpenMP not found: CCH customization will be serial")
39
+ endif()
40
+
31
41
  if(APPLE)
32
42
  target_compile_options(routingkit_static PRIVATE -Wno-deprecated -Wno-unused-parameter)
33
43
  endif()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyroutingkit
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Python bindings for RoutingKit Customizable Contraction Hierarchies (CCH)
5
5
  Author: Ryan Fisk
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyroutingkit"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "Python bindings for RoutingKit Customizable Contraction Hierarchies (CCH)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -0,0 +1,31 @@
1
+ """
2
+ pyroutingkit: Python bindings for RoutingKit Customizable Contraction Hierarchies.
3
+
4
+ Provides microsecond-scale shortest-path queries on large road networks,
5
+ with one shared topology serving many customized metrics.
6
+
7
+ Usage:
8
+ from pyroutingkit import CCHTopology, CCHMetric, INF_WEIGHT
9
+
10
+ topo = CCHTopology()
11
+ topo.build(tail, head, latitude, longitude, node_count) # or topo.load(dir)
12
+
13
+ metric = CCHMetric(topo)
14
+ metric.customize(weights_uint32) # full customization
15
+ metric.update_weights(arc_ids, new_weights) # partial (live edits)
16
+
17
+ distance, node_path, arc_path = metric.query(source, target)
18
+ matrix = metric.distances_many_to_many(sources, targets)
19
+
20
+ The legacy single-metric `CCH` class is kept for backward compatibility.
21
+ """
22
+
23
+ from pyroutingkit._core import (
24
+ CCH,
25
+ CCHMetric,
26
+ CCHTopology,
27
+ HAS_OPENMP,
28
+ INF_WEIGHT,
29
+ )
30
+
31
+ __all__ = ["CCH", "CCHMetric", "CCHTopology", "HAS_OPENMP", "INF_WEIGHT"]
@@ -0,0 +1,586 @@
1
+ /**
2
+ * pybind11 wrapper for RoutingKit's Customizable Contraction Hierarchy (CCH).
3
+ *
4
+ * Two-level API:
5
+ *
6
+ * CCHTopology — metric-independent preprocessing (nested dissection order +
7
+ * chordal supergraph). Built once per graph topology, save/load to disk.
8
+ *
9
+ * CCHMetric — a customized weight set over a shared CCHTopology. Many
10
+ * metrics (e.g. road-only/time, all-modes/cost, user-edited scratch)
11
+ * can share one topology with no duplication. Supports full
12
+ * (re-)customization, partial customization for small weight changes,
13
+ * point-to-point queries with node+arc paths, and many-to-many
14
+ * distance matrices.
15
+ *
16
+ * CCH — legacy single-metric wrapper kept for backward compatibility.
17
+ */
18
+
19
+ #include <pybind11/pybind11.h>
20
+ #include <pybind11/numpy.h>
21
+ #include <pybind11/stl.h>
22
+
23
+ #include <routingkit/customizable_contraction_hierarchy.h>
24
+ #include <routingkit/nested_dissection.h>
25
+ #include <routingkit/constants.h>
26
+ #include <routingkit/vector_io.h>
27
+
28
+ #include <vector>
29
+ #include <string>
30
+ #include <stdexcept>
31
+ #include <fstream>
32
+ #include <cstdint>
33
+ #include <cstring>
34
+
35
+ namespace py = pybind11;
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // CCHTopology — shared, metric-independent
39
+ // ---------------------------------------------------------------------------
40
+
41
+ class CCHTopologyWrapper {
42
+ public:
43
+ CCHTopologyWrapper() : node_count_(0), built_(false) {}
44
+
45
+ /**
46
+ * Build CCH topology from graph arrays.
47
+ *
48
+ * Uses compute_nested_node_dissection_order_using_inertial_flow for the
49
+ * elimination order (requires node coordinates). Releases the GIL —
50
+ * this is the expensive phase.
51
+ */
52
+ void build(
53
+ py::array_t<uint32_t> tail_arr,
54
+ py::array_t<uint32_t> head_arr,
55
+ py::array_t<float> latitude_arr,
56
+ py::array_t<float> longitude_arr,
57
+ uint32_t node_count
58
+ ) {
59
+ auto tail_buf = tail_arr.request();
60
+ auto head_buf = head_arr.request();
61
+ auto lat_buf = latitude_arr.request();
62
+ auto lon_buf = longitude_arr.request();
63
+
64
+ if (tail_buf.size != head_buf.size) {
65
+ throw std::invalid_argument("tail and head must have same length");
66
+ }
67
+ if (lat_buf.size != (ssize_t)node_count || lon_buf.size != (ssize_t)node_count) {
68
+ throw std::invalid_argument("latitude/longitude must have length == node_count");
69
+ }
70
+
71
+ node_count_ = node_count;
72
+
73
+ auto* tail_ptr = static_cast<uint32_t*>(tail_buf.ptr);
74
+ auto* head_ptr = static_cast<uint32_t*>(head_buf.ptr);
75
+ auto* lat_ptr = static_cast<float*>(lat_buf.ptr);
76
+ auto* lon_ptr = static_cast<float*>(lon_buf.ptr);
77
+
78
+ tail_.assign(tail_ptr, tail_ptr + tail_buf.size);
79
+ head_.assign(head_ptr, head_ptr + head_buf.size);
80
+ std::vector<float> latitude(lat_ptr, lat_ptr + lat_buf.size);
81
+ std::vector<float> longitude(lon_ptr, lon_ptr + lon_buf.size);
82
+
83
+ {
84
+ py::gil_scoped_release release;
85
+ auto order = RoutingKit::compute_nested_node_dissection_order_using_inertial_flow(
86
+ node_count_, tail_, head_, latitude, longitude
87
+ );
88
+ cch_ = RoutingKit::CustomizableContractionHierarchy(order, tail_, head_);
89
+ }
90
+ built_ = true;
91
+ parallelization_ready_ = false;
92
+ }
93
+
94
+ void save(const std::string& dir_path) const {
95
+ if (!built_) {
96
+ throw std::runtime_error("Must call build or load before save");
97
+ }
98
+ RoutingKit::save_vector(dir_path + "/tail", tail_);
99
+ RoutingKit::save_vector(dir_path + "/head", head_);
100
+ RoutingKit::save_vector(dir_path + "/order", cch_.order);
101
+ RoutingKit::save_vector(dir_path + "/rank", cch_.rank);
102
+ std::vector<uint32_t> nc = {node_count_};
103
+ RoutingKit::save_vector(dir_path + "/node_count", nc);
104
+ }
105
+
106
+ /** Load topology from a directory (skips the expensive nested dissection). */
107
+ void load(const std::string& dir_path) {
108
+ {
109
+ py::gil_scoped_release release;
110
+ tail_ = RoutingKit::load_vector<unsigned>(dir_path + "/tail");
111
+ head_ = RoutingKit::load_vector<unsigned>(dir_path + "/head");
112
+ auto order = RoutingKit::load_vector<unsigned>(dir_path + "/order");
113
+ auto nc = RoutingKit::load_vector<unsigned>(dir_path + "/node_count");
114
+ if (nc.empty()) {
115
+ throw std::runtime_error("Invalid topology: missing node_count");
116
+ }
117
+ node_count_ = nc[0];
118
+ cch_ = RoutingKit::CustomizableContractionHierarchy(order, tail_, head_);
119
+ }
120
+ built_ = true;
121
+ parallelization_ready_ = false;
122
+ }
123
+
124
+ uint32_t get_node_count() const { return node_count_; }
125
+ size_t get_arc_count() const { return tail_.size(); }
126
+ bool is_built() const { return built_; }
127
+
128
+ const RoutingKit::CustomizableContractionHierarchy& cch() const {
129
+ if (!built_) {
130
+ throw std::runtime_error("Topology not built");
131
+ }
132
+ return cch_;
133
+ }
134
+
135
+ /** Lazily constructed, shared across metrics for parallel customization. */
136
+ RoutingKit::CustomizableContractionHierarchyParallelization& parallelization() {
137
+ if (!parallelization_ready_) {
138
+ parallelization_.reset(cch());
139
+ parallelization_ready_ = true;
140
+ }
141
+ return parallelization_;
142
+ }
143
+
144
+ private:
145
+ uint32_t node_count_;
146
+ bool built_;
147
+ bool parallelization_ready_ = false;
148
+
149
+ std::vector<unsigned> tail_;
150
+ std::vector<unsigned> head_;
151
+
152
+ RoutingKit::CustomizableContractionHierarchy cch_;
153
+ RoutingKit::CustomizableContractionHierarchyParallelization parallelization_;
154
+ };
155
+
156
+ // ---------------------------------------------------------------------------
157
+ // CCHMetric — one customized weight set over a shared topology
158
+ // ---------------------------------------------------------------------------
159
+
160
+ class CCHMetricWrapper {
161
+ public:
162
+ explicit CCHMetricWrapper(CCHTopologyWrapper& topology)
163
+ : topology_(&topology), customized_(false) {
164
+ if (!topology.is_built()) {
165
+ throw std::runtime_error("Topology must be built before creating a metric");
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Full (re-)customization with a new weight vector.
171
+ *
172
+ * thread_count: 0 = serial (also the fallback when built without OpenMP),
173
+ * N > 0 = parallel customization with N threads.
174
+ */
175
+ void customize(py::array_t<uint32_t> weights_arr, unsigned thread_count) {
176
+ auto buf = weights_arr.request();
177
+ if (buf.size != (ssize_t)topology_->get_arc_count()) {
178
+ throw std::invalid_argument(
179
+ "weights length (" + std::to_string(buf.size) +
180
+ ") must equal arc count (" + std::to_string(topology_->get_arc_count()) + ")"
181
+ );
182
+ }
183
+ auto* ptr = static_cast<uint32_t*>(buf.ptr);
184
+ weights_.assign(ptr, ptr + buf.size);
185
+
186
+ {
187
+ py::gil_scoped_release release;
188
+ metric_.reset(topology_->cch(), weights_);
189
+ #ifdef _OPENMP
190
+ if (thread_count > 0) {
191
+ topology_->parallelization().customize(metric_, thread_count);
192
+ } else {
193
+ metric_.customize();
194
+ }
195
+ #else
196
+ // Without OpenMP the parallelization path degrades to a SLOWER
197
+ // serial level-sweep — ignore thread_count entirely.
198
+ (void)thread_count;
199
+ metric_.customize();
200
+ #endif
201
+ query_.reset(metric_);
202
+ }
203
+ customized_ = true;
204
+ }
205
+
206
+ /**
207
+ * Partial customization: change a few arc weights without a full
208
+ * re-customize. arc_ids are INPUT arc indices (row order of the arrays
209
+ * passed to CCHTopology.build). Milliseconds even on large graphs —
210
+ * this is the live-edit path (close a road, adjust a speed).
211
+ */
212
+ void update_weights(
213
+ py::array_t<uint32_t> arc_ids_arr,
214
+ py::array_t<uint32_t> new_weights_arr
215
+ ) {
216
+ if (!customized_) {
217
+ throw std::runtime_error("Must call customize before update_weights");
218
+ }
219
+ auto ids_buf = arc_ids_arr.request();
220
+ auto w_buf = new_weights_arr.request();
221
+ if (ids_buf.size != w_buf.size) {
222
+ throw std::invalid_argument("arc_ids and new_weights must have same length");
223
+ }
224
+
225
+ auto* ids = static_cast<uint32_t*>(ids_buf.ptr);
226
+ auto* vals = static_cast<uint32_t*>(w_buf.ptr);
227
+ size_t n = ids_buf.size;
228
+
229
+ for (size_t i = 0; i < n; i++) {
230
+ if (ids[i] >= weights_.size()) {
231
+ throw std::out_of_range(
232
+ "arc id " + std::to_string(ids[i]) + " out of range (" +
233
+ std::to_string(weights_.size()) + " arcs)"
234
+ );
235
+ }
236
+ }
237
+
238
+ {
239
+ py::gil_scoped_release release;
240
+ if (!partial_ready_) {
241
+ partial_.reset(topology_->cch());
242
+ partial_ready_ = true;
243
+ }
244
+ for (size_t i = 0; i < n; i++) {
245
+ weights_[ids[i]] = vals[i];
246
+ partial_.update_arc(ids[i]);
247
+ }
248
+ partial_.customize(metric_);
249
+ query_.reset(metric_);
250
+ }
251
+ }
252
+
253
+ /** Current weight vector (copy). */
254
+ py::array_t<uint32_t> get_weights() const {
255
+ auto arr = py::array_t<uint32_t>((ssize_t)weights_.size());
256
+ auto buf = arr.request();
257
+ std::memcpy(buf.ptr, weights_.data(), weights_.size() * sizeof(uint32_t));
258
+ return arr;
259
+ }
260
+
261
+ /**
262
+ * Point-to-point query.
263
+ * Returns (distance, node_path, arc_path); arc_path holds INPUT arc
264
+ * indices, usable directly to index edge-attribute arrays.
265
+ * Unreachable: (INF_WEIGHT, [], []).
266
+ */
267
+ py::tuple query(uint32_t source, uint32_t target) {
268
+ check_customized();
269
+ query_.reset().add_source(source).add_target(target).run();
270
+ unsigned dist = query_.get_distance();
271
+
272
+ if (dist == RoutingKit::inf_weight) {
273
+ return py::make_tuple(dist, py::list(), py::list());
274
+ }
275
+ auto node_path = query_.get_node_path();
276
+ auto arc_path = query_.get_arc_path();
277
+ return py::make_tuple(dist, py::cast(node_path), py::cast(arc_path));
278
+ }
279
+
280
+ /**
281
+ * Many-to-many distance matrix, shape (len(sources), len(targets)).
282
+ * Unreachable entries hold INF_WEIGHT.
283
+ */
284
+ py::array_t<uint32_t> distances_many_to_many(
285
+ py::array_t<uint32_t> sources_arr,
286
+ py::array_t<uint32_t> targets_arr
287
+ ) {
288
+ check_customized();
289
+ auto src_buf = sources_arr.request();
290
+ auto tgt_buf = targets_arr.request();
291
+ auto* src_ptr = static_cast<uint32_t*>(src_buf.ptr);
292
+ auto* tgt_ptr = static_cast<uint32_t*>(tgt_buf.ptr);
293
+
294
+ size_t n_src = src_buf.size;
295
+ size_t n_tgt = tgt_buf.size;
296
+
297
+ std::vector<uint32_t> targets(tgt_ptr, tgt_ptr + n_tgt);
298
+ std::vector<uint32_t> sources(src_ptr, src_ptr + n_src);
299
+ std::vector<uint32_t> result(n_src * n_tgt);
300
+
301
+ {
302
+ py::gil_scoped_release release;
303
+ query_.reset().pin_targets(targets);
304
+ for (size_t i = 0; i < n_src; i++) {
305
+ query_.reset_source().add_source(sources[i]).run_to_pinned_targets();
306
+ auto dists = query_.get_distances_to_targets();
307
+ std::memcpy(result.data() + i * n_tgt, dists.data(), n_tgt * sizeof(uint32_t));
308
+ }
309
+ // pinned state interferes with later point-to-point queries
310
+ query_.reset(metric_);
311
+ }
312
+
313
+ auto result_arr = py::array_t<uint32_t>({(ssize_t)n_src, (ssize_t)n_tgt});
314
+ auto result_buf = result_arr.request();
315
+ std::memcpy(result_buf.ptr, result.data(), result.size() * sizeof(uint32_t));
316
+ return result_arr;
317
+ }
318
+
319
+ bool is_customized() const { return customized_; }
320
+
321
+ private:
322
+ void check_customized() const {
323
+ if (!customized_) {
324
+ throw std::runtime_error("Must call customize before querying");
325
+ }
326
+ }
327
+
328
+ CCHTopologyWrapper* topology_;
329
+ bool customized_;
330
+ bool partial_ready_ = false;
331
+
332
+ std::vector<unsigned> weights_;
333
+ RoutingKit::CustomizableContractionHierarchyMetric metric_;
334
+ RoutingKit::CustomizableContractionHierarchyQuery query_;
335
+ RoutingKit::CustomizableContractionHierarchyPartialCustomization partial_;
336
+ };
337
+
338
+ // ---------------------------------------------------------------------------
339
+ // CCH — legacy single-metric wrapper (backward compatible)
340
+ // ---------------------------------------------------------------------------
341
+
342
+ class CCHWrapper {
343
+ public:
344
+ CCHWrapper() : node_count_(0), built_(false), customized_(false) {}
345
+
346
+ void build_topology(
347
+ py::array_t<uint32_t> tail_arr,
348
+ py::array_t<uint32_t> head_arr,
349
+ py::array_t<float> latitude_arr,
350
+ py::array_t<float> longitude_arr,
351
+ uint32_t node_count
352
+ ) {
353
+ auto tail_buf = tail_arr.request();
354
+ auto head_buf = head_arr.request();
355
+ auto lat_buf = latitude_arr.request();
356
+ auto lon_buf = longitude_arr.request();
357
+
358
+ if (tail_buf.size != head_buf.size) {
359
+ throw std::invalid_argument("tail and head must have same length");
360
+ }
361
+ if (lat_buf.size != (ssize_t)node_count || lon_buf.size != (ssize_t)node_count) {
362
+ throw std::invalid_argument("latitude/longitude must have length == node_count");
363
+ }
364
+
365
+ node_count_ = node_count;
366
+
367
+ auto* tail_ptr = static_cast<uint32_t*>(tail_buf.ptr);
368
+ auto* head_ptr = static_cast<uint32_t*>(head_buf.ptr);
369
+ auto* lat_ptr = static_cast<float*>(lat_buf.ptr);
370
+ auto* lon_ptr = static_cast<float*>(lon_buf.ptr);
371
+
372
+ tail_.assign(tail_ptr, tail_ptr + tail_buf.size);
373
+ head_.assign(head_ptr, head_ptr + head_buf.size);
374
+ std::vector<float> latitude(lat_ptr, lat_ptr + lat_buf.size);
375
+ std::vector<float> longitude(lon_ptr, lon_ptr + lon_buf.size);
376
+
377
+ auto order = RoutingKit::compute_nested_node_dissection_order_using_inertial_flow(
378
+ node_count, tail_, head_, latitude, longitude
379
+ );
380
+
381
+ cch_ = RoutingKit::CustomizableContractionHierarchy(order, tail_, head_);
382
+ built_ = true;
383
+ customized_ = false;
384
+ }
385
+
386
+ void customize_weights(py::array_t<uint32_t> weights_arr) {
387
+ if (!built_) {
388
+ throw std::runtime_error("Must call build_topology before customize_weights");
389
+ }
390
+
391
+ auto buf = weights_arr.request();
392
+ if (buf.size != (ssize_t)tail_.size()) {
393
+ throw std::invalid_argument(
394
+ "weights length (" + std::to_string(buf.size) +
395
+ ") must equal arc count (" + std::to_string(tail_.size()) + ")"
396
+ );
397
+ }
398
+
399
+ auto* ptr = static_cast<uint32_t*>(buf.ptr);
400
+ weights_.assign(ptr, ptr + buf.size);
401
+
402
+ metric_ = RoutingKit::CustomizableContractionHierarchyMetric(cch_, weights_);
403
+ metric_.customize();
404
+ query_ = RoutingKit::CustomizableContractionHierarchyQuery(metric_);
405
+ customized_ = true;
406
+ }
407
+
408
+ py::tuple query(uint32_t source, uint32_t target) {
409
+ if (!customized_) {
410
+ throw std::runtime_error("Must call customize_weights before query");
411
+ }
412
+
413
+ query_.reset().add_source(source).add_target(target).run();
414
+ unsigned dist = query_.get_distance();
415
+
416
+ if (dist == RoutingKit::inf_weight) {
417
+ return py::make_tuple(dist, py::list());
418
+ }
419
+
420
+ auto path = query_.get_node_path();
421
+ return py::make_tuple(dist, py::cast(path));
422
+ }
423
+
424
+ py::array_t<uint32_t> distances_many_to_many(
425
+ py::array_t<uint32_t> sources_arr,
426
+ py::array_t<uint32_t> targets_arr
427
+ ) {
428
+ if (!customized_) {
429
+ throw std::runtime_error("Must call customize_weights before distances_many_to_many");
430
+ }
431
+
432
+ auto src_buf = sources_arr.request();
433
+ auto tgt_buf = targets_arr.request();
434
+ auto* src_ptr = static_cast<uint32_t*>(src_buf.ptr);
435
+ auto* tgt_ptr = static_cast<uint32_t*>(tgt_buf.ptr);
436
+
437
+ size_t n_src = src_buf.size;
438
+ size_t n_tgt = tgt_buf.size;
439
+
440
+ std::vector<uint32_t> targets(tgt_ptr, tgt_ptr + n_tgt);
441
+
442
+ std::vector<uint32_t> result(n_src * n_tgt);
443
+
444
+ query_.reset().pin_targets(targets);
445
+
446
+ for (size_t i = 0; i < n_src; i++) {
447
+ query_.reset_source().add_source(src_ptr[i]).run_to_pinned_targets();
448
+ auto dists = query_.get_distances_to_targets();
449
+ for (size_t j = 0; j < n_tgt; j++) {
450
+ result[i * n_tgt + j] = dists[j];
451
+ }
452
+ }
453
+
454
+ auto result_arr = py::array_t<uint32_t>({(ssize_t)n_src, (ssize_t)n_tgt});
455
+ auto result_buf = result_arr.request();
456
+ std::memcpy(result_buf.ptr, result.data(), result.size() * sizeof(uint32_t));
457
+ return result_arr;
458
+ }
459
+
460
+ void save_topology(const std::string& dir_path) {
461
+ if (!built_) {
462
+ throw std::runtime_error("Must call build_topology before save_topology");
463
+ }
464
+
465
+ RoutingKit::save_vector(dir_path + "/tail", tail_);
466
+ RoutingKit::save_vector(dir_path + "/head", head_);
467
+ RoutingKit::save_vector(dir_path + "/order", cch_.order);
468
+ RoutingKit::save_vector(dir_path + "/rank", cch_.rank);
469
+
470
+ std::vector<uint32_t> nc = {node_count_};
471
+ RoutingKit::save_vector(dir_path + "/node_count", nc);
472
+ }
473
+
474
+ void load_topology(const std::string& dir_path) {
475
+ tail_ = RoutingKit::load_vector<unsigned>(dir_path + "/tail");
476
+ head_ = RoutingKit::load_vector<unsigned>(dir_path + "/head");
477
+ auto order = RoutingKit::load_vector<unsigned>(dir_path + "/order");
478
+ auto nc = RoutingKit::load_vector<unsigned>(dir_path + "/node_count");
479
+
480
+ if (nc.empty()) {
481
+ throw std::runtime_error("Invalid topology: missing node_count");
482
+ }
483
+ node_count_ = nc[0];
484
+
485
+ cch_ = RoutingKit::CustomizableContractionHierarchy(order, tail_, head_);
486
+ built_ = true;
487
+ customized_ = false;
488
+ }
489
+
490
+ uint32_t get_node_count() const { return node_count_; }
491
+ size_t get_arc_count() const { return tail_.size(); }
492
+ bool is_built() const { return built_; }
493
+ bool is_customized() const { return customized_; }
494
+
495
+ private:
496
+ uint32_t node_count_;
497
+ bool built_;
498
+ bool customized_;
499
+
500
+ std::vector<unsigned> tail_;
501
+ std::vector<unsigned> head_;
502
+ std::vector<unsigned> weights_;
503
+
504
+ RoutingKit::CustomizableContractionHierarchy cch_;
505
+ RoutingKit::CustomizableContractionHierarchyMetric metric_;
506
+ RoutingKit::CustomizableContractionHierarchyQuery query_;
507
+ };
508
+
509
+
510
+ PYBIND11_MODULE(_core, m) {
511
+ m.doc() = "RoutingKit CCH wrapper for Python";
512
+
513
+ py::class_<CCHTopologyWrapper>(m, "CCHTopology")
514
+ .def(py::init<>())
515
+ .def("build", &CCHTopologyWrapper::build,
516
+ py::arg("tail"), py::arg("head"),
517
+ py::arg("latitude"), py::arg("longitude"),
518
+ py::arg("node_count"),
519
+ "Build CCH topology (nested dissection + chordal supergraph). "
520
+ "Expensive; do once per graph topology and save().")
521
+ .def("save", &CCHTopologyWrapper::save, py::arg("dir_path"),
522
+ "Save topology to directory")
523
+ .def("load", &CCHTopologyWrapper::load, py::arg("dir_path"),
524
+ "Load topology from directory (skips nested dissection)")
525
+ .def_property_readonly("node_count", &CCHTopologyWrapper::get_node_count)
526
+ .def_property_readonly("arc_count", &CCHTopologyWrapper::get_arc_count)
527
+ .def_property_readonly("is_built", &CCHTopologyWrapper::is_built);
528
+
529
+ py::class_<CCHMetricWrapper>(m, "CCHMetric")
530
+ .def(py::init<CCHTopologyWrapper&>(), py::arg("topology"),
531
+ py::keep_alive<1, 2>(), // metric keeps its topology alive
532
+ "Create an uncustomized metric over a shared topology")
533
+ .def("customize", &CCHMetricWrapper::customize,
534
+ py::arg("weights"), py::arg("thread_count") = 0,
535
+ "Full (re-)customization with uint32 weights. thread_count>0 "
536
+ "uses parallel customization when built with OpenMP.")
537
+ .def("update_weights", &CCHMetricWrapper::update_weights,
538
+ py::arg("arc_ids"), py::arg("new_weights"),
539
+ "Partial customization: change a few arc weights in "
540
+ "milliseconds. arc_ids are input arc indices.")
541
+ .def("query", &CCHMetricWrapper::query,
542
+ py::arg("source"), py::arg("target"),
543
+ "Point-to-point query. Returns (distance, node_path, arc_path); "
544
+ "arc_path holds input arc indices.")
545
+ .def("distances_many_to_many", &CCHMetricWrapper::distances_many_to_many,
546
+ py::arg("sources"), py::arg("targets"),
547
+ "Many-to-many distance matrix")
548
+ .def_property_readonly("weights", &CCHMetricWrapper::get_weights)
549
+ .def_property_readonly("is_customized", &CCHMetricWrapper::is_customized);
550
+
551
+ py::class_<CCHWrapper>(m, "CCH")
552
+ .def(py::init<>())
553
+ .def("build_topology", &CCHWrapper::build_topology,
554
+ py::arg("tail"), py::arg("head"),
555
+ py::arg("latitude"), py::arg("longitude"),
556
+ py::arg("node_count"),
557
+ "Build CCH topology from graph arrays with inertial flow ordering")
558
+ .def("customize_weights", &CCHWrapper::customize_weights,
559
+ py::arg("weights"),
560
+ "Customize CCH with uint32 weights")
561
+ .def("query", &CCHWrapper::query,
562
+ py::arg("source"), py::arg("target"),
563
+ "Point-to-point query. Returns (distance, node_path)")
564
+ .def("distances_many_to_many", &CCHWrapper::distances_many_to_many,
565
+ py::arg("sources"), py::arg("targets"),
566
+ "Many-to-many distance matrix")
567
+ .def("save_topology", &CCHWrapper::save_topology,
568
+ py::arg("dir_path"),
569
+ "Save CCH topology to directory")
570
+ .def("load_topology", &CCHWrapper::load_topology,
571
+ py::arg("dir_path"),
572
+ "Load CCH topology from directory")
573
+ .def_property_readonly("node_count", &CCHWrapper::get_node_count)
574
+ .def_property_readonly("arc_count", &CCHWrapper::get_arc_count)
575
+ .def_property_readonly("is_built", &CCHWrapper::is_built)
576
+ .def_property_readonly("is_customized", &CCHWrapper::is_customized);
577
+
578
+ // Expose inf_weight constant
579
+ m.attr("INF_WEIGHT") = RoutingKit::inf_weight;
580
+
581
+ #ifdef _OPENMP
582
+ m.attr("HAS_OPENMP") = true;
583
+ #else
584
+ m.attr("HAS_OPENMP") = false;
585
+ #endif
586
+ }