diff --git a/rosomaxa/src/utils/iterators.rs b/rosomaxa/src/utils/iterators.rs index 4301fd7c8..bc0d345f5 100644 --- a/rosomaxa/src/utils/iterators.rs +++ b/rosomaxa/src/utils/iterators.rs @@ -8,7 +8,6 @@ use crate::utils::*; use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use std::hash::Hash; -use std::sync::Arc; /// An iterator which collects items into group. pub trait CollectGroupBy: Iterator { @@ -47,12 +46,12 @@ pub struct SelectionSamplingIterator { needed: usize, size: usize, iterator: I, - random: Arc, + random: DefaultPureRandom, } impl SelectionSamplingIterator { /// Creates a new instance of `SelectionSamplingIterator`. - pub fn new(iterator: I, amount: usize, random: Arc) -> Self { + pub fn new(iterator: I, amount: usize, random: DefaultPureRandom) -> Self { assert!(amount > 0); Self { // NOTE relying on lower bound size hint! @@ -131,7 +130,7 @@ pub trait SelectionSamplingSearch: Iterator { fn sample_search<'a, T, R, FM, FI, FC>( self, sample_size: usize, - random: Arc, + random: &mut DefaultPureRandom, mut map_fn: FM, index_fn: FI, compare_fn: FC, @@ -159,7 +158,7 @@ pub trait SelectionSamplingSearch: Iterator { // keeps track data to track properly right range limit if best is found at last let (orig_right, last_probe_idx) = (state.right, take.min(sample_size - 1)); - state = SelectionSamplingIterator::new(iterator, sample_size, random.clone()) + state = SelectionSamplingIterator::new(iterator, sample_size, random.new_pure_random()) .enumerate() .fold(state, |mut acc, (probe_idx, item)| { let item_idx = index_fn(&item); diff --git a/rosomaxa/src/utils/mod.rs b/rosomaxa/src/utils/mod.rs index 8e2c79e3c..d41802b6d 100644 --- a/rosomaxa/src/utils/mod.rs +++ b/rosomaxa/src/utils/mod.rs @@ -18,6 +18,9 @@ pub use self::noise::*; mod parallel; pub use self::parallel::*; +mod pure_random; +pub use self::pure_random::*; + mod random; pub use self::random::*; diff --git a/rosomaxa/src/utils/parallel.rs b/rosomaxa/src/utils/parallel.rs index f008e1cc9..a03c9ada8 100644 --- a/rosomaxa/src/utils/parallel.rs +++ b/rosomaxa/src/utils/parallel.rs @@ -2,6 +2,7 @@ #[path = "../../tests/unit/utils/parallel_test.rs"] mod parallel_test; +pub use self::actual::into_map_reduce; pub use self::actual::map_reduce; pub use self::actual::parallel_collect; pub use self::actual::parallel_foreach_mut; @@ -70,6 +71,18 @@ mod actual { source.par_iter().map(map_op).reduce(default_op, reduce_op) } + /// Performs map reduce operations in parallel. + pub fn into_map_reduce(source: Vec, map_op: FM, default_op: FD, reduce_op: FR) -> R + where + T: Send + Sync, + FM: Fn(T) -> R + Sync + Send, + FR: Fn(R, R) -> R + Sync + Send, + FD: Fn() -> R + Sync + Send, + R: Send, + { + source.into_par_iter().map(map_op).reduce(default_op, reduce_op) + } + /// Performs mutable foreach in parallel. pub fn parallel_foreach_mut(source: &mut [T], action: F) where diff --git a/rosomaxa/src/utils/pure_random.rs b/rosomaxa/src/utils/pure_random.rs new file mode 100644 index 000000000..cdf6ef13b --- /dev/null +++ b/rosomaxa/src/utils/pure_random.rs @@ -0,0 +1,131 @@ +use rand::prelude::*; +use rand::Error; + + +/// Provides the way to use randomized values in generic way. +pub trait PureRandom { + /// Creates a new RNG, different for every call. + fn new_pure_random(&mut self) -> Self; + + /// Produces integral random value, uniformly distributed on the closed interval [min, max] + fn uniform_int(&mut self, min: i32, max: i32) -> i32; + + /// Produces real random value, uniformly distributed on the closed interval [min, max) + fn uniform_real(&mut self, min: f64, max: f64) -> f64; + /// Flips a coin and returns true if it is "heads", false otherwise. + fn is_head_not_tails(&mut self) -> bool; + + /// Tests probability value in (0., 1.) range. + fn is_hit(&mut self, probability: f64) -> bool; + + /// Returns an index from collected with probability weight. + /// Uses exponential distribution where the weights are the rate of the distribution (lambda) + /// and selects the smallest sampled value. + fn weighted(&mut self, weights: &[usize]) -> usize; +} + +/// A default random implementation. +pub struct DefaultPureRandom { + rng: SmallRng, +} + +impl DefaultPureRandom { + /// This behaves like the trait `Copy`, but we don't implement the trait, as usually this behaviour + /// is not desired for random number generators + /// + /// In general, we probably don't want to use this method too often as the return value will + /// behave exactly as the original value. If we have a single (global) instance of this struct, + /// it should probably be mutable, so that newly constructed instances can be different for each call. + #[inline] + pub fn generate_copy(&self) -> Self { + Self { rng: self.rng.clone() } + } + + /// Creates an instance of `DefaultPureRandom` with repeatable (predictable) random generation. + #[inline] + pub fn with_seed(seed: u64) -> Self { + Self { rng: SmallRng::seed_from_u64(seed) } + } + + /// Creates an instance of `DefaultPureRandom` with reproducible behavior. + #[inline] + pub fn for_tests() -> Self { + Self { rng: SmallRng::seed_from_u64(1234567890) } + } + + /// Creates a randomly initialized instance of `DefaultPureRandom`. + #[inline] + pub fn new_random() -> Self { + Self { rng: SmallRng::from_rng(thread_rng()).expect("cannot get RNG from thread rng") } + } +} + +impl PureRandom for DefaultPureRandom { + #[inline] + fn new_pure_random(&mut self) -> Self { + Self { rng: SmallRng::seed_from_u64(self.next_u64())} + } + #[inline] + fn uniform_int(&mut self, min: i32, max: i32) -> i32 { + if min == max { + return min; + } + + assert!(min < max); + self.rng.gen_range(min..max + 1) + } + + #[inline] + fn uniform_real(&mut self, min: f64, max: f64) -> f64 { + if (min - max).abs() < f64::EPSILON { + return min; + } + + assert!(min < max); + self.rng.gen_range(min..max) + } + + #[inline] + fn is_head_not_tails(&mut self) -> bool { + self.rng.gen_bool(0.5) + } + + #[inline] + fn is_hit(&mut self, probability: f64) -> bool { + self.gen_bool(probability.clamp(0., 1.)) + } + + #[inline] + fn weighted(&mut self, weights: &[usize]) -> usize { + weights + .iter() + .zip(0_usize..) + .map(|(&weight, index)| (-self.uniform_real(0., 1.).ln() / weight as f64, index)) + .min_by(|a, b| a.0.partial_cmp(&b.0).unwrap()) + .unwrap() + .1 + } +} + +/// Reimplementing RngCore helps to set breakpoints and also hides the usage of SmallRng. +impl RngCore for DefaultPureRandom { + #[inline] + fn next_u32(&mut self) -> u32 { + self.rng.next_u32() + } + + #[inline] + fn next_u64(&mut self) -> u64 { + self.rng.next_u64() + } + + #[inline] + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.rng.fill_bytes(dest) + } + + #[inline] + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + self.rng.try_fill_bytes(dest) + } +} diff --git a/rosomaxa/tests/unit/utils/iterators_test.rs b/rosomaxa/tests/unit/utils/iterators_test.rs index a2013e06a..945a00393 100644 --- a/rosomaxa/tests/unit/utils/iterators_test.rs +++ b/rosomaxa/tests/unit/utils/iterators_test.rs @@ -2,11 +2,12 @@ use super::*; use crate::utils::DefaultRandom; mod selection_sampling { + use std::sync::Arc; use super::*; #[test] fn can_sample_from_large_range() { - let random = Arc::new(DefaultRandom::default()); + let random = DefaultPureRandom::for_tests(); let amount = 5; let numbers = SelectionSamplingIterator::new(0..100, amount, random).collect::>(); @@ -25,7 +26,7 @@ mod selection_sampling { #[test] fn can_sample_from_same_range() { let amount = 5; - let random = Arc::new(DefaultRandom::default()); + let random = DefaultPureRandom::for_tests(); let numbers = SelectionSamplingIterator::new(0..amount, amount, random).collect::>(); @@ -44,6 +45,7 @@ mod selection_sampling { } mod range_sampling { + use std::sync::Arc; use super::*; use crate::prelude::RandomGen; @@ -111,7 +113,6 @@ mod range_sampling { mod sampling_search { use super::*; - use crate::Environment; use std::cell::RefCell; use std::sync::RwLock; @@ -142,7 +143,7 @@ mod sampling_search { let total_size = 1000; let sample_size = 8; let target = 10; - let random = Environment::default().random; + let mut random = DefaultPureRandom::for_tests(); let mut results = (0..100) .map(|_| { @@ -156,7 +157,7 @@ mod sampling_search { let idx = data .iter() - .sample_search(sample_size, random.clone(), map_fn, |item| item.idx, compare_fn) + .sample_search(sample_size, &mut random, map_fn, |item| item.idx, compare_fn) .unwrap() .idx; let count = *counter.read().unwrap(); @@ -181,7 +182,7 @@ mod sampling_search { 66, 47, 96, 82, 34, 20, 23, 94, 11, 18, 89, 79, 47, 77, 30, 48, 8, 45, 11, 21, 54, 15, 26, 23, 37, 58, 27, 31, 11, 60, ], - 4, 12, 96, + 4, 11, 97, ), case02_at_end: ( vec![ @@ -189,14 +190,14 @@ mod sampling_search { 48, 8, 45, 11, 21, 54, 15, 26, 23, 37, 58, 27, 31, 11, 60, 76, 36, 93, 15, 21, 40, 97, 77, 35, 86, 61, 71, 7, 32, 29, ], - 4, 7, 86, + 4, 8, 86, ), case03_wave: ( vec![ 2, 5, 6, 10, 18, 24, 25, 29, 34, 35, 37, 38, 40, 43, 45, 53, 55, 60, 61, 63, 68, 69, 71, 73, 77, 80, 81, 82, 84, 91, 96, 93, 90, 86, 80, 72, 71, 65, 62, 56, 55, 52, ], - 8, 13, 96, + 8, 16, 93, ), } @@ -206,14 +207,14 @@ mod sampling_search { expected_counter: usize, expected_value: i32, ) { - let random = Arc::new(DefaultRandom::new_repeatable()); + let mut random = DefaultPureRandom::for_tests(); let counter = RefCell::new(0); let value = sequence .into_iter() .enumerate() .sample_search( sample_size, - random.clone(), + &mut random, |(_idx, i)| { *counter.borrow_mut() += 1; //println!("{} probe: {i} at {idx}", counter.borrow()); diff --git a/vrp-core/src/construction/clustering/vicinity/mod.rs b/vrp-core/src/construction/clustering/vicinity/mod.rs index b9b632ddb..d0ca57391 100644 --- a/vrp-core/src/construction/clustering/vicinity/mod.rs +++ b/vrp-core/src/construction/clustering/vicinity/mod.rs @@ -184,10 +184,10 @@ fn get_check_insertion_fn( actor_filter: Arc bool + Send + Sync>, ) -> impl Fn(&Job) -> Result<(), i32> { move |job: &Job| -> Result<(), i32> { - let eval_ctx = EvaluationContext { + let mut eval_ctx = EvaluationContext { goal: &insertion_ctx.problem.goal, job, - leg_selection: &LegSelection::Exhaustive, + leg_selection: LegSelection::Exhaustive, result_selector: &BestResultSelector::default(), }; @@ -199,7 +199,7 @@ fn get_check_insertion_fn( .try_fold(Err(-1), |_, route_ctx| { let result = eval_job_insertion_in_route( &insertion_ctx, - &eval_ctx, + &mut eval_ctx, route_ctx, InsertionPosition::Any, InsertionResult::make_failure(), diff --git a/vrp-core/src/construction/heuristics/evaluators.rs b/vrp-core/src/construction/heuristics/evaluators.rs index a4ea46a00..650a5b2c0 100644 --- a/vrp-core/src/construction/heuristics/evaluators.rs +++ b/vrp-core/src/construction/heuristics/evaluators.rs @@ -19,7 +19,7 @@ pub struct EvaluationContext<'a> { /// A job which is about to be inserted. pub job: &'a Job, /// A leg selection mode. - pub leg_selection: &'a LegSelection, + pub leg_selection: LegSelection, /// A result selector. pub result_selector: &'a (dyn ResultSelector + Send + Sync), } @@ -39,7 +39,7 @@ pub enum InsertionPosition { /// at given position constraint. pub fn eval_job_insertion_in_route( insertion_ctx: &InsertionContext, - eval_ctx: &EvaluationContext, + eval_ctx: &mut EvaluationContext, route_ctx: &RouteContext, position: InsertionPosition, alternative: InsertionResult, @@ -84,7 +84,7 @@ pub fn eval_job_insertion_in_route( /// Evaluates possibility to preform insertion in route context only. /// NOTE: doesn't evaluate constraints on route level. pub fn eval_job_constraint_in_route( - eval_ctx: &EvaluationContext, + eval_ctx: &mut EvaluationContext, route_ctx: &RouteContext, position: InsertionPosition, route_costs: InsertionCost, @@ -98,7 +98,7 @@ pub fn eval_job_constraint_in_route( pub(crate) fn eval_single_constraint_in_route( insertion_ctx: &InsertionContext, - eval_ctx: &EvaluationContext, + eval_ctx: &mut EvaluationContext, route_ctx: &RouteContext, single: &Arc, position: InsertionPosition, @@ -119,7 +119,7 @@ pub(crate) fn eval_single_constraint_in_route( } fn eval_single( - eval_ctx: &EvaluationContext, + eval_ctx: &mut EvaluationContext, route_ctx: &RouteContext, single: &Arc, position: InsertionPosition, @@ -151,7 +151,7 @@ fn eval_single( } fn eval_multi( - eval_ctx: &EvaluationContext, + eval_ctx: &mut EvaluationContext, route_ctx: &RouteContext, multi: &Arc, position: InsertionPosition, @@ -244,7 +244,7 @@ fn analyze_insertion_in_route( init } } - None => eval_ctx.leg_selection.sample_best( + None => eval_ctx.leg_selection.generate_copy().sample_best( route_ctx, eval_ctx.job, init.index, diff --git a/vrp-core/src/construction/heuristics/insertions.rs b/vrp-core/src/construction/heuristics/insertions.rs index 58b62a285..8d24ed82a 100644 --- a/vrp-core/src/construction/heuristics/insertions.rs +++ b/vrp-core/src/construction/heuristics/insertions.rs @@ -258,7 +258,7 @@ impl InsertionHeuristic { insertion_ctx: InsertionContext, job_selector: &(dyn JobSelector + Send + Sync), route_selector: &(dyn RouteSelector + Send + Sync), - leg_selection: &LegSelection, + mut leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> InsertionContext { let mut insertion_ctx = insertion_ctx; @@ -275,7 +275,7 @@ impl InsertionHeuristic { let routes = route_selector.select(&insertion_ctx, jobs.as_slice()).collect::>(); let result = - self.insertion_evaluator.evaluate_all(&insertion_ctx, &jobs, &routes, leg_selection, result_selector); + self.insertion_evaluator.evaluate_all(&insertion_ctx, &jobs, &routes, leg_selection.next(), result_selector); match result { InsertionResult::Success(success) => { diff --git a/vrp-core/src/construction/heuristics/selectors.rs b/vrp-core/src/construction/heuristics/selectors.rs index 3f10f27f9..82794cdaa 100644 --- a/vrp-core/src/construction/heuristics/selectors.rs +++ b/vrp-core/src/construction/heuristics/selectors.rs @@ -7,7 +7,7 @@ use crate::models::problem::Job; use crate::models::solution::Leg; use crate::utils::*; use rand::prelude::*; -use rosomaxa::utils::{map_reduce, parallel_collect, Random}; +use rosomaxa::utils::{into_map_reduce, Random}; use std::cmp::Ordering; use std::ops::ControlFlow; use std::sync::Arc; @@ -74,7 +74,7 @@ pub trait InsertionEvaluator { insertion_ctx: &InsertionContext, job: &Job, routes: &[&RouteContext], - leg_selection: &LegSelection, + leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> InsertionResult; @@ -84,7 +84,7 @@ pub trait InsertionEvaluator { insertion_ctx: &InsertionContext, route_ctx: &RouteContext, jobs: &[&Job], - leg_selection: &LegSelection, + leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> InsertionResult; @@ -94,7 +94,7 @@ pub trait InsertionEvaluator { insertion_ctx: &InsertionContext, jobs: &[&Job], routes: &[&RouteContext], - leg_selection: &LegSelection, + leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> InsertionResult; } @@ -122,13 +122,18 @@ impl PositionInsertionEvaluator { insertion_ctx: &InsertionContext, jobs: &[&Job], routes: &[&RouteContext], - leg_selection: &LegSelection, + leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> Vec { + let mut leg_selection = leg_selection; + // A `LegSelection` is now needed on multiple threads. `LegSelection` is not thread safe due to the rng. + // Therefore, we need to create a dedicated (different) `LegSelection` for each iteration. if Self::is_fold_jobs(insertion_ctx) { - parallel_collect(jobs, |job| self.evaluate_job(insertion_ctx, job, routes, leg_selection, result_selector)) + let jobs_and_leg_selection: Vec<(&Job, LegSelection)> = jobs.iter().map(|&job|(job, leg_selection.next())).collect(); + parallel_into_collect(jobs_and_leg_selection, |(job, leg_selection)| self.evaluate_job(insertion_ctx, job, routes, leg_selection, result_selector)) } else { - parallel_collect(routes, |route_ctx| { + let routes_and_leg_selection: Vec<(&RouteContext, LegSelection)> = routes.iter().map(|&route_ctx|(route_ctx, leg_selection.next())).collect(); + parallel_into_collect(routes_and_leg_selection, |(route_ctx, leg_selection)| { self.evaluate_route(insertion_ctx, route_ctx, jobs, leg_selection, result_selector) }) } @@ -145,13 +150,13 @@ impl InsertionEvaluator for PositionInsertionEvaluator { insertion_ctx: &InsertionContext, job: &Job, routes: &[&RouteContext], - leg_selection: &LegSelection, + leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> InsertionResult { - let eval_ctx = EvaluationContext { goal: &insertion_ctx.problem.goal, job, leg_selection, result_selector }; + let mut eval_ctx = EvaluationContext { goal: &insertion_ctx.problem.goal, job, leg_selection, result_selector }; routes.iter().fold(InsertionResult::make_failure(), |acc, route_ctx| { - eval_job_insertion_in_route(insertion_ctx, &eval_ctx, route_ctx, self.insertion_position, acc) + eval_job_insertion_in_route(insertion_ctx, &mut eval_ctx, route_ctx, self.insertion_position, acc) }) } @@ -160,12 +165,15 @@ impl InsertionEvaluator for PositionInsertionEvaluator { insertion_ctx: &InsertionContext, route_ctx: &RouteContext, jobs: &[&Job], - leg_selection: &LegSelection, + mut leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> InsertionResult { - jobs.iter().fold(InsertionResult::make_failure(), |acc, job| { - let eval_ctx = EvaluationContext { goal: &insertion_ctx.problem.goal, job, leg_selection, result_selector }; - eval_job_insertion_in_route(insertion_ctx, &eval_ctx, route_ctx, self.insertion_position, acc) + // A `LegSelection` is now needed on multiple threads. `LegSelection` is not thread safe due to the rng. + // Therefore, we need to create a dedicated (different) `LegSelection` for each iteration. + let jobs_and_leg_selection: Vec<(&Job, LegSelection)> = jobs.iter().map(|&job|(job, leg_selection.next())).collect(); + jobs_and_leg_selection.into_iter().fold(InsertionResult::make_failure(), |acc, (job, leg_selection)| { + let mut eval_ctx = EvaluationContext { goal: &insertion_ctx.problem.goal, job, leg_selection, result_selector }; + eval_job_insertion_in_route(insertion_ctx, &mut eval_ctx, route_ctx, self.insertion_position, acc) }) } @@ -174,20 +182,22 @@ impl InsertionEvaluator for PositionInsertionEvaluator { insertion_ctx: &InsertionContext, jobs: &[&Job], routes: &[&RouteContext], - leg_selection: &LegSelection, + mut leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> InsertionResult { if Self::is_fold_jobs(insertion_ctx) { - map_reduce( - jobs, - |job| self.evaluate_job(insertion_ctx, job, routes, leg_selection, result_selector), + let jobs_and_leg_selection: Vec<(&Job, LegSelection)> = jobs.iter().map(|&job|(job, leg_selection.next())).collect(); + into_map_reduce( + jobs_and_leg_selection, + |(job, leg_selection)| self.evaluate_job(insertion_ctx, job, routes, leg_selection, result_selector), InsertionResult::make_failure, |a, b| result_selector.select_insertion(insertion_ctx, a, b), ) } else { - map_reduce( - routes, - |route| self.evaluate_route(insertion_ctx, route, jobs, leg_selection, result_selector), + let routes_and_leg_selection: Vec<(&RouteContext, LegSelection)> = routes.iter().map(|&job|(job, leg_selection.next())).collect(); + into_map_reduce( + routes_and_leg_selection, + |(route, leg_selection)| self.evaluate_route(insertion_ctx, route, jobs, leg_selection, result_selector), InsertionResult::make_failure, |a, b| result_selector.select_insertion(insertion_ctx, a, b), ) @@ -403,18 +413,46 @@ impl ResultSelectorProvider { } /// Provides way to control routing leg selection mode. -#[derive(Clone)] +// #[derive(Clone)] pub enum LegSelection { /// Stochastic mode: depending on route size, not all legs could be selected. - Stochastic(Arc), + Stochastic(DefaultPureRandom), /// Exhaustive mode: all legs are selected. Exhaustive, } impl LegSelection { + /// This behaves similar the trait `Copy`, but we don't implement the trait, as usually this behaviour + /// is not desired for random number generators. + /// + /// In case of `Stochastic` the random number generator is different, so results are not the same. + pub fn generate_copy(&self) -> Self { + match self { + LegSelection::Stochastic(_) => LegSelection::Stochastic(DefaultPureRandom::new_random()), + LegSelection::Exhaustive => LegSelection::Exhaustive + } + } + + /// Can be deleted once `Random` and `DefaultRandom` are gone. + pub fn random_stochastic(random: &Arc) -> Self { + LegSelection::Stochastic(DefaultPureRandom::with_seed(random.get_rng().next_u64())) + } + + /// Creates another, similar LegSelection. In case of `Exhaustive`, the return value is also `Exhaustive`. + /// In case of `Stochastic`, it's also `Stochastic`, but including another rng. + /// + /// This can be helpful when many different LegSelections with different random behaviour have to be + /// created, for example in iterators. + pub fn next(&mut self) -> Self { + match self { + LegSelection::Stochastic(random) => LegSelection::Stochastic(DefaultPureRandom::with_seed(random.next_u64())), + LegSelection::Exhaustive => LegSelection::Exhaustive + } + } + /// Selects a best leg for insertion. pub(crate) fn sample_best( - &self, + &mut self, route_ctx: &RouteContext, job: &Job, skip: usize, @@ -427,7 +465,7 @@ impl LegSelection { FM: FnMut(Leg, R) -> ControlFlow, FC: Fn(&R, &R) -> bool, { - if let Some((sample_size, random)) = self.get_sample_data(route_ctx, job, skip) { + if let Some((sample_size, mut random)) = self.get_sample_data(route_ctx, job, skip) { route_ctx .route() .tour @@ -435,7 +473,7 @@ impl LegSelection { .skip(skip) .sample_search( sample_size, - random.clone(), + &mut random, &mut |leg: Leg<'_>| map_fn(leg, R::default()).unwrap_value(), |leg: &Leg<'_>| leg.1 - skip, &compare_fn, @@ -448,14 +486,14 @@ impl LegSelection { /// Returns a sample data for stochastic mode. fn get_sample_data( - &self, + &mut self, route_ctx: &RouteContext, job: &Job, skip: usize, - ) -> Option<(usize, Arc)> { + ) -> Option<(usize, DefaultPureRandom)> { match self { Self::Stochastic(random) => { - let gen_usize = |min: i32, max: i32| random.uniform_int(min, max) as usize; + let mut gen_usize = |min: i32, max: i32| random.uniform_int(min, max) as usize; let greedy_threshold = match job { Job::Single(_) => gen_usize(12, 24), Job::Multi(_) => gen_usize(8, 16), @@ -472,7 +510,7 @@ impl LegSelection { Job::Single(_) => 8, Job::Multi(_) => 4, }, - random.clone(), + random.new_pure_random(), )) } } diff --git a/vrp-core/src/construction/probing/repair_solution.rs b/vrp-core/src/construction/probing/repair_solution.rs index dccc629bb..df5b44a72 100644 --- a/vrp-core/src/construction/probing/repair_solution.rs +++ b/vrp-core/src/construction/probing/repair_solution.rs @@ -88,7 +88,6 @@ fn synchronize_jobs( goal: &GoalContext, ) -> HashMap>> { let position = InsertionPosition::Last; - let leg_selection = LegSelection::Exhaustive; let result_selector = BestResultSelector::default(); let (synchronized_jobs, _) = route_ctx @@ -106,17 +105,17 @@ fn synchronize_jobs( let is_invalid_multi_job = invalid_multi_job_ids.contains(&job); if !is_already_processed && !is_invalid_multi_job { - let eval_ctx = EvaluationContext { + let mut eval_ctx = EvaluationContext { goal, job: &job, - leg_selection: &leg_selection, + leg_selection: LegSelection::Exhaustive, result_selector: &result_selector, }; let route_ctx = new_insertion_ctx.solution.routes.get(route_idx).unwrap(); let insertion_result = eval_single_constraint_in_route( new_insertion_ctx, - &eval_ctx, + &mut eval_ctx, route_ctx, single, position, diff --git a/vrp-core/src/solver/processing/unassignment_reason.rs b/vrp-core/src/solver/processing/unassignment_reason.rs index 8ec7857ae..4e9745749 100644 --- a/vrp-core/src/solver/processing/unassignment_reason.rs +++ b/vrp-core/src/solver/processing/unassignment_reason.rs @@ -17,14 +17,13 @@ impl HeuristicSolutionProcessing for UnassignmentReason { let mut insertion_ctx = solution; let unassigned = insertion_ctx.solution.unassigned.drain().collect::>(); - let leg_selection = LegSelection::Exhaustive; let result_selector = BestResultSelector::default(); let unassigned = parallel_into_collect(unassigned, |(job, code)| { - let eval_ctx = EvaluationContext { + let mut eval_ctx = EvaluationContext { goal: &insertion_ctx.problem.goal, job: &job, - leg_selection: &leg_selection, + leg_selection: LegSelection::Exhaustive, result_selector: &result_selector, }; let details = insertion_ctx @@ -36,7 +35,7 @@ impl HeuristicSolutionProcessing for UnassignmentReason { .map(|leg_idx| { eval_job_insertion_in_route( &insertion_ctx, - &eval_ctx, + &mut eval_ctx, route_ctx, InsertionPosition::Concrete(leg_idx), InsertionResult::make_failure(), diff --git a/vrp-core/src/solver/search/local/exchange_inter_route.rs b/vrp-core/src/solver/search/local/exchange_inter_route.rs index b5cee8015..a3ee71f09 100644 --- a/vrp-core/src/solver/search/local/exchange_inter_route.rs +++ b/vrp-core/src/solver/search/local/exchange_inter_route.rs @@ -7,7 +7,7 @@ use crate::models::problem::Job; use crate::solver::search::{select_seed_job_with_tabu_list, LocalOperator, TabuList}; use crate::solver::RefinementContext; use crate::utils::Noise; -use rosomaxa::utils::map_reduce; +use rosomaxa::utils::{into_map_reduce}; /// A local search operator which tries to exchange jobs in best way between different routes. pub struct ExchangeInterRouteBest { @@ -100,7 +100,7 @@ fn find_best_insertion_pair( let new_insertion_ctx = get_new_insertion_ctx(insertion_ctx, &seed_job, seed_route_idx).unwrap(); let seed_route = new_insertion_ctx.solution.routes.get(seed_route_idx).unwrap(); - let leg_selection = LegSelection::Stochastic(insertion_ctx.environment.random.clone()); + let mut leg_selection = LegSelection::random_stochastic(&insertion_ctx.environment.random); let result_selector = NoiseResultSelector::new(noise.clone()); let insertion_pair = new_insertion_ctx @@ -109,23 +109,24 @@ fn find_best_insertion_pair( .iter() .enumerate() .filter(|(idx, _)| *idx != seed_route_idx && filter_route_indices(*idx)) - .fold(Option::::None, |acc, (_, test_route)| { - let new_result = map_reduce( + .map(|(_, route)| (route, leg_selection.next())) + .fold(Option::::None, |acc, (test_route, mut leg_selection)| { + let new_result = into_map_reduce( test_route .route() .tour .jobs() .enumerate() .filter(|(idx, job)| !locked.contains(*job) && filter_jobs_indices(*idx)) - .collect::>() - .as_slice(), - |(_, test_job)| { + .map(|(_, job)| (job, leg_selection.next())) + .collect::>(), + |(test_job, mut leg_selection)| { // try to insert test job into seed tour let seed_success = test_job_insertion( &new_insertion_ctx, seed_route, test_job, - &leg_selection, + leg_selection.next(), &result_selector, )?; @@ -139,7 +140,7 @@ fn find_best_insertion_pair( &new_insertion_ctx, &test_route, &seed_job, - &leg_selection, + leg_selection, &result_selector, )?; @@ -176,14 +177,14 @@ fn test_job_insertion( insertion_ctx: &InsertionContext, route_ctx: &RouteContext, job: &Job, - leg_selection: &LegSelection, + leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> Option { - let eval_ctx = EvaluationContext { goal: &insertion_ctx.problem.goal, job, leg_selection, result_selector }; + let mut eval_ctx = EvaluationContext { goal: &insertion_ctx.problem.goal, job, leg_selection, result_selector }; let insertion = eval_job_insertion_in_route( insertion_ctx, - &eval_ctx, + &mut eval_ctx, route_ctx, InsertionPosition::Any, InsertionResult::make_failure(), diff --git a/vrp-core/src/solver/search/local/exchange_intra_route.rs b/vrp-core/src/solver/search/local/exchange_intra_route.rs index 48f05d649..4a88166fa 100644 --- a/vrp-core/src/solver/search/local/exchange_intra_route.rs +++ b/vrp-core/src/solver/search/local/exchange_intra_route.rs @@ -39,23 +39,23 @@ impl LocalOperator for ExchangeIntraRouteRandom { new_insertion_ctx.solution.required.push(job.clone()); new_insertion_ctx.problem.goal.accept_route_state(new_route_ctx); - let leg_selection = LegSelection::Stochastic(random.clone()); + let leg_selection = LegSelection::random_stochastic(&random); let result_selector = NoiseResultSelector::new(Noise::new_with_addition( self.probability, self.noise_range, random.clone(), )); - let eval_ctx = EvaluationContext { + let mut eval_ctx = EvaluationContext { goal: &insertion_ctx.problem.goal, job: &job, - leg_selection: &leg_selection, + leg_selection: leg_selection, result_selector: &result_selector, }; let new_route_ctx = new_insertion_ctx.solution.routes.get(route_idx).unwrap(); let insertion = eval_job_insertion_in_route( &new_insertion_ctx, - &eval_ctx, + &mut eval_ctx, new_route_ctx, InsertionPosition::Any, InsertionResult::make_failure(), diff --git a/vrp-core/src/solver/search/local/exchange_sequence.rs b/vrp-core/src/solver/search/local/exchange_sequence.rs index 61d7a36d8..8d0a6f246 100644 --- a/vrp-core/src/solver/search/local/exchange_sequence.rs +++ b/vrp-core/src/solver/search/local/exchange_sequence.rs @@ -159,7 +159,7 @@ fn insert_jobs( shuffle_prob: f64, ) { let random = &insertion_ctx.environment.random; - let leg_selection = LegSelection::Stochastic(random.clone()); + let mut leg_selection = LegSelection::random_stochastic(random); let result_selector = BestResultSelector::default(); let mut jobs = jobs; @@ -177,11 +177,11 @@ fn insert_jobs( .uniform_int(0, get_route_ctx(insertion_ctx, route_idx).route().tour.job_activity_count() as i32) as usize; - let (failures, _) = jobs.into_iter().fold((Vec::new(), start_index), |(mut unassigned, start_index), job| { - let eval_ctx = EvaluationContext { + let (failures, _) = jobs.into_iter().map(|job| (job, leg_selection.next())).fold((Vec::new(), start_index), |(mut unassigned, start_index), (job, leg_selection)| { + let mut eval_ctx = EvaluationContext { goal: &insertion_ctx.problem.goal, job: &job, - leg_selection: &leg_selection, + leg_selection: leg_selection, result_selector: &result_selector, }; @@ -192,7 +192,7 @@ fn insert_jobs( .try_fold((InsertionResult::make_failure(), start_index), |_, insertion_idx| { let insertion = eval_job_insertion_in_route( insertion_ctx, - &eval_ctx, + &mut eval_ctx, get_route_ctx(insertion_ctx, route_idx), InsertionPosition::Concrete(insertion_idx), // NOTE we don't try to insert the best, so alternative is a failure 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 508221c38..cc9d6377d 100644 --- a/vrp-core/src/solver/search/local/exchange_swap_star.rs +++ b/vrp-core/src/solver/search/local/exchange_swap_star.rs @@ -11,6 +11,7 @@ use rand::seq::SliceRandom; use rosomaxa::utils::*; use std::iter::once; use std::sync::RwLock; +use rand::RngCore; /// Implements a SWAP* algorithm described in "Hybrid Genetic Search for the CVRP: /// Open-Source Implementation and SWAP* Neighborhood" by Thibaut Vidal. @@ -32,7 +33,7 @@ impl ExchangeSwapStar { /// Creates a new instance of `ExchangeSwapStar`. pub fn new(random: Arc, quota_limit: usize) -> Self { Self { - leg_selection: LegSelection::Stochastic(random), + leg_selection: LegSelection::random_stochastic(&random), result_selector: Box::::default(), quota_limit, } @@ -61,7 +62,7 @@ impl LocalOperator for ExchangeSwapStar { let is_quota_reached = try_exchange_jobs_in_routes( &mut insertion_ctx, route_pair, - &self.leg_selection, + self.leg_selection.generate_copy(), self.result_selector.as_ref(), ); @@ -77,7 +78,7 @@ impl LocalOperator for ExchangeSwapStar { } /// Encapsulates common data used by search phase. -type SearchContext<'a> = (&'a InsertionContext, &'a LegSelection, &'a (dyn ResultSelector + Send + Sync)); +type SearchContext<'a> = (&'a InsertionContext, LegSelection, &'a (dyn ResultSelector + Send + Sync)); fn get_route_by_idx(insertion_ctx: &InsertionContext, route_idx: usize) -> &RouteContext { insertion_ctx.solution.routes.get(route_idx).expect("invalid route index") @@ -91,7 +92,8 @@ fn get_evaluation_context<'a>(search_ctx: &'a SearchContext, job: &'a Job) -> Ev EvaluationContext { goal: search_ctx.0.problem.goal.as_ref(), job, - leg_selection: search_ctx.1, + // TODO: We have to think again whether a copy is what we want here + leg_selection: search_ctx.1.generate_copy(), result_selector: search_ctx.2, } } @@ -128,7 +130,7 @@ fn create_route_pairs(insertion_ctx: &InsertionContext, route_pairs_threshold: u .map(|inner_idx| (outer_idx, inner_idx)) }) .collect::>(); - SelectionSamplingIterator::new(distances.into_iter(), route_pairs_threshold, random.clone()).collect() + SelectionSamplingIterator::new(distances.into_iter(), route_pairs_threshold, DefaultPureRandom::with_seed(random.get_rng().next_u64())).collect() }) .unwrap_or_else(|| { let route_count = insertion_ctx.solution.routes.len(); @@ -140,7 +142,7 @@ fn create_route_pairs(insertion_ctx: &InsertionContext, route_pairs_threshold: u .map(move |inner_idx| (outer_idx, inner_idx)) }) .collect::>(); - SelectionSamplingIterator::new(all_route_pairs.into_iter(), route_pairs_threshold, random.clone()).collect() + SelectionSamplingIterator::new(all_route_pairs.into_iter(), route_pairs_threshold, DefaultPureRandom::with_seed(random.get_rng().next_u64())).collect() }) } @@ -158,10 +160,13 @@ fn find_insertion_cost(search_ctx: &SearchContext, job: &Job, route_ctx: &RouteC search_ctx.0.problem.goal.accept_route_state(&mut route_ctx); // NOTE This is not the best approach for multi-jobs - let &(insertion_ctx, leg_selection, result_selector) = search_ctx; + let (insertion_ctx, leg_selection, _) = search_ctx; + // let evaluation_context = EvaluationContext {} + // TODO: Think about whether a copy of `leg_selection` is the right thing. + let leg_selection = leg_selection.generate_copy(); eval_job_insertion_in_route( insertion_ctx, - &EvaluationContext { goal: insertion_ctx.problem.goal.as_ref(), job, leg_selection, result_selector }, + &mut EvaluationContext { goal: insertion_ctx.problem.goal.as_ref(), job, leg_selection, result_selector: search_ctx.2 }, &route_ctx, InsertionPosition::Concrete(idx - 1), InsertionResult::make_failure(), @@ -186,9 +191,9 @@ fn find_in_place_result( let route_ctx = remove_job_with_copy(search_ctx, extract_job, route_ctx); - let eval_ctx = get_evaluation_context(search_ctx, insert_job); + let mut eval_ctx = get_evaluation_context(search_ctx, insert_job); - eval_job_insertion_in_route(search_ctx.0, &eval_ctx, &route_ctx, position, InsertionResult::make_failure()) + eval_job_insertion_in_route(search_ctx.0, &mut eval_ctx, &route_ctx, position, InsertionResult::make_failure()) } fn find_top_results( @@ -200,14 +205,14 @@ fn find_top_results( jobs.iter() .map(|job| { - let eval_ctx = get_evaluation_context(search_ctx, job); + let mut eval_ctx = get_evaluation_context(search_ctx, job); let mut results = (0..legs_count) .map(InsertionPosition::Concrete) .map(|position| { eval_job_insertion_in_route( search_ctx.0, - &eval_ctx, + &mut eval_ctx, route_ctx, position, InsertionResult::make_failure(), @@ -292,7 +297,7 @@ fn remove_job_with_copy(search_ctx: &SearchContext, job: &Job, route_ctx: &Route fn try_exchange_jobs_in_routes( insertion_ctx: &mut InsertionContext, route_pair: (usize, usize), - leg_selection: &LegSelection, + mut leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> bool { let quota = insertion_ctx.environment.quota.clone(); @@ -302,7 +307,7 @@ fn try_exchange_jobs_in_routes( return true; } - let search_ctx: SearchContext = (insertion_ctx, leg_selection, result_selector); + let search_ctx: SearchContext = (insertion_ctx, leg_selection.next(), result_selector); let (outer_idx, inner_idx) = route_pair; let outer_route_ctx = get_route_by_idx(insertion_ctx, outer_idx); @@ -373,7 +378,7 @@ fn try_exchange_jobs_in_routes( fn try_exchange_jobs( insertion_ctx: &mut InsertionContext, insertion_pair: (InsertionResult, InsertionResult), - leg_selection: &LegSelection, + mut leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) { if let (InsertionResult::Success(outer_success), InsertionResult::Success(inner_success)) = insertion_pair { @@ -404,11 +409,11 @@ fn try_exchange_jobs( let position = if position < removed_idx || position == 0 { position } else { position - 1 }; let position = InsertionPosition::Concrete(position); - let search_ctx: SearchContext = (insertion_ctx, leg_selection, result_selector); - let eval_ctx = get_evaluation_context(&search_ctx, &success.job); + let search_ctx: SearchContext = (insertion_ctx, leg_selection.next(), result_selector); + let mut eval_ctx = get_evaluation_context(&search_ctx, &success.job); let alternative = InsertionResult::make_failure(); - eval_job_insertion_in_route(insertion_ctx, &eval_ctx, &route_ctx, position, alternative) + eval_job_insertion_in_route(insertion_ctx, &mut eval_ctx, &route_ctx, position, alternative) .try_into() .ok() .map(|success: InsertionSuccess| (success, Some(route_ctx))) diff --git a/vrp-core/src/solver/search/recreate/mod.rs b/vrp-core/src/solver/search/recreate/mod.rs index 9b7ed1af0..84de19698 100644 --- a/vrp-core/src/solver/search/recreate/mod.rs +++ b/vrp-core/src/solver/search/recreate/mod.rs @@ -98,7 +98,7 @@ impl Recreate for ConfigurableRecreate { insertion_ctx, self.job_selector.as_ref(), self.route_selector.as_ref(), - &self.leg_selection, + self.leg_selection.generate_copy(), result_selector, ) } diff --git a/vrp-core/src/solver/search/recreate/recreate_with_blinks.rs b/vrp-core/src/solver/search/recreate/recreate_with_blinks.rs index 4de67759a..6ebc3274f 100644 --- a/vrp-core/src/solver/search/recreate/recreate_with_blinks.rs +++ b/vrp-core/src/solver/search/recreate/recreate_with_blinks.rs @@ -124,7 +124,7 @@ impl RecreateWithBlinks { Self { job_selectors: selectors.into_iter().map(|(selector, _)| selector).collect(), route_selector: Box::::default(), - leg_selection: LegSelection::Stochastic(random.clone()), + leg_selection: LegSelection::random_stochastic(&random), result_selector: Box::new(BlinkResultSelector::new_with_defaults(random)), insertion_heuristic: Default::default(), weights, @@ -157,7 +157,7 @@ impl Recreate for RecreateWithBlinks { insertion_ctx, job_selector, self.route_selector.as_ref(), - &self.leg_selection, + self.leg_selection.generate_copy(), self.result_selector.as_ref(), ) } diff --git a/vrp-core/src/solver/search/recreate/recreate_with_cheapest.rs b/vrp-core/src/solver/search/recreate/recreate_with_cheapest.rs index 11ade9f19..b77f30084 100644 --- a/vrp-core/src/solver/search/recreate/recreate_with_cheapest.rs +++ b/vrp-core/src/solver/search/recreate/recreate_with_cheapest.rs @@ -18,7 +18,7 @@ impl RecreateWithCheapest { recreate: ConfigurableRecreate::new( Box::::default(), Box::::default(), - LegSelection::Stochastic(random), + LegSelection::random_stochastic(&random), ResultSelection::Concrete(Box::::default()), Default::default(), ), diff --git a/vrp-core/src/solver/search/recreate/recreate_with_farthest.rs b/vrp-core/src/solver/search/recreate/recreate_with_farthest.rs index dd30f2f67..a9917d23c 100644 --- a/vrp-core/src/solver/search/recreate/recreate_with_farthest.rs +++ b/vrp-core/src/solver/search/recreate/recreate_with_farthest.rs @@ -19,7 +19,7 @@ impl RecreateWithFarthest { recreate: ConfigurableRecreate::new( Box::::default(), Box::::default(), - LegSelection::Stochastic(random), + LegSelection::random_stochastic(&random), ResultSelection::Concrete(Box::::default()), Default::default(), ), diff --git a/vrp-core/src/solver/search/recreate/recreate_with_gaps.rs b/vrp-core/src/solver/search/recreate/recreate_with_gaps.rs index a8b3b2a9e..cc715ea3e 100644 --- a/vrp-core/src/solver/search/recreate/recreate_with_gaps.rs +++ b/vrp-core/src/solver/search/recreate/recreate_with_gaps.rs @@ -35,7 +35,7 @@ impl RecreateWithGaps { recreate: ConfigurableRecreate::new( Box::new(GapsJobSelector { min_jobs, max_jobs }), Box::::default(), - LegSelection::Stochastic(random.clone()), + LegSelection::random_stochastic(&random), ResultSelection::Stochastic(ResultSelectorProvider::new_default(random)), Default::default(), ), diff --git a/vrp-core/src/solver/search/recreate/recreate_with_nearest_neighbor.rs b/vrp-core/src/solver/search/recreate/recreate_with_nearest_neighbor.rs index de6391ee3..c53770695 100644 --- a/vrp-core/src/solver/search/recreate/recreate_with_nearest_neighbor.rs +++ b/vrp-core/src/solver/search/recreate/recreate_with_nearest_neighbor.rs @@ -17,7 +17,7 @@ impl RecreateWithNearestNeighbor { recreate: ConfigurableRecreate::new( Box::::default(), Box::::default(), - LegSelection::Stochastic(random), + LegSelection::random_stochastic(&random), ResultSelection::Concrete(Box::::default()), InsertionHeuristic::new(Box::new(PositionInsertionEvaluator::new(InsertionPosition::Last))), ), diff --git a/vrp-core/src/solver/search/recreate/recreate_with_perturbation.rs b/vrp-core/src/solver/search/recreate/recreate_with_perturbation.rs index 3fb0db880..f0721b395 100644 --- a/vrp-core/src/solver/search/recreate/recreate_with_perturbation.rs +++ b/vrp-core/src/solver/search/recreate/recreate_with_perturbation.rs @@ -18,7 +18,7 @@ impl RecreateWithPerturbation { recreate: ConfigurableRecreate::new( Box::::default(), Box::::default(), - LegSelection::Stochastic(random.clone()), + LegSelection::random_stochastic(&random), ResultSelection::Concrete(Box::new(NoiseResultSelector::new(noise))), Default::default(), ), diff --git a/vrp-core/src/solver/search/recreate/recreate_with_regret.rs b/vrp-core/src/solver/search/recreate/recreate_with_regret.rs index 52f017a88..1eb436b67 100644 --- a/vrp-core/src/solver/search/recreate/recreate_with_regret.rs +++ b/vrp-core/src/solver/search/recreate/recreate_with_regret.rs @@ -27,7 +27,7 @@ impl RecreateWithRegret { recreate: ConfigurableRecreate::new( Box::::default(), Box::::default(), - LegSelection::Stochastic(random.clone()), + LegSelection::random_stochastic(&random), ResultSelection::Stochastic(ResultSelectorProvider::new_default(random)), InsertionHeuristic::new(Box::new(RegretInsertionEvaluator::new(min, max))), ), @@ -57,7 +57,7 @@ impl InsertionEvaluator for RegretInsertionEvaluator { insertion_ctx: &InsertionContext, job: &Job, routes: &[&RouteContext], - leg_selection: &LegSelection, + leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> InsertionResult { self.fallback_evaluator.evaluate_job(insertion_ctx, job, routes, leg_selection, result_selector) @@ -68,7 +68,7 @@ impl InsertionEvaluator for RegretInsertionEvaluator { insertion_ctx: &InsertionContext, route_ctx: &RouteContext, jobs: &[&Job], - leg_selection: &LegSelection, + leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> InsertionResult { self.fallback_evaluator.evaluate_route(insertion_ctx, route_ctx, jobs, leg_selection, result_selector) @@ -79,7 +79,7 @@ impl InsertionEvaluator for RegretInsertionEvaluator { insertion_ctx: &InsertionContext, jobs: &[&Job], routes: &[&RouteContext], - leg_selection: &LegSelection, + mut leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> InsertionResult { let regret_index = insertion_ctx.environment.random.uniform_int(self.min as i32, self.max as i32) as usize; @@ -91,7 +91,7 @@ impl InsertionEvaluator for RegretInsertionEvaluator { let mut results = self .fallback_evaluator - .evaluate_and_collect_all(insertion_ctx, jobs, routes, leg_selection, result_selector) + .evaluate_and_collect_all(insertion_ctx, jobs, routes, leg_selection.next(), result_selector) .into_iter() .filter_map(|result| match result { InsertionResult::Success(success) => Some(success), diff --git a/vrp-core/src/solver/search/recreate/recreate_with_skip_best.rs b/vrp-core/src/solver/search/recreate/recreate_with_skip_best.rs index 9fe439740..98b2d9525 100644 --- a/vrp-core/src/solver/search/recreate/recreate_with_skip_best.rs +++ b/vrp-core/src/solver/search/recreate/recreate_with_skip_best.rs @@ -25,7 +25,7 @@ impl RecreateWithSkipBest { recreate: ConfigurableRecreate::new( Box::::default(), Box::::default(), - LegSelection::Stochastic(random.clone()), + LegSelection::random_stochastic(&random), ResultSelection::Stochastic(ResultSelectorProvider::new_default(random)), InsertionHeuristic::new(Box::new(SkipBestInsertionEvaluator::new(min, max))), ), @@ -55,7 +55,7 @@ impl InsertionEvaluator for SkipBestInsertionEvaluator { insertion_ctx: &InsertionContext, job: &Job, routes: &[&RouteContext], - leg_selection: &LegSelection, + leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> InsertionResult { self.fallback_evaluator.evaluate_job(insertion_ctx, job, routes, leg_selection, result_selector) @@ -66,7 +66,7 @@ impl InsertionEvaluator for SkipBestInsertionEvaluator { insertion_ctx: &InsertionContext, route_ctx: &RouteContext, jobs: &[&Job], - leg_selection: &LegSelection, + leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> InsertionResult { self.fallback_evaluator.evaluate_route(insertion_ctx, route_ctx, jobs, leg_selection, result_selector) @@ -77,7 +77,7 @@ impl InsertionEvaluator for SkipBestInsertionEvaluator { insertion_ctx: &InsertionContext, jobs: &[&Job], routes: &[&RouteContext], - leg_selection: &LegSelection, + leg_selection: LegSelection, result_selector: &(dyn ResultSelector + Send + Sync), ) -> InsertionResult { let skip_index = insertion_ctx.environment.random.uniform_int(self.min as i32, self.max as i32); diff --git a/vrp-core/src/solver/search/recreate/recreate_with_skip_random.rs b/vrp-core/src/solver/search/recreate/recreate_with_skip_random.rs index 2dc1cb470..3d28c2bb0 100644 --- a/vrp-core/src/solver/search/recreate/recreate_with_skip_random.rs +++ b/vrp-core/src/solver/search/recreate/recreate_with_skip_random.rs @@ -19,7 +19,7 @@ impl RecreateWithSkipRandom { recreate: ConfigurableRecreate::new( Box::::default(), Box::::default(), - LegSelection::Stochastic(random.clone()), + LegSelection::random_stochastic(&random), ResultSelection::Stochastic(ResultSelectorProvider::new_default(random)), Default::default(), ), diff --git a/vrp-core/src/solver/search/recreate/recreate_with_slice.rs b/vrp-core/src/solver/search/recreate/recreate_with_slice.rs index 1c20e9ba4..fecad829a 100644 --- a/vrp-core/src/solver/search/recreate/recreate_with_slice.rs +++ b/vrp-core/src/solver/search/recreate/recreate_with_slice.rs @@ -19,7 +19,7 @@ impl RecreateWithSlice { recreate: ConfigurableRecreate::new( Box::::default(), Box::::default(), - LegSelection::Stochastic(random.clone()), + LegSelection::random_stochastic(&random), ResultSelection::Stochastic(ResultSelectorProvider::new_default(random)), Default::default(), ), diff --git a/vrp-core/src/solver/search/redistribute_search.rs b/vrp-core/src/solver/search/redistribute_search.rs index e36073a6f..67c6d06f6 100644 --- a/vrp-core/src/solver/search/redistribute_search.rs +++ b/vrp-core/src/solver/search/redistribute_search.rs @@ -9,9 +9,10 @@ use crate::models::*; use crate::prelude::Problem; use hashbrown::HashMap; use rosomaxa::prelude::*; -use rosomaxa::utils::SelectionSamplingIterator; +use rosomaxa::utils::{DefaultPureRandom, SelectionSamplingIterator}; use std::ops::Range; use std::sync::Arc; +use rand::RngCore; /// A search operator which removes jobs from existing routes and prevents their insertion into /// the same routes again. @@ -107,14 +108,14 @@ fn remove_jobs( let unassigned = &mut insertion_ctx.solution.unassigned; let sample = random.uniform_int(routes_range.start, routes_range.end) as usize; - SelectionSamplingIterator::new(insertion_ctx.solution.routes.iter_mut(), sample, random.clone()) + SelectionSamplingIterator::new(insertion_ctx.solution.routes.iter_mut(), sample, DefaultPureRandom::with_seed(random.get_rng().next_u64())) .flat_map(|route_ctx| { #[allow(clippy::needless_collect)] let all_jobs = route_ctx.route().tour.jobs().filter(|job| !locked.contains(*job)).collect::>(); let amount = random.uniform_int(jobs_range.start, jobs_range.end) as usize; let jobs = if random.is_head_not_tails() { - let jobs = SelectionSamplingIterator::new(all_jobs.into_iter().cloned(), amount, random.clone()) + let jobs = SelectionSamplingIterator::new(all_jobs.into_iter().cloned(), amount, DefaultPureRandom::with_seed(random.get_rng().next_u64())) .collect::>(); jobs.iter().for_each(|job| { route_ctx.route_mut().tour.remove(job); diff --git a/vrp-core/tests/unit/construction/heuristics/evaluators_test.rs b/vrp-core/tests/unit/construction/heuristics/evaluators_test.rs index b4a3d271e..e98f8b49c 100644 --- a/vrp-core/tests/unit/construction/heuristics/evaluators_test.rs +++ b/vrp-core/tests/unit/construction/heuristics/evaluators_test.rs @@ -8,6 +8,7 @@ use crate::models::common::{Cost, Location, Schedule, TimeSpan, TimeWindow, Time use crate::models::problem::{Job, Single, VehicleDetail}; use crate::models::solution::{Activity, Place, Registry}; use std::sync::Arc; +use rosomaxa::utils::DefaultPureRandom; type JobPlace = crate::models::problem::Place; @@ -38,19 +39,19 @@ fn evaluate_job_insertion( insertion_position: InsertionPosition, ) -> InsertionResult { let route_selector = AllRouteSelector::default(); - let leg_selection = LegSelection::Stochastic(insertion_ctx.environment.random.clone()); + let leg_selection = LegSelection::Stochastic(DefaultPureRandom::for_tests()); let result_selector = BestResultSelector::default(); let routes = route_selector.select(insertion_ctx, &[]).collect::>(); - let eval_ctx = EvaluationContext { + let mut eval_ctx = EvaluationContext { goal: &insertion_ctx.problem.goal, job, - leg_selection: &leg_selection, + leg_selection: leg_selection, result_selector: &result_selector, }; routes.iter().fold(InsertionResult::make_failure(), |acc, route_ctx| { - eval_job_insertion_in_route(insertion_ctx, &eval_ctx, route_ctx, insertion_position, acc) + eval_job_insertion_in_route(insertion_ctx, &mut eval_ctx, route_ctx, insertion_position, acc) }) } diff --git a/vrp-core/tests/unit/construction/heuristics/selectors_test.rs b/vrp-core/tests/unit/construction/heuristics/selectors_test.rs index 77cddd60f..785f439c3 100644 --- a/vrp-core/tests/unit/construction/heuristics/selectors_test.rs +++ b/vrp-core/tests/unit/construction/heuristics/selectors_test.rs @@ -81,7 +81,7 @@ mod selections { fn can_use_stochastic_selection_mode_impl(skip: usize, activities: usize, expected_threshold: usize) { let target = 10; - let selection_mode = LegSelection::Stochastic(Environment::default().random); + let mut selection_mode = LegSelection::random_stochastic(&Environment::default().random); let (_, solution) = generate_matrix_routes_with_defaults(activities, 1, false); let route_ctx = RouteContext::new_with_state(solution.routes.into_iter().next().unwrap(), Default::default()); let mut counter = 0; diff --git a/vrp-core/tests/unit/solver/search/local/exchange_swap_star_test.rs b/vrp-core/tests/unit/solver/search/local/exchange_swap_star_test.rs index 84e156523..3d95db993 100644 --- a/vrp-core/tests/unit/solver/search/local/exchange_swap_star_test.rs +++ b/vrp-core/tests/unit/solver/search/local/exchange_swap_star_test.rs @@ -41,7 +41,7 @@ fn create_insertion_ctx( } fn create_default_selectors() -> (LegSelection, BestResultSelector) { - let leg_selection = LegSelection::Stochastic(Environment::default().random); + let leg_selection = LegSelection::random_stochastic(&Environment::default().random); let result_selector = BestResultSelector::default(); (leg_selection, result_selector) @@ -128,7 +128,7 @@ fn can_exchange_jobs_in_routes() { rearrange_jobs_in_routes(&mut insertion_ctx, job_order.as_slice()); let (leg_selection, result_selector) = create_default_selectors(); - try_exchange_jobs_in_routes(&mut insertion_ctx, route_pair, &leg_selection, &result_selector); + try_exchange_jobs_in_routes(&mut insertion_ctx, route_pair, leg_selection, &result_selector); compare_with_ignore(get_customer_ids_from_routes(&insertion_ctx).as_slice(), &expected_route_ids, ""); } @@ -165,7 +165,7 @@ fn can_exchange_single_jobs_impl( create_insertion_success(&insertion_ctx, inner_insertion), ); - try_exchange_jobs(&mut insertion_ctx, insertion_pair, &leg_selection, &result_selector); + try_exchange_jobs(&mut insertion_ctx, insertion_pair, leg_selection, &result_selector); compare_with_ignore(get_customer_ids_from_routes(&insertion_ctx).as_slice(), &expected_route_ids, ""); } @@ -184,7 +184,7 @@ fn can_find_insertion_cost_impl(job_id: &str, expected: Cost) { let matrix = (3, 1); let insertion_ctx = create_insertion_ctx(matrix, vec![], false); let (leg_selection, result_selector) = create_default_selectors(); - let search_ctx: SearchContext = (&insertion_ctx, &leg_selection, &result_selector); + let search_ctx: SearchContext = (&insertion_ctx, leg_selection, &result_selector); let job = get_jobs_by_ids(&insertion_ctx, &[job_id]).first().cloned().unwrap(); let route_ctx = insertion_ctx.solution.routes.first().unwrap(); @@ -217,7 +217,7 @@ fn can_find_in_place_result_impl( rearrange_jobs_in_routes(&mut insertion_ctx, job_order.as_slice()); let (leg_selection, result_selector) = create_default_selectors(); let jobs_map = get_jobs_map_by_ids(&insertion_ctx); - let search_ctx: SearchContext = (&insertion_ctx, &leg_selection, &result_selector); + let search_ctx: SearchContext = (&insertion_ctx, leg_selection, &result_selector); let route_ctx = insertion_ctx.solution.routes.get(route_idx).unwrap(); let insert_job = jobs_map.get(insert_job).unwrap(); let extract_job = jobs_map.get(extract_job).unwrap(); @@ -244,7 +244,7 @@ fn can_find_top_results_impl(job_id: &str, disallowed_pairs: Vec<(&str, &str)>, let matrix = (5, 2); let insertion_ctx = create_insertion_ctx(matrix, disallowed_pairs, true); let (leg_selection, result_selector) = create_default_selectors(); - let search_ctx: SearchContext = (&insertion_ctx, &leg_selection, &result_selector); + let search_ctx: SearchContext = (&insertion_ctx, leg_selection, &result_selector); let job_ids = get_jobs_by_ids(&insertion_ctx, &[job_id]); let route_ctx = insertion_ctx.solution.routes.first().unwrap();