1#![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
14pub struct VehicleRoutingOptimizer {
16 distance_matrix: Array2<f64>,
18 vehicle_capacity: f64,
20 demands: Array1<f64>,
22 time_windows: Option<Vec<TimeWindow>>,
24 num_vehicles: usize,
26 depot: usize,
28 variant: VRPVariant,
30}
31
32#[derive(Debug, Clone)]
33pub struct TimeWindow {
34 start: f64,
36 end: f64,
38 service_time: f64,
40}
41
42#[derive(Debug, Clone)]
43pub enum VRPVariant {
44 CVRP,
46 VRPTW,
48 MDVRP { depots: Vec<usize> },
50 VRPPD {
52 pickups: Vec<usize>,
53 deliveries: Vec<usize>,
54 },
55 VRPB { backhaul_customers: Vec<usize> },
57 HVRP { vehicle_types: Vec<VehicleType> },
59}
60
61#[derive(Debug, Clone)]
62pub struct VehicleType {
63 capacity: f64,
65 fixed_cost: f64,
67 distance_cost: f64,
69 max_distance: Option<f64>,
71 speed_factor: f64,
73}
74
75impl VehicleRoutingOptimizer {
76 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 pub fn with_variant(mut self, variant: VRPVariant) -> Self {
104 self.variant = variant;
105 self
106 }
107
108 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 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; 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 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 self.add_distance_objective(&mut qubo, &var_map)?;
140
141 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 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 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 for j in 1..n_locations {
194 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 qubo[[idx1, idx1]] -= 2.0 * penalty;
203
204 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 for v in 0..self.num_vehicles {
223 for i in 0..n_locations {
224 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 self.add_capacity_constraints(qubo, var_map, penalty)?;
245
246 for v in 0..self.num_vehicles {
248 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; }
254 }
255 }
256
257 Ok(())
258 }
259
260 fn add_capacity_constraints(
262 &self,
263 qubo: &mut Array2<f64>,
264 var_map: &HashMap<String, usize>,
265 penalty: f64,
266 ) -> Result<(), String> {
267 let n_locations = self.distance_matrix.shape()[0];
269
270 for v in 0..self.num_vehicles {
271 let route_demand = 0.0;
273
274 for i in 0..n_locations {
275 for j in 1..n_locations {
276 let var = format!("x_{v}_{i}_{j}");
278 if let Some(&idx) = var_map.get(&var) {
279 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 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 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 let travel_time = self.distance_matrix[[i, j]];
310
311 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 fn add_mdvrp_constraints(
337 &self,
338 qubo: &mut Array2<f64>,
339 var_map: &HashMap<String, usize>,
340 depots: &[usize],
341 ) -> Result<(), String> {
342 let penalty = 1000.0;
344
345 for v in 0..self.num_vehicles {
346 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; }
354 }
355 }
356 }
357 }
358
359 Ok(())
360 }
361
362 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 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 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 pub fn validate_solution(&self, routes: &[Route]) -> ValidationResult {
427 let mut violations = Vec::new();
428 let mut visited_customers = HashSet::new();
429
430 for route in routes {
432 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 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 for &loc in &route.path {
459 if loc != self.depot {
460 visited_customers.insert(loc);
461 }
462 }
463 }
464
465 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
516pub trait OptimizationProblem {
518 type Solution;
519
520 fn evaluate(&self, solution: &Self::Solution) -> f64;
522}
523
524pub struct VehicleRoutingProblem {
526 pub optimizer: VehicleRoutingOptimizer,
527}
528
529impl VehicleRoutingProblem {
530 pub const fn new(optimizer: VehicleRoutingOptimizer) -> Self {
531 Self { optimizer }
532 }
533
534 pub fn evaluate_continuous(&self, x: &Array1<f64>) -> f64 {
536 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; }
543
544 let mut energy = 0.0;
545
546 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 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 for j in 1..n_locations {
574 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
599pub 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 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 pub fn evaluate_binary(&self, solution: &[i8]) -> f64 {
619 let x: Array1<f64> = solution.iter().map(|&b| b as f64).collect();
621 self.inner.evaluate_continuous(&x)
622 }
623
624 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 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
661pub fn create_benchmark_problems() -> Vec<BinaryVehicleRoutingProblem> {
663 let mut problems = Vec::new();
664
665 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 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 let cvrptw_optimizer = VehicleRoutingOptimizer::new(
703 small_distances,
704 40.0, 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 }, TimeWindow {
716 start: 10.0,
717 end: 50.0,
718 service_time: 10.0,
719 }, TimeWindow {
721 start: 20.0,
722 end: 60.0,
723 service_time: 8.0,
724 }, TimeWindow {
726 start: 30.0,
727 end: 80.0,
728 service_time: 12.0,
729 }, ]);
731
732 problems.push(BinaryVehicleRoutingProblem::new(cvrptw_optimizer));
733
734 problems
735}
736
737pub struct TSPOptimizer {
739 distance_matrix: Array2<f64>,
741 variant: TSPVariant,
743 subtour_method: SubtourElimination,
745}
746
747#[derive(Debug, Clone)]
748pub enum TSPVariant {
749 Standard,
751 ATSP,
753 TSPTW { time_windows: Vec<TimeWindow> },
755 MTSP { num_salesmen: usize },
757 PCTSP { prizes: Vec<f64>, min_prize: f64 },
759}
760
761#[derive(Debug, Clone)]
762pub enum SubtourElimination {
763 MillerTuckerZemlin,
765 DantzigFulkersonJohnson,
767 FlowBased,
769 Lazy,
771}
772
773impl TSPOptimizer {
774 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 pub fn with_variant(mut self, variant: TSPVariant) -> Self {
789 self.variant = variant;
790 self
791 }
792
793 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 fn build_standard_tsp_qubo(
806 &self,
807 n: usize,
808 ) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
809 let n_vars = n * n;
811 let mut qubo = Array2::zeros((n_vars, n_vars));
812 let mut var_map = HashMap::new();
813
814 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 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 for i in 0..n {
839 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 for t in 0..n {
853 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 fn build_mtsp_qubo(
870 &self,
871 n: usize,
872 num_salesmen: usize,
873 ) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
874 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 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 Ok((qubo, var_map))
893 }
894
895 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
913pub struct SupplyChainOptimizer {
915 network: SupplyChainNetwork,
917 objectives: Vec<SupplyChainObjective>,
919 constraints: SupplyChainConstraints,
921 time_horizon: usize,
923}
924
925#[derive(Debug, Clone)]
926pub struct SupplyChainNetwork {
927 pub suppliers: Vec<Supplier>,
929 pub warehouses: Vec<Warehouse>,
931 pub distribution_centers: Vec<DistributionCenter>,
933 pub customers: Vec<Customer>,
935 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>, 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 MinimizeCost,
996 MinimizeDeliveryTime,
998 MaximizeServiceLevel,
1000 MinimizeInventory,
1002 BalanceWorkload,
1004}
1005
1006#[derive(Debug, Clone)]
1007pub struct SupplyChainConstraints {
1008 pub enforce_capacity: bool,
1010 pub min_service_level: f64,
1012 pub max_lead_time: Option<usize>,
1014 pub safety_stock: HashMap<usize, f64>,
1016 pub max_budget: Option<f64>,
1018}
1019
1020impl SupplyChainOptimizer {
1021 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 pub fn add_objective(mut self, objective: SupplyChainObjective) -> Self {
1039 self.objectives.push(objective);
1040 self
1041 }
1042
1043 pub fn with_constraints(mut self, constraints: SupplyChainConstraints) -> Self {
1045 self.constraints = constraints;
1046 self
1047 }
1048
1049 pub fn build_qubo(&self) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
1051 let mut var_map = HashMap::new();
1058 let mut var_idx = 0;
1059
1060 for t in 0..self.time_horizon {
1062 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 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 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 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 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 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 fn add_cost_objective(
1123 &self,
1124 qubo: &mut Array2<f64>,
1125 var_map: &HashMap<String, usize>,
1126 ) -> Result<(), String> {
1127 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 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 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; }
1174 }
1175 }
1176
1177 Ok(())
1178 }
1179
1180 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 for _w in &self.network.warehouses {
1190 for _t in 1..self.time_horizon {
1191 }
1196 }
1197
1198 Ok(())
1199 }
1200
1201 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 for s in &self.network.suppliers {
1211 for t in 0..self.time_horizon {
1212 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 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 for c in &self.network.customers {
1238 for t in 0..self.time_horizon.min(c.demand.len()) {
1239 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
1254pub struct WarehouseOptimizer {
1256 layout: WarehouseLayout,
1258 policies: StoragePolicies,
1260 orders: Vec<Order>,
1262 goals: WarehouseGoals,
1264}
1265
1266#[derive(Debug, Clone)]
1267pub struct WarehouseLayout {
1268 pub rows: usize,
1270 pub cols: usize,
1271 pub levels: usize,
1272 pub locations: Vec<StorageLocation>,
1274 pub picking_stations: Vec<(usize, usize)>,
1276 pub distance_type: DistanceType,
1278}
1279
1280#[derive(Debug, Clone)]
1281pub struct StorageLocation {
1282 pub id: usize,
1283 pub position: (usize, usize, usize), 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 pub assignment: AssignmentPolicy,
1301 pub replenishment: ReplenishmentPolicy,
1303 pub picking: PickingPolicy,
1305}
1306
1307#[derive(Debug, Clone)]
1308pub enum AssignmentPolicy {
1309 Random,
1311 ABC {
1313 a_locations: Vec<usize>,
1314 b_locations: Vec<usize>,
1315 c_locations: Vec<usize>,
1316 },
1317 Dedicated,
1319 ClassBased,
1321}
1322
1323#[derive(Debug, Clone)]
1324pub enum ReplenishmentPolicy {
1325 FixedQuantity { quantity: f64 },
1327 ReorderPoint { level: f64 },
1329 Periodic { interval: usize },
1331}
1332
1333#[derive(Debug, Clone)]
1334pub enum PickingPolicy {
1335 Single,
1337 Batch { size: usize },
1339 Zone { zones: Vec<Vec<usize>> },
1341 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 pub minimize_distance: bool,
1364 pub minimize_time: bool,
1366 pub balance_workload: bool,
1368 pub maximize_utilization: bool,
1370}
1371
1372impl WarehouseOptimizer {
1373 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 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 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 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 fn optimize_picking_route(&self, orders: &[Order]) -> Result<PickingRoute, String> {
1457 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 pick_locations.sort_unstable();
1470 pick_locations.dedup();
1471
1472 let n = pick_locations.len() + 1; let mut distances = Array2::zeros((n, n));
1475
1476 let station = self.layout.picking_stations[0]; 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 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 let tsp = TSPOptimizer::new(distances)?;
1497 let (_qubo, _var_map) = tsp.build_qubo()?;
1498
1499 let sequence = (0..pick_locations.len()).collect();
1501 Ok(PickingRoute {
1502 locations: pick_locations,
1503 sequence,
1504 total_distance: 0.0, })
1506 }
1507
1508 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 fn estimate_picking_time(&self, route: &PickingRoute) -> f64 {
1531 let travel_time = route.total_distance / 1.0; let pick_time = route.locations.len() as f64 * 10.0; 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); }
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 assert_eq!(binary_vrp.num_variables(), 32); let solution = binary_vrp.random_solution();
1669 assert_eq!(solution.len(), 32);
1670
1671 let energy = binary_vrp.evaluate_binary(&solution);
1673 assert!(energy.is_finite());
1674
1675 let routes = binary_vrp.decode_binary_solution(&solution);
1677 assert!(routes.len() <= 2); }
1679
1680 #[test]
1681 fn test_create_benchmark_problems() {
1682 let problems = create_benchmark_problems();
1683 assert_eq!(problems.len(), 3); 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 assert!(
1694 routes.len() <= 3,
1695 "Problem {i} should have at most 3 routes"
1696 );
1697 }
1698 }
1699}