Exact replenishment facility placement
RoutingBlocks provides an implementation of the exact replenishment facility placement algorithm described in Schiffer and Walther [SW18]. The algorithm models the problem as a resource-constrained shortest path problem (CSP) on an auxilliary graph, solved for each route in the solution independently. The auxilliary graph comprises a vertex for each customer node in the route, and allows detours to replenishment facilities by inserting copies of each facility between two consecutive customer vertices (See Figure 1). We refer to Schiffer and Walther [SW18] for a comprehensive description of this procedure.
Figure 1: Auxilliary graph for a route with three customers. The dashed lines indicate potential detours to the replenishment facilities.
RoutingBlock’s implementation of this algorithm that takes care of label
mangement, dominance-based path pruning, graph building, and other boilerplate tasks
while providing an interface to abstract problem-specific functionality, i.e., label repre-
sentation, resource extension functions, and dominance (cf. routingblocks.Propagator). The design of this
interface bases on the abstractions introduced in Irnich [Irn08].
Customizing this algorithm to a specific problem instance is analogous to implementing a custom evaluation class.
Specifically, this requires implementing the routingblocks.Propagator interface and providing a custom label class.
The solver (routingblocks.FacilityPlacementOptimizer) can be configured to use a custom propagator by passing it in the constructor.
Warning
We recommend implementing a custom Propagators by extending the native RoutingBlocks library instead of providing a python implementation for code used beyond prototyping. See the NIFTW source code for an example.
The pseudocode listed below outlines how the FacilityPlacementOptimizer accesses the propagator interface:
1def extract_label():
2 # Find the vertex with the cheapest label in the node queue
3 vertex_with_cheapest_label = find_vertex_with_cheapest_label(node_queue)
4 cheapest_label = extract_cheapest_label(vertex_with_cheapest_label)
5 for each label settled at vertex_with_cheapest_label:
6 # Check if the label is dominated
7 if propagator.dominates(settled_label, cheapest_label):
8 break
9 if propagator.order_before(cheapest_label, settled_label):
10 # If the cheapest label is ordered before the settled label, the cheapest label cannot be dominated anymore
11 # as all later settled labels will also be ordered before cheapest label
12 return cheapest_label, vertex_with_cheapest_label
13
14 # Restart the search
15 extract_label()
16
17
18def optimize(route: List[VertexID]) -> List[VertexID]:
19 # Prepare the propagator
20 propagator.prepare(route)
21 # Build the auxiliary graph from the input route, removing any existing stations
22 build_auxiliary_graph(route)
23 # Create the root label and add it to the first bucket
24 root_label = propagator.create_root_label()
25 add_unsetted_label(root_label, depot)
26 # Enqueue the first vertex (depot) in the _node_queue
27 enqueue(depot)
28 while node_queue is not empty:
29 # Extract the next cheapest label and its corresponding origin vertex from all unsettled label
30 label, origin = extract_label()
31 # Check if the extracted label is a final label (feasible solution) using
32 if propagator.is_final_label(label):
33 return propagator.extract_path(label)
34 # Propagate the extracted label to all adjacent vertices in the graph
35 for each vertex adjacent to origin:
36 label_at_adjacent_vertex = propagator.propagate(label, origin, vertex, get_arc(origin, vertex))
37 if label_at_adjacent_vertex is not None:
38 # Add the candidate label to the corresponding bucket in _buckets
39 add_unsetted_label(label_at_adjacent_vertex, vertex)
40 enqueue(vertex)
41 # Place the label in the set of settled labels
42 settle(label, origin, propagator.order_before)
- class routingblocks.FacilityPlacementOptimizer(instance: Instance, propagator: Propagator)
Algorithm that inserts visits to replenishment facilities at optimal locations into a route.
- Parameters:
instance (Instance) – The instance.
propagator (Propagator) – The propagator to use.
- optimize(route_vertex_ids: List[VertexID])
Inserts visits to replenishment facilities at optimal locations into the route represented by the given vertex IDs.
- Parameters:
route_vertex_ids (List[VertexID]) – The vertex IDs of the route.
- Returns:
The vertex IDs of the optimized route.
- Return type:
List[VertexID]
- class routingblocks.Propagator
The Propagator class implements problem-specific ordering, dominance, and propagation functions. It’s design bases on the concepts introduced in Irnich [Irn08]. Note that this class is an interface: it’s not meant to be instantiated or used directly. Please use the concrete implementations of this interface instead.
- cheaper_than(label: Any, other_label: Any)
Checks whether the label is cheaper than the other label, i.e., has lower cost.
- Parameters:
label (Any) – The (potentially) cheaper label.
other_label (Any) – The (potentially) more expensive label.
- Returns:
True if the label is cheaper than the other label, False otherwise.
- Return type:
bool
- create_root_label()
Creates the initial label for the dynamic programming algorithm. Represents the state at the source node, i.e. the depot node. :return: The initial label.
- Return type:
Any
- dominates(label: Any, other_label: Any)
Checks whether the label dominates the other label.
- Parameters:
label (Any) – The (potentially) dominating label.
other_label (Any) – The (potentially) dominated label.
- Returns:
- Return type:
bool
- extract_path(label: Any)
Extracts the path represented by label, converting it to a list of vertex IDs.
- Parameters:
label (Any) – The label that represents the path.
- Returns:
The list of vertex IDs visited on the path.
- Return type:
List[VertexID]
- is_final_label(label: Any)
Checks whether the label is the final label, i.e. whether it represents a depot-depot path. :param label: The label to check. :return: True if the label is final, False otherwise.
- Parameters:
label (Any) –
- Return type:
bool
- order_before(label: Any, other_label: Any)
Whether the label should be ordered before the other label. This is used to determine the order in which labels are stored in the set of settled labels, which is important for dominance checks: the checks considers only labels that would be ordered before the label being checked.
- Parameters:
label (Any) – The (potentially) earlier label.
other_label (Any) – The (potentially) later label.
- Returns:
True if the label should be ordered before the other label, False otherwise.
- Return type:
bool
- prepare(route_vertex_ids: List[VertexID])
Prepare the propagator before running the algorithm on the route represented by the given vertex IDs. :param route_vertex_ids: The vertex IDs of the route.
- Parameters:
route_vertex_ids (List[VertexID]) –
- Return type:
None
- propagate(label: Any, origin_vertex: Vertex, target_vertex: Vertex, arc: Arc)
Propagates the label to the target vertex, using the given arc. This creates a new label that represents the state at the target vertex. Return None if the resulting path would be infeasible.
- Parameters:
- Returns:
The propagated label, or None if the resulting path would be infeasible.
- Return type:
Optional[Any]