///|
/// Aggregated route and assignment metrics suitable for dashboards and reports.
pub struct ConstraintSummary {
route_count : Int
assigned_count : Int
unassigned_count : Int
total_distance : Double
total_duration : Double
total_waiting : Double
total_load : Double
capacity_utilization : Double
maximum_lateness : Double
feasible : Bool
} derive(Debug, ToJson)
///|
pub fn ConstraintSummary::route_count(self : ConstraintSummary) -> Int {
self.route_count
}
///|
pub fn ConstraintSummary::assigned_count(self : ConstraintSummary) -> Int {
self.assigned_count
}
///|
pub fn ConstraintSummary::unassigned_count(self : ConstraintSummary) -> Int {
self.unassigned_count
}
///|
pub fn ConstraintSummary::total_distance(self : ConstraintSummary) -> Double {
self.total_distance
}
///|
pub fn ConstraintSummary::total_duration(self : ConstraintSummary) -> Double {
self.total_duration
}
///|
pub fn ConstraintSummary::total_waiting(self : ConstraintSummary) -> Double {
self.total_waiting
}
///|
pub fn ConstraintSummary::total_load(self : ConstraintSummary) -> Double {
self.total_load
}
///|
pub fn ConstraintSummary::capacity_utilization(
self : ConstraintSummary,
) -> Double {
self.capacity_utilization
}
///|
pub fn ConstraintSummary::maximum_lateness(self : ConstraintSummary) -> Double {
self.maximum_lateness
}
///|
pub fn ConstraintSummary::is_feasible(self : ConstraintSummary) -> Bool {
self.feasible
}
///|
/// A normalized view of one route's resource consumption.
pub struct RouteProfile {
stop_count : Int
load_ratio : Double
distance : Double
duration : Double
waiting : Double
lateness : Double
slack : Double
feasible : Bool
} derive(Debug, ToJson)
///|
pub fn RouteProfile::stop_count(self : RouteProfile) -> Int {
self.stop_count
}
///|
pub fn RouteProfile::load_ratio(self : RouteProfile) -> Double {
self.load_ratio
}
///|
pub fn RouteProfile::distance(self : RouteProfile) -> Double {
self.distance
}
///|
pub fn RouteProfile::duration(self : RouteProfile) -> Double {
self.duration
}
///|
pub fn RouteProfile::waiting(self : RouteProfile) -> Double {
self.waiting
}
///|
pub fn RouteProfile::lateness(self : RouteProfile) -> Double {
self.lateness
}
///|
pub fn RouteProfile::slack(self : RouteProfile) -> Double {
self.slack
}
///|
pub fn RouteProfile::is_feasible(self : RouteProfile) -> Bool {
self.feasible
}
///|
pub fn route_profile(instance : VrpInstance, route : RoutePlan) -> RouteProfile {
let capacity = instance.vehicle.capacity
let load_ratio = if capacity <= 0.0 {
0.0
} else {
route.metrics.load / capacity
}
let lateness = route.metrics.time_violation
let duration_slack = instance.vehicle.max_route_duration -
route.metrics.duration
{
stop_count: route.stops.length(),
load_ratio,
distance: route.metrics.distance,
duration: route.metrics.duration,
waiting: route.metrics.waiting_time,
lateness,
slack: duration_slack.max(0.0),
feasible: route.metrics.feasible,
}
}
///|
pub fn Solution::constraint_summary(
self : Solution,
instance : VrpInstance,
) -> ConstraintSummary {
let mut total_duration = 0.0
let mut total_waiting = 0.0
let mut total_load = 0.0
let mut maximum_lateness = 0.0
for route in self.routes {
total_duration += route.metrics.duration
total_waiting += route.metrics.waiting_time
total_load += route.metrics.load
maximum_lateness = Double::max(
maximum_lateness,
route.metrics.time_violation,
)
}
let fleet_capacity = instance.vehicle.capacity *
Double::from_int(instance.vehicle.fleet_size)
let utilization = if fleet_capacity <= 0.0 {
0.0
} else {
total_load / fleet_capacity
}
{
route_count: self.routes.length(),
assigned_count: self.assigned_customers().length(),
unassigned_count: self.unassigned.length(),
total_distance: self.total_distance,
total_duration,
total_waiting,
total_load,
capacity_utilization: utilization,
maximum_lateness,
feasible: self.feasible,
}
}
///|
pub fn Solution::route_profiles(
self : Solution,
instance : VrpInstance,
) -> Array[RouteProfile] {
self.routes.map(fn(route) { route_profile(instance, route) })
}
///|
pub fn Solution::max_time_violation(self : Solution) -> Double {
self.routes.fold(init=0.0, fn(current, route) {
Double::max(current, route.metrics.time_violation)
})
}
///|
pub fn Solution::max_capacity_violation(self : Solution) -> Double {
self.routes.fold(init=0.0, fn(current, route) {
Double::max(current, route.metrics.capacity_violation)
})
}
///|
pub fn Solution::route_loads(self : Solution) -> Array[Double] {
self.routes.map(fn(route) { route.metrics.load })
}
///|
pub fn Solution::route_distances(self : Solution) -> Array[Double] {
self.routes.map(fn(route) { route.metrics.distance })
}
///|
pub fn Solution::route_durations(self : Solution) -> Array[Double] {
self.routes.map(fn(route) { route.metrics.duration })
}
///|
pub fn Solution::completion_ratio(
self : Solution,
instance : VrpInstance,
) -> Double {
let total = instance.customer_count()
if total == 0 {
1.0
} else {
Double::from_int(self.assigned_customers().length()) /
Double::from_int(total)
}
}
///|
pub fn RoutePlan::last_customer(self : RoutePlan) -> Int? {
self.stops.last()
}
///|
pub fn RoutePlan::first_customer(self : RoutePlan) -> Int? {
self.stops.get(0)
}
///|
pub fn VrpInstance::fleet_capacity(self : VrpInstance) -> Double {
self.vehicle.capacity * Double::from_int(self.vehicle.fleet_size)
}
///|
pub fn VrpInstance::service_horizon(self : VrpInstance) -> Double {
self.depot.due_time - self.depot.ready_time
}
///|
pub fn RouteMetrics::has_violation(self : RouteMetrics) -> Bool {
self.capacity_violation > 0.0 ||
self.time_violation > 0.0 ||
self.duration_violation > 0.0
}