|
| 1 | +# Migration from SWIG to pybind11 for Routing |
| 2 | + |
| 3 | +This document describes how to migrate your Python code using the Operations |
| 4 | +Research Routing library from the legacy SWIG-based bindings (`pywraprouting`) |
| 5 | +to the new pybind11-based bindings (`routing`). |
| 6 | + |
| 7 | +The new bindings offer better integration with Python, including: |
| 8 | + |
| 9 | + * Compliance with PEP8 naming conventions (snake_case for methods and |
| 10 | + functions). |
| 11 | + * Better type hinting support. |
| 12 | + * More pythonic APIs. |
| 13 | + |
| 14 | +## Imports |
| 15 | + |
| 16 | +### Old (SWIG) |
| 17 | + |
| 18 | +```python |
| 19 | +from google3.util.operations_research.routing import enums_pb2 |
| 20 | +from google3.util.operations_research.routing.python import pywraprouting |
| 21 | +``` |
| 22 | + |
| 23 | +### New (pybind11) |
| 24 | + |
| 25 | +```python |
| 26 | +from google3.util.operations_research.constraint_solver.python import constraint_solver |
| 27 | +from google3.util.operations_research.routing.python import routing |
| 28 | +``` |
| 29 | + |
| 30 | +*Note: The `constraint_solver` import is often needed for types like |
| 31 | +`Assignment` which are shared with the underlying CP solver.* |
| 32 | + |
| 33 | +## Naming Conventions |
| 34 | + |
| 35 | +The most significant change is the shift from CamelCase to snake_case for method |
| 36 | +names and functions. Class names remain CamelCase. |
| 37 | + |
| 38 | +Feature | Legacy (SWIG) | New (pybind11) |
| 39 | +:-------------------- | :-------------------------------------------------- | :------------- |
| 40 | +**Index Manager** | `pywraprouting.IndexManager(...)` | `routing.IndexManager(...)` |
| 41 | +**Routing Model** | `pywraprouting.Model(manager)` | `routing.Model(manager)` |
| 42 | +**Search Parameters** | `pywraprouting.DefaultRoutingSearchParameters()` | `routing.default_routing_search_parameters()` |
| 43 | +**Enums** | `enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC` | `routing.FirstSolutionStrategy.PATH_CHEAPEST_ARC` |
| 44 | + |
| 45 | +### Common Method Mappings |
| 46 | + |
| 47 | +Legacy Method | New Method |
| 48 | +:-------------------------------------- | :--------- |
| 49 | +`IndexToNode(index)` | `index_to_node(index)` |
| 50 | +`NodeToIndex(node)` | `node_to_index(node)` |
| 51 | +`RegisterTransitCallback(callback)` | `register_transit_callback(callback)` |
| 52 | +`SetArcCostEvaluatorOfAllVehicles(idx)` | `set_arc_cost_evaluator_of_all_vehicles(idx)` |
| 53 | +`SolveWithParameters(params)` | `solve_with_parameters(params)` |
| 54 | +`Start(vehicle)` | `start(vehicle)` |
| 55 | +`IsEnd(index)` | `is_end(index)` |
| 56 | +`NextVar(index)` | `next_var(index)` |
| 57 | +`GetDimensionOrDie(name)` | `get_dimension_or_die(name)` |
| 58 | +`AddDimension(...)` | `add_dimension(...)` |
| 59 | +`AddVariableMinimizedByFinalizer(var)` | `add_variable_minimized_by_finalizer(var)` |
| 60 | +`GetNumberOfVehicles()` | `num_vehicles()` |
| 61 | + |
| 62 | +### Assignment / Solution |
| 63 | + |
| 64 | +Legacy Method | New Method |
| 65 | +:-------------------------- | :--------------------------- |
| 66 | +`solution.ObjectiveValue()` | `solution.objective_value()` |
| 67 | +`solution.Value(var)` | `solution.value(var)` |
| 68 | +`solution.Min(var)` | `solution.min(var)` |
| 69 | +`solution.Max(var)` | `solution.max(var)` |
| 70 | + |
| 71 | +## Solver, Variables, and Constraints |
| 72 | + |
| 73 | +The `constraint_solver` module (often imported as `from |
| 74 | +google3.util.operations_research.constraint_solver.python import |
| 75 | +constraint_solver`) also sees significant API changes, particularly in how |
| 76 | +variables are created and constraints are added. |
| 77 | + |
| 78 | +### Variable Creation |
| 79 | + |
| 80 | +In the legacy API, variables were created using methods like `IntVar`, |
| 81 | +`BoolVar`, etc. In the new API, these are prefixed with `new_` and snake_cased. |
| 82 | + |
| 83 | +Legacy Method | New Method |
| 84 | +:------------------------------------- | :--------- |
| 85 | +`solver.IntVar(...)` | `solver.new_int_var(...)` |
| 86 | +`solver.BoolVar(...)` | `solver.new_bool_var(...)` |
| 87 | +`solver.IntervalVar(...)` | `solver.new_interval_var(...)` |
| 88 | +`solver.FixedDurationIntervalVar(...)` | `solver.new_fixed_duration_interval_var(...)` |
| 89 | + |
| 90 | +### Adding Constraints |
| 91 | + |
| 92 | +The legacy API typically involved a two-step process: creating the constraint |
| 93 | +object (often using methods named after the constraint, e.g., |
| 94 | +`solver.AllDifferent(...)`) and then adding it to the solver using |
| 95 | +`solver.Add(...)`. |
| 96 | + |
| 97 | +The new API provides dedicated `add_` methods that create and add the constraint |
| 98 | +in a single step. |
| 99 | + |
| 100 | +Legacy Pattern | New Method |
| 101 | +:---------------------------------------- | :--------------------------------- |
| 102 | +`solver.Add(solver.AllDifferent(...))` | `solver.add_all_different(...)` |
| 103 | +`solver.Add(solver.Cumulative(...))` | `solver.add_cumulative(...)` |
| 104 | +`solver.Add(solver.SumEquality(...))` | `solver.add_sum_equality(...)` |
| 105 | +`solver.Add(solver.MinEquality(...))` | `solver.add_min_equality(...)` |
| 106 | +`solver.Add(solver.MaxEquality(...))` | `solver.add_max_equality(...)` |
| 107 | +`solver.Add(solver.ElementEquality(...))` | `solver.add_element_equality(...)` |
| 108 | +`solver.Add(solver.AbsEquality(...))` | `solver.add_abs_equality(...)` |
| 109 | +`solver.Add(solver.BetweenCt(...))` | `solver.add_between_ct(...)` |
| 110 | +`solver.Add(solver.MemberCt(...))` | `solver.add_member_ct(...)` |
| 111 | +`solver.Add(solver.NotMemberCt(...))` | `solver.add_not_member_ct(...)` |
| 112 | +`solver.Add(solver.Count(...))` | `solver.add_count(...)` |
| 113 | +`solver.Add(solver.Distribute(...))` | `solver.add_distribute(...)` |
| 114 | +`solver.Add(solver.Deviation(...))` | `solver.add_deviation(...)` |
| 115 | +`solver.Add(solver.Circuit(...))` | `solver.add_circuit(...)` |
| 116 | +`solver.Add(solver.SubCircuit(...))` | `solver.add_sub_circuit(...)` |
| 117 | +`solver.Add(solver.Pack(...))` | `solver.add_pack(...)` |
| 118 | + |
| 119 | +**Note:** For general constraints not covered by specific `add_` methods, you |
| 120 | +can still use `solver.add(...)` (snake_case version of `solver.Add(...)`). |
| 121 | + |
| 122 | +## Enums and Status |
| 123 | + |
| 124 | +In the legacy API, enums were typically accessed via `enums_pb2` and treated as |
| 125 | +integers or protobuf enum descriptors. In the new API, they are exposed as |
| 126 | +nested classes within the `routing` module, behaving like standard Python Enums. |
| 127 | + |
| 128 | +### Legacy |
| 129 | + |
| 130 | +```python |
| 131 | +from google3.util.operations_research.routing import enums_pb2 |
| 132 | + |
| 133 | +status = routing.Status() |
| 134 | +print(f"Status: {enums_pb2.RoutingSearchStatus.Value.Name(status)}") |
| 135 | +if status == enums_pb2.RoutingSearchStatus.ROUTING_OPTIMAL: |
| 136 | + pass |
| 137 | +``` |
| 138 | + |
| 139 | +### New |
| 140 | + |
| 141 | +```python |
| 142 | +from google3.util.operations_research.routing.python import routing |
| 143 | + |
| 144 | +status = routing_model.status() |
| 145 | +# You can access the name directly on the enum member |
| 146 | +print(f"Status: {status.name}") |
| 147 | +if status == routing.RoutingSearchStatus.ROUTING_OPTIMAL: |
| 148 | + pass |
| 149 | +``` |
| 150 | + |
| 151 | +## Example: Simple Routing Program |
| 152 | + |
| 153 | +### Legacy Code |
| 154 | + |
| 155 | +```python |
| 156 | +from google3.util.operations_research.routing import enums_pb2 |
| 157 | +from google3.util.operations_research.routing.python import pywraprouting |
| 158 | + |
| 159 | +def main(): |
| 160 | + manager = pywraprouting.IndexManager(num_locations, num_vehicles, depot) |
| 161 | + routing = pywraprouting.Model(manager) |
| 162 | + |
| 163 | + def distance_callback(from_index, to_index): |
| 164 | + from_node = manager.IndexToNode(from_index) |
| 165 | + to_node = manager.IndexToNode(to_index) |
| 166 | + return abs(to_node - from_node) |
| 167 | + |
| 168 | + transit_callback_index = routing.RegisterTransitCallback(distance_callback) |
| 169 | + routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) |
| 170 | + |
| 171 | + search_parameters = pywraprouting.DefaultRoutingSearchParameters() |
| 172 | + search_parameters.first_solution_strategy = ( |
| 173 | + enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC |
| 174 | + ) |
| 175 | + |
| 176 | + assignment = routing.SolveWithParameters(search_parameters) |
| 177 | + print(f"Objective: {assignment.ObjectiveValue()}") |
| 178 | +``` |
| 179 | + |
| 180 | +### New Code |
| 181 | + |
| 182 | +```python |
| 183 | +from google3.util.operations_research.routing.python import routing |
| 184 | + |
| 185 | +def main(): |
| 186 | + manager = routing.IndexManager(num_locations, num_vehicles, depot) |
| 187 | + routing_model = routing.Model(manager) |
| 188 | + |
| 189 | + def distance_callback(from_index, to_index): |
| 190 | + from_node = manager.index_to_node(from_index) |
| 191 | + to_node = manager.index_to_node(to_index) |
| 192 | + return abs(to_node - from_node) |
| 193 | + |
| 194 | + transit_callback_index = routing_model.register_transit_callback(distance_callback) |
| 195 | + routing_model.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index) |
| 196 | + |
| 197 | + search_parameters = routing.default_routing_search_parameters() |
| 198 | + search_parameters.first_solution_strategy = ( |
| 199 | + routing.FirstSolutionStrategy.PATH_CHEAPEST_ARC |
| 200 | + ) |
| 201 | + |
| 202 | + assignment = routing_model.solve_with_parameters(search_parameters) |
| 203 | + print(f"Objective: {assignment.objective_value()}") |
| 204 | +``` |
| 205 | + |
| 206 | +## Dimensions and Variables |
| 207 | + |
| 208 | +When working with dimensions, the variable accessors also change to snake_case. |
| 209 | + |
| 210 | +### Legacy |
| 211 | + |
| 212 | +```python |
| 213 | +time_dimension = routing.GetDimensionOrDie("Time") |
| 214 | +index = routing.Start(0) |
| 215 | +time_var = time_dimension.CumulVar(index) |
| 216 | +time_var.SetRange(0, 10) |
| 217 | +``` |
| 218 | + |
| 219 | +### New |
| 220 | + |
| 221 | +```python |
| 222 | +time_dimension = routing_model.get_dimension_or_die("Time") |
| 223 | +index = routing_model.start(0) |
| 224 | +time_var = time_dimension.cumul_var(index) |
| 225 | +time_var.set_range(0, 10) |
| 226 | +``` |
0 commit comments