1use scirs2_core::ndarray::{Array1, Array2};
6use scirs2_core::random::prelude::*;
7use std::collections::{HashMap, HashSet};
8
9#[derive(Debug, Clone)]
10pub struct Route {
11 pub vehicle_id: usize,
12 pub path: Vec<usize>,
13 pub total_distance: f64,
14 pub total_demand: f64,
15 pub arrival_times: Vec<f64>,
16}
17pub struct SupplyChainOptimizer {
19 network: SupplyChainNetwork,
21 objectives: Vec<SupplyChainObjective>,
23 constraints: SupplyChainConstraints,
25 time_horizon: usize,
27}
28impl SupplyChainOptimizer {
29 pub fn new(network: SupplyChainNetwork, time_horizon: usize) -> Self {
31 Self {
32 network,
33 objectives: vec![SupplyChainObjective::MinimizeCost],
34 constraints: SupplyChainConstraints {
35 enforce_capacity: true,
36 min_service_level: 0.95,
37 max_lead_time: None,
38 safety_stock: HashMap::new(),
39 max_budget: None,
40 },
41 time_horizon,
42 }
43 }
44 pub fn add_objective(mut self, objective: SupplyChainObjective) -> Self {
46 self.objectives.push(objective);
47 self
48 }
49 pub fn with_constraints(mut self, constraints: SupplyChainConstraints) -> Self {
51 self.constraints = constraints;
52 self
53 }
54 pub fn build_qubo(&self) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
56 let mut var_map = HashMap::new();
57 let mut var_idx = 0;
58 for t in 0..self.time_horizon {
59 for s in &self.network.suppliers {
60 for w in &self.network.warehouses {
61 let var_name = format!("x_{}_{}_{}", s.id, w.id, t);
62 var_map.insert(var_name, var_idx);
63 var_idx += 1;
64 }
65 }
66 for w in &self.network.warehouses {
67 for d in &self.network.distribution_centers {
68 let var_name = format!("y_{}_{}_{}", w.id, d.id, t);
69 var_map.insert(var_name, var_idx);
70 var_idx += 1;
71 }
72 }
73 for d in &self.network.distribution_centers {
74 for c in &self.network.customers {
75 let var_name = format!("z_{}_{}_{}", d.id, c.id, t);
76 var_map.insert(var_name, var_idx);
77 var_idx += 1;
78 }
79 }
80 for w in &self.network.warehouses {
81 let var_name = format!("I_{}_{}", w.id, t);
82 var_map.insert(var_name, var_idx);
83 var_idx += 1;
84 }
85 }
86 let n_vars = var_idx;
87 let mut qubo = Array2::zeros((n_vars, n_vars));
88 for objective in &self.objectives {
89 match objective {
90 SupplyChainObjective::MinimizeCost => {
91 self.add_cost_objective(&mut qubo, &var_map)?;
92 }
93 SupplyChainObjective::MinimizeInventory => {
94 self.add_inventory_objective(&mut qubo, &var_map)?;
95 }
96 _ => {}
97 }
98 }
99 self.add_flow_conservation_constraints(&mut qubo, &var_map)?;
100 self.add_capacity_constraints_sc(&mut qubo, &var_map)?;
101 self.add_demand_constraints(&mut qubo, &var_map)?;
102 Ok((qubo, var_map))
103 }
104 fn add_cost_objective(
106 &self,
107 qubo: &mut Array2<f64>,
108 var_map: &HashMap<String, usize>,
109 ) -> Result<(), String> {
110 for link in &self.network.links {
111 for t in 0..self.time_horizon {
112 let var_name = match (&link.from_type, &link.to_type) {
113 (NodeType::Supplier, NodeType::Warehouse) => {
114 format!("x_{}_{}_{}", link.from_id, link.to_id, t)
115 }
116 (NodeType::Warehouse, NodeType::DistributionCenter) => {
117 format!("y_{}_{}_{}", link.from_id, link.to_id, t)
118 }
119 (NodeType::DistributionCenter, NodeType::Customer) => {
120 format!("z_{}_{}_{}", link.from_id, link.to_id, t)
121 }
122 _ => continue,
123 };
124 if let Some(&idx) = var_map.get(&var_name) {
125 qubo[[idx, idx]] += link.cost_per_unit;
126 }
127 }
128 }
129 for w in &self.network.warehouses {
130 for t in 0..self.time_horizon {
131 let var_name = format!("I_{}_{}", w.id, t);
132 if let Some(&idx) = var_map.get(&var_name) {
133 qubo[[idx, idx]] += w.holding_cost;
134 }
135 }
136 }
137 Ok(())
138 }
139 fn add_inventory_objective(
141 &self,
142 qubo: &mut Array2<f64>,
143 var_map: &HashMap<String, usize>,
144 ) -> Result<(), String> {
145 for w in &self.network.warehouses {
146 for t in 0..self.time_horizon {
147 let var_name = format!("I_{}_{}", w.id, t);
148 if let Some(&idx) = var_map.get(&var_name) {
149 qubo[[idx, idx]] += 1.0;
150 }
151 }
152 }
153 Ok(())
154 }
155 fn add_flow_conservation_constraints(
157 &self,
158 _qubo: &mut Array2<f64>,
159 _var_map: &HashMap<String, usize>,
160 ) -> Result<(), String> {
161 let _penalty = 1000.0;
162 for _w in &self.network.warehouses {
163 for _t in 1..self.time_horizon {}
164 }
165 Ok(())
166 }
167 fn add_capacity_constraints_sc(
169 &self,
170 qubo: &mut Array2<f64>,
171 var_map: &HashMap<String, usize>,
172 ) -> Result<(), String> {
173 let penalty = 1000.0;
174 for s in &self.network.suppliers {
175 for t in 0..self.time_horizon {
176 for w in &self.network.warehouses {
177 let var_name = format!("x_{}_{}_{}", s.id, w.id, t);
178 if let Some(&idx) = var_map.get(&var_name) {
179 if s.capacity > 0.0 {
180 qubo[[idx, idx]] += penalty / s.capacity;
181 }
182 }
183 }
184 }
185 }
186 Ok(())
187 }
188 fn add_demand_constraints(
190 &self,
191 qubo: &mut Array2<f64>,
192 var_map: &HashMap<String, usize>,
193 ) -> Result<(), String> {
194 let penalty = 1000.0;
195 for c in &self.network.customers {
196 for t in 0..self.time_horizon.min(c.demand.len()) {
197 for d in &self.network.distribution_centers {
198 let var_name = format!("z_{}_{}_{}", d.id, c.id, t);
199 if let Some(&idx) = var_map.get(&var_name) {
200 qubo[[idx, idx]] -= penalty * c.demand[t] * c.priority;
201 }
202 }
203 }
204 }
205 Ok(())
206 }
207}
208#[derive(Debug, Clone)]
209pub struct DistributionCenter {
210 pub id: usize,
211 pub capacity: f64,
212 pub processing_cost: f64,
213 pub location: (f64, f64),
214}
215#[derive(Debug, Clone)]
216pub struct StorageLocation {
217 pub id: usize,
218 pub position: (usize, usize, usize),
219 pub capacity: f64,
220 pub item_type: Option<String>,
221 pub accessibility: f64,
222}
223#[derive(Debug, Clone)]
224pub struct WarehouseLayout {
225 pub rows: usize,
227 pub cols: usize,
228 pub levels: usize,
229 pub locations: Vec<StorageLocation>,
231 pub picking_stations: Vec<(usize, usize)>,
233 pub distance_type: DistanceType,
235}
236#[derive(Debug, Clone)]
237pub struct Warehouse {
238 pub id: usize,
239 pub capacity: f64,
240 pub holding_cost: f64,
241 pub fixed_cost: f64,
242 pub location: (f64, f64),
243}
244#[derive(Debug, Clone)]
245pub struct OrderItem {
246 pub sku: String,
247 pub quantity: usize,
248 pub location: Option<usize>,
249}
250#[derive(Debug, Clone)]
251pub struct Order {
252 pub id: usize,
253 pub items: Vec<OrderItem>,
254 pub priority: f64,
255 pub due_time: f64,
256}
257#[derive(Debug, Clone)]
258pub struct PickingRoute {
259 pub locations: Vec<usize>,
260 pub sequence: Vec<usize>,
261 pub total_distance: f64,
262}
263pub struct WarehouseOptimizer {
265 layout: WarehouseLayout,
267 policies: StoragePolicies,
269 orders: Vec<Order>,
271 goals: WarehouseGoals,
273}
274impl WarehouseOptimizer {
275 pub const fn new(
277 layout: WarehouseLayout,
278 policies: StoragePolicies,
279 orders: Vec<Order>,
280 ) -> Self {
281 Self {
282 layout,
283 policies,
284 orders,
285 goals: WarehouseGoals {
286 minimize_distance: true,
287 minimize_time: false,
288 balance_workload: false,
289 maximize_utilization: false,
290 },
291 }
292 }
293 pub fn optimize_picking(&self) -> Result<PickingPlan, String> {
295 match &self.policies.picking {
296 PickingPolicy::Batch { size } => self.optimize_batch_picking(*size),
297 _ => self.optimize_single_picking(),
298 }
299 }
300 fn optimize_batch_picking(&self, batch_size: usize) -> Result<PickingPlan, String> {
302 let mut batches = Vec::new();
303 let mut remaining_orders = self.orders.clone();
304 while !remaining_orders.is_empty() {
305 let batch_orders: Vec<_> = remaining_orders
306 .drain(..batch_size.min(remaining_orders.len()))
307 .collect();
308 let route = self.optimize_picking_route(&batch_orders)?;
309 let estimated_time = self.estimate_picking_time(&route);
310 batches.push(Batch {
311 orders: batch_orders,
312 route,
313 estimated_time,
314 });
315 }
316 let total_distance = batches.iter().map(|b| b.route.total_distance).sum();
317 let total_time = batches.iter().map(|b| b.estimated_time).sum();
318 Ok(PickingPlan {
319 batches,
320 total_distance,
321 total_time,
322 })
323 }
324 fn optimize_single_picking(&self) -> Result<PickingPlan, String> {
326 let mut batches = Vec::new();
327 for order in &self.orders {
328 let route = self.optimize_picking_route(&[order.clone()])?;
329 let estimated_time = self.estimate_picking_time(&route);
330 batches.push(Batch {
331 orders: vec![order.clone()],
332 route,
333 estimated_time,
334 });
335 }
336 let total_distance = batches.iter().map(|b| b.route.total_distance).sum();
337 let total_time = batches.iter().map(|b| b.estimated_time).sum();
338 Ok(PickingPlan {
339 batches,
340 total_distance,
341 total_time,
342 })
343 }
344 fn optimize_picking_route(&self, orders: &[Order]) -> Result<PickingRoute, String> {
346 let mut pick_locations = Vec::new();
347 for order in orders {
348 for item in &order.items {
349 if let Some(loc) = item.location {
350 pick_locations.push(loc);
351 }
352 }
353 }
354 pick_locations.sort_unstable();
355 pick_locations.dedup();
356 let n = pick_locations.len() + 1;
357 let mut distances = Array2::zeros((n, n));
358 let station = self.layout.picking_stations[0];
359 for (i, &loc) in pick_locations.iter().enumerate() {
360 let loc_pos = self.layout.locations[loc].position;
361 distances[[0, i + 1]] = self.calculate_distance((station.0, station.1, 0), loc_pos);
362 distances[[i + 1, 0]] = distances[[0, i + 1]];
363 }
364 for (i, &loc1) in pick_locations.iter().enumerate() {
365 for (j, &loc2) in pick_locations.iter().enumerate() {
366 if i != j {
367 let pos1 = self.layout.locations[loc1].position;
368 let pos2 = self.layout.locations[loc2].position;
369 distances[[i + 1, j + 1]] = self.calculate_distance(pos1, pos2);
370 }
371 }
372 }
373 let tsp = TSPOptimizer::new(distances)?;
374 let (_qubo, _var_map) = tsp.build_qubo()?;
375 let sequence = (0..pick_locations.len()).collect();
376 Ok(PickingRoute {
377 locations: pick_locations,
378 sequence,
379 total_distance: 0.0,
380 })
381 }
382 fn calculate_distance(&self, pos1: (usize, usize, usize), pos2: (usize, usize, usize)) -> f64 {
384 match self.layout.distance_type {
385 DistanceType::Manhattan => {
386 ((pos1.0 as i32 - pos2.0 as i32).abs()
387 + (pos1.1 as i32 - pos2.1 as i32).abs()
388 + (pos1.2 as i32 - pos2.2 as i32).abs()) as f64
389 }
390 DistanceType::Euclidean => (pos1.2 as f64 - pos2.2 as f64)
391 .mul_add(
392 pos1.2 as f64 - pos2.2 as f64,
393 (pos1.1 as f64 - pos2.1 as f64).mul_add(
394 pos1.1 as f64 - pos2.1 as f64,
395 (pos1.0 as f64 - pos2.0 as f64).powi(2),
396 ),
397 )
398 .sqrt(),
399 _ => 0.0,
400 }
401 }
402 fn estimate_picking_time(&self, route: &PickingRoute) -> f64 {
404 let travel_time = route.total_distance / 1.0;
405 let pick_time = route.locations.len() as f64 * 10.0;
406 travel_time + pick_time
407 }
408}
409#[derive(Debug, Clone)]
410pub struct Customer {
411 pub id: usize,
412 pub demand: Array1<f64>,
413 pub priority: f64,
414 pub location: (f64, f64),
415}
416#[derive(Debug, Clone)]
417pub enum ConstraintViolation {
418 CapacityExceeded {
419 vehicle: usize,
420 demand: f64,
421 capacity: f64,
422 },
423 TimeWindowViolation {
424 location: usize,
425 arrival: f64,
426 window_end: f64,
427 },
428 CustomersNotVisited {
429 missing: usize,
430 },
431}
432#[derive(Debug, Clone)]
433pub struct TransportLink {
434 pub from_type: NodeType,
435 pub from_id: usize,
436 pub to_type: NodeType,
437 pub to_id: usize,
438 pub capacity: f64,
439 pub cost_per_unit: f64,
440 pub lead_time: usize,
441}
442#[derive(Debug, Clone)]
443pub struct ValidationResult {
444 pub is_valid: bool,
445 pub violations: Vec<ConstraintViolation>,
446 pub total_distance: f64,
447 pub num_vehicles_used: usize,
448}
449#[derive(Debug, Clone)]
450pub struct Supplier {
451 pub id: usize,
452 pub capacity: f64,
453 pub cost_per_unit: f64,
454 pub lead_time: usize,
455 pub reliability: f64,
456}
457#[derive(Debug, Clone)]
458pub struct WarehouseGoals {
459 pub minimize_distance: bool,
461 pub minimize_time: bool,
463 pub balance_workload: bool,
465 pub maximize_utilization: bool,
467}
468#[derive(Debug, Clone)]
469pub enum ReplenishmentPolicy {
470 FixedQuantity { quantity: f64 },
472 ReorderPoint { level: f64 },
474 Periodic { interval: usize },
476}
477pub struct VehicleRoutingOptimizer {
479 distance_matrix: Array2<f64>,
481 vehicle_capacity: f64,
483 demands: Array1<f64>,
485 time_windows: Option<Vec<TimeWindow>>,
487 num_vehicles: usize,
489 depot: usize,
491 variant: VRPVariant,
493}
494impl VehicleRoutingOptimizer {
495 pub fn new(
497 distance_matrix: Array2<f64>,
498 vehicle_capacity: f64,
499 demands: Array1<f64>,
500 num_vehicles: usize,
501 ) -> Result<Self, String> {
502 if distance_matrix.shape()[0] != distance_matrix.shape()[1] {
503 return Err("Distance matrix must be square".to_string());
504 }
505 if distance_matrix.shape()[0] != demands.len() {
506 return Err("Distance matrix and demands size mismatch".to_string());
507 }
508 Ok(Self {
509 distance_matrix,
510 vehicle_capacity,
511 demands,
512 time_windows: None,
513 num_vehicles,
514 depot: 0,
515 variant: VRPVariant::CVRP,
516 })
517 }
518 pub fn with_variant(mut self, variant: VRPVariant) -> Self {
520 self.variant = variant;
521 self
522 }
523 pub fn with_time_windows(mut self, time_windows: Vec<TimeWindow>) -> Self {
525 self.time_windows = Some(time_windows);
526 self.variant = VRPVariant::VRPTW;
527 self
528 }
529 pub fn build_qubo(&self) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
531 let n_locations = self.distance_matrix.shape()[0];
532 let _n_customers = n_locations - 1;
533 let n_vars = self.num_vehicles * n_locations * n_locations;
534 let mut qubo = Array2::zeros((n_vars, n_vars));
535 let mut var_map = HashMap::new();
536 let mut var_idx = 0;
537 for v in 0..self.num_vehicles {
538 for i in 0..n_locations {
539 for j in 0..n_locations {
540 let var_name = format!("x_{v}_{i}_{j}");
541 var_map.insert(var_name, var_idx);
542 var_idx += 1;
543 }
544 }
545 }
546 self.add_distance_objective(&mut qubo, &var_map)?;
547 match &self.variant {
548 VRPVariant::CVRP => {
549 self.add_cvrp_constraints(&mut qubo, &var_map)?;
550 }
551 VRPVariant::VRPTW => {
552 self.add_cvrp_constraints(&mut qubo, &var_map)?;
553 self.add_time_window_constraints(&mut qubo, &var_map)?;
554 }
555 VRPVariant::MDVRP { depots } => {
556 self.add_mdvrp_constraints(&mut qubo, &var_map, depots)?;
557 }
558 _ => {
559 self.add_cvrp_constraints(&mut qubo, &var_map)?;
560 }
561 }
562 Ok((qubo, var_map))
563 }
564 fn add_distance_objective(
566 &self,
567 qubo: &mut Array2<f64>,
568 var_map: &HashMap<String, usize>,
569 ) -> Result<(), String> {
570 for v in 0..self.num_vehicles {
571 for i in 0..self.distance_matrix.shape()[0] {
572 for j in 0..self.distance_matrix.shape()[1] {
573 if i != j {
574 let var_name = format!("x_{v}_{i}_{j}");
575 if let Some(&var_idx) = var_map.get(&var_name) {
576 qubo[[var_idx, var_idx]] += self.distance_matrix[[i, j]];
577 }
578 }
579 }
580 }
581 }
582 Ok(())
583 }
584 fn add_cvrp_constraints(
586 &self,
587 qubo: &mut Array2<f64>,
588 var_map: &HashMap<String, usize>,
589 ) -> Result<(), String> {
590 let penalty = 1000.0;
591 let n_locations = self.distance_matrix.shape()[0];
592 for j in 1..n_locations {
593 for v1 in 0..self.num_vehicles {
594 for i1 in 0..n_locations {
595 if i1 != j {
596 let var1 = format!("x_{v1}_{i1}_{j}");
597 if let Some(&idx1) = var_map.get(&var1) {
598 qubo[[idx1, idx1]] -= 2.0 * penalty;
599 for v2 in 0..self.num_vehicles {
600 for i2 in 0..n_locations {
601 if i2 != j {
602 let var2 = format!("x_{v2}_{i2}_{j}");
603 if let Some(&idx2) = var_map.get(&var2) {
604 qubo[[idx1, idx2]] += penalty;
605 }
606 }
607 }
608 }
609 }
610 }
611 }
612 }
613 }
614 for v in 0..self.num_vehicles {
615 for i in 0..n_locations {
616 for j1 in 0..n_locations {
617 if j1 != i {
618 let var_out = format!("x_{v}_{i}_{j1}");
619 if let Some(&idx_out) = var_map.get(&var_out) {
620 for j2 in 0..n_locations {
621 if j2 != i {
622 let var_in = format!("x_{v}_{j2}_{i}");
623 if let Some(&idx_in) = var_map.get(&var_in) {
624 qubo[[idx_out, idx_in]] -= penalty;
625 }
626 }
627 }
628 }
629 }
630 }
631 }
632 }
633 self.add_capacity_constraints(qubo, var_map, penalty)?;
634 for v in 0..self.num_vehicles {
635 for j in 1..n_locations {
636 let var = format!("x_{}_{}_{}", v, 0, j);
637 if let Some(&idx) = var_map.get(&var) {
638 qubo[[idx, idx]] -= penalty * 0.1;
639 }
640 }
641 }
642 Ok(())
643 }
644 fn add_capacity_constraints(
646 &self,
647 qubo: &mut Array2<f64>,
648 var_map: &HashMap<String, usize>,
649 penalty: f64,
650 ) -> Result<(), String> {
651 let n_locations = self.distance_matrix.shape()[0];
652 for v in 0..self.num_vehicles {
653 let route_demand = 0.0;
654 for i in 0..n_locations {
655 for j in 1..n_locations {
656 let var = format!("x_{v}_{i}_{j}");
657 if let Some(&idx) = var_map.get(&var) {
658 if route_demand + self.demands[j] > self.vehicle_capacity {
659 qubo[[idx, idx]] += penalty * 10.0;
660 }
661 }
662 }
663 }
664 }
665 Ok(())
666 }
667 fn add_time_window_constraints(
669 &self,
670 qubo: &mut Array2<f64>,
671 var_map: &HashMap<String, usize>,
672 ) -> Result<(), String> {
673 if let Some(time_windows) = &self.time_windows {
674 let penalty = 1000.0;
675 let n_locations = self.distance_matrix.shape()[0];
676 for v in 0..self.num_vehicles {
677 for i in 0..n_locations {
678 for j in 0..n_locations {
679 if i != j {
680 let var = format!("x_{v}_{i}_{j}");
681 if let Some(&idx) = var_map.get(&var) {
682 let travel_time = self.distance_matrix[[i, j]];
683 if j < time_windows.len() {
684 let earliest_arrival = if i < time_windows.len() {
685 time_windows[i].start
686 + time_windows[i].service_time
687 + travel_time
688 } else {
689 travel_time
690 };
691 if earliest_arrival > time_windows[j].end {
692 qubo[[idx, idx]] += penalty * 5.0;
693 }
694 }
695 }
696 }
697 }
698 }
699 }
700 }
701 Ok(())
702 }
703 fn add_mdvrp_constraints(
705 &self,
706 qubo: &mut Array2<f64>,
707 var_map: &HashMap<String, usize>,
708 depots: &[usize],
709 ) -> Result<(), String> {
710 let penalty = 1000.0;
711 for v in 0..self.num_vehicles {
712 for &depot in depots {
713 for j in 0..self.distance_matrix.shape()[0] {
714 if !depots.contains(&j) {
715 let var = format!("x_{v}_{depot}_{j}");
716 if let Some(&idx) = var_map.get(&var) {
717 qubo[[idx, idx]] -= penalty * 0.1;
718 }
719 }
720 }
721 }
722 }
723 Ok(())
724 }
725 pub fn decode_solution(&self, solution: &HashMap<String, bool>) -> Vec<Route> {
727 let mut routes = Vec::new();
728 let n_locations = self.distance_matrix.shape()[0];
729 for v in 0..self.num_vehicles {
730 let mut route = Route {
731 vehicle_id: v,
732 path: vec![self.depot],
733 total_distance: 0.0,
734 total_demand: 0.0,
735 arrival_times: vec![0.0],
736 };
737 let mut current = self.depot;
738 let mut visited = HashSet::new();
739 visited.insert(self.depot);
740 loop {
741 let mut next_location = None;
742 for j in 0..n_locations {
743 if !visited.contains(&j) {
744 let var = format!("x_{v}_{current}_{j}");
745 if *solution.get(&var).unwrap_or(&false) {
746 next_location = Some(j);
747 break;
748 }
749 }
750 }
751 if let Some(next) = next_location {
752 route.path.push(next);
753 route.total_distance += self.distance_matrix[[current, next]];
754 route.total_demand += self.demands[next];
755 let arrival_time = route.arrival_times.last().copied().unwrap_or(0.0)
756 + self.distance_matrix[[current, next]];
757 route.arrival_times.push(arrival_time);
758 visited.insert(next);
759 current = next;
760 } else {
761 break;
762 }
763 }
764 if route.path.len() > 1 {
765 route.path.push(self.depot);
766 route.total_distance += self.distance_matrix[[current, self.depot]];
767 route.arrival_times.push(
768 route.arrival_times.last().copied().unwrap_or(0.0)
769 + self.distance_matrix[[current, self.depot]],
770 );
771 routes.push(route);
772 }
773 }
774 routes
775 }
776 pub fn validate_solution(&self, routes: &[Route]) -> ValidationResult {
778 let mut violations = Vec::new();
779 let mut visited_customers = HashSet::new();
780 for route in routes {
781 if route.total_demand > self.vehicle_capacity {
782 violations.push(ConstraintViolation::CapacityExceeded {
783 vehicle: route.vehicle_id,
784 demand: route.total_demand,
785 capacity: self.vehicle_capacity,
786 });
787 }
788 if let Some(time_windows) = &self.time_windows {
789 for (i, &loc) in route.path.iter().enumerate() {
790 if loc < time_windows.len()
791 && i < route.arrival_times.len()
792 && route.arrival_times[i] > time_windows[loc].end
793 {
794 violations.push(ConstraintViolation::TimeWindowViolation {
795 location: loc,
796 arrival: route.arrival_times[i],
797 window_end: time_windows[loc].end,
798 });
799 }
800 }
801 }
802 for &loc in &route.path {
803 if loc != self.depot {
804 visited_customers.insert(loc);
805 }
806 }
807 }
808 let n_customers = self.distance_matrix.shape()[0] - 1;
809 if visited_customers.len() < n_customers {
810 violations.push(ConstraintViolation::CustomersNotVisited {
811 missing: n_customers - visited_customers.len(),
812 });
813 }
814 ValidationResult {
815 is_valid: violations.is_empty(),
816 violations,
817 total_distance: routes.iter().map(|r| r.total_distance).sum(),
818 num_vehicles_used: routes.len(),
819 }
820 }
821}
822#[derive(Debug, Clone)]
823pub struct PickingPlan {
824 pub batches: Vec<Batch>,
825 pub total_distance: f64,
826 pub total_time: f64,
827}
828#[derive(Debug, Clone)]
829pub struct SupplyChainConstraints {
830 pub enforce_capacity: bool,
832 pub min_service_level: f64,
834 pub max_lead_time: Option<usize>,
836 pub safety_stock: HashMap<usize, f64>,
838 pub max_budget: Option<f64>,
840}
841#[derive(Debug, Clone)]
842pub struct Batch {
843 pub orders: Vec<Order>,
844 pub route: PickingRoute,
845 pub estimated_time: f64,
846}
847#[derive(Debug, Clone)]
848pub enum DistanceType {
849 Manhattan,
850 Euclidean,
851 Rectilinear,
852 Custom,
853}
854#[derive(Debug, Clone)]
855pub struct VehicleType {
856 capacity: f64,
858 fixed_cost: f64,
860 distance_cost: f64,
862 max_distance: Option<f64>,
864 speed_factor: f64,
866}
867#[derive(Debug, Clone)]
868pub struct StoragePolicies {
869 pub assignment: AssignmentPolicy,
871 pub replenishment: ReplenishmentPolicy,
873 pub picking: PickingPolicy,
875}
876#[derive(Debug, Clone)]
877pub enum TSPVariant {
878 Standard,
880 ATSP,
882 TSPTW { time_windows: Vec<TimeWindow> },
884 MTSP { num_salesmen: usize },
886 PCTSP { prizes: Vec<f64>, min_prize: f64 },
888}
889#[derive(Debug, Clone)]
890pub enum VRPVariant {
891 CVRP,
893 VRPTW,
895 MDVRP { depots: Vec<usize> },
897 VRPPD {
899 pickups: Vec<usize>,
900 deliveries: Vec<usize>,
901 },
902 VRPB { backhaul_customers: Vec<usize> },
904 HVRP { vehicle_types: Vec<VehicleType> },
906}
907pub struct TSPOptimizer {
909 distance_matrix: Array2<f64>,
911 variant: TSPVariant,
913 subtour_method: SubtourElimination,
915}
916impl TSPOptimizer {
917 pub fn new(distance_matrix: Array2<f64>) -> Result<Self, String> {
919 if distance_matrix.shape()[0] != distance_matrix.shape()[1] {
920 return Err("Distance matrix must be square".to_string());
921 }
922 Ok(Self {
923 distance_matrix,
924 variant: TSPVariant::Standard,
925 subtour_method: SubtourElimination::MillerTuckerZemlin,
926 })
927 }
928 pub fn with_variant(mut self, variant: TSPVariant) -> Self {
930 self.variant = variant;
931 self
932 }
933 pub fn build_qubo(&self) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
935 let n = self.distance_matrix.shape()[0];
936 match &self.variant {
937 TSPVariant::Standard => self.build_standard_tsp_qubo(n),
938 TSPVariant::MTSP { num_salesmen } => self.build_mtsp_qubo(n, *num_salesmen),
939 _ => self.build_standard_tsp_qubo(n),
940 }
941 }
942 fn build_standard_tsp_qubo(
944 &self,
945 n: usize,
946 ) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
947 let n_vars = n * n;
948 let mut qubo = Array2::zeros((n_vars, n_vars));
949 let mut var_map = HashMap::new();
950 for i in 0..n {
951 for t in 0..n {
952 let var_name = format!("x_{i}_{t}");
953 var_map.insert(var_name, i * n + t);
954 }
955 }
956 let penalty = 1000.0;
957 for t in 0..n - 1 {
958 for i in 0..n {
959 for j in 0..n {
960 if i != j {
961 let var1 = i * n + t;
962 let var2 = j * n + (t + 1);
963 qubo[[var1, var2]] += self.distance_matrix[[i, j]];
964 }
965 }
966 }
967 }
968 for i in 0..n {
969 for t1 in 0..n {
970 let idx1 = i * n + t1;
971 qubo[[idx1, idx1]] -= 2.0 * penalty;
972 for t2 in 0..n {
973 let idx2 = i * n + t2;
974 qubo[[idx1, idx2]] += penalty;
975 }
976 }
977 }
978 for t in 0..n {
979 for i1 in 0..n {
980 let idx1 = i1 * n + t;
981 qubo[[idx1, idx1]] -= 2.0 * penalty;
982 for i2 in 0..n {
983 let idx2 = i2 * n + t;
984 qubo[[idx1, idx2]] += penalty;
985 }
986 }
987 }
988 Ok((qubo, var_map))
989 }
990 fn build_mtsp_qubo(
992 &self,
993 n: usize,
994 num_salesmen: usize,
995 ) -> Result<(Array2<f64>, HashMap<String, usize>), String> {
996 let n_vars = num_salesmen * n * n;
997 let qubo = Array2::zeros((n_vars, n_vars));
998 let mut var_map = HashMap::new();
999 for s in 0..num_salesmen {
1000 for i in 0..n {
1001 for t in 0..n {
1002 let var_name = format!("x_{s}_{i}_{t}");
1003 var_map.insert(var_name, s * n * n + i * n + t);
1004 }
1005 }
1006 }
1007 Ok((qubo, var_map))
1008 }
1009 pub fn decode_solution(&self, solution: &HashMap<String, bool>) -> Vec<usize> {
1011 let n = self.distance_matrix.shape()[0];
1012 let mut tour = vec![0; n];
1013 for i in 0..n {
1014 for t in 0..n {
1015 let var_name = format!("x_{i}_{t}");
1016 if *solution.get(&var_name).unwrap_or(&false) {
1017 tour[t] = i;
1018 }
1019 }
1020 }
1021 tour
1022 }
1023}
1024#[derive(Debug, Clone)]
1025pub struct SupplyChainNetwork {
1026 pub suppliers: Vec<Supplier>,
1028 pub warehouses: Vec<Warehouse>,
1030 pub distribution_centers: Vec<DistributionCenter>,
1032 pub customers: Vec<Customer>,
1034 pub links: Vec<TransportLink>,
1036}
1037#[derive(Debug, Clone, PartialEq, Eq)]
1038pub enum NodeType {
1039 Supplier,
1040 Warehouse,
1041 DistributionCenter,
1042 Customer,
1043}
1044#[derive(Debug, Clone)]
1045pub enum PickingPolicy {
1046 Single,
1048 Batch { size: usize },
1050 Zone { zones: Vec<Vec<usize>> },
1052 Wave { interval: usize },
1054}
1055#[derive(Debug, Clone)]
1056pub enum SubtourElimination {
1057 MillerTuckerZemlin,
1059 DantzigFulkersonJohnson,
1061 FlowBased,
1063 Lazy,
1065}
1066#[derive(Debug, Clone)]
1067pub enum AssignmentPolicy {
1068 Random,
1070 ABC {
1072 a_locations: Vec<usize>,
1073 b_locations: Vec<usize>,
1074 c_locations: Vec<usize>,
1075 },
1076 Dedicated,
1078 ClassBased,
1080}
1081#[derive(Debug, Clone)]
1082pub enum SupplyChainObjective {
1083 MinimizeCost,
1085 MinimizeDeliveryTime,
1087 MaximizeServiceLevel,
1089 MinimizeInventory,
1091 BalanceWorkload,
1093}
1094pub struct VehicleRoutingProblem {
1096 pub optimizer: VehicleRoutingOptimizer,
1097}
1098impl VehicleRoutingProblem {
1099 pub const fn new(optimizer: VehicleRoutingOptimizer) -> Self {
1100 Self { optimizer }
1101 }
1102 pub fn evaluate_continuous(&self, x: &Array1<f64>) -> f64 {
1104 let n_locations = self.optimizer.distance_matrix.shape()[0];
1105 let n_vars = self.optimizer.num_vehicles * n_locations * n_locations;
1106 if x.len() != n_vars {
1107 return f64::INFINITY;
1108 }
1109 let mut energy = 0.0;
1110 let mut var_idx = 0;
1111 for _v in 0..self.optimizer.num_vehicles {
1112 for i in 0..n_locations {
1113 for j in 0..n_locations {
1114 if i != j {
1115 let decision = if x[var_idx] > 0.5 { 1.0 } else { 0.0 };
1116 energy += decision * self.optimizer.distance_matrix[[i, j]];
1117 }
1118 var_idx += 1;
1119 }
1120 }
1121 }
1122 energy += self.calculate_constraint_penalties(x);
1123 energy
1124 }
1125 fn calculate_constraint_penalties(&self, x: &Array1<f64>) -> f64 {
1126 let penalty = 1000.0;
1127 let mut total_penalty = 0.0;
1128 let n_locations = self.optimizer.distance_matrix.shape()[0];
1129 for j in 1..n_locations {
1130 let mut visits = 0.0;
1131 let mut var_idx = 0;
1132 for _v in 0..self.optimizer.num_vehicles {
1133 for i in 0..n_locations {
1134 if i != j {
1135 let decision = if x[var_idx + i * n_locations + j] > 0.5 {
1136 1.0
1137 } else {
1138 0.0
1139 };
1140 visits += decision;
1141 }
1142 }
1143 var_idx += n_locations * n_locations;
1144 }
1145 total_penalty += penalty * (visits - 1.0f64).abs();
1146 }
1147 total_penalty
1148 }
1149}
1150#[derive(Debug, Clone)]
1151pub struct TimeWindow {
1152 pub start: f64,
1154 pub end: f64,
1156 pub service_time: f64,
1158}
1159pub struct BinaryVehicleRoutingProblem {
1161 inner: VehicleRoutingProblem,
1162}
1163impl BinaryVehicleRoutingProblem {
1164 pub const fn new(optimizer: VehicleRoutingOptimizer) -> Self {
1165 Self {
1166 inner: VehicleRoutingProblem::new(optimizer),
1167 }
1168 }
1169 pub fn num_variables(&self) -> usize {
1171 let n_locations = self.inner.optimizer.distance_matrix.shape()[0];
1172 self.inner.optimizer.num_vehicles * n_locations * n_locations
1173 }
1174 pub fn evaluate_binary(&self, solution: &[i8]) -> f64 {
1176 let x: Array1<f64> = solution.iter().map(|&b| b as f64).collect();
1177 self.inner.evaluate_continuous(&x)
1178 }
1179 pub fn random_solution(&self) -> Vec<i8> {
1181 let mut rng = thread_rng();
1182 let n_vars = self.num_variables();
1183 (0..n_vars)
1184 .map(|_| i8::from(rng.random::<f64>() > 0.8))
1185 .collect()
1186 }
1187 pub fn decode_binary_solution(&self, solution: &[i8]) -> Vec<Route> {
1189 let mut bool_solution = HashMap::new();
1190 let n_locations = self.inner.optimizer.distance_matrix.shape()[0];
1191 let mut var_idx = 0;
1192 for v in 0..self.inner.optimizer.num_vehicles {
1193 for i in 0..n_locations {
1194 for j in 0..n_locations {
1195 let var_name = format!("x_{v}_{i}_{j}");
1196 bool_solution.insert(var_name, solution[var_idx] == 1);
1197 var_idx += 1;
1198 }
1199 }
1200 }
1201 self.inner.optimizer.decode_solution(&bool_solution)
1202 }
1203}