diff --git a/vrp-core/src/construction/heuristics/metrics.rs b/vrp-core/src/construction/heuristics/metrics.rs index d06369d3..a8c7599f 100644 --- a/vrp-core/src/construction/heuristics/metrics.rs +++ b/vrp-core/src/construction/heuristics/metrics.rs @@ -178,8 +178,8 @@ pub fn get_last_distance_customer_mean(insertion_ctx: &InsertionContext) -> Floa /// Estimates distances between all routes by sampling locations from routes and measuring /// average distance between them. -pub fn group_routes_by_proximity(insertion_ctx: &InsertionContext) -> Option>> { - const LOCATION_SAMPLE_SIZE: usize = 8; +pub fn group_routes_by_proximity(insertion_ctx: &InsertionContext) -> Vec> { + const LOCATION_SAMPLE_SIZE: usize = 4; let routes = &insertion_ctx.solution.routes; let transport = insertion_ctx.problem.transport.as_ref(); @@ -200,7 +200,7 @@ pub fn group_routes_by_proximity(insertion_ctx: &InsertionContext) -> Option>(); - Some(parallel_collect(&indexed_route_clusters, |(outer_idx, outer_clusters)| { + parallel_collect(&indexed_route_clusters, |(outer_idx, outer_clusters)| { let mut route_distances = indexed_route_clusters .iter() .filter(move |(inner_idx, _)| *outer_idx != *inner_idx) @@ -240,7 +240,7 @@ pub fn group_routes_by_proximity(insertion_ctx: &InsertionContext) -> Option, Vec<_>) = route_distances.into_iter().unzip(); indices - })) + }) } fn get_values_from_route_state<'a>( diff --git a/vrp-core/src/solver/search/decompose_search.rs b/vrp-core/src/solver/search/decompose_search.rs index 3b683fdf..af255cf3 100644 --- a/vrp-core/src/solver/search/decompose_search.rs +++ b/vrp-core/src/solver/search/decompose_search.rs @@ -6,7 +6,6 @@ use crate::construction::heuristics::*; use crate::models::GoalContext; use crate::solver::search::create_environment_with_custom_quota; use crate::solver::*; -use rand::prelude::SliceRandom; use rosomaxa::utils::parallel_into_collect; use std::cell::RefCell; use std::cmp::Ordering; @@ -116,31 +115,24 @@ fn create_multiple_insertion_contexts( environment: Arc, max_routes_range: (i32, i32), ) -> Option)>> { - let mut route_groups_distances = group_routes_by_proximity(insertion_ctx)?; - route_groups_distances.iter_mut().for_each(|route_group_distance| { - let random = &environment.random; - let shuffle_count = random.uniform_int(2, (route_group_distance.len() as i32 / 5).max(2)) as usize; - route_group_distance.partial_shuffle(&mut random.get_rng(), shuffle_count); - }); + if insertion_ctx.solution.routes.is_empty() { + return None; + } + let route_groups = group_routes_by_proximity(insertion_ctx); let (min, max) = max_routes_range; let max = if insertion_ctx.solution.routes.len() < 4 { 2 } else { max }; // identify route groups and create contexts from them let used_indices = RefCell::new(HashSet::new()); - let insertion_ctxs = route_groups_distances - .iter() + let insertion_ctxs = route_groups + .into_iter() .enumerate() .filter(|(outer_idx, _)| !used_indices.borrow().contains(outer_idx)) - .map(|(outer_idx, route_group_distance)| { + .map(|(outer_idx, route_group)| { let group_size = environment.random.uniform_int(min, max) as usize; let route_group = once(outer_idx) - .chain( - route_group_distance - .iter() - .filter(|&inner_idx| !used_indices.borrow().contains(inner_idx)) - .cloned(), - ) + .chain(route_group.into_iter().filter(|inner_idx| !used_indices.borrow().contains(inner_idx))) .take(group_size) .collect::>(); diff --git a/vrp-core/src/solver/search/local/exchange_swap_star.rs b/vrp-core/src/solver/search/local/exchange_swap_star.rs index 0d838dec..abff5e19 100644 --- a/vrp-core/src/solver/search/local/exchange_swap_star.rs +++ b/vrp-core/src/solver/search/local/exchange_swap_star.rs @@ -6,7 +6,6 @@ use super::*; use crate::models::problem::Job; use crate::solver::search::create_environment_with_custom_quota; use crate::utils::Either; -use rand::seq::SliceRandom; use rosomaxa::utils::*; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; @@ -97,50 +96,47 @@ fn get_evaluation_context<'a>(search_ctx: &'a SearchContext, job: &'a Job) -> Ev } /// Creates route pairs to exchange jobs. -#[allow(clippy::needless_collect)] // NOTE enforce size hint to be non-zero fn create_route_pairs(insertion_ctx: &InsertionContext, route_pairs_threshold: usize) -> Vec<(usize, usize)> { let random = insertion_ctx.environment.random.clone(); - if random.is_hit(0.1) { None } else { group_routes_by_proximity(insertion_ctx) } - .map(|route_groups_distances| { - let used_indices = RefCell::new(HashSet::<(usize, usize)>::new()); - let distances = route_groups_distances - .into_iter() - .enumerate() - .flat_map(|(outer_idx, mut route_group_distance)| { - let shuffle_amount = (route_group_distance.len() as Float * 0.1) as usize; - route_group_distance.partial_shuffle(&mut random.get_rng(), shuffle_amount); - route_group_distance - .iter() - .cloned() - .filter(|inner_idx| { - let used_indices = used_indices.borrow(); - !used_indices.contains(&(outer_idx, *inner_idx)) - && !used_indices.contains(&(*inner_idx, outer_idx)) - }) - .inspect(|&inner_idx| { - let mut used_indices = used_indices.borrow_mut(); - used_indices.insert((outer_idx, inner_idx)); - used_indices.insert((inner_idx, outer_idx)); - }) - .next() - .map(|inner_idx| (outer_idx, inner_idx)) - }) - .collect::>(); - SelectionSamplingIterator::new(distances.into_iter(), route_pairs_threshold, random.clone()).collect() - }) - .unwrap_or_else(|| { - let route_count = insertion_ctx.solution.routes.len(); - // NOTE this is needed to have size hint properly set - let all_route_pairs = (0..route_count) - .flat_map(move |outer_idx| { - (0..route_count) - .filter(move |&inner_idx| outer_idx > inner_idx) - .map(move |inner_idx| (outer_idx, inner_idx)) - }) - .collect::>(); - SelectionSamplingIterator::new(all_route_pairs.into_iter(), route_pairs_threshold, random.clone()).collect() - }) + if random.is_hit(0.1) { + let route_count = insertion_ctx.solution.routes.len(); + // NOTE this is needed to have size hint properly set + let all_route_pairs = (0..route_count) + .flat_map(move |outer_idx| { + (0..route_count) + .filter(move |&inner_idx| outer_idx > inner_idx) + .map(move |inner_idx| (outer_idx, inner_idx)) + }) + .collect::>(); + + SelectionSamplingIterator::new(all_route_pairs.into_iter(), route_pairs_threshold, random).collect() + } else { + let route_groups = group_routes_by_proximity(insertion_ctx); + let used_indices = RefCell::new(HashSet::<(usize, usize)>::new()); + let distances = route_groups + .into_iter() + .enumerate() + .flat_map(|(outer_idx, route_group)| { + route_group + .into_iter() + .filter(|inner_idx| { + let used_indices = used_indices.borrow(); + !used_indices.contains(&(outer_idx, *inner_idx)) + && !used_indices.contains(&(*inner_idx, outer_idx)) + }) + .inspect(|inner_idx| { + let mut used_indices = used_indices.borrow_mut(); + used_indices.insert((outer_idx, *inner_idx)); + used_indices.insert((*inner_idx, outer_idx)); + }) + .next() + .map(|inner_idx| (outer_idx, inner_idx)) + }) + .collect::>(); + + SelectionSamplingIterator::new(distances.into_iter(), route_pairs_threshold, random).collect() + } } /// Finds insertion cost of the existing job in the route. diff --git a/vrp-core/src/solver/search/ruin/route_removal.rs b/vrp-core/src/solver/search/ruin/route_removal.rs index 6c6b5a12..afd669ba 100644 --- a/vrp-core/src/solver/search/ruin/route_removal.rs +++ b/vrp-core/src/solver/search/ruin/route_removal.rs @@ -58,32 +58,30 @@ impl Ruin for CloseRouteRemoval { return insertion_ctx; } - if let Some(route_groups_distances) = group_routes_by_proximity(&insertion_ctx) { - let random = insertion_ctx.environment.random.clone(); - - let stale_routes = insertion_ctx - .solution - .routes - .iter() - .enumerate() - .filter_map(|(idx, route)| if route.is_stale() { Some(idx) } else { None }) - .collect::>(); - - let route_index = if !stale_routes.is_empty() && random.is_head_not_tails() { - stale_routes[random.uniform_int(0, (stale_routes.len() - 1) as i32) as usize] - } else { - random.uniform_int(0, (route_groups_distances.len() - 1) as i32) as usize - }; - - #[allow(clippy::needless_collect)] - let routes = route_groups_distances[route_index] - .iter() - .filter_map(|idx| insertion_ctx.solution.routes.get(*idx)) - .map(|route_ctx| route_ctx.route().actor.clone()) - .collect::>(); - - remove_routes_with_actors(&mut insertion_ctx.solution, &self.limits, random.as_ref(), routes.into_iter()); - } + let random = insertion_ctx.environment.random.clone(); + let route_groups = group_routes_by_proximity(&insertion_ctx); + + let stale_routes = insertion_ctx + .solution + .routes + .iter() + .enumerate() + .filter_map(|(idx, route)| if route.is_stale() { Some(idx) } else { None }) + .collect::>(); + + let route_index = if !stale_routes.is_empty() && random.is_head_not_tails() { + stale_routes[random.uniform_int(0, (stale_routes.len() - 1) as i32) as usize] + } else { + random.uniform_int(0, (route_groups.len() - 1) as i32) as usize + }; + + let routes = route_groups[route_index] + .iter() + .filter_map(|idx| insertion_ctx.solution.routes.get(*idx)) + .map(|route_ctx| route_ctx.route().actor.clone()) + .collect::>(); + + remove_routes_with_actors(&mut insertion_ctx.solution, &self.limits, random.as_ref(), routes.into_iter()); insertion_ctx }