ExVrp.Solution (ExVrp v0.4.2)

Copy Markdown View Source

Represents a solution to a VRP.

A solution consists of routes (one per vehicle used) and provides functions to query costs, distances, feasibility, and detailed schedules.

Accessing Solutions

Solutions are returned as result.best from the solver:

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

solution.routes       #=> [[1, 3], [2]]
solution.distance     #=> 4521
solution.is_feasible  #=> true
solution.num_clients  #=> 3

Querying Routes

Use routes/1 to get ExVrp.Route structs with full query capabilities:

routes = ExVrp.Solution.routes(solution)

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

Or query route properties directly by index:

ExVrp.Solution.route_distance(solution, 0)
ExVrp.Solution.route_delivery(solution, 0)
ExVrp.Solution.route_feasible?(solution, 0)

Schedules

Get detailed timing for each visit in a route:

schedule = ExVrp.Solution.route_schedule(solution, 0)

for visit <- schedule do
  IO.puts("Location #{visit.location}: #{visit.start_service}-#{visit.end_service}")
end

Constraint Violations

Check for specific constraint violations:

ExVrp.Solution.has_time_warp?(solution)
ExVrp.Solution.has_excess_load?(solution)
ExVrp.Solution.has_excess_distance?(solution)
ExVrp.Solution.time_warp(solution)        #=> total time warp
ExVrp.Solution.excess_load(solution)       #=> per-dimension excess

Unassigned Clients

For prize-collecting problems, check which clients were not visited:

ExVrp.Solution.unassigned(solution)  #=> [4, 7]

Summary

Functions

Checks if the solution is complete (visits all required clients).

Returns the total cost of the solution.

Returns the total distance of the solution.

Returns the total distance cost of the solution.

Returns the total duration of the solution.

Returns the total duration cost of the solution.

Returns the total excess distance of the solution (sum across all routes).

Returns the total excess load of the solution per dimension.

Checks if the solution is feasible (satisfies all constraints).

Returns the total fixed vehicle cost of the solution.

Checks if the solution is group feasible (same-vehicle constraints satisfied).

Returns true if the solution has any excess distance.

Returns true if the solution has any excess load.

Returns true if the solution has any time warp.

Returns the number of assigned clients in the solution.

Returns the number of routes in the solution.

Returns the total overtime of the solution (sum across all routes).

Returns the penalised cost of the solution given a cost evaluator.

Returns the total reload cost of the solution.

Returns the route at the given index as a Route struct.

Returns the centroid of a specific route as {x, y}.

Returns the delivery load of a specific route in the solution.

Returns the distance of a specific route in the solution.

Returns the distance cost of a specific route.

Returns the duration of a specific route in the solution.

Returns the duration cost of a specific route.

Returns the end depot of a specific route.

Returns the end time of a specific route.

Returns the excess distance of a specific route.

Returns the excess load of a specific route (per dimension).

Checks if a specific route in the solution is feasible.

Checks if a specific route has excess distance.

Checks if a specific route has excess load.

Checks if a specific route has time warp.

Returns the number of trips in a specific route.

Returns the overtime of a specific route.

Returns the pickup load of a specific route in the solution.

Returns the prizes collected on a specific route.

Returns the reload cost of a specific route.

Returns the schedule of a specific route.

Returns the service duration of a specific route.

Returns the slack time of a specific route.

Returns the start depot of a specific route.

Returns the start time of a specific route.

Returns the time warp of a specific route.

Returns the travel duration of a specific route.

Returns the vehicle type of a specific route.

Returns the visits (client indices) of a specific route.

Returns the wait duration of a specific route.

Returns all routes as Route structs.

Returns the total time warp of the solution (sum across all routes).

Returns a list of unassigned client indices.

Types

t()

@type t() :: %ExVrp.Solution{
  distance: non_neg_integer(),
  duration: non_neg_integer(),
  is_complete: boolean(),
  is_feasible: boolean(),
  num_clients: non_neg_integer(),
  problem_data: reference() | nil,
  routes: [[non_neg_integer()]],
  solution_ref: reference() | nil,
  stats: map() | nil
}

Functions

complete?(solution)

@spec complete?(t()) :: boolean()

Checks if the solution is complete (visits all required clients).

cost(solution)

@spec cost(t()) :: non_neg_integer()

