use crate::error::{OptimizationError, Result};
use crate::types::{Edge, EulerianCircuit, Node};
use std::collections::HashMap;
pub struct RouteOptimizer {
nodes: Vec<Node>,
edges: Vec<Edge>,
node_map: HashMap<i64, usize>,
}
impl RouteOptimizer {
pub fn new(nodes: Vec<Node>, edges: Vec<Edge>) -> Self {
let node_map: HashMap<i64, usize> = nodes
.iter()
.enumerate()
.map(|(idx, node)| (node.id, idx))
.collect();
Self {
nodes,
edges,
node_map,
}
}
pub fn optimize_eulerian(
&self,
start_node: i64,
_enforce_right_side: bool,
) -> Result<EulerianCircuit> {
if self.nodes.is_empty() {
return Err(OptimizationError::EmptyGraph);
}
if self.edges.is_empty() {
return Err(OptimizationError::NoEdges);
}
if !self.node_map.contains_key(&start_node) {
return Err(OptimizationError::StartNodeNotFound(start_node));
}
let circuit_nodes = self.create_simple_circuit(start_node)?;
let total_cost = self.calculate_circuit_cost(&circuit_nodes);
Ok(EulerianCircuit {
nodes: circuit_nodes,
edges: self.edges.clone(),
total_cost,
augmenting_edges: 0,
})
}
pub fn haversine_distance(node1: &Node, node2: &Node) -> f64 {
const EARTH_RADIUS: f64 = 6371000.0;
let lat1 = node1.lat.to_radians();
let lat2 = node2.lat.to_radians();
let delta_lat = (node2.lat - node1.lat).to_radians();
let delta_lon = (node2.lon - node1.lon).to_radians();
let a = (delta_lat / 2.0).sin().powi(2)
+ lat1.cos() * lat2.cos() * (delta_lon / 2.0).sin().powi(2);
let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
EARTH_RADIUS * c
}
fn create_simple_circuit(&self, start_node: i64) -> Result<Vec<i64>> {
let mut circuit = vec![start_node];
for node in &self.nodes {
if node.id != start_node {
circuit.push(node.id);
}
}
circuit.push(start_node);
Ok(circuit)
}
fn calculate_circuit_cost(&self, circuit: &[i64]) -> f64 {
let mut total_cost = 0.0;
for window in circuit.windows(2) {
if let (Some(&from), Some(&to)) = (window.get(0), window.get(1)) {
if let Some(edge) = self.edges.iter().find(|e| e.from == from && e.to == to) {
total_cost += edge.cost;
} else if let (Some(&from_idx), Some(&to_idx)) =
(self.node_map.get(&from), self.node_map.get(&to))
{
let from_node = &self.nodes[from_idx];
let to_node = &self.nodes[to_idx];
total_cost += Self::haversine_distance(from_node, to_node);
}
}
}
total_cost
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::PickupSide;
#[test]
fn test_haversine_distance() {
let node1 = Node::new(1, 40.7128, -74.0060);
let node2 = Node::new(2, 40.7138, -74.0070);
let distance = RouteOptimizer::haversine_distance(&node1, &node2);
assert!(distance > 0.0);
assert!(distance < 200.0); }
#[test]
fn test_simple_triangle() {
let nodes = vec![
Node::new(1, 40.7128, -74.0060),
Node::new(2, 40.7138, -74.0070),
Node::new(3, 40.7148, -74.0080),
];
let edges = vec![
Edge::new(1, 2, 100.0, false, PickupSide::Either),
Edge::new(2, 3, 100.0, false, PickupSide::Either),
Edge::new(3, 1, 100.0, false, PickupSide::Either),
];
let optimizer = RouteOptimizer::new(nodes, edges);
let circuit = optimizer.optimize_eulerian(1, false).unwrap();
assert_eq!(circuit.nodes.first(), circuit.nodes.last());
assert!(circuit.total_cost > 0.0);
}
#[test]
fn test_empty_graph() {
let optimizer = RouteOptimizer::new(vec![], vec![]);
let result = optimizer.optimize_eulerian(1, false);
assert!(result.is_err());
}
#[test]
fn test_start_node_not_found() {
let nodes = vec![Node::new(1, 40.7128, -74.0060)];
let edges = vec![];
let optimizer = RouteOptimizer::new(nodes, edges);
let result = optimizer.optimize_eulerian(999, false);
assert!(result.is_err());
}
}