#[cfg(test)]
#[path = "../../../../tests/unit/solver/mutation/ruin/adjusted_string_removal_test.rs"]
mod adjusted_string_removal_test;
use std::sync::{Arc, RwLock};
use super::{select_seed_jobs, Ruin};
use crate::construction::heuristics::{InsertionContext, RouteContext};
use crate::models::problem::{Actor, Job};
use crate::models::solution::Tour;
use crate::solver::RefinementContext;
use crate::utils::Random;
use hashbrown::HashSet;
pub struct AdjustedStringRemoval {
lmax: usize,
cavg: usize,
alpha: f64,
}
impl AdjustedStringRemoval {
pub fn new(lmax: usize, cavg: usize, alpha: f64) -> Self {
Self { lmax, cavg, alpha }
}
fn calculate_limits(&self, routes: &[RouteContext], random: &Arc<dyn Random + Send + Sync>) -> (usize, usize) {
let lsmax = calculate_average_tour_cardinality(routes).min(self.lmax as f64);
let ksmax = 4. * (self.cavg as f64) / (1. + lsmax) - 1.;
let ks = random.uniform_real(1., ksmax + 1.).floor() as usize;
(lsmax as usize, ks)
}
}
impl Default for AdjustedStringRemoval {
fn default() -> Self {
Self::new(10, 10, 0.01)
}
}
impl Ruin for AdjustedStringRemoval {
fn run(&self, _refinement_ctx: &RefinementContext, mut insertion_ctx: InsertionContext) -> InsertionContext {
let jobs: RwLock<HashSet<Job>> = RwLock::new(HashSet::new());
let actors: RwLock<HashSet<Arc<Actor>>> = RwLock::new(HashSet::new());
let routes: Vec<RouteContext> = insertion_ctx.solution.routes.clone();
let problem = insertion_ctx.problem.clone();
let locked = insertion_ctx.solution.locked.clone();
let random = insertion_ctx.random.clone();
let (lsmax, ks) = self.calculate_limits(&routes, &random);
select_seed_jobs(&problem, &routes, &random)
.filter(|job| !jobs.read().unwrap().contains(job))
.take_while(|_| actors.read().unwrap().len() != ks)
.for_each(|job| {
insertion_ctx
.solution
.routes
.iter_mut()
.find(|rc| !actors.read().unwrap().contains(&rc.route.actor) && rc.route.tour.index(&job).is_some())
.iter_mut()
.for_each(|rc| {
let ltmax = rc.route.tour.activity_count().min(lsmax);
let lt = random.uniform_real(1.0, ltmax as f64 + 1.).floor() as usize;
if let Some(index) = rc.route.tour.index(&job) {
actors.write().unwrap().insert(rc.route.actor.clone());
select_string((&rc.route.tour, index), lt, self.alpha, &random)
.filter(|job| !locked.contains(job))
.collect::<Vec<Job>>()
.into_iter()
.for_each(|job| {
rc.route_mut().tour.remove(&job);
jobs.write().unwrap().insert(job);
});
}
});
});
jobs.write().unwrap().iter().for_each(|job| insertion_ctx.solution.required.push(job.clone()));
insertion_ctx
}
}
type JobIter<'a> = Box<dyn Iterator<Item = Job> + 'a>;
fn calculate_average_tour_cardinality(routes: &[RouteContext]) -> f64 {
(routes.iter().map(|rc| rc.route.tour.activity_count() as f64).sum::<f64>() / (routes.len() as f64)).round()
}
fn select_string<'a>(
seed_tour: (&'a Tour, usize),
cardinality: usize,
alpha: f64,
random: &Arc<dyn Random + Send + Sync>,
) -> JobIter<'a> {
if random.is_head_not_tails() {
sequential_string(seed_tour, cardinality, random)
} else {
preserved_string(seed_tour, cardinality, alpha, random)
}
}
fn sequential_string<'a>(
seed_tour: (&'a Tour, usize),
cardinality: usize,
random: &Arc<dyn Random + Send + Sync>,
) -> JobIter<'a> {
let (begin, end) = lower_bounds(cardinality, seed_tour.0.activity_count(), seed_tour.1);
let start = random.uniform_int(begin as i32, end as i32) as usize;
Box::new((start..(start + cardinality)).filter_map(move |i| seed_tour.0.get(i).and_then(|a| a.retrieve_job())))
}
fn preserved_string<'a>(
seed_tour: (&'a Tour, usize),
cardinality: usize,
alpha: f64,
random: &Arc<dyn Random + Send + Sync>,
) -> JobIter<'a> {
let size = seed_tour.0.activity_count();
let index = seed_tour.1;
let split_size = preserved_cardinality(cardinality, size, alpha, random);
let total = cardinality + split_size;
let (begin, end) = lower_bounds(total, size, index);
let start_total = random.uniform_int(begin as i32, end as i32) as usize;
let split_start = random.uniform_int(start_total as i32, (start_total + cardinality - 1) as i32) as usize;
let split_end = split_start + split_size;
let total = total - if index >= split_start && index < split_end { 1 } else { 0 };
Box::new(
(start_total..(start_total + total))
.filter(move |&i| i < split_start || i >= split_end || i == index)
.filter_map(move |i| seed_tour.0.get(i).and_then(|a| a.retrieve_job())),
)
}
fn lower_bounds(string_crd: usize, tour_crd: usize, index: usize) -> (usize, usize) {
let string_crd = string_crd as i32;
let tour_crd = tour_crd as i32;
let index = index as i32;
let start = (index - string_crd).max(1);
let end = (index + string_crd).min(tour_crd - string_crd).max(start);
(start as usize, end as usize)
}
fn preserved_cardinality(
string_crd: usize,
tour_crd: usize,
alpha: f64,
random: &Arc<dyn Random + Send + Sync>,
) -> usize {
if string_crd == tour_crd {
return 0;
}
let mut preserved_crd = 1_usize;
while string_crd + preserved_crd < tour_crd {
if random.uniform_real(0.0, 1.0) < alpha {
break;
} else {
preserved_crd += 1
}
}
preserved_crd
}