Returns the total cost of the solution.

When called with a cost evaluator, uses that evaluator. When called without, uses the distance as the cost (default unit_distance_cost=1).

cost(solution, cost_evaluator)

@spec cost(t(), reference()) :: non_neg_integer() | :infinity

distance(solution)

@spec distance(t()) :: non_neg_integer()

Returns the total distance of the solution.

distance_cost(sol)

@spec distance_cost(t()) :: non_neg_integer()

Returns the total distance cost of the solution.

duration(solution)

@spec duration(t()) :: non_neg_integer()

Returns the total duration of the solution.

duration_cost(sol)

@spec duration_cost(t()) :: non_neg_integer()

Returns the total duration cost of the solution.

excess_distance(sol)

@spec excess_distance(t()) :: non_neg_integer()

Returns the total excess distance of the solution (sum across all routes).

excess_load(sol)

@spec excess_load(t()) :: [non_neg_integer()]

Returns the total excess load of the solution per dimension.

feasible?(solution)

@spec feasible?(t()) :: boolean()

Checks if the solution is feasible (satisfies all constraints).

fixed_vehicle_cost(solution)

@spec fixed_vehicle_cost(t()) :: non_neg_integer()

Returns the total fixed vehicle cost of the solution.

group_feasible?(solution)

@spec group_feasible?(t()) :: boolean()

Checks if the solution is group feasible (same-vehicle constraints satisfied).

Returns true if all clients in each same-vehicle group that are visited are on the same route.

has_excess_distance?(sol)

@spec has_excess_distance?(t()) :: boolean()

Returns true if the solution has any excess distance.

has_excess_load?(sol)

@spec has_excess_load?(t()) :: boolean()

Returns true if the solution has any excess load.

has_time_warp?(sol)

@spec has_time_warp?(t()) :: boolean()

Returns true if the solution has any time warp.

num_clients(solution)

@spec num_clients(t()) :: non_neg_integer()

Returns the number of assigned clients in the solution.

num_routes(solution)

@spec num_routes(t()) :: non_neg_integer()

Returns the number of routes in the solution.

overtime(sol)

@spec overtime(t()) :: non_neg_integer()

Returns the total overtime of the solution (sum across all routes).

penalised_cost(solution, cost_evaluator)

@spec penalised_cost(t(), reference()) :: non_neg_integer()

Returns the penalised cost of the solution given a cost evaluator.

reload_cost(sol)

@spec reload_cost(t()) :: non_neg_integer()

Returns the total reload cost of the solution.

route(solution, idx)

@spec route(t(), non_neg_integer()) :: ExVrp.Route.t()

Returns the route at the given index as a Route struct.

The returned Route struct has its solution_ref and route_idx populated, enabling methods like Route.distance/1, Route.feasible?/1, etc.

Example

{:ok, result} = Solver.solve(model)
route = Solution.route(result.best, 0)
Route.distance(route)

route_centroid(solution, route_idx)

@spec route_centroid(t(), non_neg_integer()) :: {float(), float()}

Returns the centroid of a specific route as {x, y}.

route_delivery(solution, route_idx)

@spec route_delivery(t(), non_neg_integer()) :: [non_neg_integer()]

Returns the delivery load of a specific route in the solution.

route_distance(solution, route_idx)

@spec route_distance(t(), non_neg_integer()) :: non_neg_integer()

Returns the distance of a specific route in the solution.

route_distance_cost(solution, route_idx)

@spec route_distance_cost(t(), non_neg_integer()) :: non_neg_integer()

Returns the distance cost of a specific route.

route_duration(solution, route_idx)

@spec route_duration(t(), non_neg_integer()) :: non_neg_integer()

Returns the duration of a specific route in the solution.

route_duration_cost(solution, route_idx)

@spec route_duration_cost(t(), non_neg_integer()) :: non_neg_integer()

Returns the duration cost of a specific route.

route_end_depot(solution, route_idx)

@spec route_end_depot(t(), non_neg_integer()) :: non_neg_integer()

Returns the end depot of a specific route.

route_end_time(solution, route_idx)

@spec route_end_time(t(), non_neg_integer()) :: non_neg_integer()

Returns the end time of a specific route.

route_excess_distance(solution, route_idx)

