quantrs2_anneal/applications/
logistics.rs

1//! Logistics Industry Optimization
2//!
3//! This module provides optimization solutions for the logistics industry,
4//! including vehicle routing, supply chain optimization, warehouse management,
5//! and delivery scheduling.
6
7use super::{
8    ApplicationError, ApplicationResult, IndustryConstraint, IndustryObjective, IndustrySolution,
9    OptimizationProblem,
10};
11use crate::ising::IsingModel;
12use crate::qubo::{QuboBuilder, QuboFormulation};
13use crate::simulator::{AnnealingParams, ClassicalAnnealingSimulator};
14use std::collections::HashMap;
15
16use std::fmt::Write;
17/// Vehicle Routing Problem (VRP) with time windows and capacity constraints
18#[derive(Debug, Clone)]
19pub struct VehicleRoutingProblem {
20    /// Number of vehicles available
21    pub num_vehicles: usize,
22    /// Vehicle capacities
23    pub vehicle_capacities: Vec<f64>,
24    /// Customer locations (including depot at index 0)
25    pub locations: Vec<(f64, f64)>,
26    /// Customer demands
27    pub demands: Vec<f64>,
28    /// Distance matrix between locations
29    pub distance_matrix: Vec<Vec<f64>>,
30    /// Time windows for each location (`start_time`, `end_time`)
31    pub time_windows: Vec<(f64, f64)>,
32    /// Service times at each location
33    pub service_times: Vec<f64>,
34    /// Vehicle operating costs per unit distance
35    pub vehicle_costs: Vec<f64>,
36    /// Maximum route duration
37    pub max_route_duration: f64,
38    /// Penalty for violating constraints
39    pub constraint_penalty: f64,
40}
41
42impl VehicleRoutingProblem {
43    /// Create a new vehicle routing problem
44    pub fn new(
45        num_vehicles: usize,
46        vehicle_capacities: Vec<f64>,
47        locations: Vec<(f64, f64)>,
48        demands: Vec<f64>,
49    ) -> ApplicationResult<Self> {
50        if vehicle_capacities.len() != num_vehicles {
51            return Err(ApplicationError::InvalidConfiguration(
52                "Vehicle capacities must match number of vehicles".to_string(),
53            ));
54        }
55
56        if locations.len() != demands.len() {
57            return Err(ApplicationError::InvalidConfiguration(
58                "Number of locations must match number of demands".to_string(),
59            ));
60        }
61
62        let num_locations = locations.len();
63        let distance_matrix = Self::calculate_distance_matrix(&locations);
64
65        Ok(Self {
66            num_vehicles,
67            vehicle_capacities,
68            locations,
69            demands,
70            distance_matrix,
71            time_windows: vec![(0.0, 1440.0); num_locations], // Default: full day
72            service_times: vec![10.0; num_locations],         // Default: 10 minutes
73            vehicle_costs: vec![1.0; num_vehicles],           // Default: unit cost
74            max_route_duration: 480.0,                        // Default: 8 hours
75            constraint_penalty: 1000.0,
76        })
77    }
78
79    /// Calculate Euclidean distance matrix
80    fn calculate_distance_matrix(locations: &[(f64, f64)]) -> Vec<Vec<f64>> {
81        let n = locations.len();
82        let mut matrix = vec![vec![0.0; n]; n];
83
84        for i in 0..n {
85            for j in 0..n {
86                if i != j {
87                    let dx = locations[i].0 - locations[j].0;
88                    let dy = locations[i].1 - locations[j].1;
89                    matrix[i][j] = dx.hypot(dy);
90                }
91            }
92        }
93
94        matrix
95    }
96
97    /// Set time windows for locations
98    pub fn set_time_windows(&mut self, time_windows: Vec<(f64, f64)>) -> ApplicationResult<()> {
99        if time_windows.len() != self.locations.len() {
100            return Err(ApplicationError::InvalidConfiguration(
101                "Time windows must match number of locations".to_string(),
102            ));
103        }
104
105        self.time_windows = time_windows;
106        Ok(())
107    }
108
109    /// Set service times
110    pub fn set_service_times(&mut self, service_times: Vec<f64>) -> ApplicationResult<()> {
111        if service_times.len() != self.locations.len() {
112            return Err(ApplicationError::InvalidConfiguration(
113                "Service times must match number of locations".to_string(),
114            ));
115        }
116
117        self.service_times = service_times;
118        Ok(())
119    }
120
121    /// Calculate total route distance
122    #[must_use]
123    pub fn calculate_route_distance(&self, route: &[usize]) -> f64 {
124        if route.len() < 2 {
125            return 0.0;
126        }
127
128        route
129            .windows(2)
130            .map(|pair| self.distance_matrix[pair[0]][pair[1]])
131            .sum()
132    }
133
134    /// Calculate total route duration
135    #[must_use]
136    pub fn calculate_route_duration(&self, route: &[usize]) -> f64 {
137        if route.len() < 2 {
138            return 0.0;
139        }
140
141        let travel_time: f64 = route
142            .windows(2)
143            .map(|pair| self.distance_matrix[pair[0]][pair[1]])
144            .sum();
145
146        let service_time: f64 = route.iter().map(|&loc| self.service_times[loc]).sum();
147
148        travel_time + service_time
149    }
150
151    /// Check if route satisfies capacity constraint
152    #[must_use]
153    pub fn check_capacity_constraint(&self, route: &[usize], vehicle_idx: usize) -> bool {
154        let total_demand: f64 = route.iter().map(|&loc| self.demands[loc]).sum();
155
156        total_demand <= self.vehicle_capacities[vehicle_idx]
157    }
158
159    /// Check if route satisfies time window constraints
160    #[must_use]
161    pub fn check_time_windows(&self, route: &[usize]) -> bool {
162        if route.len() < 2 {
163            return true;
164        }
165
166        let mut current_time = 0.0;
167
168        for i in 1..route.len() {
169            let prev_loc = route[i - 1];
170            let curr_loc = route[i];
171
172            // Travel time
173            current_time += self.distance_matrix[prev_loc][curr_loc];
174
175            // Check if we arrive within time window
176            let (earliest, latest) = self.time_windows[curr_loc];
177            if current_time > latest {
178                return false;
179            }
180
181            // Wait if we arrive early
182            current_time = current_time.max(earliest);
183
184            // Add service time
185            current_time += self.service_times[curr_loc];
186        }
187
188        true
189    }
190}
191
192impl OptimizationProblem for VehicleRoutingProblem {
193    type Solution = VehicleRoutingSolution;
194    type ObjectiveValue = f64;
195
196    fn description(&self) -> String {
197        format!(
198            "Vehicle Routing Problem with {} vehicles and {} customers",
199            self.num_vehicles,
200            self.locations.len() - 1 // Subtract depot
201        )
202    }
203
204    fn size_metrics(&self) -> HashMap<String, usize> {
205        let mut metrics = HashMap::new();
206        metrics.insert("num_vehicles".to_string(), self.num_vehicles);
207        metrics.insert("num_customers".to_string(), self.locations.len() - 1);
208        metrics.insert("num_locations".to_string(), self.locations.len());
209        metrics
210    }
211
212    fn validate(&self) -> ApplicationResult<()> {
213        if self.num_vehicles == 0 {
214            return Err(ApplicationError::DataValidationError(
215                "At least one vehicle required".to_string(),
216            ));
217        }
218
219        if self.locations.len() < 2 {
220            return Err(ApplicationError::DataValidationError(
221                "At least depot and one customer required".to_string(),
222            ));
223        }
224
225        // Check that depot has zero demand
226        if self.demands[0] != 0.0 {
227            return Err(ApplicationError::DataValidationError(
228                "Depot must have zero demand".to_string(),
229            ));
230        }
231
232        // Check positive vehicle capacities
233        for (i, &capacity) in self.vehicle_capacities.iter().enumerate() {
234            if capacity <= 0.0 {
235                return Err(ApplicationError::DataValidationError(format!(
236                    "Vehicle {i} has non-positive capacity"
237                )));
238            }
239        }
240
241        Ok(())
242    }
243
244    fn to_qubo(&self) -> ApplicationResult<(crate::ising::QuboModel, HashMap<String, usize>)> {
245        let num_customers = self.locations.len() - 1;
246        let mut builder = QuboBuilder::new();
247
248        // Binary variables: x[v][i][j] = 1 if vehicle v travels from i to j
249        let mut var_counter = 0;
250        let mut var_map = HashMap::new();
251        let mut string_var_map = HashMap::new();
252
253        for v in 0..self.num_vehicles {
254            for i in 0..self.locations.len() {
255                for j in 0..self.locations.len() {
256                    if i != j {
257                        let var_name = format!("x_{v}_{i}__{j}");
258                        var_map.insert((v, i, j), var_counter);
259                        string_var_map.insert(var_name, var_counter);
260                        var_counter += 1;
261                    }
262                }
263            }
264        }
265
266        // Objective: minimize total distance
267        for v in 0..self.num_vehicles {
268            for i in 0..self.locations.len() {
269                for j in 0..self.locations.len() {
270                    if i != j {
271                        let var_idx = var_map[&(v, i, j)];
272                        let distance = self.distance_matrix[i][j];
273                        let cost = distance * self.vehicle_costs[v];
274                        builder.add_bias(var_idx, cost);
275                    }
276                }
277            }
278        }
279
280        // Constraint: each customer visited exactly once
281        for customer in 1..self.locations.len() {
282            let mut constraint_vars = Vec::new();
283
284            for v in 0..self.num_vehicles {
285                for i in 0..self.locations.len() {
286                    if i != customer {
287                        if let Some(&var_idx) = var_map.get(&(v, i, customer)) {
288                            constraint_vars.push(var_idx);
289                        }
290                    }
291                }
292            }
293
294            // Add penalty for not visiting exactly once
295            for &var1 in &constraint_vars {
296                builder.add_bias(var1, -self.constraint_penalty);
297                for &var2 in &constraint_vars {
298                    if var1 != var2 {
299                        builder.add_coupling(var1, var2, self.constraint_penalty);
300                    }
301                }
302            }
303        }
304
305        // Constraint: flow conservation
306        for v in 0..self.num_vehicles {
307            for loc in 0..self.locations.len() {
308                let mut in_vars = Vec::new();
309                let mut out_vars = Vec::new();
310
311                // Incoming edges
312                for i in 0..self.locations.len() {
313                    if i != loc {
314                        if let Some(&var_idx) = var_map.get(&(v, i, loc)) {
315                            in_vars.push(var_idx);
316                        }
317                    }
318                }
319
320                // Outgoing edges
321                for j in 0..self.locations.len() {
322                    if j != loc {
323                        if let Some(&var_idx) = var_map.get(&(v, loc, j)) {
324                            out_vars.push(var_idx);
325                        }
326                    }
327                }
328
329                // Flow conservation: in = out
330                for &in_var in &in_vars {
331                    for &out_var in &out_vars {
332                        builder.add_coupling(in_var, out_var, -self.constraint_penalty);
333                    }
334                }
335            }
336        }
337
338        Ok((builder.build(), string_var_map))
339    }
340
341    fn evaluate_solution(
342        &self,
343        solution: &Self::Solution,
344    ) -> ApplicationResult<Self::ObjectiveValue> {
345        let mut total_cost = 0.0;
346
347        for (vehicle_idx, route) in solution.routes.iter().enumerate() {
348            if route.len() > 1 {
349                let distance = self.calculate_route_distance(route);
350                total_cost += distance * self.vehicle_costs[vehicle_idx];
351            }
352        }
353
354        Ok(total_cost)
355    }
356
357    fn is_feasible(&self, solution: &Self::Solution) -> bool {
358        // Check that all customers are visited exactly once
359        let mut visited = vec![false; self.locations.len()];
360        visited[0] = true; // Depot doesn't need to be visited by customers
361
362        for route in &solution.routes {
363            for &location in route {
364                if visited[location] {
365                    return false; // Location visited more than once
366                }
367                visited[location] = true;
368            }
369        }
370
371        // Check all customers are visited
372        if !visited[1..].iter().all(|&v| v) {
373            return false;
374        }
375
376        // Check capacity and time constraints for each route
377        for (vehicle_idx, route) in solution.routes.iter().enumerate() {
378            if !self.check_capacity_constraint(route, vehicle_idx) {
379                return false;
380            }
381
382            if !self.check_time_windows(route) {
383                return false;
384            }
385
386            if self.calculate_route_duration(route) > self.max_route_duration {
387                return false;
388            }
389        }
390
391        true
392    }
393}
394
395/// Binary wrapper for vehicle routing optimization that works with `Vec<i8>` solutions
396#[derive(Debug, Clone)]
397pub struct BinaryVehicleRoutingProblem {
398    inner: VehicleRoutingProblem,
399}
400
401impl BinaryVehicleRoutingProblem {
402    #[must_use]
403    pub const fn new(inner: VehicleRoutingProblem) -> Self {
404        Self { inner }
405    }
406}
407
408impl OptimizationProblem for BinaryVehicleRoutingProblem {
409    type Solution = Vec<i8>;
410    type ObjectiveValue = f64;
411
412    fn description(&self) -> String {
413        self.inner.description()
414    }
415
416    fn size_metrics(&self) -> HashMap<String, usize> {
417        self.inner.size_metrics()
418    }
419
420    fn validate(&self) -> ApplicationResult<()> {
421        self.inner.validate()
422    }
423
424    fn to_qubo(&self) -> ApplicationResult<(crate::ising::QuboModel, HashMap<String, usize>)> {
425        self.inner.to_qubo()
426    }
427
428    fn evaluate_solution(
429        &self,
430        solution: &Self::Solution,
431    ) -> ApplicationResult<Self::ObjectiveValue> {
432        // Simplified VRP evaluation: treat binary solution as edge selection
433        let num_locations = self.inner.locations.len();
434        let num_vehicles = self.inner.num_vehicles;
435
436        // Extract selected edges from binary solution
437        let mut total_cost = 0.0;
438        let mut vehicle_loads = vec![0.0; num_vehicles];
439
440        let edge_vars_per_vehicle = num_locations * num_locations;
441
442        for vehicle in 0..num_vehicles {
443            let vehicle_offset = vehicle * edge_vars_per_vehicle;
444
445            for i in 0..num_locations {
446                for j in 0..num_locations {
447                    if i != j {
448                        let var_idx = vehicle_offset + i * num_locations + j;
449                        if var_idx < solution.len() && solution[var_idx] == 1 {
450                            // This edge is selected
451                            total_cost += self.inner.distance_matrix[i][j]
452                                * self.inner.vehicle_costs[vehicle];
453
454                            // Add demand if going to a customer location (not depot)
455                            if j > 0 && j < self.inner.demands.len() {
456                                vehicle_loads[vehicle] += self.inner.demands[j];
457                            }
458                        }
459                    }
460                }
461            }
462
463            // Capacity penalty
464            if vehicle_loads[vehicle] > self.inner.vehicle_capacities[vehicle] {
465                total_cost +=
466                    1000.0 * (vehicle_loads[vehicle] - self.inner.vehicle_capacities[vehicle]);
467            }
468        }
469
470        // Minimize cost (negative for maximization algorithms)
471        Ok(-total_cost)
472    }
473
474    fn is_feasible(&self, solution: &Self::Solution) -> bool {
475        // Basic feasibility: at least some edges must be selected
476        solution.iter().any(|&x| x == 1)
477    }
478}
479
480/// Solution for Vehicle Routing Problem
481#[derive(Debug, Clone)]
482pub struct VehicleRoutingSolution {
483    /// Routes for each vehicle (sequence of location indices)
484    pub routes: Vec<Vec<usize>>,
485    /// Total distance traveled
486    pub total_distance: f64,
487    /// Total cost
488    pub total_cost: f64,
489    /// Route statistics
490    pub route_stats: Vec<RouteStatistics>,
491}
492
493/// Statistics for individual routes
494#[derive(Debug, Clone)]
495pub struct RouteStatistics {
496    /// Vehicle index
497    pub vehicle_id: usize,
498    /// Route distance
499    pub distance: f64,
500    /// Route duration
501    pub duration: f64,
502    /// Capacity utilization
503    pub capacity_utilization: f64,
504    /// Number of customers served
505    pub customers_served: usize,
506    /// Time window violations
507    pub time_violations: usize,
508}
509
510impl IndustrySolution for VehicleRoutingSolution {
511    type Problem = VehicleRoutingProblem;
512
513    fn from_binary(problem: &Self::Problem, binary_solution: &[i8]) -> ApplicationResult<Self> {
514        // Simplified binary solution interpretation
515        // In practice, this would involve complex routing construction
516        let num_customers = problem.locations.len() - 1;
517        let mut routes = vec![Vec::new(); problem.num_vehicles];
518
519        // Simple greedy assignment for demonstration
520        for customer in 1..problem.locations.len() {
521            let vehicle_idx = customer % problem.num_vehicles;
522            routes[vehicle_idx].push(customer);
523        }
524
525        // Add depot at start and end of each route
526        for route in &mut routes {
527            if !route.is_empty() {
528                route.insert(0, 0); // Start at depot
529                route.push(0); // Return to depot
530            }
531        }
532
533        let mut total_distance = 0.0;
534        let mut total_cost = 0.0;
535        let mut route_stats = Vec::new();
536
537        for (vehicle_idx, route) in routes.iter().enumerate() {
538            let distance = problem.calculate_route_distance(route);
539            let duration = problem.calculate_route_duration(route);
540            let cost = distance * problem.vehicle_costs[vehicle_idx];
541
542            let demand: f64 = route.iter().map(|&loc| problem.demands[loc]).sum();
543            let capacity_utilization = demand / problem.vehicle_capacities[vehicle_idx];
544
545            let customers_served = route.len().saturating_sub(2); // Exclude depot visits
546
547            route_stats.push(RouteStatistics {
548                vehicle_id: vehicle_idx,
549                distance,
550                duration,
551                capacity_utilization,
552                customers_served,
553                time_violations: 0, // Simplified
554            });
555
556            total_distance += distance;
557            total_cost += cost;
558        }
559
560        Ok(Self {
561            routes,
562            total_distance,
563            total_cost,
564            route_stats,
565        })
566    }
567
568    fn summary(&self) -> HashMap<String, String> {
569        let mut summary = HashMap::new();
570        summary.insert("type".to_string(), "Vehicle Routing".to_string());
571        summary.insert("num_routes".to_string(), self.routes.len().to_string());
572        summary.insert(
573            "total_distance".to_string(),
574            format!("{:.2}", self.total_distance),
575        );
576        summary.insert("total_cost".to_string(), format!("{:.2}", self.total_cost));
577
578        let active_vehicles = self.routes.iter().filter(|route| route.len() > 2).count();
579        summary.insert("active_vehicles".to_string(), active_vehicles.to_string());
580
581        let total_customers: usize = self
582            .route_stats
583            .iter()
584            .map(|stats| stats.customers_served)
585            .sum();
586        summary.insert("customers_served".to_string(), total_customers.to_string());
587
588        let avg_utilization = self
589            .route_stats
590            .iter()
591            .map(|stats| stats.capacity_utilization)
592            .sum::<f64>()
593            / self.route_stats.len() as f64;
594        summary.insert(
595            "avg_capacity_utilization".to_string(),
596            format!("{:.1}%", avg_utilization * 100.0),
597        );
598
599        summary
600    }
601
602    fn metrics(&self) -> HashMap<String, f64> {
603        let mut metrics = HashMap::new();
604        metrics.insert("total_distance".to_string(), self.total_distance);
605        metrics.insert("total_cost".to_string(), self.total_cost);
606
607        if !self.route_stats.is_empty() {
608            let avg_distance = self
609                .route_stats
610                .iter()
611                .map(|stats| stats.distance)
612                .sum::<f64>()
613                / self.route_stats.len() as f64;
614            metrics.insert("avg_route_distance".to_string(), avg_distance);
615
616            let max_distance = self
617                .route_stats
618                .iter()
619                .map(|stats| stats.distance)
620                .fold(0.0, f64::max);
621            metrics.insert("max_route_distance".to_string(), max_distance);
622
623            let avg_utilization = self
624                .route_stats
625                .iter()
626                .map(|stats| stats.capacity_utilization)
627                .sum::<f64>()
628                / self.route_stats.len() as f64;
629            metrics.insert("avg_capacity_utilization".to_string(), avg_utilization);
630
631            let route_balance = self
632                .route_stats
633                .iter()
634                .map(|stats| stats.customers_served as f64)
635                .collect::<Vec<_>>();
636            let mean_customers = route_balance.iter().sum::<f64>() / route_balance.len() as f64;
637            let variance = route_balance
638                .iter()
639                .map(|&x| (x - mean_customers).powi(2))
640                .sum::<f64>()
641                / route_balance.len() as f64;
642            metrics.insert("route_balance_variance".to_string(), variance);
643        }
644
645        metrics
646    }
647
648    fn export_format(&self) -> ApplicationResult<String> {
649        let mut output = String::new();
650        output.push_str("# Vehicle Routing Solution\n\n");
651
652        output.push_str("## Solution Summary\n");
653        writeln!(output, "Total Distance: {:.2} km", self.total_distance)
654            .expect("Writing to String should not fail");
655        writeln!(output, "Total Cost: ${:.2}", self.total_cost)
656            .expect("Writing to String should not fail");
657        writeln!(
658            output,
659            "Active Vehicles: {}",
660            self.routes.iter().filter(|route| route.len() > 2).count()
661        )
662        .expect("Writing to String should not fail");
663
664        output.push_str("\n## Route Details\n");
665        for (i, route) in self.routes.iter().enumerate() {
666            if route.len() > 2 {
667                // Skip empty routes
668                let stats = &self.route_stats[i];
669                writeln!(output, "\n### Vehicle {} Route", i + 1)
670                    .expect("Writing to String should not fail");
671                writeln!(output, "Sequence: {route:?}").expect("Writing to String should not fail");
672                writeln!(output, "Distance: {:.2} km", stats.distance)
673                    .expect("Writing to String should not fail");
674                writeln!(output, "Duration: {:.1} minutes", stats.duration)
675                    .expect("Writing to String should not fail");
676                writeln!(output, "Customers: {}", stats.customers_served)
677                    .expect("Writing to String should not fail");
678                write!(
679                    output,
680                    "Capacity Utilization: {:.1}%\n",
681                    stats.capacity_utilization * 100.0
682                )
683                .expect("Writing to String should not fail");
684            }
685        }
686
687        output.push_str("\n## Performance Metrics\n");
688        for (key, value) in self.metrics() {
689            writeln!(output, "{key}: {value:.3}").expect("Writing to String should not fail");
690        }
691
692        Ok(output)
693    }
694}
695
696/// Supply Chain Optimization Problem
697#[derive(Debug, Clone)]
698pub struct SupplyChainOptimization {
699    /// Number of suppliers
700    pub num_suppliers: usize,
701    /// Number of distribution centers
702    pub num_distribution_centers: usize,
703    /// Number of retail locations
704    pub num_retailers: usize,
705    /// Supplier capacities
706    pub supplier_capacities: Vec<f64>,
707    /// Distribution center capacities
708    pub dc_capacities: Vec<f64>,
709    /// Retailer demands
710    pub retailer_demands: Vec<f64>,
711    /// Transportation costs (supplier -> DC)
712    pub supplier_dc_costs: Vec<Vec<f64>>,
713    /// Transportation costs (DC -> retailer)
714    pub dc_retailer_costs: Vec<Vec<f64>>,
715    /// Fixed costs for opening DCs
716    pub dc_fixed_costs: Vec<f64>,
717    /// Service level requirements
718    pub service_levels: Vec<f64>,
719}
720
721impl SupplyChainOptimization {
722    /// Create a new supply chain optimization problem
723    pub fn new(
724        num_suppliers: usize,
725        num_distribution_centers: usize,
726        num_retailers: usize,
727        supplier_capacities: Vec<f64>,
728        dc_capacities: Vec<f64>,
729        retailer_demands: Vec<f64>,
730    ) -> ApplicationResult<Self> {
731        // Initialize cost matrices with default values
732        let supplier_dc_costs = vec![vec![1.0; num_distribution_centers]; num_suppliers];
733        let dc_retailer_costs = vec![vec![1.0; num_retailers]; num_distribution_centers];
734        let dc_fixed_costs = vec![100.0; num_distribution_centers];
735        let service_levels = vec![0.95; num_retailers];
736
737        Ok(Self {
738            num_suppliers,
739            num_distribution_centers,
740            num_retailers,
741            supplier_capacities,
742            dc_capacities,
743            retailer_demands,
744            supplier_dc_costs,
745            dc_retailer_costs,
746            dc_fixed_costs,
747            service_levels,
748        })
749    }
750
751    /// Set transportation costs
752    pub fn set_transportation_costs(
753        &mut self,
754        supplier_dc_costs: Vec<Vec<f64>>,
755        dc_retailer_costs: Vec<Vec<f64>>,
756    ) -> ApplicationResult<()> {
757        if supplier_dc_costs.len() != self.num_suppliers {
758            return Err(ApplicationError::InvalidConfiguration(
759                "Supplier-DC cost matrix size mismatch".to_string(),
760            ));
761        }
762
763        self.supplier_dc_costs = supplier_dc_costs;
764        self.dc_retailer_costs = dc_retailer_costs;
765        Ok(())
766    }
767
768    /// Calculate total transportation cost
769    #[must_use]
770    pub fn calculate_transportation_cost(&self, solution: &SupplyChainSolution) -> f64 {
771        let mut total_cost = 0.0;
772
773        // Supplier to DC costs
774        for (s, supplier_flows) in solution.supplier_dc_flows.iter().enumerate() {
775            for (dc, &flow) in supplier_flows.iter().enumerate() {
776                total_cost += flow * self.supplier_dc_costs[s][dc];
777            }
778        }
779
780        // DC to retailer costs
781        for (dc, dc_flows) in solution.dc_retailer_flows.iter().enumerate() {
782            for (r, &flow) in dc_flows.iter().enumerate() {
783                total_cost += flow * self.dc_retailer_costs[dc][r];
784            }
785        }
786
787        total_cost
788    }
789}
790
791/// Supply Chain Solution
792#[derive(Debug, Clone)]
793pub struct SupplyChainSolution {
794    /// Flow from suppliers to distribution centers
795    pub supplier_dc_flows: Vec<Vec<f64>>,
796    /// Flow from distribution centers to retailers
797    pub dc_retailer_flows: Vec<Vec<f64>>,
798    /// Which distribution centers are open
799    pub dc_open: Vec<bool>,
800    /// Total cost
801    pub total_cost: f64,
802    /// Service level achieved
803    pub service_level_achieved: Vec<f64>,
804}
805
806/// Create benchmark logistics problems
807pub fn create_benchmark_problems(
808    size: usize,
809) -> ApplicationResult<Vec<Box<dyn OptimizationProblem<Solution = Vec<i8>, ObjectiveValue = f64>>>>
810{
811    let mut problems = Vec::new();
812
813    // Problem 1: Small VRP
814    let locations = vec![(0.0, 0.0), (1.0, 1.0), (2.0, 0.5), (1.5, 2.0), (0.5, 1.5)];
815    let demands = vec![0.0, 10.0, 15.0, 8.0, 12.0];
816    let capacities = vec![30.0, 25.0];
817
818    let vrp = VehicleRoutingProblem::new(2, capacities, locations, demands)?;
819    problems.push(Box::new(BinaryVehicleRoutingProblem::new(vrp))
820        as Box<
821            dyn OptimizationProblem<Solution = Vec<i8>, ObjectiveValue = f64>,
822        >);
823
824    // Problem 2: Medium VRP with larger instance
825    if size >= 8 {
826        let mut large_locations = vec![(0.0, 0.0)]; // Depot
827        let mut large_demands = vec![0.0]; // Depot demand
828
829        for i in 0..size {
830            let angle = 2.0 * std::f64::consts::PI * i as f64 / size as f64;
831            let radius = (i as f64).mul_add(0.1, 1.0);
832            large_locations.push((radius * angle.cos(), radius * angle.sin()));
833            large_demands.push((i as f64).mul_add(2.0, 5.0));
834        }
835
836        let large_capacities = vec![50.0; 3];
837        let large_vrp =
838            VehicleRoutingProblem::new(3, large_capacities, large_locations, large_demands)?;
839        problems.push(Box::new(BinaryVehicleRoutingProblem::new(large_vrp))
840            as Box<
841                dyn OptimizationProblem<Solution = Vec<i8>, ObjectiveValue = f64>,
842            >);
843    }
844
845    Ok(problems)
846}
847
848/// Solve VRP using quantum annealing
849pub fn solve_vehicle_routing(
850    problem: &VehicleRoutingProblem,
851    params: Option<AnnealingParams>,
852) -> ApplicationResult<VehicleRoutingSolution> {
853    // Convert to QUBO
854    let (qubo, _var_map) = problem.to_qubo()?;
855
856    // Convert to Ising
857    let ising = IsingModel::from_qubo(&qubo);
858
859    // Set up annealing parameters
860    let annealing_params = params.unwrap_or_else(|| {
861        let mut p = AnnealingParams::default();
862        p.num_sweeps = 15_000;
863        p.num_repetitions = 25;
864        p.initial_temperature = 3.0;
865        p.final_temperature = 0.005;
866        p
867    });
868
869    // Solve with classical annealing
870    let simulator = ClassicalAnnealingSimulator::new(annealing_params)
871        .map_err(|e| ApplicationError::OptimizationError(e.to_string()))?;
872
873    let result = simulator
874        .solve(&ising)
875        .map_err(|e| ApplicationError::OptimizationError(e.to_string()))?;
876
877    // Convert solution back to routing solution
878    VehicleRoutingSolution::from_binary(problem, &result.best_spins)
879}
880
881#[cfg(test)]
882mod tests {
883    use super::*;
884
885    #[test]
886    fn test_vrp_creation() {
887        let locations = vec![(0.0, 0.0), (1.0, 1.0), (2.0, 0.0)];
888        let demands = vec![0.0, 10.0, 15.0];
889        let capacities = vec![30.0];
890
891        let vrp = VehicleRoutingProblem::new(1, capacities, locations, demands)
892            .expect("VehicleRoutingProblem creation should succeed");
893        assert_eq!(vrp.num_vehicles, 1);
894        assert_eq!(vrp.locations.len(), 3);
895    }
896
897    #[test]
898    fn test_distance_calculation() {
899        let locations = vec![(0.0, 0.0), (3.0, 4.0)]; // 3-4-5 triangle
900        let demands = vec![0.0, 10.0];
901        let capacities = vec![20.0];
902
903        let vrp = VehicleRoutingProblem::new(1, capacities, locations, demands)
904            .expect("VehicleRoutingProblem creation should succeed");
905        assert_eq!(vrp.distance_matrix[0][1], 5.0);
906        assert_eq!(vrp.distance_matrix[1][0], 5.0);
907    }
908
909    #[test]
910    fn test_capacity_constraint() {
911        let locations = vec![(0.0, 0.0), (1.0, 1.0), (2.0, 0.0)];
912        let demands = vec![0.0, 10.0, 15.0];
913        let capacities = vec![20.0];
914
915        let vrp = VehicleRoutingProblem::new(1, capacities, locations, demands)
916            .expect("VehicleRoutingProblem creation should succeed");
917
918        let route1 = vec![0, 1, 0]; // Demand: 10
919        let route2 = vec![0, 1, 2, 0]; // Demand: 25
920
921        assert!(vrp.check_capacity_constraint(&route1, 0));
922        assert!(!vrp.check_capacity_constraint(&route2, 0));
923    }
924
925    #[test]
926    fn test_time_windows() {
927        let locations = vec![(0.0, 0.0), (1.0, 0.0), (2.0, 0.0)];
928        let demands = vec![0.0, 10.0, 15.0];
929        let capacities = vec![30.0];
930
931        let mut vrp = VehicleRoutingProblem::new(1, capacities, locations, demands)
932            .expect("VehicleRoutingProblem creation should succeed");
933
934        // Set tight time windows
935        vrp.set_time_windows(vec![(0.0, 100.0), (10.0, 20.0), (50.0, 60.0)])
936            .expect("Setting time windows should succeed");
937        vrp.set_service_times(vec![0.0, 5.0, 5.0])
938            .expect("Setting service times should succeed");
939
940        let route = vec![0, 1, 2, 0];
941        assert!(vrp.check_time_windows(&route));
942    }
943
944    #[test]
945    fn test_supply_chain_creation() {
946        let supply_chain = SupplyChainOptimization::new(
947            2,                            // suppliers
948            3,                            // DCs
949            4,                            // retailers
950            vec![100.0, 150.0],           // supplier capacities
951            vec![80.0, 90.0, 70.0],       // DC capacities
952            vec![20.0, 25.0, 30.0, 15.0], // retailer demands
953        )
954        .expect("SupplyChainOptimization creation should succeed");
955
956        assert_eq!(supply_chain.num_suppliers, 2);
957        assert_eq!(supply_chain.num_distribution_centers, 3);
958        assert_eq!(supply_chain.num_retailers, 4);
959    }
960
961    #[test]
962    fn test_benchmark_problems() {
963        let problems =
964            create_benchmark_problems(8).expect("Creating benchmark problems should succeed");
965        assert_eq!(problems.len(), 2);
966
967        for problem in &problems {
968            assert!(problem.validate().is_ok());
969        }
970    }
971}