///|
pub fn VrpInstance::customer_count(self : VrpInstance) -> Int {
self.customers.length()
}
///|
pub fn VrpInstance::depot_index(self : VrpInstance) -> Int {
self.customer_count()
}
///|
pub fn VrpInstance::customer(
self : VrpInstance,
customer_index : Int,
) -> Customer {
self.customers[customer_index]
}
///|
pub fn VrpInstance::label_of(self : VrpInstance, customer_index : Int) -> Int {
self.customer(customer_index).external_id
}
///|
pub fn ProblemVariant::name(self : ProblemVariant) -> String {
match self {
ProblemVariant::CVRP => "CVRP"
ProblemVariant::VRPTW => "VRPTW"
}
}
///|
pub fn VrpInstance::distance(
self : VrpInstance,
from_index : Int,
to_index : Int,
) -> Double {
self.distance_matrix[from_index][to_index]
}
///|
pub fn evaluate_route(
instance : VrpInstance,
stops : Array[Int],
) -> RouteMetrics {
let depot_index = instance.depot_index()
let arrivals : Array[Double] = []
let mut previous = depot_index
let mut load = 0.0
let mut duration = 0.0
let mut distance = 0.0
let mut waiting_time = 0.0
let mut time_violation = 0.0
for stop in stops {
let customer = instance.customer(stop)
let leg = instance.distance(previous, stop)
duration += leg
distance += leg
let arrival = duration
let service_start = if arrival < customer.ready_time {
customer.ready_time
} else {
arrival
}
waiting_time += service_start - arrival
if service_start > customer.due_time {
time_violation += service_start - customer.due_time
}
arrivals.push(service_start)
duration = service_start + customer.service_time
load += customer.demand
previous = stop
}
if !stops.is_empty() {
let back = instance.distance(previous, depot_index)
duration += back
distance += back
}
let capacity_violation = if load > instance.vehicle.capacity {
load - instance.vehicle.capacity
} else {
0.0
}
let duration_violation = if duration > instance.vehicle.max_route_duration {
duration - instance.vehicle.max_route_duration
} else {
0.0
}
{
distance,
duration,
waiting_time,
load,
capacity_violation,
time_violation,
duration_violation,
feasible: capacity_violation == 0.0 &&
time_violation == 0.0 &&
duration_violation == 0.0,
arrivals,
}
}
///|
pub fn build_route(instance : VrpInstance, stops : Array[Int]) -> RoutePlan {
{ stops, metrics: evaluate_route(instance, stops) }
}
///|
fn compute_solution_objective(
total_distance : Double,
total_capacity_violation : Double,
total_time_violation : Double,
total_duration_violation : Double,
unassigned_count : Int,
config : SolveConfig,
) -> Double {
total_distance +
total_capacity_violation * config.capacity_penalty +
total_time_violation * config.time_penalty +
total_duration_violation * config.duration_penalty +
Double::from_int(unassigned_count) * config.unassigned_penalty
}
///|
pub fn build_solution(
instance : VrpInstance,
route_stops : Array[Array[Int]],
unassigned : Array[Int],
config : SolveConfig,
) -> Solution {
let routes : Array[RoutePlan] = []
let mut total_distance = 0.0
let mut total_capacity_violation = 0.0
let mut total_time_violation = 0.0
let mut total_duration_violation = 0.0
let mut feasible = unassigned.is_empty()
for stops in route_stops {
if stops.is_empty() {
continue
}
let route = build_route(instance, stops)
routes.push(route)
total_distance += route.metrics.distance
total_capacity_violation += route.metrics.capacity_violation
total_time_violation += route.metrics.time_violation
total_duration_violation += route.metrics.duration_violation
if !route.metrics.feasible {
feasible = false
}
}
if routes.length() > instance.vehicle.fleet_size {
feasible = false
}
{
routes,
unassigned,
total_distance,
objective: compute_solution_objective(
total_distance,
total_capacity_violation,
total_time_violation,
total_duration_violation,
unassigned.length(),
config,
),
feasible: feasible &&
total_capacity_violation == 0.0 &&
total_time_violation == 0.0 &&
total_duration_violation == 0.0,
total_capacity_violation,
total_time_violation,
total_duration_violation,
}
}
///|
pub fn Solution::copy(self : Solution) -> Solution {
let routes : Array[RoutePlan] = []
for route in self.routes {
routes.push(copy_route_plan(route))
}
{
routes,
unassigned: self.unassigned.copy(),
total_distance: self.total_distance,
objective: self.objective,
feasible: self.feasible,
total_capacity_violation: self.total_capacity_violation,
total_time_violation: self.total_time_violation,
total_duration_violation: self.total_duration_violation,
}
}
///|
fn copy_route_plan(route : RoutePlan) -> RoutePlan {
{
stops: route.stops.copy(),
metrics: {
distance: route.metrics.distance,
duration: route.metrics.duration,
waiting_time: route.metrics.waiting_time,
load: route.metrics.load,
capacity_violation: route.metrics.capacity_violation,
time_violation: route.metrics.time_violation,
duration_violation: route.metrics.duration_violation,
feasible: route.metrics.feasible,
arrivals: route.metrics.arrivals.copy(),
},
}
}
///|
pub fn Solution::route_stops(self : Solution) -> Array[Array[Int]] {
self.routes.map(fn(route) { route.stops.copy() })
}
///|
pub fn Solution::assigned_customers(self : Solution) -> Array[Int] {
let assigned : Array[Int] = []
for route in self.routes {
assigned.append(route.stops)
}
assigned
}
///|
pub fn Solution::customer_labels(
self : Solution,
instance : VrpInstance,
) -> Array[Array[Int]] {
self.routes.map(fn(route) {
route.stops.map(fn(stop) { instance.label_of(stop) })
})
}
///|
pub fn all_customer_indices(instance : VrpInstance) -> Array[Int] {
Array::makei(instance.customer_count(), fn(index) { index })
}
///|
pub fn empty_solution(instance : VrpInstance, config : SolveConfig) -> Solution {
build_solution(instance, [], all_customer_indices(instance), config)
}
///|
pub fn singleton_solution(
instance : VrpInstance,
config : SolveConfig,
) -> Solution {
let routes = all_customer_indices(instance).map(fn(index) { [index] })
build_solution(instance, routes, [], config)
}