--- title: "Getting started with vrpr" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting started with vrpr} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r} #| include: false knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 4.2, fig.align = "center" ) ``` `vrpr` is a tidyverse-style interface to the [PyVRP](https://github.com/PyVRP/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**. ```{r} #| label: setup library(vrpr) ``` ## A first CVRP 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. ```{r} #| label: cvrp 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 ``` Inspect the result with `cost()`, `routes()` (a tidy long table) and `summary()`: ```{r} #| label: inspect cost(res) head(routes(res)) summary(res) ``` If `{ggplot2}` is installed, `plot()` draws the routes: ```{r} #| label: plot-cvrp #| eval: !expr requireNamespace("ggplot2", quietly = TRUE) plot(res) ``` ## Stopping criteria `vrp_solve()` runs until a stopping criterion fires. Combine time- and iteration-based limits as needed: ```{r} #| label: stops #| eval: false vrp_solve(model, stop = max_runtime(seconds = 10)) # wall-clock budget vrp_solve(model, stop = max_iterations(5000)) # iteration budget vrp_solve(model, stop = no_improvement(1000)) # stop when stuck ``` ## Time windows (VRPTW) 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. ```{r} #| label: vrptw 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")] ``` ## Heterogeneous fleet Call `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. ```{r} #| label: heterogeneous 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) ``` ## Multiple depots (MDVRP) Add several depots and base each vehicle type at one of them with `add_vehicle_type(depot = i)`. The `routes()` output gains a `depot` column. ```{r} #| label: mdvrp 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")] ``` ## Prize-collecting Mark 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. ```{r} #| label: prize 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) ``` ## Reading standard instances `read_vrplib()` and `read_solomon()` read CVRP/VRPTW instances in the standard VRPLIB/TSPLIB and Solomon formats, returning a `vrpr_model` ready to solve. ```{r} #| label: read path <- system.file("extdata", "sample-n6-k2.vrp", package = "vrpr") read_vrplib(path) |> vrp_solve(stop = max_iterations(200), seed = 1, display = FALSE) |> cost() ``` ## Other variants The same data boundary supports more variants: - **Pickup & delivery / backhaul** -- add a `pickup` column to clients; the collected load counts toward capacity along the route. - **Multi-trip** -- `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.