///|
/// Structural statistics used to select a heuristic and explain a benchmark.
pub struct InstanceStatistics {
customer_count : Int
total_demand : Double
average_demand : Double
maximum_demand : Double
minimum_demand : Double
demand_capacity_ratio : Double
fleet_size : Int
average_nearest_neighbor : Double
average_depot_distance : Double
time_windowed_customers : Int
} derive(Debug, ToJson)
///|
pub fn InstanceStatistics::customer_count(self : InstanceStatistics) -> Int {
self.customer_count
}
///|
pub fn InstanceStatistics::total_demand(self : InstanceStatistics) -> Double {
self.total_demand
}
///|
pub fn InstanceStatistics::average_demand(self : InstanceStatistics) -> Double {
self.average_demand
}
///|
pub fn InstanceStatistics::maximum_demand(self : InstanceStatistics) -> Double {
self.maximum_demand
}
///|
pub fn InstanceStatistics::minimum_demand(self : InstanceStatistics) -> Double {
self.minimum_demand
}
///|
pub fn InstanceStatistics::demand_capacity_ratio(
self : InstanceStatistics,
) -> Double {
self.demand_capacity_ratio
}
///|
pub fn InstanceStatistics::fleet_size(self : InstanceStatistics) -> Int {
self.fleet_size
}
///|
pub fn InstanceStatistics::average_nearest_neighbor(
self : InstanceStatistics,
) -> Double {
self.average_nearest_neighbor
}
///|
pub fn InstanceStatistics::average_depot_distance(
self : InstanceStatistics,
) -> Double {
self.average_depot_distance
}
///|
pub fn InstanceStatistics::time_windowed_customers(
self : InstanceStatistics,
) -> Int {
self.time_windowed_customers
}
///|
fn nearest_neighbor_distance(
instance : VrpInstance,
customer_index : Int,
) -> Double {
let best = 1_000_000_000.0
let mut selected = best
for candidate in 0.. InstanceStatistics {
let count = instance.customer_count()
let mut total = 0.0
let mut maximum = 0.0
let mut minimum = 1_000_000_000.0
let mut nearest_total = 0.0
let mut depot_total = 0.0
let mut time_windowed = 0
let depot_index = instance.depot_index()
for index, customer in instance.customers {
total += customer.demand
maximum = Double::max(maximum, customer.demand)
minimum = Double::min(minimum, customer.demand)
nearest_total += nearest_neighbor_distance(instance, index)
depot_total += instance.distance(depot_index, index)
if customer.ready_time > 0.0 || customer.due_time < instance.depot.due_time {
time_windowed += 1
}
}
let average = if count == 0 { 0.0 } else { total / Double::from_int(count) }
let nearest_average = if count <= 1 {
0.0
} else {
nearest_total / Double::from_int(count)
}
let depot_average = if count == 0 {
0.0
} else {
depot_total / Double::from_int(count)
}
let capacity = instance.fleet_capacity()
{
customer_count: count,
total_demand: total,
average_demand: average,
maximum_demand: if count == 0 {
0.0
} else {
maximum
},
minimum_demand: if count == 0 {
0.0
} else {
minimum
},
demand_capacity_ratio: if capacity <= 0.0 {
0.0
} else {
total / capacity
},
fleet_size: instance.vehicle.fleet_size,
average_nearest_neighbor: nearest_average,
average_depot_distance: depot_average,
time_windowed_customers: time_windowed,
}
}
///|
pub fn route_customer_overlap(left : RoutePlan, right : RoutePlan) -> Int {
let mut count = 0
for customer in left.stops {
if right.stops.contains(customer) {
count += 1
}
}
count
}
///|
pub fn solution_duplicate_count(solution : Solution) -> Int {
let assigned = solution.assigned_customers()
let unique = assigned.copy()
unique.sort()
unique.dedup()
assigned.length() - unique.length()
}
///|
pub fn solution_route_count_histogram(solution : Solution) -> Array[Int] {
let histogram : Array[Int] = []
for route in solution.routes {
let bucket = route.stops.length().min(20)
while histogram.length() <= bucket {
histogram.push(0)
}
histogram[bucket] += 1
}
histogram
}
///|
pub fn solution_route_length_percentiles(
solution : Solution,
) -> (Double, Double, Double) {
let values = solution.routes.map(fn(route) { route.metrics.distance })
if values.is_empty() {
(0.0, 0.0, 0.0)
} else {
values.sort()
let low = values[(values.length() - 1) / 4]
let middle = values[(values.length() - 1) / 2]
let high = values[(values.length() - 1) * 3 / 4]
(low, middle, high)
}
}