1use 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#[derive(Debug, Clone)]
19pub struct VehicleRoutingProblem {
20 pub num_vehicles: usize,
22 pub vehicle_capacities: Vec<f64>,
24 pub locations: Vec<(f64, f64)>,
26 pub demands: Vec<f64>,
28 pub distance_matrix: Vec<Vec<f64>>,
30 pub time_windows: Vec<(f64, f64)>,
32 pub service_times: Vec<f64>,
34 pub vehicle_costs: Vec<f64>,
36 pub max_route_duration: f64,
38 pub constraint_penalty: f64,
40}
41
42impl VehicleRoutingProblem {
43 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], service_times: vec![10.0; num_locations], vehicle_costs: vec![1.0; num_vehicles], max_route_duration: 480.0, constraint_penalty: 1000.0,
76 })
77 }
78
79 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 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 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 #[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 #[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 #[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 #[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 current_time += self.distance_matrix[prev_loc][curr_loc];
174
175 let (earliest, latest) = self.time_windows[curr_loc];
177 if current_time > latest {
178 return false;
179 }
180
181 current_time = current_time.max(earliest);
183
184 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 )
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 if self.demands[0] != 0.0 {
227 return Err(ApplicationError::DataValidationError(
228 "Depot must have zero demand".to_string(),
229 ));
230 }
231
232 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 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 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 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 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 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 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 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 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 let mut visited = vec![false; self.locations.len()];
360 visited[0] = true; for route in &solution.routes {
363 for &location in route {
364 if visited[location] {
365 return false; }
367 visited[location] = true;
368 }
369 }
370
371 if !visited[1..].iter().all(|&v| v) {
373 return false;
374 }
375
376 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#[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 let num_locations = self.inner.locations.len();
434 let num_vehicles = self.inner.num_vehicles;
435
436 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 total_cost += self.inner.distance_matrix[i][j]
452 * self.inner.vehicle_costs[vehicle];
453
454 if j > 0 && j < self.inner.demands.len() {
456 vehicle_loads[vehicle] += self.inner.demands[j];
457 }
458 }
459 }
460 }
461 }
462
463 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 Ok(-total_cost)
472 }
473
474 fn is_feasible(&self, solution: &Self::Solution) -> bool {
475 solution.iter().any(|&x| x == 1)
477 }
478}
479
480#[derive(Debug, Clone)]
482pub struct VehicleRoutingSolution {
483 pub routes: Vec<Vec<usize>>,
485 pub total_distance: f64,
487 pub total_cost: f64,
489 pub route_stats: Vec<RouteStatistics>,
491}
492
493#[derive(Debug, Clone)]
495pub struct RouteStatistics {
496 pub vehicle_id: usize,
498 pub distance: f64,
500 pub duration: f64,
502 pub capacity_utilization: f64,
504 pub customers_served: usize,
506 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 let num_customers = problem.locations.len() - 1;
517 let mut routes = vec![Vec::new(); problem.num_vehicles];
518
519 for customer in 1..problem.locations.len() {
521 let vehicle_idx = customer % problem.num_vehicles;
522 routes[vehicle_idx].push(customer);
523 }
524
525 for route in &mut routes {
527 if !route.is_empty() {
528 route.insert(0, 0); route.push(0); }
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); route_stats.push(RouteStatistics {
548 vehicle_id: vehicle_idx,
549 distance,
550 duration,
551 capacity_utilization,
552 customers_served,
553 time_violations: 0, });
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 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#[derive(Debug, Clone)]
698pub struct SupplyChainOptimization {
699 pub num_suppliers: usize,
701 pub num_distribution_centers: usize,
703 pub num_retailers: usize,
705 pub supplier_capacities: Vec<f64>,
707 pub dc_capacities: Vec<f64>,
709 pub retailer_demands: Vec<f64>,
711 pub supplier_dc_costs: Vec<Vec<f64>>,
713 pub dc_retailer_costs: Vec<Vec<f64>>,
715 pub dc_fixed_costs: Vec<f64>,
717 pub service_levels: Vec<f64>,
719}
720
721impl SupplyChainOptimization {
722 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 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 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 #[must_use]
770 pub fn calculate_transportation_cost(&self, solution: &SupplyChainSolution) -> f64 {
771 let mut total_cost = 0.0;
772
773 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 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#[derive(Debug, Clone)]
793pub struct SupplyChainSolution {
794 pub supplier_dc_flows: Vec<Vec<f64>>,
796 pub dc_retailer_flows: Vec<Vec<f64>>,
798 pub dc_open: Vec<bool>,
800 pub total_cost: f64,
802 pub service_level_achieved: Vec<f64>,
804}
805
806pub 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 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 if size >= 8 {
826 let mut large_locations = vec![(0.0, 0.0)]; let mut large_demands = vec![0.0]; 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
848pub fn solve_vehicle_routing(
850 problem: &VehicleRoutingProblem,
851 params: Option<AnnealingParams>,
852) -> ApplicationResult<VehicleRoutingSolution> {
853 let (qubo, _var_map) = problem.to_qubo()?;
855
856 let ising = IsingModel::from_qubo(&qubo);
858
859 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 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 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)]; 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]; let route2 = vec![0, 1, 2, 0]; 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 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, 3, 4, vec![100.0, 150.0], vec![80.0, 90.0, 70.0], vec![20.0, 25.0, 30.0, 15.0], )
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}