quantrs2_tytan/applications/
logistics.rs

1//! Logistics applications: Route optimization toolkit.
2//!
3//! This module provides quantum optimization tools for logistics applications
4//! including vehicle routing, supply chain optimization, and warehouse management.
5
6// Sampler types available for logistics applications
7#![allow(dead_code)]
8
9use scirs2_core::ndarray::{Array1, Array2};
10use scirs2_core::random::prelude::*;
11use scirs2_core::random::prelude::*;
12use std::collections::{HashMap, HashSet};
13
14/// Vehicle Routing Problem (VRP) optimizer
15pub struct VehicleRoutingOptimizer {
16    /// Distance matrix between locations
17    distance_matrix: Array2<f64>,
18    /// Vehicle capacity
19    vehicle_capacity: f64,
20    /// Demand at each location
21    demands: Array1<f64>,
22    /// Time windows for each location
23    time_windows: Option<Vec<TimeWindow>>,
24    /// Number of vehicles
25    num_vehicles: usize,
26    /// Depot location
27    depot: usize,
28    /// Problem variant
29    variant: VRPVariant,
30}
31
32#[derive(Debug, Clone)]
33pub struct TimeWindow {
34    /// Earliest arrival time
35    start: f64,
36    /// Latest arrival time
37    end: f64,
38    /// Service time at location
39    service_time: f64,
40}
41
42#[derive(Debug, Clone)]
43pub enum VRPVariant {
44    /// Capacitated VRP
45    CVRP,
46    /// VRP with Time Windows
47    VRPTW,
48    /// Multi-Depot VRP
49    MDVRP { depots: Vec<usize> },
50    /// Pickup and Delivery
51    VRPPD {
52        pickups: Vec<usize>,
53        deliveries: Vec<usize>,
54    },
55    /// VRP with Backhauls
56    VRPB { backhaul_customers: Vec<usize> },
57    /// Heterogeneous Fleet VRP
58    HVRP { vehicle_types: Vec<VehicleType> },
59}
60
61#[derive(Debug, Clone)]
62pub struct VehicleType {
63    /// Vehicle capacity
64    capacity: f64,
65    /// Fixed cost
66    fixed_cost: f64,
67    /// Cost per distance
68    distance_cost: f64,
69    /// Maximum distance
70    max_distance: Option<f64>,
71    /// Speed factor
72    speed_factor: f64,
73}
74
75impl VehicleRoutingOptimizer {
76    /// Create new VRP optimizer
77    pub fn new(
78        distance_matrix: Array2<f64>,
79        vehicle_capacity: f64,
80        demands: Array1<f64>,
81        num_vehicles: usize,
82    ) -> Result<Self, String> {
83        if distance_matrix.shape()[0] != distance_matrix.shape()[1] {
84            return Err("Distance matrix must be square".to_string());
85        }
86
87        if distance_matrix.shape()[0] != demands.len() {
88            return Err("Distance matrix and demands size mismatch".to_string());
89        }
90
91        Ok(Self {
92            distance_matrix,
93            vehicle_capacity,
94            demands,
95            time_windows: None,
96            num_vehicles,
97            depot: 0,
98            variant: VRPVariant::CVRP,
99        })
100    }
101
102    /// Set problem variant
103    pub fn with_variant(mut self, variant: VRPVariant) -> Self {
104        self.variant = variant;
105        self
106    }
107
108    /// Set time windows
109    pub fn with_time_windows(mut self, time_windows: Vec<TimeWindow>) -> Self {
110        self.time_windows = Some(time_windows);
111        self.variant = VRPVariant::VRPTW;
112        self
113    }
114
115    /// Build QUBO formulation
116    pub fn build_qubo(&self) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
117        let n_locations = self.distance_matrix.shape()[0];
118        let _n_customers = n_locations - 1; // Excluding depot
119
120        // Variables: x_{v,i,j} = 1 if vehicle v travels from i to j
121        let n_vars = self.num_vehicles * n_locations * n_locations;
122
123        let mut qubo = Array2::zeros((n_vars, n_vars));
124        let mut var_map = HashMap::new();
125
126        // Create variable mapping
127        let mut var_idx = 0;
128        for v in 0..self.num_vehicles {
129            for i in 0..n_locations {
130                for j in 0..n_locations {
131                    let var_name = format!("x_{v}_{i}_{j}");
132                    var_map.insert(var_name, var_idx);
133                    var_idx += 1;
134                }
135            }
136        }
137
138        // Add objective: minimize total distance
139        self.add_distance_objective(&mut qubo, &var_map)?;
140
141        // Add constraints based on variant
142        match &self.variant {
143            VRPVariant::CVRP => {
144                self.add_cvrp_constraints(&mut qubo, &var_map)?;
145            }
146            VRPVariant::VRPTW => {
147                self.add_cvrp_constraints(&mut qubo, &var_map)?;
148                self.add_time_window_constraints(&mut qubo, &var_map)?;
149            }
150            VRPVariant::MDVRP { depots } => {
151                self.add_mdvrp_constraints(&mut qubo, &var_map, depots)?;
152            }
153            _ => {
154                self.add_cvrp_constraints(&mut qubo, &var_map)?;
155            }
156        }
157
158        Ok((qubo, var_map))
159    }
160
161    /// Add distance objective
162    fn add_distance_objective(
163        &self,
164        qubo: &mut Array2<f64>,
165        var_map: &HashMap<String, usize>,
166    ) -> Result<(), String> {
167        for v in 0..self.num_vehicles {
168            for i in 0..self.distance_matrix.shape()[0] {
169                for j in 0..self.distance_matrix.shape()[1] {
170                    if i != j {
171                        let var_name = format!("x_{v}_{i}_{j}");
172                        if let Some(&var_idx) = var_map.get(&var_name) {
173                            qubo[[var_idx, var_idx]] += self.distance_matrix[[i, j]];
174                        }
175                    }
176                }
177            }
178        }
179
180        Ok(())
181    }
182
183    /// Add CVRP constraints
184    fn add_cvrp_constraints(
185        &self,
186        qubo: &mut Array2<f64>,
187        var_map: &HashMap<String, usize>,
188    ) -> Result<(), String> {
189        let penalty = 1000.0;
190        let n_locations = self.distance_matrix.shape()[0];
191
192        // 1. Each customer visited exactly once
193        for j in 1..n_locations {
194            // Skip depot
195            // (sum_{v,i} x_{v,i,j} - 1)^2
196            for v1 in 0..self.num_vehicles {
197                for i1 in 0..n_locations {
198                    if i1 != j {
199                        let var1 = format!("x_{v1}_{i1}_{j}");
200                        if let Some(&idx1) = var_map.get(&var1) {
201                            // Linear term
202                            qubo[[idx1, idx1]] -= 2.0 * penalty;
203
204                            // Quadratic terms
205                            for v2 in 0..self.num_vehicles {
206                                for i2 in 0..n_locations {
207                                    if i2 != j {
208                                        let var2 = format!("x_{v2}_{i2}_{j}");
209                                        if let Some(&idx2) = var_map.get(&var2) {
210                                            qubo[[idx1, idx2]] += penalty;
211                                        }
212                                    }
213                                }
214                            }
215                        }
216                    }
217                }
218            }
219        }
220
221        // 2. Flow conservation
222        for v in 0..self.num_vehicles {
223            for i in 0..n_locations {
224                // sum_j x_{v,i,j} = sum_j x_{v,j,i}
225                for j1 in 0..n_locations {
226                    if j1 != i {
227                        let var_out = format!("x_{v}_{i}_{j1}");
228                        if let Some(&idx_out) = var_map.get(&var_out) {
229                            for j2 in 0..n_locations {
230                                if j2 != i {
231                                    let var_in = format!("x_{v}_{j2}_{i}");
232                                    if let Some(&idx_in) = var_map.get(&var_in) {
233                                        qubo[[idx_out, idx_in]] -= penalty;
234                                    }
235                                }
236                            }
237                        }
238                    }
239                }
240            }
241        }
242
243        // 3. Capacity constraints (simplified)
244        self.add_capacity_constraints(qubo, var_map, penalty)?;
245
246        // 4. Each vehicle starts and ends at depot
247        for v in 0..self.num_vehicles {
248            // Start at depot
249            for j in 1..n_locations {
250                let var = format!("x_{}_{}_{}", v, 0, j);
251                if let Some(&idx) = var_map.get(&var) {
252                    qubo[[idx, idx]] -= penalty * 0.1; // Encourage depot start
253                }
254            }
255        }
256
257        Ok(())
258    }
259
260    /// Add capacity constraints
261    fn add_capacity_constraints(
262        &self,
263        qubo: &mut Array2<f64>,
264        var_map: &HashMap<String, usize>,
265        penalty: f64,
266    ) -> Result<(), String> {
267        // Simplified: penalize routes that exceed capacity
268        let n_locations = self.distance_matrix.shape()[0];
269
270        for v in 0..self.num_vehicles {
271            // Approximate capacity usage
272            let route_demand = 0.0;
273
274            for i in 0..n_locations {
275                for j in 1..n_locations {
276                    // Skip depot
277                    let var = format!("x_{v}_{i}_{j}");
278                    if let Some(&idx) = var_map.get(&var) {
279                        // Penalize if demand exceeds capacity
280                        if route_demand + self.demands[j] > self.vehicle_capacity {
281                            qubo[[idx, idx]] += penalty * 10.0;
282                        }
283                    }
284                }
285            }
286        }
287
288        Ok(())
289    }
290
291    /// Add time window constraints
292    fn add_time_window_constraints(
293        &self,
294        qubo: &mut Array2<f64>,
295        var_map: &HashMap<String, usize>,
296    ) -> Result<(), String> {
297        if let Some(time_windows) = &self.time_windows {
298            let penalty = 1000.0;
299            let n_locations = self.distance_matrix.shape()[0];
300
301            // Simplified: penalize violations of time windows
302            for v in 0..self.num_vehicles {
303                for i in 0..n_locations {
304                    for j in 0..n_locations {
305                        if i != j {
306                            let var = format!("x_{v}_{i}_{j}");
307                            if let Some(&idx) = var_map.get(&var) {
308                                // Travel time
309                                let travel_time = self.distance_matrix[[i, j]];
310
311                                // Check if arrival at j violates time window
312                                if j < time_windows.len() {
313                                    let earliest_arrival = if i < time_windows.len() {
314                                        time_windows[i].start
315                                            + time_windows[i].service_time
316                                            + travel_time
317                                    } else {
318                                        travel_time
319                                    };
320
321                                    if earliest_arrival > time_windows[j].end {
322                                        qubo[[idx, idx]] += penalty * 5.0;
323                                    }
324                                }
325                            }
326                        }
327                    }
328                }
329            }
330        }
331
332        Ok(())
333    }
334
335    /// Add multi-depot constraints
336    fn add_mdvrp_constraints(
337        &self,
338        qubo: &mut Array2<f64>,
339        var_map: &HashMap<String, usize>,
340        depots: &[usize],
341    ) -> Result<(), String> {
342        // Each vehicle assigned to exactly one depot
343        let penalty = 1000.0;
344
345        for v in 0..self.num_vehicles {
346            // Vehicle must start from one of the depots
347            for &depot in depots {
348                for j in 0..self.distance_matrix.shape()[0] {
349                    if !depots.contains(&j) {
350                        let var = format!("x_{v}_{depot}_{j}");
351                        if let Some(&idx) = var_map.get(&var) {
352                            qubo[[idx, idx]] -= penalty * 0.1; // Encourage depot start
353                        }
354                    }
355                }
356            }
357        }
358
359        Ok(())
360    }
361
362    /// Decode solution to routes
363    pub fn decode_solution(&self, solution: &HashMap<String, bool>) -> Vec<Route> {
364        let mut routes = Vec::new();
365        let n_locations = self.distance_matrix.shape()[0];
366
367        for v in 0..self.num_vehicles {
368            let mut route = Route {
369                vehicle_id: v,
370                path: vec![self.depot],
371                total_distance: 0.0,
372                total_demand: 0.0,
373                arrival_times: vec![0.0],
374            };
375
376            let mut current = self.depot;
377            let mut visited = HashSet::new();
378            visited.insert(self.depot);
379
380            // Build route by following edges
381            loop {
382                let mut next_location = None;
383
384                for j in 0..n_locations {
385                    if !visited.contains(&j) {
386                        let var = format!("x_{v}_{current}_{j}");
387                        if *solution.get(&var).unwrap_or(&false) {
388                            next_location = Some(j);
389                            break;
390                        }
391                    }
392                }
393
394                if let Some(next) = next_location {
395                    route.path.push(next);
396                    route.total_distance += self.distance_matrix[[current, next]];
397                    route.total_demand += self.demands[next];
398
399                    let arrival_time = route.arrival_times.last().copied().unwrap_or(0.0)
400                        + self.distance_matrix[[current, next]];
401                    route.arrival_times.push(arrival_time);
402
403                    visited.insert(next);
404                    current = next;
405                } else {
406                    break;
407                }
408            }
409
410            // Return to depot if route is not empty
411            if route.path.len() > 1 {
412                route.path.push(self.depot);
413                route.total_distance += self.distance_matrix[[current, self.depot]];
414                route.arrival_times.push(
415                    route.arrival_times.last().copied().unwrap_or(0.0)
416                        + self.distance_matrix[[current, self.depot]],
417                );
418                routes.push(route);
419            }
420        }
421
422        routes
423    }
424
425    /// Validate solution
426    pub fn validate_solution(&self, routes: &[Route]) -> ValidationResult {
427        let mut violations = Vec::new();
428        let mut visited_customers = HashSet::new();
429
430        // Check each route
431        for route in routes {
432            // Capacity check
433            if route.total_demand > self.vehicle_capacity {
434                violations.push(ConstraintViolation::CapacityExceeded {
435                    vehicle: route.vehicle_id,
436                    demand: route.total_demand,
437                    capacity: self.vehicle_capacity,
438                });
439            }
440
441            // Time window checks
442            if let Some(time_windows) = &self.time_windows {
443                for (i, &loc) in route.path.iter().enumerate() {
444                    if loc < time_windows.len()
445                        && i < route.arrival_times.len()
446                        && route.arrival_times[i] > time_windows[loc].end
447                    {
448                        violations.push(ConstraintViolation::TimeWindowViolation {
449                            location: loc,
450                            arrival: route.arrival_times[i],
451                            window_end: time_windows[loc].end,
452                        });
453                    }
454                }
455            }
456
457            // Track visited customers
458            for &loc in &route.path {
459                if loc != self.depot {
460                    visited_customers.insert(loc);
461                }
462            }
463        }
464
465        // Check all customers visited
466        let n_customers = self.distance_matrix.shape()[0] - 1;
467        if visited_customers.len() < n_customers {
468            violations.push(ConstraintViolation::CustomersNotVisited {
469                missing: n_customers - visited_customers.len(),
470            });
471        }
472
473        ValidationResult {
474            is_valid: violations.is_empty(),
475            violations,
476            total_distance: routes.iter().map(|r| r.total_distance).sum(),
477            num_vehicles_used: routes.len(),
478        }
479    }
480}
481
482#[derive(Debug, Clone)]
483pub struct Route {
484    pub vehicle_id: usize,
485    pub path: Vec<usize>,
486    pub total_distance: f64,
487    pub total_demand: f64,
488    pub arrival_times: Vec<f64>,
489}
490
491#[derive(Debug, Clone)]
492pub enum ConstraintViolation {
493    CapacityExceeded {
494        vehicle: usize,
495        demand: f64,
496        capacity: f64,
497    },
498    TimeWindowViolation {
499        location: usize,
500        arrival: f64,
501        window_end: f64,
502    },
503    CustomersNotVisited {
504        missing: usize,
505    },
506}
507
508#[derive(Debug, Clone)]
509pub struct ValidationResult {
510    pub is_valid: bool,
511    pub violations: Vec<ConstraintViolation>,
512    pub total_distance: f64,
513    pub num_vehicles_used: usize,
514}
515
516/// Simplified optimization problem trait for binary VRP
517pub trait OptimizationProblem {
518    type Solution;
519
520    /// Evaluate the objective function
521    fn evaluate(&self, solution: &Self::Solution) -> f64;
522}
523
524/// Vehicle Routing Problem for optimization
525pub struct VehicleRoutingProblem {
526    pub optimizer: VehicleRoutingOptimizer,
527}
528
529impl VehicleRoutingProblem {
530    pub const fn new(optimizer: VehicleRoutingOptimizer) -> Self {
531        Self { optimizer }
532    }
533
534    /// Evaluate floating point solution
535    pub fn evaluate_continuous(&self, x: &Array1<f64>) -> f64 {
536        // Convert continuous solution to binary decisions
537        let n_locations = self.optimizer.distance_matrix.shape()[0];
538        let n_vars = self.optimizer.num_vehicles * n_locations * n_locations;
539
540        if x.len() != n_vars {
541            return f64::INFINITY; // Invalid solution
542        }
543
544        let mut energy = 0.0;
545
546        // Calculate distance objective
547        let mut var_idx = 0;
548        for _v in 0..self.optimizer.num_vehicles {
549            for i in 0..n_locations {
550                for j in 0..n_locations {
551                    if i != j {
552                        let decision = if x[var_idx] > 0.5 { 1.0 } else { 0.0 };
553                        energy += decision * self.optimizer.distance_matrix[[i, j]];
554                    }
555                    var_idx += 1;
556                }
557            }
558        }
559
560        // Add constraint penalties
561        energy += self.calculate_constraint_penalties(x);
562
563        energy
564    }
565
566    fn calculate_constraint_penalties(&self, x: &Array1<f64>) -> f64 {
567        let penalty = 1000.0;
568        let mut total_penalty = 0.0;
569
570        let n_locations = self.optimizer.distance_matrix.shape()[0];
571
572        // Customer visit constraint: each customer visited exactly once
573        for j in 1..n_locations {
574            // Skip depot
575            let mut visits = 0.0;
576            let mut var_idx = 0;
577
578            for _v in 0..self.optimizer.num_vehicles {
579                for i in 0..n_locations {
580                    if i != j {
581                        let decision = if x[var_idx + i * n_locations + j] > 0.5 {
582                            1.0
583                        } else {
584                            0.0
585                        };
586                        visits += decision;
587                    }
588                }
589                var_idx += n_locations * n_locations;
590            }
591
592            total_penalty += penalty * (visits - 1.0f64).abs();
593        }
594
595        total_penalty
596    }
597}
598
599/// Binary Vehicle Routing Problem wrapper
600pub struct BinaryVehicleRoutingProblem {
601    inner: VehicleRoutingProblem,
602}
603
604impl BinaryVehicleRoutingProblem {
605    pub const fn new(optimizer: VehicleRoutingOptimizer) -> Self {
606        Self {
607            inner: VehicleRoutingProblem::new(optimizer),
608        }
609    }
610
611    /// Get the number of variables needed for binary representation
612    pub fn num_variables(&self) -> usize {
613        let n_locations = self.inner.optimizer.distance_matrix.shape()[0];
614        self.inner.optimizer.num_vehicles * n_locations * n_locations
615    }
616
617    /// Evaluate binary solution directly
618    pub fn evaluate_binary(&self, solution: &[i8]) -> f64 {
619        // Convert i8 binary solution to f64 array for the inner problem
620        let x: Array1<f64> = solution.iter().map(|&b| b as f64).collect();
621        self.inner.evaluate_continuous(&x)
622    }
623
624    /// Create random binary solution
625    pub fn random_solution(&self) -> Vec<i8> {
626        let mut rng = thread_rng();
627        let n_vars = self.num_variables();
628        (0..n_vars)
629            .map(|_| i8::from(rng.gen::<f64>() > 0.8))
630            .collect()
631    }
632
633    /// Convert binary solution to routes
634    pub fn decode_binary_solution(&self, solution: &[i8]) -> Vec<Route> {
635        let mut bool_solution = HashMap::new();
636        let n_locations = self.inner.optimizer.distance_matrix.shape()[0];
637
638        let mut var_idx = 0;
639        for v in 0..self.inner.optimizer.num_vehicles {
640            for i in 0..n_locations {
641                for j in 0..n_locations {
642                    let var_name = format!("x_{v}_{i}_{j}");
643                    bool_solution.insert(var_name, solution[var_idx] == 1);
644                    var_idx += 1;
645                }
646            }
647        }
648
649        self.inner.optimizer.decode_solution(&bool_solution)
650    }
651}
652
653impl OptimizationProblem for BinaryVehicleRoutingProblem {
654    type Solution = Vec<i8>;
655
656    fn evaluate(&self, solution: &Self::Solution) -> f64 {
657        self.evaluate_binary(solution)
658    }
659}
660
661/// Create benchmark problems for testing
662pub fn create_benchmark_problems() -> Vec<BinaryVehicleRoutingProblem> {
663    let mut problems = Vec::new();
664
665    // Small VRP problem
666    let small_distances = Array2::from_shape_vec(
667        (4, 4),
668        vec![
669            0.0, 10.0, 15.0, 20.0, 10.0, 0.0, 25.0, 30.0, 15.0, 25.0, 0.0, 35.0, 20.0, 30.0, 35.0,
670            0.0,
671        ],
672    )
673    .expect("Small benchmark distance matrix has valid shape");
674
675    let small_demands = Array1::from_vec(vec![0.0, 10.0, 15.0, 20.0]);
676
677    let small_optimizer =
678        VehicleRoutingOptimizer::new(small_distances.clone(), 50.0, small_demands.clone(), 2)
679            .expect("Small benchmark VRP has valid configuration");
680
681    problems.push(BinaryVehicleRoutingProblem::new(small_optimizer));
682
683    // Medium VRP problem
684    let medium_distances = Array2::from_shape_vec(
685        (6, 6),
686        vec![
687            0.0, 10.0, 15.0, 20.0, 25.0, 30.0, 10.0, 0.0, 25.0, 30.0, 35.0, 40.0, 15.0, 25.0, 0.0,
688            35.0, 40.0, 45.0, 20.0, 30.0, 35.0, 0.0, 45.0, 50.0, 25.0, 35.0, 40.0, 45.0, 0.0, 55.0,
689            30.0, 40.0, 45.0, 50.0, 55.0, 0.0,
690        ],
691    )
692    .expect("Medium benchmark distance matrix has valid shape");
693
694    let medium_demands = Array1::from_vec(vec![0.0, 12.0, 18.0, 22.0, 16.0, 14.0]);
695
696    let medium_optimizer = VehicleRoutingOptimizer::new(medium_distances, 60.0, medium_demands, 3)
697        .expect("Medium benchmark VRP has valid configuration");
698
699    problems.push(BinaryVehicleRoutingProblem::new(medium_optimizer));
700
701    // Capacitated VRP with time windows
702    let cvrptw_optimizer = VehicleRoutingOptimizer::new(
703        small_distances,
704        40.0, // Tighter capacity
705        small_demands,
706        2,
707    )
708    .expect("CVRPTW benchmark VRP has valid configuration")
709    .with_time_windows(vec![
710        TimeWindow {
711            start: 0.0,
712            end: 100.0,
713            service_time: 5.0,
714        }, // Depot
715        TimeWindow {
716            start: 10.0,
717            end: 50.0,
718            service_time: 10.0,
719        }, // Customer 1
720        TimeWindow {
721            start: 20.0,
722            end: 60.0,
723            service_time: 8.0,
724        }, // Customer 2
725        TimeWindow {
726            start: 30.0,
727            end: 80.0,
728            service_time: 12.0,
729        }, // Customer 3
730    ]);
731
732    problems.push(BinaryVehicleRoutingProblem::new(cvrptw_optimizer));
733
734    problems
735}
736
737/// Traveling Salesman Problem (TSP) optimizer
738pub struct TSPOptimizer {
739    /// Distance matrix
740    distance_matrix: Array2<f64>,
741    /// Problem variant
742    variant: TSPVariant,
743    /// Subtour elimination method
744    subtour_method: SubtourElimination,
745}
746
747#[derive(Debug, Clone)]
748pub enum TSPVariant {
749    /// Standard TSP
750    Standard,
751    /// Asymmetric TSP
752    ATSP,
753    /// TSP with time windows
754    TSPTW { time_windows: Vec<TimeWindow> },
755    /// Multiple TSP
756    MTSP { num_salesmen: usize },
757    /// Prize-collecting TSP
758    PCTSP { prizes: Vec<f64>, min_prize: f64 },
759}
760
761#[derive(Debug, Clone)]
762pub enum SubtourElimination {
763    /// MTZ constraints
764    MillerTuckerZemlin,
765    /// DFJ constraints
766    DantzigFulkersonJohnson,
767    /// Flow-based
768    FlowBased,
769    /// Lazy constraints
770    Lazy,
771}
772
773impl TSPOptimizer {
774    /// Create new TSP optimizer
775    pub fn new(distance_matrix: Array2<f64>) -> Result<Self, String> {
776        if distance_matrix.shape()[0] != distance_matrix.shape()[1] {
777            return Err("Distance matrix must be square".to_string());
778        }
779
780        Ok(Self {
781            distance_matrix,
782            variant: TSPVariant::Standard,
783            subtour_method: SubtourElimination::MillerTuckerZemlin,
784        })
785    }
786
787    /// Set variant
788    pub fn with_variant(mut self, variant: TSPVariant) -> Self {
789        self.variant = variant;
790        self
791    }
792
793    /// Build QUBO formulation
794    pub fn build_qubo(&self) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
795        let n = self.distance_matrix.shape()[0];
796
797        match &self.variant {
798            TSPVariant::Standard => self.build_standard_tsp_qubo(n),
799            TSPVariant::MTSP { num_salesmen } => self.build_mtsp_qubo(n, *num_salesmen),
800            _ => self.build_standard_tsp_qubo(n),
801        }
802    }
803
804    /// Build standard TSP QUBO
805    fn build_standard_tsp_qubo(
806        &self,
807        n: usize,
808    ) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
809        // Variables: x_{i,t} = 1 if city i is visited at time t
810        let n_vars = n * n;
811        let mut qubo = Array2::zeros((n_vars, n_vars));
812        let mut var_map = HashMap::new();
813
814        // Create variable mapping
815        for i in 0..n {
816            for t in 0..n {
817                let var_name = format!("x_{i}_{t}");
818                var_map.insert(var_name, i * n + t);
819            }
820        }
821
822        let penalty = 1000.0;
823
824        // Objective: minimize distance
825        for t in 0..n - 1 {
826            for i in 0..n {
827                for j in 0..n {
828                    if i != j {
829                        let var1 = i * n + t;
830                        let var2 = j * n + (t + 1);
831                        qubo[[var1, var2]] += self.distance_matrix[[i, j]];
832                    }
833                }
834            }
835        }
836
837        // Constraint 1: Each city visited exactly once
838        for i in 0..n {
839            // (sum_t x_{i,t} - 1)^2
840            for t1 in 0..n {
841                let idx1 = i * n + t1;
842                qubo[[idx1, idx1]] -= 2.0 * penalty;
843
844                for t2 in 0..n {
845                    let idx2 = i * n + t2;
846                    qubo[[idx1, idx2]] += penalty;
847                }
848            }
849        }
850
851        // Constraint 2: Each time slot has exactly one city
852        for t in 0..n {
853            // (sum_i x_{i,t} - 1)^2
854            for i1 in 0..n {
855                let idx1 = i1 * n + t;
856                qubo[[idx1, idx1]] -= 2.0 * penalty;
857
858                for i2 in 0..n {
859                    let idx2 = i2 * n + t;
860                    qubo[[idx1, idx2]] += penalty;
861                }
862            }
863        }
864
865        Ok((qubo, var_map))
866    }
867
868    /// Build Multiple TSP QUBO
869    fn build_mtsp_qubo(
870        &self,
871        n: usize,
872        num_salesmen: usize,
873    ) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
874        // Variables: x_{s,i,t} = 1 if salesman s visits city i at time t
875        let n_vars = num_salesmen * n * n;
876        let qubo = Array2::zeros((n_vars, n_vars));
877        let mut var_map = HashMap::new();
878
879        // Create variable mapping
880        for s in 0..num_salesmen {
881            for i in 0..n {
882                for t in 0..n {
883                    let var_name = format!("x_{s}_{i}_{t}");
884                    var_map.insert(var_name, s * n * n + i * n + t);
885                }
886            }
887        }
888
889        // Add objective and constraints
890        // Similar to standard TSP but for each salesman
891
892        Ok((qubo, var_map))
893    }
894
895    /// Decode TSP solution
896    pub fn decode_solution(&self, solution: &HashMap<String, bool>) -> Vec<usize> {
897        let n = self.distance_matrix.shape()[0];
898        let mut tour = vec![0; n];
899
900        for i in 0..n {
901            for t in 0..n {
902                let var_name = format!("x_{i}_{t}");
903                if *solution.get(&var_name).unwrap_or(&false) {
904                    tour[t] = i;
905                }
906            }
907        }
908
909        tour
910    }
911}
912
913/// Supply chain optimizer
914pub struct SupplyChainOptimizer {
915    /// Network structure
916    network: SupplyChainNetwork,
917    /// Optimization objectives
918    objectives: Vec<SupplyChainObjective>,
919    /// Constraints
920    constraints: SupplyChainConstraints,
921    /// Time horizon
922    time_horizon: usize,
923}
924
925#[derive(Debug, Clone)]
926pub struct SupplyChainNetwork {
927    /// Suppliers
928    pub suppliers: Vec<Supplier>,
929    /// Warehouses
930    pub warehouses: Vec<Warehouse>,
931    /// Distribution centers
932    pub distribution_centers: Vec<DistributionCenter>,
933    /// Customers
934    pub customers: Vec<Customer>,
935    /// Transportation links
936    pub links: Vec<TransportLink>,
937}
938
939#[derive(Debug, Clone)]
940pub struct Supplier {
941    pub id: usize,
942    pub capacity: f64,
943    pub cost_per_unit: f64,
944    pub lead_time: usize,
945    pub reliability: f64,
946}
947
948#[derive(Debug, Clone)]
949pub struct Warehouse {
950    pub id: usize,
951    pub capacity: f64,
952    pub holding_cost: f64,
953    pub fixed_cost: f64,
954    pub location: (f64, f64),
955}
956
957#[derive(Debug, Clone)]
958pub struct DistributionCenter {
959    pub id: usize,
960    pub capacity: f64,
961    pub processing_cost: f64,
962    pub location: (f64, f64),
963}
964
965#[derive(Debug, Clone)]
966pub struct Customer {
967    pub id: usize,
968    pub demand: Array1<f64>, // Demand over time
969    pub priority: f64,
970    pub location: (f64, f64),
971}
972
973#[derive(Debug, Clone)]
974pub struct TransportLink {
975    pub from_type: NodeType,
976    pub from_id: usize,
977    pub to_type: NodeType,
978    pub to_id: usize,
979    pub capacity: f64,
980    pub cost_per_unit: f64,
981    pub lead_time: usize,
982}
983
984#[derive(Debug, Clone, PartialEq, Eq)]
985pub enum NodeType {
986    Supplier,
987    Warehouse,
988    DistributionCenter,
989    Customer,
990}
991
992#[derive(Debug, Clone)]
993pub enum SupplyChainObjective {
994    /// Minimize total cost
995    MinimizeCost,
996    /// Minimize delivery time
997    MinimizeDeliveryTime,
998    /// Maximize service level
999    MaximizeServiceLevel,
1000    /// Minimize inventory
1001    MinimizeInventory,
1002    /// Balance workload
1003    BalanceWorkload,
1004}
1005
1006#[derive(Debug, Clone)]
1007pub struct SupplyChainConstraints {
1008    /// Capacity constraints
1009    pub enforce_capacity: bool,
1010    /// Service level requirements
1011    pub min_service_level: f64,
1012    /// Maximum lead time
1013    pub max_lead_time: Option<usize>,
1014    /// Safety stock requirements
1015    pub safety_stock: HashMap<usize, f64>,
1016    /// Budget constraint
1017    pub max_budget: Option<f64>,
1018}
1019
1020impl SupplyChainOptimizer {
1021    /// Create new supply chain optimizer
1022    pub fn new(network: SupplyChainNetwork, time_horizon: usize) -> Self {
1023        Self {
1024            network,
1025            objectives: vec![SupplyChainObjective::MinimizeCost],
1026            constraints: SupplyChainConstraints {
1027                enforce_capacity: true,
1028                min_service_level: 0.95,
1029                max_lead_time: None,
1030                safety_stock: HashMap::new(),
1031                max_budget: None,
1032            },
1033            time_horizon,
1034        }
1035    }
1036
1037    /// Add objective
1038    pub fn add_objective(mut self, objective: SupplyChainObjective) -> Self {
1039        self.objectives.push(objective);
1040        self
1041    }
1042
1043    /// Set constraints
1044    pub fn with_constraints(mut self, constraints: SupplyChainConstraints) -> Self {
1045        self.constraints = constraints;
1046        self
1047    }
1048
1049    /// Build QUBO formulation
1050    pub fn build_qubo(&self) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
1051        // Variables:
1052        // - x_{s,w,t}: flow from supplier s to warehouse w at time t
1053        // - y_{w,d,t}: flow from warehouse w to DC d at time t
1054        // - z_{d,c,t}: flow from DC d to customer c at time t
1055        // - I_{w,t}: inventory at warehouse w at time t
1056
1057        let mut var_map = HashMap::new();
1058        let mut var_idx = 0;
1059
1060        // Create variables for flows
1061        for t in 0..self.time_horizon {
1062            // Supplier to warehouse
1063            for s in &self.network.suppliers {
1064                for w in &self.network.warehouses {
1065                    let var_name = format!("x_{}_{}_{}", s.id, w.id, t);
1066                    var_map.insert(var_name, var_idx);
1067                    var_idx += 1;
1068                }
1069            }
1070
1071            // Warehouse to DC
1072            for w in &self.network.warehouses {
1073                for d in &self.network.distribution_centers {
1074                    let var_name = format!("y_{}_{}_{}", w.id, d.id, t);
1075                    var_map.insert(var_name, var_idx);
1076                    var_idx += 1;
1077                }
1078            }
1079
1080            // DC to customer
1081            for d in &self.network.distribution_centers {
1082                for c in &self.network.customers {
1083                    let var_name = format!("z_{}_{}_{}", d.id, c.id, t);
1084                    var_map.insert(var_name, var_idx);
1085                    var_idx += 1;
1086                }
1087            }
1088
1089            // Inventory variables
1090            for w in &self.network.warehouses {
1091                let var_name = format!("I_{}_{}", w.id, t);
1092                var_map.insert(var_name, var_idx);
1093                var_idx += 1;
1094            }
1095        }
1096
1097        let n_vars = var_idx;
1098        let mut qubo = Array2::zeros((n_vars, n_vars));
1099
1100        // Add objectives
1101        for objective in &self.objectives {
1102            match objective {
1103                SupplyChainObjective::MinimizeCost => {
1104                    self.add_cost_objective(&mut qubo, &var_map)?;
1105                }
1106                SupplyChainObjective::MinimizeInventory => {
1107                    self.add_inventory_objective(&mut qubo, &var_map)?;
1108                }
1109                _ => {}
1110            }
1111        }
1112
1113        // Add constraints
1114        self.add_flow_conservation_constraints(&mut qubo, &var_map)?;
1115        self.add_capacity_constraints_sc(&mut qubo, &var_map)?;
1116        self.add_demand_constraints(&mut qubo, &var_map)?;
1117
1118        Ok((qubo, var_map))
1119    }
1120
1121    /// Add cost objective
1122    fn add_cost_objective(
1123        &self,
1124        qubo: &mut Array2<f64>,
1125        var_map: &HashMap<String, usize>,
1126    ) -> Result<(), String> {
1127        // Transportation costs
1128        for link in &self.network.links {
1129            for t in 0..self.time_horizon {
1130                let var_name = match (&link.from_type, &link.to_type) {
1131                    (NodeType::Supplier, NodeType::Warehouse) => {
1132                        format!("x_{}_{}_{}", link.from_id, link.to_id, t)
1133                    }
1134                    (NodeType::Warehouse, NodeType::DistributionCenter) => {
1135                        format!("y_{}_{}_{}", link.from_id, link.to_id, t)
1136                    }
1137                    (NodeType::DistributionCenter, NodeType::Customer) => {
1138                        format!("z_{}_{}_{}", link.from_id, link.to_id, t)
1139                    }
1140                    _ => continue,
1141                };
1142
1143                if let Some(&idx) = var_map.get(&var_name) {
1144                    qubo[[idx, idx]] += link.cost_per_unit;
1145                }
1146            }
1147        }
1148
1149        // Holding costs
1150        for w in &self.network.warehouses {
1151            for t in 0..self.time_horizon {
1152                let var_name = format!("I_{}_{}", w.id, t);
1153                if let Some(&idx) = var_map.get(&var_name) {
1154                    qubo[[idx, idx]] += w.holding_cost;
1155                }
1156            }
1157        }
1158
1159        Ok(())
1160    }
1161
1162    /// Add inventory objective
1163    fn add_inventory_objective(
1164        &self,
1165        qubo: &mut Array2<f64>,
1166        var_map: &HashMap<String, usize>,
1167    ) -> Result<(), String> {
1168        for w in &self.network.warehouses {
1169            for t in 0..self.time_horizon {
1170                let var_name = format!("I_{}_{}", w.id, t);
1171                if let Some(&idx) = var_map.get(&var_name) {
1172                    qubo[[idx, idx]] += 1.0; // Minimize inventory
1173                }
1174            }
1175        }
1176
1177        Ok(())
1178    }
1179
1180    /// Add flow conservation constraints
1181    fn add_flow_conservation_constraints(
1182        &self,
1183        _qubo: &mut Array2<f64>,
1184        _var_map: &HashMap<String, usize>,
1185    ) -> Result<(), String> {
1186        let _penalty = 1000.0;
1187
1188        // Warehouse flow conservation
1189        for _w in &self.network.warehouses {
1190            for _t in 1..self.time_horizon {
1191                // I_{w,t} = I_{w,t-1} + sum_s x_{s,w,t} - sum_d y_{w,d,t}
1192
1193                // This is a complex constraint, simplified here
1194                // Penalize imbalanced flows
1195            }
1196        }
1197
1198        Ok(())
1199    }
1200
1201    /// Add capacity constraints
1202    fn add_capacity_constraints_sc(
1203        &self,
1204        qubo: &mut Array2<f64>,
1205        var_map: &HashMap<String, usize>,
1206    ) -> Result<(), String> {
1207        let penalty = 1000.0;
1208
1209        // Supplier capacity
1210        for s in &self.network.suppliers {
1211            for t in 0..self.time_horizon {
1212                // sum_w x_{s,w,t} <= capacity_s
1213                // Simplified: penalize excessive flow
1214                for w in &self.network.warehouses {
1215                    let var_name = format!("x_{}_{}_{}", s.id, w.id, t);
1216                    if let Some(&idx) = var_map.get(&var_name) {
1217                        if s.capacity > 0.0 {
1218                            qubo[[idx, idx]] += penalty / s.capacity;
1219                        }
1220                    }
1221                }
1222            }
1223        }
1224
1225        Ok(())
1226    }
1227
1228    /// Add demand constraints
1229    fn add_demand_constraints(
1230        &self,
1231        qubo: &mut Array2<f64>,
1232        var_map: &HashMap<String, usize>,
1233    ) -> Result<(), String> {
1234        let penalty = 1000.0;
1235
1236        // Customer demand satisfaction
1237        for c in &self.network.customers {
1238            for t in 0..self.time_horizon.min(c.demand.len()) {
1239                // sum_d z_{d,c,t} = demand_{c,t}
1240                // Simplified: encourage meeting demand
1241                for d in &self.network.distribution_centers {
1242                    let var_name = format!("z_{}_{}_{}", d.id, c.id, t);
1243                    if let Some(&idx) = var_map.get(&var_name) {
1244                        qubo[[idx, idx]] -= penalty * c.demand[t] * c.priority;
1245                    }
1246                }
1247            }
1248        }
1249
1250        Ok(())
1251    }
1252}
1253
1254/// Warehouse optimization
1255pub struct WarehouseOptimizer {
1256    /// Warehouse layout
1257    layout: WarehouseLayout,
1258    /// Storage policies
1259    policies: StoragePolicies,
1260    /// Order data
1261    orders: Vec<Order>,
1262    /// Optimization goals
1263    goals: WarehouseGoals,
1264}
1265
1266#[derive(Debug, Clone)]
1267pub struct WarehouseLayout {
1268    /// Grid dimensions
1269    pub rows: usize,
1270    pub cols: usize,
1271    pub levels: usize,
1272    /// Storage locations
1273    pub locations: Vec<StorageLocation>,
1274    /// Picking stations
1275    pub picking_stations: Vec<(usize, usize)>,
1276    /// Distance function
1277    pub distance_type: DistanceType,
1278}
1279
1280#[derive(Debug, Clone)]
1281pub struct StorageLocation {
1282    pub id: usize,
1283    pub position: (usize, usize, usize), // row, col, level
1284    pub capacity: f64,
1285    pub item_type: Option<String>,
1286    pub accessibility: f64,
1287}
1288
1289#[derive(Debug, Clone)]
1290pub enum DistanceType {
1291    Manhattan,
1292    Euclidean,
1293    Rectilinear,
1294    Custom,
1295}
1296
1297#[derive(Debug, Clone)]
1298pub struct StoragePolicies {
1299    /// Storage assignment policy
1300    pub assignment: AssignmentPolicy,
1301    /// Replenishment policy
1302    pub replenishment: ReplenishmentPolicy,
1303    /// Picking policy
1304    pub picking: PickingPolicy,
1305}
1306
1307#[derive(Debug, Clone)]
1308pub enum AssignmentPolicy {
1309    /// Random storage
1310    Random,
1311    /// ABC classification
1312    ABC {
1313        a_locations: Vec<usize>,
1314        b_locations: Vec<usize>,
1315        c_locations: Vec<usize>,
1316    },
1317    /// Dedicated storage
1318    Dedicated,
1319    /// Class-based storage
1320    ClassBased,
1321}
1322
1323#[derive(Debug, Clone)]
1324pub enum ReplenishmentPolicy {
1325    /// Fixed order quantity
1326    FixedQuantity { quantity: f64 },
1327    /// Reorder point
1328    ReorderPoint { level: f64 },
1329    /// Periodic review
1330    Periodic { interval: usize },
1331}
1332
1333#[derive(Debug, Clone)]
1334pub enum PickingPolicy {
1335    /// Single order picking
1336    Single,
1337    /// Batch picking
1338    Batch { size: usize },
1339    /// Zone picking
1340    Zone { zones: Vec<Vec<usize>> },
1341    /// Wave picking
1342    Wave { interval: usize },
1343}
1344
1345#[derive(Debug, Clone)]
1346pub struct Order {
1347    pub id: usize,
1348    pub items: Vec<OrderItem>,
1349    pub priority: f64,
1350    pub due_time: f64,
1351}
1352
1353#[derive(Debug, Clone)]
1354pub struct OrderItem {
1355    pub sku: String,
1356    pub quantity: usize,
1357    pub location: Option<usize>,
1358}
1359
1360#[derive(Debug, Clone)]
1361pub struct WarehouseGoals {
1362    /// Minimize picking distance
1363    pub minimize_distance: bool,
1364    /// Minimize order completion time
1365    pub minimize_time: bool,
1366    /// Balance workload
1367    pub balance_workload: bool,
1368    /// Maximize space utilization
1369    pub maximize_utilization: bool,
1370}
1371
1372impl WarehouseOptimizer {
1373    /// Create new warehouse optimizer
1374    pub const fn new(
1375        layout: WarehouseLayout,
1376        policies: StoragePolicies,
1377        orders: Vec<Order>,
1378    ) -> Self {
1379        Self {
1380            layout,
1381            policies,
1382            orders,
1383            goals: WarehouseGoals {
1384                minimize_distance: true,
1385                minimize_time: false,
1386                balance_workload: false,
1387                maximize_utilization: false,
1388            },
1389        }
1390    }
1391
1392    /// Optimize order picking
1393    pub fn optimize_picking(&self) -> Result<PickingPlan, String> {
1394        match &self.policies.picking {
1395            PickingPolicy::Batch { size } => self.optimize_batch_picking(*size),
1396            _ => self.optimize_single_picking(),
1397        }
1398    }
1399
1400    /// Optimize batch picking
1401    fn optimize_batch_picking(&self, batch_size: usize) -> Result<PickingPlan, String> {
1402        let mut batches = Vec::new();
1403        let mut remaining_orders = self.orders.clone();
1404
1405        while !remaining_orders.is_empty() {
1406            let batch_orders: Vec<_> = remaining_orders
1407                .drain(..batch_size.min(remaining_orders.len()))
1408                .collect();
1409
1410            let route = self.optimize_picking_route(&batch_orders)?;
1411
1412            let estimated_time = self.estimate_picking_time(&route);
1413            batches.push(Batch {
1414                orders: batch_orders,
1415                route,
1416                estimated_time,
1417            });
1418        }
1419
1420        let total_distance = batches.iter().map(|b| b.route.total_distance).sum();
1421        let total_time = batches.iter().map(|b| b.estimated_time).sum();
1422
1423        Ok(PickingPlan {
1424            batches,
1425            total_distance,
1426            total_time,
1427        })
1428    }
1429
1430    /// Optimize single order picking
1431    fn optimize_single_picking(&self) -> Result<PickingPlan, String> {
1432        let mut batches = Vec::new();
1433
1434        for order in &self.orders {
1435            let route = self.optimize_picking_route(&[order.clone()])?;
1436            let estimated_time = self.estimate_picking_time(&route);
1437
1438            batches.push(Batch {
1439                orders: vec![order.clone()],
1440                route,
1441                estimated_time,
1442            });
1443        }
1444
1445        let total_distance = batches.iter().map(|b| b.route.total_distance).sum();
1446        let total_time = batches.iter().map(|b| b.estimated_time).sum();
1447
1448        Ok(PickingPlan {
1449            batches,
1450            total_distance,
1451            total_time,
1452        })
1453    }
1454
1455    /// Optimize picking route for orders
1456    fn optimize_picking_route(&self, orders: &[Order]) -> Result<PickingRoute, String> {
1457        // Collect all pick locations
1458        let mut pick_locations = Vec::new();
1459
1460        for order in orders {
1461            for item in &order.items {
1462                if let Some(loc) = item.location {
1463                    pick_locations.push(loc);
1464                }
1465            }
1466        }
1467
1468        // Remove duplicates
1469        pick_locations.sort_unstable();
1470        pick_locations.dedup();
1471
1472        // Build distance matrix including picking station
1473        let n = pick_locations.len() + 1; // +1 for picking station
1474        let mut distances = Array2::zeros((n, n));
1475
1476        // Distance from picking station to locations
1477        let station = self.layout.picking_stations[0]; // Use first station
1478        for (i, &loc) in pick_locations.iter().enumerate() {
1479            let loc_pos = self.layout.locations[loc].position;
1480            distances[[0, i + 1]] = self.calculate_distance((station.0, station.1, 0), loc_pos);
1481            distances[[i + 1, 0]] = distances[[0, i + 1]];
1482        }
1483
1484        // Distance between locations
1485        for (i, &loc1) in pick_locations.iter().enumerate() {
1486            for (j, &loc2) in pick_locations.iter().enumerate() {
1487                if i != j {
1488                    let pos1 = self.layout.locations[loc1].position;
1489                    let pos2 = self.layout.locations[loc2].position;
1490                    distances[[i + 1, j + 1]] = self.calculate_distance(pos1, pos2);
1491                }
1492            }
1493        }
1494
1495        // Solve TSP
1496        let tsp = TSPOptimizer::new(distances)?;
1497        let (_qubo, _var_map) = tsp.build_qubo()?;
1498
1499        // Simplified: return S-shaped route
1500        let sequence = (0..pick_locations.len()).collect();
1501        Ok(PickingRoute {
1502            locations: pick_locations,
1503            sequence,
1504            total_distance: 0.0, // Would calculate from solution
1505        })
1506    }
1507
1508    /// Calculate distance between positions
1509    fn calculate_distance(&self, pos1: (usize, usize, usize), pos2: (usize, usize, usize)) -> f64 {
1510        match self.layout.distance_type {
1511            DistanceType::Manhattan => {
1512                ((pos1.0 as i32 - pos2.0 as i32).abs()
1513                    + (pos1.1 as i32 - pos2.1 as i32).abs()
1514                    + (pos1.2 as i32 - pos2.2 as i32).abs()) as f64
1515            }
1516            DistanceType::Euclidean => (pos1.2 as f64 - pos2.2 as f64)
1517                .mul_add(
1518                    pos1.2 as f64 - pos2.2 as f64,
1519                    (pos1.1 as f64 - pos2.1 as f64).mul_add(
1520                        pos1.1 as f64 - pos2.1 as f64,
1521                        (pos1.0 as f64 - pos2.0 as f64).powi(2),
1522                    ),
1523                )
1524                .sqrt(),
1525            _ => 0.0,
1526        }
1527    }
1528
1529    /// Estimate picking time for route
1530    fn estimate_picking_time(&self, route: &PickingRoute) -> f64 {
1531        // Simplified: distance * speed + pick time
1532        let travel_time = route.total_distance / 1.0; // 1 unit/time
1533        let pick_time = route.locations.len() as f64 * 10.0; // 10 time units per pick
1534
1535        travel_time + pick_time
1536    }
1537}
1538
1539#[derive(Debug, Clone)]
1540pub struct PickingPlan {
1541    pub batches: Vec<Batch>,
1542    pub total_distance: f64,
1543    pub total_time: f64,
1544}
1545
1546#[derive(Debug, Clone)]
1547pub struct Batch {
1548    pub orders: Vec<Order>,
1549    pub route: PickingRoute,
1550    pub estimated_time: f64,
1551}
1552
1553#[derive(Debug, Clone)]
1554pub struct PickingRoute {
1555    pub locations: Vec<usize>,
1556    pub sequence: Vec<usize>,
1557    pub total_distance: f64,
1558}
1559
1560#[cfg(test)]
1561mod tests {
1562    use super::*;
1563
1564    #[test]
1565    fn test_vrp_optimizer() {
1566        let distances = Array2::from_shape_vec(
1567            (4, 4),
1568            vec![
1569                0.0, 10.0, 15.0, 20.0, 10.0, 0.0, 25.0, 30.0, 15.0, 25.0, 0.0, 35.0, 20.0, 30.0,
1570                35.0, 0.0,
1571            ],
1572        )
1573        .expect("Test distance matrix has valid shape");
1574
1575        let demands = Array1::from_vec(vec![0.0, 10.0, 15.0, 20.0]);
1576
1577        let optimizer = VehicleRoutingOptimizer::new(distances, 50.0, demands, 2)
1578            .expect("Test VRP optimizer should be created with valid inputs");
1579
1580        let (_qubo, var_map) = optimizer
1581            .build_qubo()
1582            .expect("VRP QUBO should build successfully");
1583        assert!(!var_map.is_empty());
1584    }
1585
1586    #[test]
1587    fn test_tsp_optimizer() {
1588        let distances = Array2::from_shape_vec(
1589            (4, 4),
1590            vec![
1591                0.0, 10.0, 15.0, 20.0, 10.0, 0.0, 25.0, 30.0, 15.0, 25.0, 0.0, 35.0, 20.0, 30.0,
1592                35.0, 0.0,
1593            ],
1594        )
1595        .expect("Test distance matrix has valid shape");
1596
1597        let optimizer =
1598            TSPOptimizer::new(distances).expect("TSP optimizer should be created with valid input");
1599        let (_qubo, var_map) = optimizer
1600            .build_qubo()
1601            .expect("TSP QUBO should build successfully");
1602
1603        assert_eq!(var_map.len(), 16); // 4 cities * 4 time slots
1604    }
1605
1606    #[test]
1607    fn test_supply_chain() {
1608        let network = SupplyChainNetwork {
1609            suppliers: vec![Supplier {
1610                id: 0,
1611                capacity: 100.0,
1612                cost_per_unit: 10.0,
1613                lead_time: 2,
1614                reliability: 0.95,
1615            }],
1616            warehouses: vec![Warehouse {
1617                id: 0,
1618                capacity: 200.0,
1619                holding_cost: 1.0,
1620                fixed_cost: 1000.0,
1621                location: (0.0, 0.0),
1622            }],
1623            distribution_centers: vec![DistributionCenter {
1624                id: 0,
1625                capacity: 150.0,
1626                processing_cost: 2.0,
1627                location: (10.0, 10.0),
1628            }],
1629            customers: vec![Customer {
1630                id: 0,
1631                demand: Array1::from_vec(vec![20.0, 25.0, 30.0]),
1632                priority: 1.0,
1633                location: (20.0, 20.0),
1634            }],
1635            links: vec![],
1636        };
1637
1638        let optimizer = SupplyChainOptimizer::new(network, 3);
1639        let (_qubo, var_map) = optimizer
1640            .build_qubo()
1641            .expect("Supply chain QUBO should build successfully");
1642
1643        assert!(!var_map.is_empty());
1644    }
1645
1646    #[test]
1647    fn test_binary_vrp_wrapper() {
1648        let distances = Array2::from_shape_vec(
1649            (4, 4),
1650            vec![
1651                0.0, 10.0, 15.0, 20.0, 10.0, 0.0, 25.0, 30.0, 15.0, 25.0, 0.0, 35.0, 20.0, 30.0,
1652                35.0, 0.0,
1653            ],
1654        )
1655        .expect("Test distance matrix has valid shape");
1656
1657        let demands = Array1::from_vec(vec![0.0, 10.0, 15.0, 20.0]);
1658
1659        let optimizer = VehicleRoutingOptimizer::new(distances, 50.0, demands, 2)
1660            .expect("Test VRP optimizer should be created with valid inputs");
1661
1662        let binary_vrp = BinaryVehicleRoutingProblem::new(optimizer);
1663
1664        // Test number of variables
1665        assert_eq!(binary_vrp.num_variables(), 32); // 2 vehicles * 4 locations * 4 locations
1666
1667        // Test random solution generation
1668        let solution = binary_vrp.random_solution();
1669        assert_eq!(solution.len(), 32);
1670
1671        // Test solution evaluation
1672        let energy = binary_vrp.evaluate_binary(&solution);
1673        assert!(energy.is_finite());
1674
1675        // Test solution decoding
1676        let routes = binary_vrp.decode_binary_solution(&solution);
1677        assert!(routes.len() <= 2); // Maximum 2 vehicles
1678    }
1679
1680    #[test]
1681    fn test_create_benchmark_problems() {
1682        let problems = create_benchmark_problems();
1683        assert_eq!(problems.len(), 3); // Small, medium, and CVRPTW problems
1684
1685        // Test each problem
1686        for (i, problem) in problems.iter().enumerate() {
1687            let solution = problem.random_solution();
1688            let energy = problem.evaluate_binary(&solution);
1689            assert!(energy.is_finite(), "Problem {i} should have finite energy");
1690
1691            let routes = problem.decode_binary_solution(&solution);
1692            // Each problem should be able to decode solutions
1693            assert!(
1694                routes.len() <= 3,
1695                "Problem {i} should have at most 3 routes"
1696            );
1697        }
1698    }
1699}