The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
vrpr is a tidyverse-style interface to the PyVRP vehicle-routing solver.
You build a model by piping together depots, clients and vehicle types,
then call vrp_solve(). The heavy lifting runs in PyVRP’s
C++ core (rewired with cpp11), so there is no Python
dependency.
The capacitated VRP (CVRP) is the base case: clients have a
demand, vehicles a capacity, and we minimise
total distance. The data boundary is a tibble.
set.seed(1)
clients <- tibble::tibble(
x = round(runif(20, -50, 50)),
y = round(runif(20, -50, 50)),
demand = sample(5:15, 20, replace = TRUE)
)
model <- vrp_model() |>
add_depot(x = 0, y = 0) |>
add_clients(clients) |>
add_vehicle_type(num_available = 5, capacity = 50)
res <- vrp_solve(model, stop = max_iterations(500), seed = 1, display = FALSE)
res
#>
#> ── vrpr result ─────────────────────────────────────────────────────────────────
#> • cost 567 - feasible
#> • 5 routes - 20 clients
#> • 500 iterations - 0.11sInspect the result with cost(), routes() (a
tidy long table) and summary():
cost(res)
#> [1] 567
head(routes(res))
#> # A tibble: 6 × 7
#> route_id depot position client vehicle_type start_service wait
#> <int> <int> <int> <int> <int> <dbl> <dbl>
#> 1 1 1 1 11 1 29 0
#> 2 1 1 2 12 1 41 0
#> 3 1 1 3 1 1 75 0
#> 4 1 1 4 19 1 99 0
#> 5 2 1 1 14 1 33 0
#> 6 2 1 2 2 1 35 0
summary(res)
#> # A tibble: 1 × 8
#> cost is_feasible num_routes num_trips num_clients distance iterations runtime
#> <dbl> <lgl> <int> <int> <int> <dbl> <int> <dbl>
#> 1 567 TRUE 5 5 20 567 500 0.107If {ggplot2} is installed, plot() draws the
routes:
vrp_solve() runs until a stopping criterion fires.
Combine time- and iteration-based limits as needed:
Add tw_early, tw_late and
service columns to the clients to turn the model into a VRP
with time windows. The solver respects the windows, and
routes() reports the start_service and
wait time of each visit.
tw_clients <- tibble::tibble(
x = c(10, 20, 30, 40, 50, 60),
y = 0,
demand = 10,
tw_early = c(0, 30, 60, 90, 120, 150),
tw_late = c(50, 80, 110, 140, 170, 200),
service = 10
)
vrptw <- vrp_model() |>
add_depot(0, 0, tw_early = 0, tw_late = 500) |>
add_clients(tw_clients) |>
add_vehicle_type(num_available = 2, capacity = 60, tw_early = 0, tw_late = 500)
res_tw <- vrp_solve(vrptw, stop = max_iterations(500), seed = 1, display = FALSE)
routes(res_tw)[, c("route_id", "client", "start_service", "wait")]
#> # A tibble: 6 × 4
#> route_id client start_service wait
#> <int> <int> <dbl> <dbl>
#> 1 1 1 50 0
#> 2 1 2 70 0
#> 3 1 3 90 0
#> 4 1 4 110 0
#> 5 1 5 130 0
#> 6 1 6 150 0Call add_vehicle_type() several times for a fleet of
different vehicles. Here a cheap type and an expensive one share the
same capacity; the solver prefers the cheaper type and only uses what it
needs.
het <- vrp_model() |>
add_depot(0, 0) |>
add_clients(clients) |>
add_vehicle_type(num_available = 3, capacity = 50, unit_distance_cost = 1) |>
add_vehicle_type(num_available = 3, capacity = 50, unit_distance_cost = 5)
res_het <- vrp_solve(het, stop = max_iterations(500), seed = 1, display = FALSE)
table(routes(res_het)$vehicle_type)
#>
#> 1 2
#> 13 7Add several depots and base each vehicle type at one of them with
add_vehicle_type(depot = i). The routes()
output gains a depot column.
mdvrp <- vrp_model() |>
add_depot(x = -50, y = 0) |>
add_depot(x = 50, y = 0) |>
add_clients(tibble::tibble(
x = c(-55, -45, -50, 55, 45, 50),
y = c(5, -5, 10, 5, -5, 8),
demand = 10
)) |>
add_vehicle_type(num_available = 3, capacity = 50, depot = 1) |>
add_vehicle_type(num_available = 3, capacity = 50, depot = 2)
res_md <- vrp_solve(mdvrp, stop = max_iterations(500), seed = 1, display = FALSE)
routes(res_md)[, c("route_id", "depot", "client")]
#> # A tibble: 6 × 3
#> route_id depot client
#> <int> <int> <int>
#> 1 1 1 1
#> 2 1 1 3
#> 3 1 1 2
#> 4 2 2 4
#> 5 2 2 6
#> 6 2 2 5Mark clients as optional with required = FALSE and give
them a prize. The solver visits an optional client only
when the prize offsets the routing cost;
unvisited_clients() lists those left out.
add_client_group() defines mutually exclusive
alternatives.
pc <- vrp_model() |>
add_depot(0, 0) |>
add_clients(tibble::tibble(
x = c(5, -5, 0, 100, 100),
y = c(5, -5, 8, 10, -10),
demand = 10,
required = c(TRUE, TRUE, TRUE, FALSE, FALSE),
prize = c(0, 0, 0, 5, 500)
)) |>
add_vehicle_type(num_available = 4, capacity = 50)
res_pc <- vrp_solve(pc, stop = max_iterations(500), seed = 1, display = FALSE)
unvisited_clients(res_pc)
#> [1] 4read_vrplib() and read_solomon() read
CVRP/VRPTW instances in the standard VRPLIB/TSPLIB and Solomon formats,
returning a vrpr_model ready to solve.
The same data boundary supports more variants:
pickup column to clients; the collected load counts toward
capacity along the route.add_vehicle_type(reload_depots = i, max_reloads = k) lets a
vehicle return to a depot to reload and run several trips.See ?add_vehicle_type and ?add_clients for
the full set of options.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.