A general algorithm for resource-constrained shortest path problems in Julia, which returns multiple shortest paths.
The Resource-Constrained Shortest Path Problem (RCSPP) occurs frequently as the subproblem of a column generation approach for routing problems such as VRP, VRPTW, CVRPTW. In those settings, one wishes to find paths with negative reduced cost to add to the restricted master problem. This is traditionally done via shortest-paths algorithms which return a single path obeying the resource constraints, via dynamic programming; however, one can use the same DP algorithm to obtain multiple shortest paths, and add all such paths with negative reduced cost to the master problem.
Given a directed graph
For example, to solve the subproblem for the Capacitated VRP with Time Windows, one might use the following resources:
- A time resource (via
TimeWindowResource) which computes the time vehicles arrive at nodes, taking into account time window constraints, and prevents paths from exceeding the time horizon. - A load resource (via the generic
AdditiveResource) which tracks the load that vehicles carry at nodes, and prevents paths from exceeding capacity. - An elementary service requirement (via the binary vector
ElementaryResource) which tracks the number of times the path has served each customer, and prevents paths from serving customers more than once.
using ResourceConstrainedShortestPaths
# Initialize the problem
prob = RCSPP(
adjlists, # adjacency list representation of G
[
TimeWindowResource(times, ub, start_times, end_times),
AdditiveResource(:load, load, 0, capacity),
ElementaryResource(n_customers + 2, 2:n_customers + 1),
],
1, # source node
n_customers + 2, # destination node
)
# Get all non-dominated shortest paths from source to destination, according to costs
# One can call `shortest_paths` with different values of `costs`
paths = shortest_paths(prob, costs)cspy: This package supports multiple resources, a variety of REFs and the ability to write custom REFs, but only yields one shortest path. In contrast, we yield the set of non-dominated paths from source to destination (this makes our algorithm slower).ConstrainedShortestPaths.jlAlso allows multiple resources and custom REFs. Only applies to acyclic directed graphs, and only returns one path.
To define your own resource, one needs to implement the following interface:
using ResourceConstrainedShortestPaths
struct YourResource <: Resource
name::Symbol # convenience
end
is_monotone(res::YourResource) = false # TODO: this is the default
function get_next_resource_value(
res::YourResource,
current_resource_value::T,
current_node::Int,
next_node::Int,
)
# TODO: check if extension is feasible according to this resource,
# else return current_resource_value, false
# TODO: compute new_resource_value
return new_resource_value, true
end
dominates(k1::T, k2::T) = false # TODO if your resource value type T is not <:Real or a BitVector
- Backwards labelling, bi-directional labelling
- More commonly-encountered resources e.g.
NGRouteResource - A custom implementation of
NonDominatedDict <: SortedDictwhich allows:- indexing by key (raising
KeyErrorif the key is not found) - iteration over key-value pairs
push!(c, key=>val)which efficiently removes all keyskdominated bykey
- indexing by key (raising
Pull requests are welcomed!