@spec route_excess_distance(t(), non_neg_integer()) :: non_neg_integer()

Returns the excess distance of a specific route.

route_excess_load(solution, route_idx)

@spec route_excess_load(t(), non_neg_integer()) :: [non_neg_integer()]

Returns the excess load of a specific route (per dimension).

route_feasible?(solution, route_idx)

@spec route_feasible?(t(), non_neg_integer()) :: boolean()

Checks if a specific route in the solution is feasible.

route_has_excess_distance?(solution, route_idx)

@spec route_has_excess_distance?(t(), non_neg_integer()) :: boolean()

Checks if a specific route has excess distance.

route_has_excess_load?(solution, route_idx)

@spec route_has_excess_load?(t(), non_neg_integer()) :: boolean()

Checks if a specific route has excess load.

route_has_time_warp?(solution, route_idx)

@spec route_has_time_warp?(t(), non_neg_integer()) :: boolean()

Checks if a specific route has time warp.

route_num_trips(solution, route_idx)

@spec route_num_trips(t(), non_neg_integer()) :: non_neg_integer()

Returns the number of trips in a specific route.

route_overtime(solution, route_idx)

@spec route_overtime(t(), non_neg_integer()) :: non_neg_integer()

Returns the overtime of a specific route.

route_pickup(solution, route_idx)

@spec route_pickup(t(), non_neg_integer()) :: [non_neg_integer()]

Returns the pickup load of a specific route in the solution.

route_prizes(solution, route_idx)

@spec route_prizes(t(), non_neg_integer()) :: non_neg_integer()

Returns the prizes collected on a specific route.

route_reload_cost(solution, route_idx)

@spec route_reload_cost(t(), non_neg_integer()) :: non_neg_integer()

Returns the reload cost of a specific route.

route_schedule(solution, route_idx)

@spec route_schedule(t(), non_neg_integer()) :: [ExVrp.ScheduledVisit.t()]

Returns the schedule of a specific route.

The schedule contains detailed timing information for each visit, including service start/end times, waiting times, and any time warp (late arrival).

Example

schedule = Solution.route_schedule(solution, 0)

Enum.each(schedule, fn visit ->
  IO.puts("Location #{visit.location}: #{visit.start_service}-#{visit.end_service}")
end)

route_service_duration(solution, route_idx)

@spec route_service_duration(t(), non_neg_integer()) :: non_neg_integer()

Returns the service duration of a specific route.

route_slack(solution, route_idx)

@spec route_slack(t(), non_neg_integer()) :: non_neg_integer()

Returns the slack time of a specific route.

route_start_depot(solution, route_idx)

@spec route_start_depot(t(), non_neg_integer()) :: non_neg_integer()

Returns the start depot of a specific route.

route_start_time(solution, route_idx)

@spec route_start_time(t(), non_neg_integer()) :: non_neg_integer()

Returns the start time of a specific route.

route_time_warp(solution, route_idx)

@spec route_time_warp(t(), non_neg_integer()) :: non_neg_integer()

Returns the time warp of a specific route.

route_travel_duration(solution, route_idx)

@spec route_travel_duration(t(), non_neg_integer()) :: non_neg_integer()

Returns the travel duration of a specific route.

route_vehicle_type(solution, route_idx)

@spec route_vehicle_type(t(), non_neg_integer()) :: non_neg_integer()

Returns the vehicle type of a specific route.

route_visits(solution, route_idx)

@spec route_visits(t(), non_neg_integer()) :: [non_neg_integer()]

Returns the visits (client indices) of a specific route.

route_wait_duration(solution, route_idx)

@spec route_wait_duration(t(), non_neg_integer()) :: non_neg_integer()

Returns the wait duration of a specific route.

routes(solution)

@spec routes(t()) :: [ExVrp.Route.t()]

Returns all routes as Route structs.

Each returned Route struct has its solution_ref and route_idx populated, enabling methods like Route.distance/1, Route.feasible?/1, etc.

Example

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

Enum.each(routes, fn route ->
  IO.puts("Route distance: #{Route.distance(route)}")
end)

time_warp(sol)

@spec time_warp(t()) :: non_neg_integer()

Returns the total time warp of the solution (sum across all routes).

unassigned(solution)

@spec unassigned(t()) :: [non_neg_integer()]

Returns a list of unassigned client indices.