ExVrp (ExVrp v0.4.2)

Copy Markdown View Source

Elixir bindings for PyVRP, a state-of-the-art Vehicle Routing Problem (VRP) solver.

ExVrp uses the same C++ core as PyVRP via NIFs, providing high-performance solving for a wide range of VRP variants.

Supported Problem Types

  • Capacitated VRP (CVRP)
  • VRP with Time Windows (VRPTW)
  • VRP with Pickups and Deliveries
  • Multi-depot VRP
  • Heterogeneous fleet VRP
  • Prize-collecting (optional clients)
  • Multi-trip / reload routes
  • Same-vehicle grouping constraints

Quick Start

Build a model, solve it, inspect the result:

model =
  ExVrp.Model.new()
  |> ExVrp.Model.add_depot(x: 0, y: 0)
  |> ExVrp.Model.add_vehicle_type(num_available: 2, capacity: [100])
  |> ExVrp.Model.add_client(x: 10, y: 10, delivery: [20])
  |> ExVrp.Model.add_client(x: 20, y: 0, delivery: [30])
  |> ExVrp.Model.add_client(x: 0, y: 20, delivery: [25])

{:ok, result} = ExVrp.solve(model, max_iterations: 1000, seed: 42)

result.best.routes    #=> [[1, 2], [3]]
result.best.distance  #=> 8944
result.best.is_feasible #=> true

Solver Options

The solver accepts these options:

  • :max_iterations - Maximum ILS iterations (default: 10_000)
  • :max_runtime - Maximum runtime in milliseconds (default: unlimited)
  • :seed - Random seed for reproducibility (default: random)
  • :num_starts - Parallel independent solver starts (default: :auto). :auto uses div(System.schedulers_online(), 2) cores.
  • :stop - Custom ExVrp.StoppingCriteria (overrides max_iterations/max_runtime)
  • :penalty_params - ExVrp.PenaltyManager.Params for penalty tuning
  • :ils_params - ExVrp.IteratedLocalSearch.Params for ILS behavior
  • :on_progress - Callback function receiving progress maps

Parallel Multi-Start

By default, ExVrp runs multiple independent solver instances in parallel (one per two CPU cores) and returns the best result. Each start uses a different seed. Override with num_starts: 1 for single-threaded solving:

{:ok, result} = ExVrp.solve(model, num_starts: 1, seed: 42)

Time Windows

model =
  ExVrp.Model.new()
  |> ExVrp.Model.add_depot(x: 0, y: 0, tw_early: 0, tw_late: 28_800)
  |> ExVrp.Model.add_vehicle_type(
    num_available: 3,
    capacity: [100],
    tw_early: 0,
    tw_late: 28_800
  )
  |> ExVrp.Model.add_client(
    x: 10, y: 10,
    delivery: [20],
    service_duration: 300,
    tw_early: 3600,
    tw_late: 7200
  )

{:ok, result} = ExVrp.solve(model)

Custom Stopping Criteria

alias ExVrp.StoppingCriteria

# Stop after 60 seconds OR 5000 iterations, whichever comes first
stop = StoppingCriteria.any([
  StoppingCriteria.max_runtime(60.0),
  StoppingCriteria.max_iterations(5000)
])

{:ok, result} = ExVrp.solve(model, stop: stop)

# Stop when no improvement for 1000 iterations
stop = StoppingCriteria.no_improvement(1000)
{:ok, result} = ExVrp.solve(model, stop: stop)

Inspecting Routes

alias ExVrp.Solution

{:ok, result} = ExVrp.solve(model)
routes = Solution.routes(result.best)

for route <- routes do
  IO.puts("Vehicle type: #{route.vehicle_type}")
  IO.puts("Visits: #{inspect(route.visits)}")
  IO.puts("Distance: #{ExVrp.Route.distance(route)}")
end

Multi-Trip (Reload) Routes

Vehicles can return to a reload depot mid-route to replenish capacity:

model =
  ExVrp.Model.new()
  |> ExVrp.Model.add_depot(x: 0, y: 0)
  |> ExVrp.Model.add_vehicle_type(
    num_available: 1,
    capacity: [50],
    reload_depots: [0],
    max_reloads: 3
  )
  |> ExVrp.Model.add_client(x: 10, y: 0, delivery: [30])
  |> ExVrp.Model.add_client(x: 20, y: 0, delivery: [30])

Prize-Collecting (Optional Clients)

Clients can be marked as optional with a prize. The solver decides which clients to visit to maximise profit:

model
|> ExVrp.Model.add_client(
  x: 50, y: 50,
  delivery: [10],
  required: false,
  prize: 5000
)

Architecture

Summary

Functions

Solve a VRP model.

Solve a VRP model, raising on error.

Functions

solve(model, opts \\ [])

@spec solve(
  ExVrp.Model.t(),
  keyword()
) :: {:ok, ExVrp.Solution.t()} | {:error, term()}

Solve a VRP model.

Returns {:ok, result} on success, {:error, reason} on failure. The result contains result.best (the best ExVrp.Solution) and metadata like result.num_iterations and result.runtime.

See the module documentation for available options.

Examples

{:ok, result} = ExVrp.solve(model)
result.best.routes
result.best.distance

{:ok, result} = ExVrp.solve(model, max_iterations: 5000, seed: 42)

{:ok, result} = ExVrp.solve(model, max_runtime: 30_000)

solve!(model, opts \\ [])

@spec solve!(
  ExVrp.Model.t(),
  keyword()
) :: ExVrp.Solution.t()

Solve a VRP model, raising on error.

See solve/2 for options.

Examples

result = ExVrp.solve!(model, max_iterations: 1000)
result.best.distance