diff --git a/CHANGELOG.md b/CHANGELOG.md index 735042876..5bec9e974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ All notable changes to this project will be documented in this file. * minor refactorings * increase limits for ruin methods * tweak rosomaxa algorithm +* tweak job index ## [v1.23.0]- 2023-12-22 diff --git a/vrp-core/src/models/problem/jobs.rs b/vrp-core/src/models/problem/jobs.rs index 403a708d7..aa3ead5fc 100644 --- a/vrp-core/src/models/problem/jobs.rs +++ b/vrp-core/src/models/problem/jobs.rs @@ -4,7 +4,7 @@ mod jobs_test; use crate::models::common::*; use crate::models::problem::{Costs, Fleet, TransportCost}; -use crate::utils::short_type_name; +use crate::utils::{short_type_name, Either}; use hashbrown::HashMap; use rosomaxa::utils::compare_floats_f32; use std::cmp::Ordering::Less; @@ -219,9 +219,9 @@ const DEFAULT_COST: LowPrecisionCost = 0.; const UNREACHABLE_COST: LowPrecisionCost = f32::MAX; /// Maximum amount of job's neighbours stored in index. We restrict this side to lower impact on -/// memory footprint. It is unlikely that more than 1000 neighbours needed to be processed in reality, +/// memory footprint. It is unlikely that more than 100 neighbours needed to be processed in reality, /// but we keep it 5x times more. -const MAX_NEIGHBOURS: usize = 5000; +const MAX_NEIGHBOURS: usize = 512; /// Stores all jobs taking into account their neighborhood. pub struct Jobs { @@ -289,10 +289,10 @@ impl Hash for Job { } /// Returns job locations. -pub fn get_job_locations<'a>(job: &'a Job) -> Box> + 'a> { +pub fn get_job_locations<'a>(job: &'a Job) -> impl Iterator> + 'a { match job { - Job::Single(single) => Box::new(single.places.iter().map(|p| p.location)), - Job::Multi(multi) => Box::new(multi.jobs.iter().flat_map(|j| j.places.iter().map(|p| p.location))), + Job::Single(single) => Either::Left(single.places.iter().map(|p| p.location)), + Job::Multi(multi) => Either::Right(multi.jobs.iter().flat_map(|j| j.places.iter().map(|p| p.location))), } } @@ -321,10 +321,12 @@ fn create_index( .iter() .filter(|j| **j != job) .map(|j| (j.clone(), get_cost_between_jobs(profile, avg_costs, transport, &job, j))) - .take(MAX_NEIGHBOURS) .collect(); sorted_job_costs.sort_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Less)); + sorted_job_costs.truncate(MAX_NEIGHBOURS); + sorted_job_costs.shrink_to_fit(); + let fleet_costs = starts .iter() .cloned() @@ -368,12 +370,10 @@ fn get_cost_between_job_and_location( to: Location, ) -> LowPrecisionCost { get_job_locations(job) - .map(|from| match from { - Some(from) => get_cost_between_locations(profile, costs, transport, from, to), - _ => DEFAULT_COST, - }) + .flatten() + .map(|from| get_cost_between_locations(profile, costs, transport, from, to)) .min_by(|a, b| a.partial_cmp(b).unwrap_or(Less)) - .unwrap_or(DEFAULT_COST) + .unwrap_or(UNREACHABLE_COST) } /// Returns minimal cost between jobs. @@ -387,38 +387,19 @@ fn get_cost_between_jobs( let outer: Vec> = get_job_locations(lhs).collect(); let inner: Vec> = get_job_locations(rhs).collect(); - let (location_cost, duration) = outer + let routing_cost = outer .iter() .flat_map(|o| inner.iter().map(move |i| (*o, *i))) .map(|pair| match pair { - (Some(from), Some(to)) => { - let total = get_cost_between_locations(profile, costs, transport, from, to); - let duration = transport.duration_approx(profile, from, to); - (total, duration) - } - _ => (DEFAULT_COST, 0.), - }) - .min_by(|(a, _), (b, _)| compare_floats_f32(*a, *b)) - .unwrap_or((DEFAULT_COST, 0.)); - - let time_cost = lhs - .places() - .flat_map(|place| place.times.iter()) - .flat_map(|left| { - rhs.places().flat_map(|place| place.times.iter()).map(move |right| match (left, right) { - (TimeSpan::Window(left), TimeSpan::Window(right)) => { - // NOTE exclude traveling duration between jobs - (left.distance(right) - duration).max(0.) - } - _ => 0., - }) + (Some(from), Some(to)) => get_cost_between_locations(profile, costs, transport, from, to), + _ => DEFAULT_COST, }) - .map(|cost| cost as LowPrecisionCost) .min_by(|a, b| compare_floats_f32(*a, *b)) - .unwrap_or(0.) - * costs.per_waiting_time as LowPrecisionCost; + .unwrap_or(DEFAULT_COST); + + // NOTE: ignore time window difference costs as it is hard to balance with routing costs - location_cost + time_cost + routing_cost } fn get_avg_profile_costs(fleet: &Fleet) -> HashMap { diff --git a/vrp-pragmatic/src/validation/objectives.rs b/vrp-pragmatic/src/validation/objectives.rs index 6c5661c15..abd2b9b7c 100644 --- a/vrp-pragmatic/src/validation/objectives.rs +++ b/vrp-pragmatic/src/validation/objectives.rs @@ -5,6 +5,7 @@ mod objectives_test; use super::*; use crate::format::problem::Objective::*; use crate::utils::combine_error_results; +use hashbrown::HashSet; /// Checks that objective is not empty when specified. fn check_e1600_empty_objective(objectives: &[&Objective]) -> Result<(), FormatError> { @@ -21,38 +22,9 @@ fn check_e1600_empty_objective(objectives: &[&Objective]) -> Result<(), FormatEr /// Checks that each objective type specified only once. fn check_e1601_duplicate_objectives(objectives: &[&Objective]) -> Result<(), FormatError> { - let mut duplicates = objectives - .iter() - .fold(HashMap::new(), |mut acc, objective| { - match objective { - MinimizeCost => acc.entry("minimize-cost"), - MinimizeDistance => acc.entry("minimize-distance"), - MinimizeDuration => acc.entry("minimize-duration"), - MinimizeTours => acc.entry("minimize-tours"), - MaximizeTours => acc.entry("maximize-tours"), - MaximizeValue { .. } => acc.entry("maximize-value"), - MinimizeUnassigned { .. } => acc.entry("minimize-unassigned"), - MinimizeArrivalTime => acc.entry("minimize-arrival-time"), - BalanceMaxLoad { .. } => acc.entry("balance-max-load"), - BalanceActivities { .. } => acc.entry("balance-activities"), - BalanceDistance { .. } => acc.entry("balance-distance"), - BalanceDuration { .. } => acc.entry("balance-duration"), - CompactTour { .. } => acc.entry("compact-tour"), - TourOrder => acc.entry("tour-order"), - FastService { .. } => acc.entry("fast-service"), - } - .and_modify(|count| *count += 1) - .or_insert(1_usize); - - acc - }) - .iter() - .filter_map(|(name, count)| if *count > 1 { Some((*name).to_string()) } else { None }) - .collect::>(); - - duplicates.sort(); + let unique = objectives.iter().cloned().map(std::mem::discriminant).collect::>(); - if duplicates.is_empty() { + if unique.len() == objectives.len() { Ok(()) } else { Err(FormatError::new(