mirror of
https://github.com/tig-foundation/tig-monorepo.git
synced 2026-02-21 10:27:49 +08:00
Update vehicle routing challenge to VRPTW.
Some checks failed
Test Workspace / Test Workspace (push) Has been cancelled
Some checks failed
Test Workspace / Test Workspace (push) Has been cancelled
This commit is contained in:
parent
d91882207e
commit
f6447afef3
@ -1,60 +1,92 @@
|
||||
# Capacitated Vehicle Routing
|
||||
# Vehicle Routing with Time Windows
|
||||
The Vehicle Routing Problem with Time Windows (VRPTW) problem is an established extension of [The classic Vehicle Routing Problem (VRP)](https://en.wikipedia.org/wiki/Vehicle_routing_problem), distinguished by the introduction of time window constraints for each customer, adding a temporal dimension to the already intricate tasks of fleet sizing, route planning, and capacity management. These additional constraints make the VRPTW a better reflection of real-world logistical challenges and opens up a broader landscape for algorithmic innovation. The presence of time windows makes the problem computationally more challenging and encourages the exploration of novel algorithmic frameworks.
|
||||
|
||||
[The CVRP, or Capacitated Vehicle Routing Problem, is a well-studied optimisation problem in the field of operations research and transportation logistics](https://en.wikipedia.org/wiki/Vehicle_routing_problem). It involves the task of determining the optimal set of routes a fleet of vehicles should undertake in order to service a given set of customers, while meeting certain constraints.
|
||||
|
||||
In the CVRP, a fleet of identical vehicles based at a central depot must be routed to deliver goods to a set of geographically dispersed customers. Each vehicle has a fixed capacity, and each customer has a known demand for goods. The objective is to determine the minimum total distance that the fleet must travel to deliver goods to all customers and return to the depot, such that:
|
||||
## Challenge Formulation
|
||||
VRPTW involves determining a set of cost-effective routes for a fleet of identical vehicles operating from a single depot to serve a geographically dispersed set of customers. Each vehicle has a fixed capacity and each customer has a known demand for goods and a defined time window during which service must begin. If a vehicle arrives before this time window, it must wait; if it arrives after, service is considered infeasible. The primary objective is to minimise the total distance the fleet must travel to deliver goods to all customers and return to the depot, such that:
|
||||
|
||||
1. Each customer is visited by exactly one vehicle,
|
||||
2. The total demand serviced by each vehicle does not exceed its capacity, and
|
||||
3. Each vehicle starts and ends its route at the depot.
|
||||
2. The total demand serviced by each vehicle does not exceed its capacity,
|
||||
3. Each vehicle starts and ends its route at the depot,
|
||||
4. Vehicles must arrive at each customer at latest by their `due_time`,
|
||||
5. Vehicles wait until a customer's `ready_time` if they arrive early,
|
||||
6. After arriving at a customer, the vehicle only leaves after a `service_time`,
|
||||
7. Each vehicle must return to the depot at latest by the depot's `due_time`, and
|
||||
8. The number of vehicles utilised is less than a set fleet size
|
||||
|
||||
# Example
|
||||
**Notes:**
|
||||
* Each unit of distance takes the equivalent amount of time to travel. i.e. a vehicle will take 100 time units to reach a customer that is 100 distance units away.
|
||||
|
||||
The following is an example of the Capacitated Vehicle Routing problem with configurable difficulty. Two parameters can be adjusted in order to vary the difficulty of the challenge instance:
|
||||
## Example
|
||||
|
||||
- Parameter 1: $num\textunderscore{ }nodes$ is the number of customers (plus 1 depot) which are placed uniformly at random on a grid of 500x500 with the depot at the centre (250, 250).
|
||||
The following is an example of the Vehicle Routing Problem with Time Windows with configurable difficulty. Two parameters can be adjusted in order to vary the difficulty of the challenge instance:
|
||||
|
||||
- Parameter 1: $num\textunderscore{ }nodes$ is the number of customers (plus 1 depot) which are distributed across a grid of 1000x1000 with the depot at the centre (500, 500).
|
||||
- Parameter 2: $better\textunderscore{ }than\textunderscore{ }baseline$ is the factor by which a solution must be better than the baseline value [link TIG challenges for explanation of baseline value].
|
||||
|
||||
The demand of each customer is selected independently and uniformly at random from the range [25, 50]. The maximum capacity of each vehicle is set to 100.
|
||||
Demand of each customer is selected independently and uniformly at random from the range [1, 35]. Each customer is assigned a time window between which they must be serviced. Service duration is set to a fixed value of 10 time units per customer. The maximum capacity of each vehicle is set to 200.
|
||||
|
||||
Consider an example instance with `num_nodes=5` and `better_than_baseline=0.8` with the `baseline=175`:
|
||||
|
||||
Consider an example instance with `num_nodes=8` and `better_than_baseline=0.8` with the `baseline=3875`:
|
||||
|
||||
```
|
||||
demands = [0, 25, 30, 40, 50] # a N array where index (i) is the demand at node i
|
||||
distance_matrix = [ # a NxN symmetric matrix where index (i,j) is distance from node i to node j
|
||||
[0, 10, 20, 30, 40],
|
||||
[10, 0, 15, 25, 35],
|
||||
[20, 15, 0, 20, 30],
|
||||
[30, 25, 20, 0, 10],
|
||||
[40, 35, 30, 10, 0]
|
||||
]
|
||||
max_capacity = 100 # total demand for each route must not exceed this number
|
||||
max_total_distance = baseline*better_than_baseline = 140 # (better_than_baseline * baseline) routes must have total distance under this number to be a solution
|
||||
# A sample generated example
|
||||
CUSTOMER
|
||||
CUST NO. XCOORD. YCOORD. DEMAND READY TIME DUE TIME SERVICE TIME
|
||||
0 500 500 0 0 2318 0
|
||||
1 75 250 10 0 868 10
|
||||
2 940 582 11 825 884 10
|
||||
3 398 419 22 0 682 10
|
||||
4 424 690 6 256 273 10
|
||||
5 143 482 19 674 717 10
|
||||
6 187 292 27 0 1785 10
|
||||
7 382 204 3 0 832 10
|
||||
8 465 274 25 1386 1437 10
|
||||
|
||||
max_capacity = 200 # total demand for each route must not exceed this number
|
||||
fleet_size = 4 # the total number of routes must not exceed this number
|
||||
max_total_distance = baseline*better_than_baseline = 3100 # (better_than_baseline * baseline) routes must have total distance under this number to be a solution
|
||||
```
|
||||
|
||||
The depot is the first node (node 0) with demand 0. The vehicle capacity is set to 100. In this example, routes must have a total distance of 140 or less to be a solution.
|
||||
The depot is the first node (node 0) with demand 0. The vehicle capacity is set to 200 and the fleet capacity to 4. In this example, routes must have a total distance of 3100 or less to be a solution.
|
||||
|
||||
Now consider the following routes:
|
||||
|
||||
```
|
||||
routes = [
|
||||
[0, 3, 4, 0],
|
||||
[0, 1, 2, 0]
|
||||
]
|
||||
Route 1: [0, 6, 1, 7, 8, 0]
|
||||
Route 2: [0, 4, 2, 0]
|
||||
Route 3: [0, 3, 5, 0]
|
||||
```
|
||||
|
||||
When evaluating these routes, each route has demand less than 100, and the total distance is shorter than 140, thereby these routes are a solution:
|
||||
When evaluating these routes, each route has demand less than 200, the number of vehicles used, 3, is less than the fleet capacity, the time windows are not violated, and the total distance is shorter than 3100, thereby these routes are a solution:
|
||||
|
||||
* Route 1:
|
||||
* Depot -> 3 -> 4 -> Depot
|
||||
* Demand = 40 + 50 = 90
|
||||
* Distance = 30 + 10 + 40 = 80
|
||||
* Depot -> 6 -> 1 -> 7 -> 8 -> Depot
|
||||
* Demand = 27 + 10 + 3 + 25 = 65
|
||||
* Distance = 376 + 120 + 310 + 109 + 229 = 1144
|
||||
* Route 2:
|
||||
* Depot -> 1 -> 2 -> Depot
|
||||
* Demand = 25 + 30 = 55
|
||||
* Distance = 10 + 15 + 20 = 45
|
||||
* Total Distance = 80 + 45 = 125
|
||||
* Depot -> 4 -> 2 -> Depot
|
||||
* Demand = 6 + 11 = 17
|
||||
* Distance = 205 + 527 + 448 = 1180
|
||||
* Route 3:
|
||||
* Depot -> 3 -> 5 -> Depot
|
||||
* Demand = 22 + 19 = 41
|
||||
* Distance = 130 + 263 + 357 = 750
|
||||
* Total Distance = 1144 + 1180 + 750 = 3074
|
||||
|
||||
## Our Challenge
|
||||
In TIG, the baseline route is determined by using a greedy algorithm that iteratively selects the closest unvisited node (returning to the depot when necessary) until all drop-offs are made. Please see the challenge code for a precise specification.
|
||||
In TIG, the baseline route is determined by using Solomon's I1 insertion heuristic that iteratively inserts customers into routes based on a cost function that balances distance and time constraints. The routes are built one by one until all customers are served. The goal is to produce a solution better than the baseline’s total distance by at least the specified factor (`better_than_baseline`), while ensuring all VRPTW constraints are satisfied. Please see the challenge code for a precise specification.
|
||||
|
||||
## Applications
|
||||
* **Logistics & Delivery Services:** Optimizes parcel and ship routing by ensuring vehicles meet customer and operational time constraints, reducing operational costs and environmental impact [^1].
|
||||
* **E-Commerce & Last-Mile Delivery:** Enables precise scheduling for tight delivery windows in online retail, boosting customer satisfaction and operational efficiency [^2][^3].
|
||||
* **Healthcare & Home Services:** Schedules mobile healthcare visits within set time slots, enhancing care quality and resource utilization [^4].
|
||||
* **Waste Collection & Disposal:** Streamlines municipal waste collection by routing vehicles to service areas within prescribed time windows, minimizing fuel use and costs. [^5].
|
||||
* **Public Transportation & Paratransit:** Coordinates demand-responsive transit and school bus routes, ensuring timely pickups and drop-offs for users [^6].
|
||||
* **Emergency Response & Disaster Relief:** Routes emergency vehicles and supplies to critical sites within urgent time frames, optimizing response times and resource allocation [^1].
|
||||
|
||||
[^1]: Toth, P., & Vigo, D. (Eds.). (2014). Vehicle routing: problems, methods, and applications. *Society for industrial and applied mathematics*.
|
||||
[^2]: Giuffrida, N., Fajardo-Calderin, J., Masegosa, A. D., Werner, F., Steudter, M., & Pilla, F. (2022). Optimization and machine learning applied to last-mile logistics: A review. *Sustainability, 14*(9), 5329.
|
||||
[^3]: Zennaro, I., Finco, S., Calzavara, M., & Persona, A. (2022). Implementing E-commerce from logistic perspective: Literature review and methodological framework. *Sustainability, 14*(2), 911.
|
||||
[^4]: Du, G., Liang, X., & Sun, C. (2017). Scheduling optimization of home health care service considering patients’ priorities and time windows. *Sustainability, 9*(2), 253.
|
||||
[^5]: Babaee Tirkolaee, E., Abbasian, P., Soltani, M., & Ghaffarian, S. A. (2019). Developing an applied algorithm for multi-trip vehicle routing problem with time windows in urban waste collection: A case study. *Waste Management & Research, 37*(1_suppl), 4-13.
|
||||
[^6]: Huang, A., Dou, Z., Qi, L., & Wang, L. (2020). Flexible route optimization for demand-responsive public transit service. *Journal of Transportation Engineering, Part A: Systems, 146*(12), 04020132.
|
||||
@ -19,6 +19,7 @@ rand = { version = "0.8.5", default-features = false, features = [
|
||||
] }
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde_json = { version = "1.0.113" }
|
||||
statrs = { version = "0.18.0" }
|
||||
|
||||
[features]
|
||||
cuda = ["cudarc"]
|
||||
|
||||
@ -5,6 +5,8 @@ use rand::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{from_value, Map, Value};
|
||||
use statrs::function::erf::{erf, erf_inv};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[cfg(feature = "cuda")]
|
||||
use crate::CudaKernel;
|
||||
@ -55,6 +57,10 @@ pub struct Challenge {
|
||||
pub distance_matrix: Vec<Vec<i32>>,
|
||||
pub max_total_distance: i32,
|
||||
pub max_capacity: i32,
|
||||
pub fleet_size: usize,
|
||||
pub service_time: i32,
|
||||
pub ready_times: Vec<i32>,
|
||||
pub due_times: Vec<i32>,
|
||||
}
|
||||
|
||||
// TIG dev bounty available for a GPU optimisation for instance generation!
|
||||
@ -77,15 +83,40 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
|
||||
let mut rng = SmallRng::from_seed(StdRng::from_seed(seed).gen());
|
||||
|
||||
let num_nodes = difficulty.num_nodes;
|
||||
let max_capacity = 100;
|
||||
let max_capacity = 200;
|
||||
|
||||
let mut node_positions: Vec<(f64, f64)> = (0..num_nodes)
|
||||
.map(|_| (rng.gen::<f64>() * 500.0, rng.gen::<f64>() * 500.0))
|
||||
.collect();
|
||||
node_positions[0] = (250.0, 250.0); // Depot is node 0, and in the center
|
||||
let num_clusters = rng.gen_range(3..=8);
|
||||
let mut node_positions: Vec<(f64, f64)> = Vec::with_capacity(num_nodes);
|
||||
node_positions.push((500.0, 500.0)); // Depot is node 0, and in the center
|
||||
|
||||
let mut demands: Vec<i32> = (0..num_nodes).map(|_| rng.gen_range(15..30)).collect();
|
||||
demands[0] = 0; // Depot demand is 0
|
||||
let mut cluster_assignments = HashMap::new();
|
||||
for node in 1..num_nodes {
|
||||
if node <= num_clusters || rng.gen::<f64>() < 0.5 {
|
||||
node_positions.push((rng.gen::<f64>() * 1000.0, rng.gen::<f64>() * 1000.0));
|
||||
} else {
|
||||
let cluster_idx = rng.gen_range(1..=num_clusters);
|
||||
node_positions.push((
|
||||
truncated_normal_sample(
|
||||
&mut rng,
|
||||
node_positions[cluster_idx].0,
|
||||
60.0,
|
||||
0.0,
|
||||
1000.0,
|
||||
),
|
||||
truncated_normal_sample(
|
||||
&mut rng,
|
||||
node_positions[cluster_idx].1,
|
||||
60.0,
|
||||
0.0,
|
||||
1000.0,
|
||||
),
|
||||
));
|
||||
cluster_assignments.insert(node, cluster_idx);
|
||||
}
|
||||
}
|
||||
|
||||
let mut demands: Vec<i32> = (0..num_nodes).map(|_| rng.gen_range(1..=35)).collect();
|
||||
demands[0] = 0;
|
||||
|
||||
let distance_matrix: Vec<Vec<i32>> = node_positions
|
||||
.iter()
|
||||
@ -101,16 +132,58 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let baseline_routes =
|
||||
calc_baseline_routes(num_nodes, max_capacity, &demands, &distance_matrix)?;
|
||||
let baseline_routes_total_distance = calc_routes_total_distance(
|
||||
let average_demand = demands.iter().sum::<i32>() as f64 / num_nodes as f64;
|
||||
let average_route_size = max_capacity as f64 / average_demand;
|
||||
let average_distance = (1000.0 / 4.0) * 0.5214;
|
||||
let furthest_node = (1..num_nodes)
|
||||
.max_by_key(|&node| distance_matrix[0][node])
|
||||
.unwrap();
|
||||
|
||||
let service_time = 10;
|
||||
let mut ready_times = vec![0; num_nodes];
|
||||
let mut due_times = vec![0; num_nodes];
|
||||
|
||||
// time to return to depot
|
||||
due_times[0] = distance_matrix[0][furthest_node]
|
||||
+ ((average_distance + service_time as f64) * average_route_size).ceil() as i32;
|
||||
|
||||
for node in 1..num_nodes {
|
||||
let min_due_time = distance_matrix[0][node];
|
||||
let max_due_time = due_times[0] - distance_matrix[0][node] - service_time;
|
||||
due_times[node] = rng.gen_range(min_due_time..=max_due_time);
|
||||
|
||||
if let Some(&closest_cluster) = cluster_assignments.get(&node) {
|
||||
due_times[node] = (due_times[node] + due_times[closest_cluster]) / 2;
|
||||
due_times[node] = due_times[node].clamp(min_due_time, max_due_time);
|
||||
}
|
||||
|
||||
if rng.gen::<f64>() < 0.5 {
|
||||
ready_times[node] = due_times[node] - rng.gen_range(10..=60);
|
||||
ready_times[node] = ready_times[node].max(0);
|
||||
}
|
||||
}
|
||||
|
||||
let baseline_routes = calc_baseline_routes(
|
||||
num_nodes,
|
||||
max_capacity,
|
||||
&demands,
|
||||
&distance_matrix,
|
||||
service_time,
|
||||
&ready_times,
|
||||
&due_times,
|
||||
)?;
|
||||
|
||||
let baseline_total_distance = calc_routes_total_distance(
|
||||
num_nodes,
|
||||
max_capacity,
|
||||
&demands,
|
||||
&distance_matrix,
|
||||
&baseline_routes,
|
||||
service_time,
|
||||
&ready_times,
|
||||
&due_times,
|
||||
)?;
|
||||
let max_total_distance = (baseline_routes_total_distance
|
||||
let max_total_distance = (baseline_total_distance
|
||||
* (1000 - difficulty.better_than_baseline as i32)
|
||||
/ 1000) as i32;
|
||||
|
||||
@ -121,16 +194,30 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
|
||||
distance_matrix,
|
||||
max_total_distance,
|
||||
max_capacity,
|
||||
fleet_size: baseline_routes.len(),
|
||||
service_time,
|
||||
ready_times,
|
||||
due_times,
|
||||
})
|
||||
}
|
||||
|
||||
fn verify_solution(&self, solution: &Solution) -> Result<()> {
|
||||
if solution.routes.len() > self.fleet_size {
|
||||
return Err(anyhow!(
|
||||
"Number of routes ({}) exceeds fleet size ({})",
|
||||
solution.routes.len(),
|
||||
self.fleet_size
|
||||
));
|
||||
}
|
||||
let total_distance = calc_routes_total_distance(
|
||||
self.difficulty.num_nodes,
|
||||
self.max_capacity,
|
||||
&self.demands,
|
||||
&self.distance_matrix,
|
||||
&solution.routes,
|
||||
self.service_time,
|
||||
&self.ready_times,
|
||||
&self.due_times,
|
||||
)?;
|
||||
if total_distance <= self.max_total_distance {
|
||||
Ok(())
|
||||
@ -144,41 +231,151 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn truncated_normal_sample<T: Rng>(
|
||||
rng: &mut T,
|
||||
mean: f64,
|
||||
std_dev: f64,
|
||||
min_val: f64,
|
||||
max_val: f64,
|
||||
) -> f64 {
|
||||
let cdf_min = 0.5 * (1.0 + erf((min_val - mean) / (std_dev * (2.0_f64).sqrt())));
|
||||
let cdf_max = 0.5 * (1.0 + erf((max_val - mean) / (std_dev * (2.0_f64).sqrt())));
|
||||
let sample = rng.gen::<f64>() * (cdf_max - cdf_min) + cdf_min;
|
||||
mean + std_dev * (2.0_f64).sqrt() * erf_inv(2.0 * sample - 1.0)
|
||||
}
|
||||
|
||||
fn is_feasible(
|
||||
route: &Vec<usize>,
|
||||
distance_matrix: &Vec<Vec<i32>>,
|
||||
service_time: i32,
|
||||
ready_times: &Vec<i32>,
|
||||
due_times: &Vec<i32>,
|
||||
mut curr_node: usize,
|
||||
mut curr_time: i32,
|
||||
start_pos: usize,
|
||||
) -> bool {
|
||||
let mut valid = true;
|
||||
for pos in start_pos..route.len() {
|
||||
let next_node = route[pos];
|
||||
curr_time += distance_matrix[curr_node][next_node];
|
||||
if curr_time > due_times[route[pos]] {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
curr_time = curr_time.max(ready_times[next_node]) + service_time;
|
||||
curr_node = next_node;
|
||||
}
|
||||
valid
|
||||
}
|
||||
|
||||
pub fn find_best_insertion(
|
||||
route: &Vec<usize>,
|
||||
remaining_nodes: Vec<usize>,
|
||||
distance_matrix: &Vec<Vec<i32>>,
|
||||
service_time: i32,
|
||||
ready_times: &Vec<i32>,
|
||||
due_times: &Vec<i32>,
|
||||
) -> Option<(usize, usize)> {
|
||||
let alpha1 = 1;
|
||||
let alpha2 = 0;
|
||||
let lambda = 1;
|
||||
|
||||
let mut best_c2 = None;
|
||||
let mut best = None;
|
||||
for insert_node in remaining_nodes {
|
||||
let mut best_c1 = None;
|
||||
|
||||
let mut curr_time = 0;
|
||||
let mut curr_node = 0;
|
||||
for pos in 1..route.len() {
|
||||
let next_node = route[pos];
|
||||
let new_arrival_time =
|
||||
ready_times[insert_node].max(curr_time + distance_matrix[curr_node][insert_node]);
|
||||
if new_arrival_time > due_times[insert_node] {
|
||||
continue;
|
||||
}
|
||||
let old_arrival_time =
|
||||
ready_times[next_node].max(curr_time + distance_matrix[curr_node][next_node]);
|
||||
|
||||
// Distance criterion: c11 = d(i,u) + d(u,j) - mu * d(i,j)
|
||||
let c11 = distance_matrix[curr_node][insert_node]
|
||||
+ distance_matrix[insert_node][next_node]
|
||||
- distance_matrix[curr_node][next_node];
|
||||
|
||||
// Time criterion: c12 = b_ju - b_j (the shift in arrival time at position 'pos').
|
||||
let c12 = new_arrival_time - old_arrival_time;
|
||||
|
||||
let c1 = -(alpha1 * c11 + alpha2 * c12);
|
||||
let c2 = lambda * distance_matrix[0][insert_node] + c1;
|
||||
|
||||
if best_c1.is_none_or(|x| c1 > x)
|
||||
&& best_c2.is_none_or(|x| c2 > x)
|
||||
&& is_feasible(
|
||||
route,
|
||||
distance_matrix,
|
||||
service_time,
|
||||
ready_times,
|
||||
due_times,
|
||||
insert_node,
|
||||
new_arrival_time + service_time,
|
||||
pos,
|
||||
)
|
||||
{
|
||||
best_c1 = Some(c1);
|
||||
best_c2 = Some(c2);
|
||||
best = Some((insert_node, pos));
|
||||
}
|
||||
|
||||
curr_time = ready_times[next_node]
|
||||
.max(curr_time + distance_matrix[curr_node][next_node])
|
||||
+ service_time;
|
||||
curr_node = next_node;
|
||||
}
|
||||
}
|
||||
best
|
||||
}
|
||||
|
||||
pub fn calc_baseline_routes(
|
||||
num_nodes: usize,
|
||||
max_capacity: i32,
|
||||
demands: &Vec<i32>,
|
||||
distance_matrix: &Vec<Vec<i32>>,
|
||||
service_time: i32,
|
||||
ready_times: &Vec<i32>,
|
||||
due_times: &Vec<i32>,
|
||||
) -> Result<Vec<Vec<usize>>> {
|
||||
let mut routes = Vec::new();
|
||||
let mut visited = vec![false; num_nodes];
|
||||
visited[0] = true;
|
||||
|
||||
while visited.iter().any(|&v| !v) {
|
||||
let mut route = vec![0];
|
||||
let mut current_node = 0;
|
||||
let mut capacity = max_capacity;
|
||||
let mut nodes: Vec<usize> = (1..num_nodes).collect();
|
||||
nodes.sort_by(|&a, &b| distance_matrix[0][a].cmp(&distance_matrix[0][b]));
|
||||
|
||||
while capacity > 0 && visited.iter().any(|&v| !v) {
|
||||
let eligible_nodes: Vec<usize> = (0..num_nodes)
|
||||
.filter(|&node| !visited[node] && demands[node] <= capacity)
|
||||
.collect();
|
||||
let mut remaining: HashSet<usize> = nodes.iter().cloned().collect();
|
||||
|
||||
if !eligible_nodes.is_empty() {
|
||||
let &closest_node = eligible_nodes
|
||||
.iter()
|
||||
.min_by_key(|&&node| distance_matrix[current_node][node])
|
||||
.unwrap();
|
||||
capacity -= demands[closest_node];
|
||||
route.push(closest_node);
|
||||
visited[closest_node] = true;
|
||||
current_node = closest_node;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
// popping furthest node from depot
|
||||
while let Some(node) = nodes.pop() {
|
||||
if !remaining.remove(&node) {
|
||||
continue;
|
||||
}
|
||||
let mut route = vec![0, node, 0];
|
||||
let mut route_demand = demands[node];
|
||||
|
||||
while let Some((best_node, best_pos)) = find_best_insertion(
|
||||
&route,
|
||||
remaining
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|&n| route_demand + demands[n] <= max_capacity)
|
||||
.collect(),
|
||||
distance_matrix,
|
||||
service_time,
|
||||
ready_times,
|
||||
due_times,
|
||||
) {
|
||||
remaining.remove(&best_node);
|
||||
route_demand += demands[best_node];
|
||||
route.insert(best_pos, best_node);
|
||||
}
|
||||
|
||||
route.push(0);
|
||||
routes.push(route);
|
||||
}
|
||||
|
||||
@ -191,6 +388,9 @@ pub fn calc_routes_total_distance(
|
||||
demands: &Vec<i32>,
|
||||
distance_matrix: &Vec<Vec<i32>>,
|
||||
routes: &Vec<Vec<usize>>,
|
||||
service_time: i32,
|
||||
ready_times: &Vec<i32>,
|
||||
due_times: &Vec<i32>,
|
||||
) -> Result<i32> {
|
||||
let mut total_distance = 0;
|
||||
let mut visited = vec![false; num_nodes];
|
||||
@ -203,7 +403,7 @@ pub fn calc_routes_total_distance(
|
||||
|
||||
let mut capacity = max_capacity;
|
||||
let mut current_node = 0;
|
||||
|
||||
let mut curr_time = 0;
|
||||
for &node in &route[1..route.len() - 1] {
|
||||
if visited[node] {
|
||||
return Err(anyhow!(
|
||||
@ -215,12 +415,24 @@ pub fn calc_routes_total_distance(
|
||||
"The total demand on each route must not exceed max capacity"
|
||||
));
|
||||
}
|
||||
curr_time += distance_matrix[current_node][node];
|
||||
if curr_time > due_times[node] {
|
||||
return Err(anyhow!("Node must be visited before due time"));
|
||||
}
|
||||
if curr_time < ready_times[node] {
|
||||
curr_time = ready_times[node];
|
||||
}
|
||||
curr_time += service_time;
|
||||
visited[node] = true;
|
||||
capacity -= demands[node];
|
||||
total_distance += distance_matrix[current_node][node];
|
||||
current_node = node;
|
||||
}
|
||||
|
||||
curr_time += distance_matrix[current_node][0];
|
||||
if curr_time > due_times[0] {
|
||||
return Err(anyhow!("Must return to depot before due time"));
|
||||
}
|
||||
total_distance += distance_matrix[current_node][0];
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user