Main solver interface for VRP problems.
This module provides the solve/2 function which is a direct port of PyVRP's
solve() function. It sets up the solver components and runs Iterated Local
Search with Late Acceptance Hill-Climbing.
Summary
Functions
Solves a VRP model using Iterated Local Search.
Types
@type solve_opts() :: [ max_iterations: pos_integer(), max_runtime: pos_integer(), stop: ExVrp.StoppingCriteria.t(), seed: non_neg_integer(), num_starts: pos_integer() | :auto, penalty_params: ExVrp.PenaltyManager.Params.t(), ils_params: ExVrp.IteratedLocalSearch.Params.t(), on_progress: (map() -> any()) | nil ]
Functions
@spec solve(ExVrp.Model.t(), solve_opts()) :: {:ok, ExVrp.IteratedLocalSearch.Result.t()} | {:error, term()}
Solves a VRP model using Iterated Local Search.
This is a port of PyVRP's solve() function. It:
- Creates the problem data from the model
- Initializes the PenaltyManager for dynamic penalty adjustment
- Creates an initial solution using local search on empty solution
- Runs Iterated Local Search until stopping criterion is met
Options
:max_iterations- Maximum number of iterations (default: 10_000):max_runtime- Maximum runtime in seconds (default: unlimited). Matches PyVRP.:stop- Custom StoppingCriteria (overrides max_iterations/max_runtime):seed- Random seed for reproducibility (default: random):num_starts- Number of parallel independent solver starts (default::auto). Each start uses a different seed and runs its own ILS chain. The best result across all starts is returned. Use:autoto pick based on available cores (div(schedulers_online, 2)).:penalty_params- PenaltyManager.Params for penalty adjustment:ils_params- IteratedLocalSearch.Params for ILS behavior:on_progress- Optional callback function receiving progress maps during ILS iterations (time-gated at ~1s intervals). Whennum_starts > 1, progress maps include:seed_idxand:seedfields.
Returns
{:ok, result}- Successfully found a solution. Result has:result.best- Best Solution foundresult.cost()- Cost of best solution (infinity if infeasible)result.feasible?()- Whether solution is feasibleresult.num_iterations- Total iterationsresult.runtime- Runtime in milliseconds
{:error, reason}- Failed to solve
Example
model = Model.new()
|> Model.add_depot(x: 0, y: 0)
|> Model.add_vehicle_type(num_available: 2, capacity: [100])
|> Model.add_client(x: 10, y: 0, delivery: [20])
{:ok, result} = Solver.solve(model, max_iterations: 1000)
IO.puts("Best distance: #{result.best.distance}")
# With time limit (seconds, like PyVRP)
{:ok, result} = Solver.solve(model, max_runtime: 60.0)