use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Customer {
pub id: usize,
pub name: String,
pub x: f64,
pub y: f64,
pub window_open: i64,
pub window_close: i64,
pub service_time: i64,
}
impl Customer {
pub fn travel_to(&self, other: &Customer) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
(dx * dx + dy * dy).sqrt()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Depot {
pub x: f64,
pub y: f64,
pub ready_time: i64,
pub due_time: i64,
}
impl Depot {
pub fn travel_to_customer(&self, c: &Customer) -> f64 {
let dx = self.x - c.x;
let dy = self.y - c.y;
(dx * dx + dy * dy).sqrt()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VrptwRequest {
pub id: String,
pub depot: Depot,
pub customers: Vec<Customer>,
#[serde(default = "default_time_limit")]
pub time_limit_seconds: f64,
}
fn default_time_limit() -> f64 {
30.0
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RouteStop {
pub customer_id: usize,
pub customer_name: String,
pub arrival: i64,
pub departure: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VrptwPlan {
pub request_id: String,
pub route: Vec<RouteStop>,
pub customers_total: usize,
pub customers_visited: usize,
pub total_distance: f64,
pub return_time: i64,
pub solver: String,
pub status: String,
pub wall_time_seconds: f64,
}
impl VrptwPlan {
#[allow(clippy::cast_precision_loss)]
pub fn visit_ratio(&self) -> f64 {
if self.customers_total == 0 {
return 0.0;
}
self.customers_visited as f64 / self.customers_total as f64
}
}
#[cfg(test)]
mod tests {
use super::*;
fn cust(x: f64, y: f64) -> Customer {
Customer {
id: 1,
name: "c".into(),
x,
y,
window_open: 0,
window_close: 100,
service_time: 1,
}
}
#[test]
fn customer_travel_is_euclidean() {
let a = cust(0.0, 0.0);
let b = cust(3.0, 4.0);
assert!((a.travel_to(&b) - 5.0).abs() < 1e-9);
}
#[test]
fn depot_travel_to_customer_is_euclidean() {
let d = Depot {
x: 0.0,
y: 0.0,
ready_time: 0,
due_time: 100,
};
assert!((d.travel_to_customer(&cust(0.0, 5.0)) - 5.0).abs() < 1e-9);
}
#[test]
fn visit_ratio_zero_when_no_customers() {
let p = VrptwPlan {
request_id: "r".into(),
route: vec![],
customers_total: 0,
customers_visited: 0,
total_distance: 0.0,
return_time: 0,
solver: "x".into(),
status: "feasible".into(),
wall_time_seconds: 0.0,
};
assert!((p.visit_ratio() - 0.0).abs() < f64::EPSILON);
}
#[test]
fn request_default_time_limit() {
let json =
r#"{"id":"r","depot":{"x":0,"y":0,"ready_time":0,"due_time":100},"customers":[]}"#;
let r: VrptwRequest = serde_json::from_str(json).unwrap();
assert!((r.time_limit_seconds - 30.0).abs() < f64::EPSILON);
}
}