Skip to content

Routing: Exception in transit callback is silently ignored and gives cost 0 (Python) #3224

@PeterSR

Description

@PeterSR

What version of OR-Tools and what language are you using?

Version: 9.1.9490
Language: Python

Which solver are you using (e.g. CP-SAT, Routing Solver, GLOP, BOP, Gurobi)

Routing Solver

What operating system (Linux, Windows, ...) and version?

Linux

What did you do?

Steps to reproduce the behavior:

  1. Download complete program from https://developers.google.com/optimization/routing/vrp#entire_program1. Add raise RuntimeError() somewhere in distance_callback. Example:
    # Create and register a transit callback.
    def distance_callback(from_index, to_index):
        """Returns the distance between the two nodes."""
        # Convert from routing variable Index to distance matrix NodeIndex.
        from_node = manager.IndexToNode(from_index)
        to_node = manager.IndexToNode(to_index)
        raise RuntimeError()  # <----------- Simulate exception in callback
        return data['distance_matrix'][from_node][to_node]
  1. Run program.
  2. Objective value will be 0. Similarly, evaluations to routing.GetArcCostForVehicle will return 0.

What did you expect to see

Early termination of the program with a Python stacktrace. Alternatively a C++ crash or even a segmentation fault. Or simply a warning printed from C++.

What did you see instead?

Solution is found and objective value is 0. Similarly, evaluations to routing.GetArcCostForVehicle returns 0.

Objective: 0
Route for vehicle 0:
 0 -> 0
Distance of the route: 0m

Route for vehicle 1:
 0 -> 0
Distance of the route: 0m

Route for vehicle 2:
 0 -> 0
Distance of the route: 0m

Route for vehicle 3:
 0 ->  16 ->  15 ->  14 ->  13 ->  12 ->  11 ->  10 ->  9 ->  8 ->  7 ->  6 ->  5 ->  4 ->  3 ->  2 ->  1 -> 0
Distance of the route: 0m

Maximum of the route distances: 0m

Note

This potential bug was discovered in a much more subtle setting:
A dummy node had been added and instead of updating the distance matrix with values, the transit cost was simply handled in the callback as follows:

    def distance_callback(from_index, to_index):
        """Returns the distance between the two nodes."""
        from_node = manager.IndexToNode(from_index)
        to_node = manager.IndexToNode(to_index)

        if from_node == dummy_depot_node:
            return 0

        # Naively assumed it would never try to drive back to the dummy_depot_node
        # as the end node was specified to something else.

        return dist_matrix[from_node][to_node]

It worked fine when from_node was the dummy node, but when to_node was the dummy node, it would yield an IndexError on dist_matrix that would be silently ignored.
My solution was of course to do something like

        if to_node == dummy_depot_node:
            return 2**30

But I believe there is still some unintended behavior here.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions