From f7d736354af920f903224dcd2c41bd76aff99b7b Mon Sep 17 00:00:00 2001 From: reinterpretcat Date: Thu, 20 Jun 2024 21:58:39 +0200 Subject: [PATCH] Introduce a new way of working with route state --- vrp-core/Cargo.toml | 1 + .../src/construction/enablers/multi_trip.rs | 5 +- .../src/construction/features/capacity.rs | 182 +++++++----------- vrp-core/src/construction/features/mod.rs | 3 +- .../src/construction/features/recharge.rs | 8 - vrp-core/src/construction/features/reloads.rs | 55 +++--- .../src/construction/features/work_balance.rs | 10 +- .../src/construction/heuristics/context.rs | 42 +++- .../src/construction/heuristics/metrics.rs | 46 ++--- vrp-core/src/lib.rs | 2 + vrp-core/src/macros.rs | 83 ++++++++ vrp-core/src/models/common/load.rs | 41 ---- vrp-core/src/models/examples.rs | 32 +-- vrp-core/src/models/extras.rs | 15 -- vrp-core/src/models/solution/tour.rs | 2 +- .../tests/helpers/construction/features.rs | 42 +--- .../tests/helpers/construction/heuristics.rs | 5 - .../tests/helpers/models/problem/fleet.rs | 5 +- vrp-core/tests/helpers/models/problem/jobs.rs | 3 +- .../construction/features/capacity_test.rs | 39 ++-- .../features/fast_service_test.rs | 3 +- .../construction/features/reloads_test.rs | 65 ++----- .../features/shared_resource_test.rs | 9 +- .../construction/heuristics/metrics_test.rs | 17 +- .../probing/repair_solution_test.rs | 11 +- vrp-core/tests/unit/models/goal_test.rs | 10 +- vrp-pragmatic/src/format/problem/aspects.rs | 56 +----- .../src/format/problem/fleet_reader.rs | 5 +- .../src/format/problem/goal_reader.rs | 32 +-- .../src/format/problem/job_reader.rs | 5 +- .../src/format/solution/solution_writer.rs | 5 +- .../tests/unit/format/problem/reader_test.rs | 9 +- vrp-scientific/src/common/aspects.rs | 39 ---- vrp-scientific/src/common/mod.rs | 1 - vrp-scientific/src/common/text_reader.rs | 9 +- vrp-scientific/src/lilim/reader.rs | 3 +- vrp-scientific/src/solomon/reader.rs | 3 +- vrp-scientific/src/tsplib/reader.rs | 3 +- vrp-scientific/tests/helpers/analysis.rs | 5 +- 39 files changed, 336 insertions(+), 575 deletions(-) create mode 100644 vrp-core/src/macros.rs delete mode 100644 vrp-scientific/src/common/aspects.rs diff --git a/vrp-core/Cargo.toml b/vrp-core/Cargo.toml index 80cb8822c..22eddc4cc 100644 --- a/vrp-core/Cargo.toml +++ b/vrp-core/Cargo.toml @@ -20,3 +20,4 @@ rustc-hash.workspace = true nohash-hasher = "0.2.0" tinyvec = { version = "1.6.0", features = ["alloc"] } +paste = "1.0.15" diff --git a/vrp-core/src/construction/enablers/multi_trip.rs b/vrp-core/src/construction/enablers/multi_trip.rs index 5c2bde104..4a9df77bf 100644 --- a/vrp-core/src/construction/enablers/multi_trip.rs +++ b/vrp-core/src/construction/enablers/multi_trip.rs @@ -39,15 +39,14 @@ pub enum MarkerInsertionPolicy { /// Creates a feature with multi trip functionality. pub fn create_multi_trip_feature( name: &str, - state_keys: Vec, violation_code: ViolationCode, policy: MarkerInsertionPolicy, multi_trip: Arc, ) -> Result { // NOTE guard from a mistake for not including an interval key. Should we just assert? let state_keys = match multi_trip.get_route_intervals().get_interval_key() { - Some(key) if !state_keys.contains(&key) => state_keys.iter().copied().chain(once(key)).collect(), - _ => state_keys.to_vec(), + Some(key) => vec![key], + _ => vec![], }; FeatureBuilder::default() diff --git a/vrp-core/src/construction/features/capacity.rs b/vrp-core/src/construction/features/capacity.rs index afdb99bbe..aa1cb2ecc 100644 --- a/vrp-core/src/construction/features/capacity.rs +++ b/vrp-core/src/construction/features/capacity.rs @@ -7,108 +7,66 @@ mod capacity_test; use super::*; use crate::construction::enablers::*; use crate::models::solution::Activity; -use std::iter::once; use std::marker::PhantomData; use std::sync::Arc; -/// Provides way to use capacity feature. -pub trait CapacityAspects: Send + Sync { - /// Gets vehicle's capacity. - fn get_capacity<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a T>; +custom_activity_state!(CurrentCapacity typeof T: LoadOps); - /// Gets job's demand. - fn get_demand<'a>(&self, single: &'a Single) -> Option<&'a Demand>; +custom_activity_state!(MaxFutureCapacity typeof T: LoadOps); - /// Sets job's new demand. - fn set_demand(&self, single: &mut Single, demand: Demand); +custom_activity_state!(MaxPastCapacity typeof T: LoadOps); - /// Gets capacity state keys. - fn get_state_keys(&self) -> &CapacityKeys; +custom_tour_state!(MaxVehicleLoad typeof f64); - /// Gets violation code. - fn get_violation_code(&self) -> ViolationCode; -} +custom_dimension!(VehicleCapacity typeof T: LoadOps); -/// Combines all state keys needed for capacity feature usage. -#[derive(Clone, Debug)] -pub struct CapacityKeys { - /// A key which tracks current vehicle capacity. - pub current_capacity: StateKey, - /// A key which tracks maximum vehicle capacity ahead in route. - pub max_future_capacity: StateKey, - /// A key which tracks maximum capacity backward in route. - pub max_past_capacity: StateKey, - /// A key which tracks max load in tour. - pub max_load: StateKey, -} +/// A trait to get or set job demand. +pub trait JobDemandDimension { + /// Sets job demand. + fn set_job_demand(&mut self, demand: Demand) -> &mut Self; -impl From<&mut StateKeyRegistry> for CapacityKeys { - fn from(state_registry: &mut StateKeyRegistry) -> Self { - Self { - current_capacity: state_registry.next_key(), - max_future_capacity: state_registry.next_key(), - max_past_capacity: state_registry.next_key(), - max_load: state_registry.next_key(), - } - } -} - -impl CapacityKeys { - fn iter(&self) -> impl Iterator { - once(self.current_capacity) - .chain(once(self.max_future_capacity)) - .chain(once(self.max_past_capacity)) - .chain(once(self.max_load)) - } + /// Gets job demand. + fn get_job_demand(&self) -> Option<&Demand>; } /// Creates capacity feature as a hard constraint with multi trip functionality as a soft constraint. -pub fn create_capacity_limit_with_multi_trip_feature( +pub fn create_capacity_limit_with_multi_trip_feature( name: &str, route_intervals: RouteIntervals, - aspects: A, + violation_code: ViolationCode, ) -> Result where T: LoadOps, - A: CapacityAspects + 'static, { - let feature_keys = aspects.get_state_keys().iter().collect::>(); - let capacity_code = aspects.get_violation_code(); create_multi_trip_feature( name, - feature_keys.clone(), - capacity_code, + violation_code, MarkerInsertionPolicy::Last, - Arc::new(CapacitatedMultiTrip:: { route_intervals, aspects, phantom: Default::default() }), + Arc::new(CapacitatedMultiTrip:: { route_intervals, violation_code, phantom: Default::default() }), ) } /// Creates capacity feature as a hard constraint. -pub fn create_capacity_limit_feature(name: &str, aspects: A) -> Result +pub fn create_capacity_limit_feature(name: &str, violation_code: ViolationCode) -> Result where T: LoadOps, - A: CapacityAspects + 'static, { - let feature_keys = aspects.get_state_keys().iter().collect::>(); - let capacity_code = aspects.get_violation_code(); // TODO theoretically, the code can be easily refactored to get opt-out from no-op multi-trip runtime overhead here create_multi_trip_feature( name, - feature_keys, - capacity_code, + violation_code, MarkerInsertionPolicy::Last, - Arc::new(CapacitatedMultiTrip:: { + Arc::new(CapacitatedMultiTrip:: { route_intervals: RouteIntervals::Single, - aspects, + violation_code, phantom: Default::default(), }), ) } -impl FeatureConstraint for CapacitatedMultiTrip +impl FeatureConstraint for CapacitatedMultiTrip where T: LoadOps, - A: CapacityAspects + 'static, { fn evaluate(&self, move_ctx: &MoveContext<'_>) -> Option { match move_ctx { @@ -120,8 +78,8 @@ where fn merge(&self, source: Job, candidate: Job) -> Result { match (&source, &candidate) { (Job::Single(s_source), Job::Single(s_candidate)) => { - let source_demand: Option<&Demand> = self.aspects.get_demand(s_source); - let candidate_demand: Option<&Demand> = self.aspects.get_demand(s_candidate); + let source_demand: Option<&Demand> = s_source.dimens.get_job_demand(); + let candidate_demand: Option<&Demand> = s_candidate.dimens.get_job_demand(); match (source_demand, candidate_demand) { (None, None) | (Some(_), None) => Ok(source), @@ -131,31 +89,29 @@ where let new_demand = source_demand + candidate_demand; let mut single = Single { places: s_source.places.clone(), dimens: s_source.dimens.clone() }; - self.aspects.set_demand(&mut single, new_demand); + single.dimens.set_job_demand(new_demand); Ok(Job::Single(Arc::new(single))) } } } - _ => Err(self.aspects.get_violation_code()), + _ => Err(self.violation_code), } } } -struct CapacitatedMultiTrip +struct CapacitatedMultiTrip where T: LoadOps, - A: CapacityAspects + 'static, { route_intervals: RouteIntervals, - aspects: A, + violation_code: ViolationCode, phantom: PhantomData, } -impl MultiTrip for CapacitatedMultiTrip +impl MultiTrip for CapacitatedMultiTrip where T: LoadOps, - A: CapacityAspects + 'static, { fn get_route_intervals(&self) -> &RouteIntervals { &self.route_intervals @@ -166,8 +122,6 @@ where } fn recalculate_states(&self, route_ctx: &mut RouteContext) { - let state_keys = self.aspects.get_state_keys(); - let marker_intervals = self .get_route_intervals() .get_marker_intervals(route_ctx) @@ -221,12 +175,12 @@ where (current - end_pickup, current_max.max_load(max)) }); - route_ctx.state_mut().put_activity_states(state_keys.current_capacity, current_capacities); - route_ctx.state_mut().put_activity_states(state_keys.max_past_capacity, max_past_capacities); - route_ctx.state_mut().put_activity_states(state_keys.max_future_capacity, max_future_capacities); + route_ctx.state_mut().set_current_capacity_states(current_capacities); + route_ctx.state_mut().set_max_past_capacity_states(max_past_capacities); + route_ctx.state_mut().set_max_future_capacity_states(max_future_capacities); - if let Some(capacity) = self.aspects.get_capacity(&route_ctx.route().actor.clone().vehicle) { - route_ctx.state_mut().put_route_state(state_keys.max_load, max_load.ratio(capacity)); + if let Some(capacity) = route_ctx.route().actor.clone().vehicle.dimens.get_vehicle_capacity::() { + route_ctx.state_mut().set_max_vehicle_load(max_load.ratio(capacity)); } } @@ -236,24 +190,23 @@ where } } -impl CapacitatedMultiTrip +impl CapacitatedMultiTrip where T: LoadOps, - A: CapacityAspects + 'static, { fn evaluate_job(&self, route_ctx: &RouteContext, job: &Job) -> Option { let can_handle = match job { - Job::Single(job) => self.can_handle_demand_on_intervals(route_ctx, self.aspects.get_demand(job), None), + Job::Single(job) => self.can_handle_demand_on_intervals(route_ctx, job.dimens.get_job_demand(), None), Job::Multi(job) => job .jobs .iter() - .any(|job| self.can_handle_demand_on_intervals(route_ctx, self.aspects.get_demand(job), None)), + .any(|job| self.can_handle_demand_on_intervals(route_ctx, job.dimens.get_job_demand(), None)), }; if can_handle { ConstraintViolation::success() } else { - ConstraintViolation::fail(self.aspects.get_violation_code()) + ConstraintViolation::fail(self.violation_code) } } @@ -272,17 +225,10 @@ where Some(false) } } else { - has_demand_violation( - route_ctx.state(), - activity_ctx.index, - self.aspects.get_capacity(&route_ctx.route().actor.vehicle), - demand, - self.aspects.get_state_keys(), - !self.has_markers(route_ctx), - ) + has_demand_violation(route_ctx, activity_ctx.index, demand, !self.has_markers(route_ctx)) }; - violation.map(|stopped| ConstraintViolation { code: self.aspects.get_violation_code(), stopped }) + violation.map(|stopped| ConstraintViolation { code: self.violation_code, stopped }) } fn has_markers(&self, route_ctx: &RouteContext) -> bool { @@ -295,16 +241,7 @@ where demand: Option<&Demand>, insert_idx: Option, ) -> bool { - let has_demand_violation = |activity_idx: usize| { - has_demand_violation( - route_ctx.state(), - activity_idx, - self.aspects.get_capacity(&route_ctx.route().actor.vehicle), - demand, - self.aspects.get_state_keys(), - true, - ) - }; + let has_demand_violation = |activity_idx: usize| has_demand_violation(route_ctx, activity_idx, demand, true); let has_demand_violation_on_borders = |start_idx: usize, end_idx: usize| { has_demand_violation(start_idx).is_none() || has_demand_violation(end_idx).is_none() @@ -326,34 +263,37 @@ where if let Some(insert_idx) = insert_idx { has_demand_violation(insert_idx).is_none() } else { - has_demand_violation_on_borders(0, route_ctx.route().tour.total().max(1) - 1) + let last_idx = route_ctx.route().tour.end_idx().unwrap_or_default(); + has_demand_violation_on_borders(0, last_idx) } }) } fn get_demand<'a>(&self, activity: &'a Activity) -> Option<&'a Demand> { - activity.job.as_ref().and_then(|single| self.aspects.get_demand(single)) + activity.job.as_ref().and_then(|single| single.dimens.get_job_demand()) } } fn has_demand_violation( - state: &RouteState, + route_ctx: &RouteContext, pivot_idx: usize, - capacity: Option<&T>, demand: Option<&Demand>, - feature_keys: &CapacityKeys, stopped: bool, ) -> Option { + let capacity: Option<&T> = route_ctx.route().actor.vehicle.dimens.get_vehicle_capacity(); let demand = demand?; - let capacity = if let Some(capacity) = capacity.copied() { + + let capacity = if let Some(capacity) = capacity { capacity } else { return Some(stopped); }; - // check how static delivery affect past max load + let state = route_ctx.state(); + + // check how static delivery affects a past max load if demand.delivery.0.is_not_empty() { - let past: T = state.get_activity_state(feature_keys.max_past_capacity, pivot_idx).copied().unwrap_or_default(); + let past: T = state.get_max_past_capacity_at(pivot_idx).copied().unwrap_or_default(); if !capacity.can_fit(&(past + demand.delivery.0)) { return Some(stopped); } @@ -361,8 +301,7 @@ fn has_demand_violation( // check how static pickup affect future max load if demand.pickup.0.is_not_empty() { - let future: T = - state.get_activity_state(feature_keys.max_future_capacity, pivot_idx).copied().unwrap_or_default(); + let future: T = state.get_max_future_capacity_at(pivot_idx).copied().unwrap_or_default(); if !capacity.can_fit(&(future + demand.pickup.0)) { return Some(false); } @@ -371,14 +310,12 @@ fn has_demand_violation( // check dynamic load change let change = demand.change(); if change.is_not_empty() { - let future: T = - state.get_activity_state(feature_keys.max_future_capacity, pivot_idx).copied().unwrap_or_default(); + let future: T = state.get_max_future_capacity_at(pivot_idx).copied().unwrap_or_default(); if !capacity.can_fit(&(future + change)) { return Some(false); } - let current: T = - state.get_activity_state(feature_keys.current_capacity, pivot_idx).copied().unwrap_or_default(); + let current: T = state.get_current_capacity_at(pivot_idx).copied().unwrap_or_default(); if !capacity.can_fit(&(current + change)) { return Some(false); } @@ -386,3 +323,16 @@ fn has_demand_violation( None } + +// TODO extend macro to support this. +struct JobDemandDimenKey; +impl JobDemandDimension for Dimensions { + fn set_job_demand(&mut self, demand: Demand) -> &mut Self { + self.set_value::(demand); + self + } + + fn get_job_demand(&self) -> Option<&Demand> { + self.get_value::() + } +} diff --git a/vrp-core/src/construction/features/mod.rs b/vrp-core/src/construction/features/mod.rs index fd3f15c48..9128eec19 100644 --- a/vrp-core/src/construction/features/mod.rs +++ b/vrp-core/src/construction/features/mod.rs @@ -11,8 +11,7 @@ use std::sync::Arc; mod breaks; pub use self::breaks::*; -mod capacity; -pub use self::capacity::*; +pub mod capacity; mod compatibility; pub use self::compatibility::*; diff --git a/vrp-core/src/construction/features/recharge.rs b/vrp-core/src/construction/features/recharge.rs index f0f50ff1c..613fdd7ed 100644 --- a/vrp-core/src/construction/features/recharge.rs +++ b/vrp-core/src/construction/features/recharge.rs @@ -10,7 +10,6 @@ use crate::construction::enablers::*; use crate::models::solution::Route; use std::cmp::Ordering; use std::collections::HashSet; -use std::iter::once; use std::sync::Arc; /// Specifies dependencies needed to use recharge feature. @@ -45,12 +44,6 @@ pub struct RechargeKeys { pub intervals: StateKey, } -impl RechargeKeys { - fn iter(&self) -> impl Iterator { - once(self.distance).chain(once(self.intervals)) - } -} - /// Creates a feature to insert charge stations along the route. pub fn create_recharge_feature( name: &str, @@ -70,7 +63,6 @@ pub fn create_recharge_feature( create_multi_trip_feature( name, - recharge_keys.iter().collect(), code, MarkerInsertionPolicy::Any, Arc::new(RechargeableMultiTrip { diff --git a/vrp-core/src/construction/features/reloads.rs b/vrp-core/src/construction/features/reloads.rs index 73ed028e5..8aeb6ac61 100644 --- a/vrp-core/src/construction/features/reloads.rs +++ b/vrp-core/src/construction/features/reloads.rs @@ -6,6 +6,9 @@ mod reloads_test; use super::*; use crate::construction::enablers::{FeatureCombinator, RouteIntervals}; +use crate::construction::features::capacity::{ + JobDemandDimension, MaxFutureCapacityActivityState, MaxPastCapacityActivityState, VehicleCapacityDimension, +}; use crate::models::problem::Single; use crate::models::solution::Route; use std::cmp::Ordering; @@ -13,18 +16,12 @@ use std::collections::HashMap; use std::ops::Range; /// Specifies dependencies needed to use reload feature. -pub trait ReloadAspects: Clone + Send + Sync { +pub trait ReloadAspects: Clone + Send + Sync { /// Checks whether the job is a reload job and it can be assigned to the given route. fn belongs_to_route(&self, route: &Route, job: &Job) -> bool; /// Checks whether the single job is reload job. fn is_reload_single(&self, single: &Single) -> bool; - - /// Returns capacity of the vehicle. - fn get_capacity<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a T>; - - /// Returns demand of the single job if it is specified. - fn get_demand<'a>(&self, single: &'a Single) -> Option<&'a Demand>; } /// Specifies load schedule threshold function. @@ -39,8 +36,6 @@ type PlaceCapacityThresholdFn = Box bool pub struct ReloadKeys { /// Reload intervals key. pub intervals: StateKey, - /// Capacity feature keys. - pub capacity_keys: CapacityKeys, } /// Keys to track state of reload feature. @@ -66,7 +61,7 @@ pub fn create_shared_reload_multi_trip_feature( ) -> Result where T: SharedResource + LoadOps, - A: ReloadAspects + 'static, + A: ReloadAspects + 'static, { let shared_resource = create_shared_reload_constraint( name, @@ -103,7 +98,7 @@ pub fn create_simple_reload_multi_trip_feature( ) -> Result where T: SharedResource + LoadOps, - A: ReloadAspects + 'static, + A: ReloadAspects + 'static, { (capacity_feature_factory)( name, @@ -119,7 +114,7 @@ pub fn create_simple_reload_route_intervals( ) -> RouteIntervals where T: SharedResource + LoadOps, - A: ReloadAspects + 'static, + A: ReloadAspects + 'static, { create_reload_route_intervals(reload_keys, load_schedule_threshold_fn, None, aspects) } @@ -132,30 +127,25 @@ fn create_reload_route_intervals( ) -> RouteIntervals where T: SharedResource + LoadOps, - A: ReloadAspects + 'static, + A: ReloadAspects + 'static, { - let capacity_keys = reload_keys.capacity_keys; RouteIntervals::Multiple { is_marker_single_fn: { let aspects = aspects.clone(); Arc::new(move |single| aspects.is_reload_single(single)) }, is_new_interval_needed_fn: { - let aspects = aspects.clone(); Arc::new(move |route_ctx| { route_ctx .route() .tour .end_idx() .map(|end_idx| { - let current: T = route_ctx - .state() - .get_activity_state(capacity_keys.max_past_capacity, end_idx) - .cloned() - .unwrap_or_default(); + let current: T = + route_ctx.state().get_max_past_capacity_at(end_idx).cloned().unwrap_or_default(); let max_capacity = - aspects.get_capacity(&route_ctx.route().actor.vehicle).cloned().unwrap_or_default(); + route_ctx.route().actor.vehicle.dimens.get_vehicle_capacity().cloned().unwrap_or_default(); let threshold_capacity = (load_schedule_threshold_fn)(&max_capacity); current.partial_cmp(&threshold_capacity) != Some(Ordering::Less) @@ -164,13 +154,9 @@ where }) }, is_obsolete_interval_fn: { - let aspects = aspects.clone(); Arc::new(move |route_ctx, left, right| { - let capacity: T = aspects.get_capacity(&route_ctx.route().actor.vehicle).cloned().unwrap_or_default(); - - let get_load = |activity_idx: usize, state_key: StateKey| { - route_ctx.state().get_activity_state::(state_key, activity_idx).cloned().unwrap_or_default() - }; + let capacity: T = + route_ctx.route().actor.vehicle.dimens.get_vehicle_capacity().cloned().unwrap_or_default(); let fold_demand = |range: Range, demand_fn: fn(&Demand) -> T| { route_ctx.route().tour.activities_slice(range.start, range.end).iter().fold( @@ -179,7 +165,7 @@ where activity .job .as_ref() - .and_then(|job| aspects.get_demand(job)) + .and_then(|job| job.dimens.get_job_demand()) .map(|demand| acc + demand_fn(demand)) .unwrap_or_else(|| acc) }, @@ -190,9 +176,14 @@ where let right_delivery = fold_demand(right.clone(), |demand| demand.delivery.0); // static delivery moved to left - let new_max_load_left = get_load(left.start, capacity_keys.max_future_capacity) + right_delivery; + let new_max_load_left = + route_ctx.state().get_max_future_capacity_at::(left.start).cloned().unwrap_or_default() + + right_delivery; + // static pickup moved to right - let new_max_load_right = get_load(right.start, capacity_keys.max_future_capacity) + left_pickup; + let new_max_load_right = + route_ctx.state().get_max_future_capacity_at::(right.start).cloned().unwrap_or_default() + + left_pickup; let has_enough_vehicle_capacity = capacity.can_fit(&new_max_load_left) && capacity.can_fit(&new_max_load_right); @@ -222,7 +213,7 @@ fn create_shared_reload_constraint( ) -> Result where T: SharedResource + LoadOps, - A: ReloadAspects + 'static, + A: ReloadAspects + 'static, { let intervals_key = shared_reload_keys.reload_keys.intervals; create_shared_resource_feature( @@ -243,6 +234,6 @@ where }) }) }, - Arc::new(move |single| aspects.get_demand(single).map(|demand| demand.delivery.0)), + Arc::new(move |single| single.dimens.get_job_demand().map(|demand| demand.delivery.0)), ) } diff --git a/vrp-core/src/construction/features/work_balance.rs b/vrp-core/src/construction/features/work_balance.rs index 25f039b22..d82487ae7 100644 --- a/vrp-core/src/construction/features/work_balance.rs +++ b/vrp-core/src/construction/features/work_balance.rs @@ -1,6 +1,7 @@ //! Provides features to balance work. use super::*; +use crate::construction::features::capacity::MaxFutureCapacityActivityState; use crate::models::common::LoadOps; use rosomaxa::algorithms::math::get_cv_safe; use std::cmp::Ordering; @@ -16,8 +17,6 @@ pub type VehicleCapacityFn = Arc &T + Send + Sync>; pub struct LoadBalanceKeys { /// A key which tracks reload intervals. pub reload_interval: StateKey, - /// A key which tracks maximum vehicle capacity ahead in route. - pub max_future_capacity: StateKey, /// A key for balancing max load. pub balance_max_load: StateKey, } @@ -42,12 +41,7 @@ pub fn create_max_load_balanced_feature( intervals .iter() - .map(|(start_idx, _)| { - route_ctx - .state() - .get_activity_state::(feature_keys.max_future_capacity, *start_idx) - .unwrap_or(&default_capacity) - }) + .map(|(start_idx, _)| route_ctx.state().get_max_future_capacity_at(*start_idx).unwrap_or(&default_capacity)) .map(|max_load| (load_balance_fn)(max_load, capacity)) .max_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Less)) .unwrap_or(0_f64) diff --git a/vrp-core/src/construction/heuristics/context.rs b/vrp-core/src/construction/heuristics/context.rs index d490be94a..1dae28ede 100644 --- a/vrp-core/src/construction/heuristics/context.rs +++ b/vrp-core/src/construction/heuristics/context.rs @@ -11,10 +11,11 @@ use crate::models::{Problem, Solution}; use nohash_hasher::{BuildNoHashHasher, IsEnabled}; use rosomaxa::evolution::TelemetryMetrics; use rosomaxa::prelude::*; -use std::any::Any; +use rustc_hash::FxHasher; +use std::any::{Any, TypeId}; use std::collections::{HashMap, HashSet}; use std::fmt::{Debug, Formatter}; -use std::hash::Hasher; +use std::hash::{BuildHasherDefault, Hasher}; use std::ops::Deref; use std::sync::Arc; @@ -255,6 +256,7 @@ pub struct RouteContext { #[derive(Clone)] pub struct RouteState { route_states: HashMap>, + index: HashMap, BuildHasherDefault>, } impl RouteContext { @@ -339,7 +341,10 @@ impl Debug for RouteContext { impl Default for RouteState { fn default() -> RouteState { - RouteState { route_states: HashMap::with_capacity_and_hasher(4, BuildNoHashHasher::::default()) } + RouteState { + route_states: HashMap::with_capacity_and_hasher(4, BuildNoHashHasher::::default()), + index: HashMap::with_capacity_and_hasher(4, BuildHasherDefault::::default()), + } } } @@ -372,9 +377,40 @@ impl RouteState { self.route_states.insert(key.0, Arc::new(values)); } + // TODO remove methods above and rename below + + /// Gets a value associated with the tour using `K` type as a key. + pub fn get_tour_state_ex(&self) -> Option<&V> { + self.index.get(&TypeId::of::()).and_then(|any| any.downcast_ref::()) + } + + /// Sets the value associated with the tour using `K` type as a key. + pub fn set_tour_state_ex(&mut self, value: V) { + self.index.insert(TypeId::of::(), Arc::new(value)); + } + + /// Gets value associated with a key converted to a given type. + pub fn get_activity_state_ex(&self, activity_idx: usize) -> Option<&V> { + self.index + .get(&TypeId::of::()) + .and_then(|s| s.downcast_ref::>()) + .and_then(|activity_states| activity_states.get(activity_idx)) + } + + /// Gets values associated with key and activities. + pub fn get_activity_states_ex(&self) -> Option<&Vec> { + self.index.get(&TypeId::of::()).and_then(|s| s.downcast_ref::>()) + } + + /// Adds values associated with activities. + pub fn set_activity_states_ex(&mut self, values: Vec) { + self.index.insert(TypeId::of::(), Arc::new(values)); + } + /// Clear all states. pub fn clear(&mut self) { self.route_states.clear(); + self.index.clear(); } } diff --git a/vrp-core/src/construction/heuristics/metrics.rs b/vrp-core/src/construction/heuristics/metrics.rs index 83f67c42b..974498bb0 100644 --- a/vrp-core/src/construction/heuristics/metrics.rs +++ b/vrp-core/src/construction/heuristics/metrics.rs @@ -2,7 +2,8 @@ #[path = "../../../tests/unit/construction/heuristics/metrics_test.rs"] mod metrics_test; -use crate::construction::heuristics::{InsertionContext, RouteContext, StateKey}; +use crate::construction::features::capacity::MaxVehicleLoadTourState; +use crate::construction::heuristics::{InsertionContext, RouteContext, RouteState}; use crate::models::problem::{TransportCost, TravelTime}; use crate::models::CoreStateKeys; use rosomaxa::algorithms::math::*; @@ -11,40 +12,25 @@ use std::cmp::Ordering; /// Gets max load variance in tours. pub fn get_max_load_variance(insertion_ctx: &InsertionContext) -> f64 { - let max_load_key = if let Some(capacity_keys) = insertion_ctx.problem.extras.get_capacity_keys() { - capacity_keys.max_load - } else { - return 0.; - }; - - get_variance(get_values_from_route_state(insertion_ctx, max_load_key).collect::>().as_slice()) + get_variance( + get_values_from_route_state(insertion_ctx, |state| state.get_max_vehicle_load()).collect::>().as_slice(), + ) } /// Gets max load mean in tours. pub fn get_max_load_mean(insertion_ctx: &InsertionContext) -> f64 { - let max_load_key = if let Some(capacity_keys) = insertion_ctx.problem.extras.get_capacity_keys() { - capacity_keys.max_load - } else { - return 0.; - }; - - get_mean_iter(get_values_from_route_state(insertion_ctx, max_load_key)) + get_mean_iter(get_values_from_route_state(insertion_ctx, |state| state.get_max_vehicle_load())) } /// Gets tours with max_load at least 0.9. pub fn get_full_load_ratio(insertion_ctx: &InsertionContext) -> f64 { - let max_load_key = if let Some(capacity_keys) = insertion_ctx.problem.extras.get_capacity_keys() { - capacity_keys.max_load - } else { - return 0.; - }; - let total = insertion_ctx.solution.routes.len(); if total == 0 { 0. } else { - let full_capacity = - get_values_from_route_state(insertion_ctx, max_load_key).filter(|&max_load| max_load > 0.9).count(); + let full_capacity = get_values_from_route_state(insertion_ctx, |state| state.get_max_vehicle_load()) + .filter(|&max_load| max_load > 0.9) + .count(); full_capacity as f64 / total as f64 } @@ -70,7 +56,7 @@ pub fn get_duration_mean(insertion_ctx: &InsertionContext) -> f64 { return 0.; }; - get_mean_iter(get_values_from_route_state(insertion_ctx, total_duration_key)) + get_mean_iter(get_values_from_route_state(insertion_ctx, |state| state.get_route_state(total_duration_key))) } /// Gets mean of route distances. @@ -81,7 +67,7 @@ pub fn get_distance_mean(insertion_ctx: &InsertionContext) -> f64 { return 0.; }; - get_mean_iter(get_values_from_route_state(insertion_ctx, total_distance_key)) + get_mean_iter(get_values_from_route_state(insertion_ctx, |state| state.get_route_state(total_distance_key))) } /// Gets mean of future waiting time. @@ -285,15 +271,15 @@ pub fn group_routes_by_proximity(insertion_ctx: &InsertionContext) -> RouteProxi ) } -fn get_values_from_route_state( - insertion_ctx: &InsertionContext, - state_key: StateKey, -) -> impl Iterator + '_ { +fn get_values_from_route_state<'a>( + insertion_ctx: &'a InsertionContext, + state_value_fn: impl Fn(&'a RouteState) -> Option<&f64> + 'a, +) -> impl Iterator + 'a { insertion_ctx .solution .routes .iter() - .map(move |route_ctx| route_ctx.state().get_route_state::(state_key).cloned().unwrap_or(0.)) + .map(move |route_ctx| state_value_fn(route_ctx.state()).copied().unwrap_or_default()) } /// Gets medoid location of given route context. diff --git a/vrp-core/src/lib.rs b/vrp-core/src/lib.rs index c89de45ec..5b25eab34 100644 --- a/vrp-core/src/lib.rs +++ b/vrp-core/src/lib.rs @@ -106,6 +106,8 @@ #[macro_use] pub mod helpers; +#[macro_use] +pub mod macros; pub mod prelude; pub mod algorithms; diff --git a/vrp-core/src/macros.rs b/vrp-core/src/macros.rs new file mode 100644 index 000000000..cb1328566 --- /dev/null +++ b/vrp-core/src/macros.rs @@ -0,0 +1,83 @@ +//! Provides some useful macros to avoid repetitive code. + +/// A macro to define custom dimension on [crate::models::common::Dimensions]. +macro_rules! custom_dimension { + ($name:ident typeof $type:ty $(: $gen:ident)?) => { + paste::paste! { + #[doc = " Extends [Dimensions] within a new ["[<$name Dimension>]"]."] + pub trait [<$name Dimension>] { + #[doc = " Gets "$name " property."] + fn []$(<$type : $gen>)?(&self) -> Option<&$type>; + #[doc = " Sets "$name " property."] + fn []$(<$type : $gen>)?(&mut self, value: $type) -> &mut Self; + } + + // Define a dummy struct type which is used as a key. + struct [<$name DimensionKey>]; + impl [<$name Dimension>] for Dimensions { + fn []$(<$type : $gen>)?(&self) -> Option<&$type> { + self.get_value::<[<$name DimensionKey>], _>() + } + + fn []$(<$type : $gen>)?(&mut self, value: $type) -> &mut Self { + self.set_value::<[<$name DimensionKey>], _>(value); + self + } + } + } + }; +} + +/// A macro to define custom activity state on [crate::construction::heuristics::RouteState]. +macro_rules! custom_activity_state { + ($name:ident typeof $type:ty $(: $gen:ident)?) => { + paste::paste! { + #[doc = " Extends [RouteState] within a new ["[<$name ActivityState>]"]."] + pub trait [<$name ActivityState>] { + #[doc = " Gets `"$name "` activity state."] + fn []$(<$type : $gen>)?(&self, activity_idx: usize) -> Option<&$type>; + #[doc = " Sets `"$name "` activity states."] + fn []$(<$type : $gen>)?(&mut self, values: Vec<$type>); + } + + // Define a dummy struct type which is used as a key. + struct [<$name ActivityStateKey>]; + impl [<$name ActivityState>] for RouteState { + fn []$(<$type : $gen>)?(&self, activity_idx: usize) -> Option<&$type> { + self.get_activity_state_ex::<[<$name ActivityStateKey>], _>(activity_idx) + } + + fn []$(<$type : $gen>)?(&mut self, values: Vec<$type>) { + self.set_activity_states_ex::<[<$name ActivityStateKey>], _>(values); + } + } + } + }; +} + +/// A macro to define custom route state on [crate::construction::heuristics::RouteState]. +macro_rules! custom_tour_state { + ($name:ident typeof $type:ty $(: $gen:ident)?) => { + paste::paste! { + #[doc = " Extends [RouteState] within a new ["[<$name TourState>]"]."] + pub trait [<$name TourState>] { + #[doc = " Gets `"$name "` tour state."] + fn []$(<$type : $gen>)?(&self) -> Option<&$type>; + #[doc = " Sets `"$name "` tour state."] + fn []$(<$type : $gen>)?(&mut self, value: $type); + } + + // Define a dummy struct type which is used as a key + struct [<$name TourStateKey>]; + impl [<$name TourState>] for RouteState { + fn []$(<$type : $gen>)?(&self) -> Option<&$type> { + self.get_tour_state_ex::<[<$name TourStateKey>], _>() + } + + fn []$(<$type : $gen>)?(&mut self, value: $type) { + self.set_tour_state_ex::<[<$name TourStateKey>], _>(value); + } + } + } + }; +} diff --git a/vrp-core/src/models/common/load.rs b/vrp-core/src/models/common/load.rs index ce0b1e9f5..f281f9dad 100644 --- a/vrp-core/src/models/common/load.rs +++ b/vrp-core/src/models/common/load.rs @@ -2,7 +2,6 @@ #[path = "../../../tests/unit/models/common/load_test.rs"] mod load_test; -use crate::models::common::Dimensions; use rosomaxa::prelude::UnwrapValue; use std::cmp::Ordering; use std::fmt::{Debug, Display, Formatter}; @@ -66,22 +65,6 @@ pub enum DemandType { Mixed, } -/// A trait to get or set vehicle's capacity. -pub trait CapacityDimension { - /// Sets capacity. - fn set_capacity(&mut self, demand: T) -> &mut Self; - /// Gets capacity. - fn get_capacity(&self) -> Option<&T>; -} - -/// A trait to get or set demand. -pub trait DemandDimension { - /// Sets demand. - fn set_demand(&mut self, demand: Demand) -> &mut Self; - /// Gets demand. - fn get_demand(&self) -> Option<&Demand>; -} - impl Demand { /// Returns capacity change as difference between pickup and delivery. pub fn change(&self) -> T { @@ -112,30 +95,6 @@ impl Add for Demand { } } -struct CapacityDimenKey; -impl CapacityDimension for Dimensions { - fn set_capacity(&mut self, demand: T) -> &mut Self { - self.set_value::(demand); - self - } - - fn get_capacity(&self) -> Option<&T> { - self.get_value::() - } -} - -struct DemandDimenKey; -impl DemandDimension for Dimensions { - fn set_demand(&mut self, demand: Demand) -> &mut Self { - self.set_value::(demand); - self - } - - fn get_demand(&self) -> Option<&Demand> { - self.get_value::() - } -} - /// Specifies single dimensional load type. #[derive(Clone, Copy, Debug, Default)] pub struct SingleDimLoad { diff --git a/vrp-core/src/models/examples.rs b/vrp-core/src/models/examples.rs index 646142648..8339d6cda 100644 --- a/vrp-core/src/models/examples.rs +++ b/vrp-core/src/models/examples.rs @@ -1,3 +1,4 @@ +use crate::construction::features::capacity::*; use crate::construction::features::*; use crate::construction::heuristics::StateKeyRegistry; use crate::models::common::*; @@ -69,33 +70,6 @@ fn create_example_extras() -> Extras { ExtrasBuilder::new(&mut registry).build().expect("cannot build example extras") } -/// Creates example capacity aspects. -struct ExampleCapacityAspects { - capacity_keys: CapacityKeys, -} - -impl CapacityAspects for ExampleCapacityAspects { - fn get_capacity<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a SingleDimLoad> { - vehicle.dimens.get_capacity() - } - - fn get_demand<'a>(&self, single: &'a Single) -> Option<&'a Demand> { - single.dimens.get_demand() - } - - fn set_demand(&self, single: &mut Single, demand: Demand) { - single.dimens.set_demand(demand); - } - - fn get_state_keys(&self) -> &CapacityKeys { - &self.capacity_keys - } - - fn get_violation_code(&self) -> ViolationCode { - 2 - } -} - /// Creates and example VRP goal: CVRPTW. fn create_example_goal_ctx( transport: Arc, @@ -103,14 +77,12 @@ fn create_example_goal_ctx( extras: &Extras, ) -> GenericResult { let schedule_keys = extras.get_schedule_keys().expect("no schedule keys").clone(); - let capacity_keys = extras.get_capacity_keys().expect("no capacity keys").clone(); - let aspects = ExampleCapacityAspects { capacity_keys }; let features = vec![ create_minimize_unassigned_jobs_feature("min_jobs", Arc::new(|_, _| 1.))?, create_minimize_tours_feature("min_tours")?, create_minimize_distance_feature("min_distance", transport, activity, schedule_keys, 1)?, - create_capacity_limit_feature::("capacity", aspects)?, + create_capacity_limit_feature::("capacity", 2)?, ]; GoalContextBuilder::with_features(features)? diff --git a/vrp-core/src/models/extras.rs b/vrp-core/src/models/extras.rs index 5e214eff0..ee2ba2b34 100644 --- a/vrp-core/src/models/extras.rs +++ b/vrp-core/src/models/extras.rs @@ -1,5 +1,4 @@ use crate::construction::enablers::ScheduleKeys; -use crate::construction::features::CapacityKeys; use crate::construction::heuristics::StateKeyRegistry; use crate::solver::HeuristicKeys; use rosomaxa::prelude::GenericError; @@ -59,7 +58,6 @@ impl ExtrasBuilder { builder .with_schedule_keys(ScheduleKeys::from(&mut *state_registry)) - .with_capacity_keys(CapacityKeys::from(&mut *state_registry)) .with_heuristic_keys(HeuristicKeys::from(&mut *state_registry)); builder @@ -71,12 +69,6 @@ impl ExtrasBuilder { self } - /// Adds capacity keys. - pub fn with_capacity_keys(&mut self, capacity_keys: CapacityKeys) -> &mut Self { - self.0.set_value::(capacity_keys); - self - } - /// Adds heuristic keys. pub fn with_heuristic_keys(&mut self, heuristic_keys: HeuristicKeys) -> &mut Self { self.0.set_value::(heuristic_keys); @@ -119,9 +111,6 @@ pub trait CoreStateKeys { /// Get state keys for scheduling. fn get_schedule_keys(&self) -> Option<&ScheduleKeys>; - /// Gets state keys for capacity feature. - fn get_capacity_keys(&self) -> Option<&CapacityKeys>; - /// Gets state keys for heuristic. fn get_heuristic_keys(&self) -> Option<&HeuristicKeys>; } @@ -131,10 +120,6 @@ impl CoreStateKeys for Extras { self.get_value::() } - fn get_capacity_keys(&self) -> Option<&CapacityKeys> { - self.get_value::() - } - fn get_heuristic_keys(&self) -> Option<&HeuristicKeys> { self.get_value::() } diff --git a/vrp-core/src/models/solution/tour.rs b/vrp-core/src/models/solution/tour.rs index 12b82cf1c..58aeb0948 100644 --- a/vrp-core/src/models/solution/tour.rs +++ b/vrp-core/src/models/solution/tour.rs @@ -158,7 +158,7 @@ impl Tour { /// Returns end activity in tour. pub fn end_idx(&self) -> Option { - self.activities.last().map(|_| self.activities.len() - 1) + self.activities.len().checked_sub(1) } /// Checks whether job is present in tour diff --git a/vrp-core/tests/helpers/construction/features.rs b/vrp-core/tests/helpers/construction/features.rs index df42dcfe7..5744b6cdf 100644 --- a/vrp-core/tests/helpers/construction/features.rs +++ b/vrp-core/tests/helpers/construction/features.rs @@ -1,8 +1,4 @@ -use crate::construction::features::{CapacityAspects, CapacityKeys}; -use crate::models::common::{CapacityDimension, Demand, DemandDimension, LoadOps, MultiDimLoad, SingleDimLoad}; -use crate::models::problem::{Single, Vehicle}; -use crate::models::ViolationCode; -use std::marker::PhantomData; +use crate::models::common::{Demand, MultiDimLoad, SingleDimLoad}; pub fn create_simple_demand(size: i32) -> Demand { if size > 0 { @@ -43,39 +39,3 @@ pub fn single_demand_as_multi(pickup: (i32, i32), delivery: (i32, i32)) -> Deman Demand { pickup: (make(pickup.0), make(pickup.1)), delivery: (make(delivery.0), make(delivery.1)) } } - -/// Creates test capacity aspects. -pub struct TestCapacityAspects { - capacity_keys: CapacityKeys, - violation_code: ViolationCode, - phantom: PhantomData, -} - -impl TestCapacityAspects { - /// Creates a new instance of `TestCapacityAspects`. - pub fn new(capacity_keys: CapacityKeys, violation_code: ViolationCode) -> Self { - Self { capacity_keys, violation_code, phantom: Default::default() } - } -} - -impl CapacityAspects for TestCapacityAspects { - fn get_capacity<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a T> { - vehicle.dimens.get_capacity() - } - - fn get_demand<'a>(&self, single: &'a Single) -> Option<&'a Demand> { - single.dimens.get_demand() - } - - fn set_demand(&self, single: &mut Single, demand: Demand) { - single.dimens.set_demand(demand); - } - - fn get_state_keys(&self) -> &CapacityKeys { - &self.capacity_keys - } - - fn get_violation_code(&self) -> ViolationCode { - self.violation_code - } -} diff --git a/vrp-core/tests/helpers/construction/heuristics.rs b/vrp-core/tests/helpers/construction/heuristics.rs index a5ead2881..12a21e88a 100644 --- a/vrp-core/tests/helpers/construction/heuristics.rs +++ b/vrp-core/tests/helpers/construction/heuristics.rs @@ -1,5 +1,4 @@ use crate::construction::enablers::ScheduleKeys; -use crate::construction::features::CapacityKeys; use crate::construction::heuristics::*; use crate::helpers::models::domain::{test_random, TestGoalContextBuilder}; use crate::helpers::models::problem::{test_fleet, TestActivityCost, TestTransportCost}; @@ -18,10 +17,6 @@ pub fn create_schedule_keys() -> ScheduleKeys { ScheduleKeys::from(&mut StateKeyRegistry::default()) } -pub fn create_capacity_keys() -> CapacityKeys { - CapacityKeys::from(&mut StateKeyRegistry::default()) -} - #[derive(Default)] pub struct InsertionContextBuilder { problem: Option, diff --git a/vrp-core/tests/helpers/models/problem/fleet.rs b/vrp-core/tests/helpers/models/problem/fleet.rs index 007d8b0ca..d12957697 100644 --- a/vrp-core/tests/helpers/models/problem/fleet.rs +++ b/vrp-core/tests/helpers/models/problem/fleet.rs @@ -1,3 +1,4 @@ +use crate::construction::features::capacity::VehicleCapacityDimension; use crate::models::common::*; use crate::models::problem::*; use std::collections::{HashMap, HashSet}; @@ -93,12 +94,12 @@ impl VehicleBuilder { } pub fn capacity(&mut self, capacity: i32) -> &mut VehicleBuilder { - self.0.dimens.set_capacity(SingleDimLoad::new(capacity)); + self.0.dimens.set_vehicle_capacity(SingleDimLoad::new(capacity)); self } pub fn capacity_mult(&mut self, capacity: Vec) -> &mut VehicleBuilder { - self.0.dimens.set_capacity(MultiDimLoad::new(capacity)); + self.0.dimens.set_vehicle_capacity(MultiDimLoad::new(capacity)); self } diff --git a/vrp-core/tests/helpers/models/problem/jobs.rs b/vrp-core/tests/helpers/models/problem/jobs.rs index a6fe811c2..5c2c41b4c 100644 --- a/vrp-core/tests/helpers/models/problem/jobs.rs +++ b/vrp-core/tests/helpers/models/problem/jobs.rs @@ -1,3 +1,4 @@ +use crate::construction::features::capacity::JobDemandDimension; use crate::models::common::*; use crate::models::problem::{FixedJobPermutation, Job, JobIdDimension, Multi, Place, Single}; use std::sync::Arc; @@ -82,7 +83,7 @@ impl SingleBuilder { } pub fn demand(&mut self, demand: Demand) -> &mut Self { - self.0.dimens.set_demand(demand); + self.0.dimens.set_job_demand(demand); self } diff --git a/vrp-core/tests/unit/construction/features/capacity_test.rs b/vrp-core/tests/unit/construction/features/capacity_test.rs index 2eea1d45b..0d5f91fd3 100644 --- a/vrp-core/tests/unit/construction/features/capacity_test.rs +++ b/vrp-core/tests/unit/construction/features/capacity_test.rs @@ -1,18 +1,17 @@ use super::*; use crate::construction::heuristics::{ActivityContext, RouteState}; use crate::helpers::construction::features::*; -use crate::helpers::construction::heuristics::{create_capacity_keys, InsertionContextBuilder}; +use crate::helpers::construction::heuristics::InsertionContextBuilder; use crate::helpers::models::problem::*; use crate::helpers::models::solution::*; -use crate::models::common::{Demand, DemandDimension, SingleDimLoad}; +use crate::models::common::{Demand, SingleDimLoad}; use crate::models::problem::{Job, Vehicle}; use crate::models::solution::Activity; const VIOLATION_CODE: ViolationCode = 2; -fn create_feature(capacity_keys: CapacityKeys) -> Feature { - let aspects = TestCapacityAspects::new(capacity_keys, VIOLATION_CODE); - create_capacity_limit_feature::("capacity", aspects).unwrap() +fn create_feature() -> Feature { + create_capacity_limit_feature::("capacity", VIOLATION_CODE).unwrap() } fn create_test_vehicle(capacity: i32) -> Vehicle { @@ -28,8 +27,8 @@ fn create_activity_with_simple_demand(size: i32) -> Activity { ActivityBuilder::default().job(Some(job)).build() } -fn get_simple_capacity_state(key: StateKey, state: &RouteState, activity_idx: Option) -> i32 { - state.get_activity_state::(key, activity_idx.unwrap()).expect("expect single capacity").value +fn get_current_capacity_state(state: &RouteState, activity_idx: usize) -> i32 { + state.get_current_capacity_at::(activity_idx).expect("expect single capacity").value } parameterized_test! {can_calculate_current_capacity_state_values, (s1, s2, s3, start, end, exp_s1, exp_s2, exp_s3), { @@ -53,7 +52,6 @@ fn can_calculate_current_capacity_state_values_impl( exp_s2: i32, exp_s3: i32, ) { - let capacity_keys = create_capacity_keys(); let fleet = FleetBuilder::default().add_driver(test_driver()).add_vehicle(create_test_vehicle(10)).build(); let mut route_ctx = RouteContextBuilder::default() .with_route( @@ -65,15 +63,15 @@ fn can_calculate_current_capacity_state_values_impl( .build(), ) .build(); - create_feature(capacity_keys.clone()).state.unwrap().accept_route_state(&mut route_ctx); + create_feature().state.unwrap().accept_route_state(&mut route_ctx); let tour = &route_ctx.route().tour; let state = route_ctx.state(); - assert_eq!(get_simple_capacity_state(capacity_keys.current_capacity, state, Some(0)), start); - assert_eq!(get_simple_capacity_state(capacity_keys.current_capacity, state, tour.end_idx()), end); - assert_eq!(get_simple_capacity_state(capacity_keys.current_capacity, state, Some(1)), exp_s1); - assert_eq!(get_simple_capacity_state(capacity_keys.current_capacity, state, Some(2)), exp_s2); - assert_eq!(get_simple_capacity_state(capacity_keys.current_capacity, state, Some(3)), exp_s3); + assert_eq!(get_current_capacity_state(state, 0), start); + assert_eq!(get_current_capacity_state(state, tour.end_idx().unwrap()), end); + assert_eq!(get_current_capacity_state(state, 1), exp_s1); + assert_eq!(get_current_capacity_state(state, 2), exp_s2); + assert_eq!(get_current_capacity_state(state, 3), exp_s3); } parameterized_test! {can_evaluate_demand_on_route, (size, expected), { @@ -93,11 +91,8 @@ fn can_evaluate_demand_on_route_impl(size: i32, expected: Option, i32> = - constraint.merge(cluster, candidate).and_then(|job| job.dimens().get_demand().cloned().ok_or(-1)); + constraint.merge(cluster, candidate).and_then(|job| job.dimens().get_job_demand().cloned().ok_or(-1)); match (result, expected) { (Ok(result), Ok((pickup0, pickup1, delivery0, delivery1))) => { diff --git a/vrp-core/tests/unit/construction/features/fast_service_test.rs b/vrp-core/tests/unit/construction/features/fast_service_test.rs index 1925c720a..16c10ad27 100644 --- a/vrp-core/tests/unit/construction/features/fast_service_test.rs +++ b/vrp-core/tests/unit/construction/features/fast_service_test.rs @@ -1,4 +1,5 @@ use super::*; +use crate::construction::features::capacity::JobDemandDimension; use crate::helpers::construction::heuristics::create_state_key; use crate::helpers::models::problem::*; use crate::helpers::models::solution::*; @@ -13,7 +14,7 @@ impl FastServiceAspects for TestFastServiceAspects { } fn get_demand_type(&self, single: &Single) -> Option { - single.dimens.get_demand().map(|demand: &Demand| demand.get_type()) + single.dimens.get_job_demand().map(|demand: &Demand| demand.get_type()) } } diff --git a/vrp-core/tests/unit/construction/features/reloads_test.rs b/vrp-core/tests/unit/construction/features/reloads_test.rs index a02575207..db6a57a39 100644 --- a/vrp-core/tests/unit/construction/features/reloads_test.rs +++ b/vrp-core/tests/unit/construction/features/reloads_test.rs @@ -1,23 +1,20 @@ use super::*; +use crate::construction::features::capacity::create_capacity_limit_with_multi_trip_feature; use crate::helpers::construction::features::single_demand_as_multi; use crate::helpers::construction::heuristics::InsertionContextBuilder; use crate::helpers::models::problem::{test_driver, FleetBuilder, SingleBuilder, VehicleBuilder}; use crate::helpers::models::solution::{ActivityBuilder, RouteBuilder, RouteContextBuilder}; use crate::models::solution::Activity; -use std::marker::PhantomData; const VIOLATION_CODE: ViolationCode = 1; -#[derive(Clone)] -struct TestReloadAspects { - capacity_keys: CapacityKeys, - phantom: PhantomData, -} +#[derive(Clone, Default)] +struct TestReloadAspects {} struct VehicleIdDimenKey; struct JobTypeDimenKey; -impl ReloadAspects for TestReloadAspects { +impl ReloadAspects for TestReloadAspects { fn belongs_to_route(&self, route: &Route, job: &Job) -> bool { job.as_single() .filter(|single| self.is_reload_single(single.as_ref())) @@ -29,36 +26,6 @@ impl ReloadAspects for TestReloadAspects { fn is_reload_single(&self, single: &Single) -> bool { single.dimens.get_value::().map_or(false, |job_type| job_type == "reload") } - - fn get_capacity<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a T> { - vehicle.dimens.get_capacity() - } - - fn get_demand<'a>(&self, single: &'a Single) -> Option<&'a Demand> { - single.dimens.get_demand() - } -} - -impl CapacityAspects for TestReloadAspects { - fn get_capacity<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a T> { - vehicle.dimens.get_capacity() - } - - fn get_demand<'a>(&self, single: &'a Single) -> Option<&'a Demand> { - single.dimens.get_demand() - } - - fn set_demand(&self, single: &mut Single, demand: Demand) { - single.dimens.set_demand(demand); - } - - fn get_state_keys(&self) -> &CapacityKeys { - &self.capacity_keys - } - - fn get_violation_code(&self) -> ViolationCode { - VIOLATION_CODE - } } fn create_activity_with_demand( @@ -111,7 +78,7 @@ fn create_route_context(capacity: Vec, activities: Vec) -> RouteC fn create_reload_keys() -> ReloadKeys { let mut state_registry = StateKeyRegistry::default(); - ReloadKeys { intervals: state_registry.next_key(), capacity_keys: CapacityKeys::from(&mut state_registry) } + ReloadKeys { intervals: state_registry.next_key() } } #[test] @@ -122,18 +89,13 @@ fn can_handle_reload_jobs_with_merge() { let feature = create_simple_reload_multi_trip_feature( "reload", { - let reload_keys = reload_keys.clone(); Box::new(move |name, route_intervals| { - create_capacity_limit_with_multi_trip_feature::( - name, - route_intervals, - TestReloadAspects { capacity_keys: reload_keys.capacity_keys, phantom: Default::default() }, - ) + create_capacity_limit_with_multi_trip_feature::(name, route_intervals, VIOLATION_CODE) }) }, Box::new(|_| SingleDimLoad::default()), reload_keys.clone(), - TestReloadAspects { capacity_keys: reload_keys.capacity_keys, phantom: Default::default() }, + TestReloadAspects::default(), ); let constraint = feature.unwrap().constraint.unwrap(); @@ -272,18 +234,13 @@ fn can_remove_trivial_reloads_when_used_from_capacity_constraint_impl( let reload_feature = create_simple_reload_multi_trip_feature::( "reload", Box::new({ - let capacity_keys = reload_keys.capacity_keys.clone(); move |name, route_intervals| { - create_capacity_limit_with_multi_trip_feature::( - name, - route_intervals, - TestReloadAspects { capacity_keys, phantom: Default::default() }, - ) + create_capacity_limit_with_multi_trip_feature::(name, route_intervals, VIOLATION_CODE) } }), Box::new(move |capacity| *capacity * threshold), reload_keys.clone(), - TestReloadAspects { capacity_keys: reload_keys.capacity_keys, phantom: Default::default() }, + TestReloadAspects::default(), ) .unwrap(); let min_jobs_feature = create_minimize_unassigned_jobs_feature("min_jobs", Arc::new(|_, _| 1.)).unwrap(); @@ -333,13 +290,13 @@ fn can_handle_new_interval_needed_for_multi_dim_load_impl( reload_keys.clone(), Box::new(move |capacity| *capacity * threshold), None, - TestReloadAspects { capacity_keys: reload_keys.capacity_keys.clone(), phantom: Default::default() }, + TestReloadAspects::default(), ); let mut route_ctx = create_route_context(vehicle_capacity, Vec::default()); let (route, state) = route_ctx.as_mut(); let mut current_capacities = vec![MultiDimLoad::default(); route.tour.total()]; current_capacities[route.tour.end_idx().unwrap()] = MultiDimLoad::new(current_capacity); - state.put_activity_states(reload_keys.capacity_keys.max_past_capacity, current_capacities); + state.set_max_past_capacity_states(current_capacities); let result = route_intervals.is_new_interval_needed(&route_ctx); diff --git a/vrp-core/tests/unit/construction/features/shared_resource_test.rs b/vrp-core/tests/unit/construction/features/shared_resource_test.rs index e19c70a68..2b53cc5da 100644 --- a/vrp-core/tests/unit/construction/features/shared_resource_test.rs +++ b/vrp-core/tests/unit/construction/features/shared_resource_test.rs @@ -1,6 +1,7 @@ use self::ActivityType::*; use super::*; use crate::construction::enablers::get_route_intervals; +use crate::construction::features::capacity::{JobDemandDimension, VehicleCapacityDimension}; use crate::helpers::construction::features::create_simple_demand; use crate::helpers::construction::heuristics::InsertionContextBuilder; use crate::helpers::models::problem::*; @@ -24,7 +25,7 @@ fn create_resource_activity(capacity: i32, resource_id: Option if let Some(resource_id) = resource_id { single.dimens.set_value::(resource_id); } - single.dimens.set_capacity(SingleDimLoad::new(capacity)); + single.dimens.set_vehicle_capacity(SingleDimLoad::new(capacity)); Activity { job: Some(Arc::new(single)), ..ActivityBuilder::default().build() } } @@ -39,7 +40,7 @@ fn create_feature(intervals_key: StateKey, resource_key: StateKey, total_jobs: u Arc::new(|activity| { activity.job.as_ref().and_then(|job| { job.dimens - .get_capacity() + .get_vehicle_capacity() .cloned() .zip(job.dimens.get_value::().cloned()) }) @@ -68,7 +69,7 @@ fn create_route_ctx( .build(); let intervals = get_route_intervals(route_ctx.route(), |activity| { activity.job.as_ref().map_or(false, |job| { - let capacity: Option<&SingleDimLoad> = job.dimens.get_capacity(); + let capacity: Option<&SingleDimLoad> = job.dimens.get_vehicle_capacity(); capacity.is_some() }) }); @@ -108,7 +109,7 @@ fn create_interval_fn(intervals_key: StateKey) -> SharedResourceIntervalFn { } fn create_resource_demand_fn() -> SharedResourceDemandFn { - Arc::new(|single| single.dimens.get_demand().map(|demand| demand.delivery.0)) + Arc::new(|single| single.dimens.get_job_demand().map(|demand| demand.delivery.0)) } fn create_state_keys() -> (StateKey, StateKey) { diff --git a/vrp-core/tests/unit/construction/heuristics/metrics_test.rs b/vrp-core/tests/unit/construction/heuristics/metrics_test.rs index 8f1e3db0d..099355e5a 100644 --- a/vrp-core/tests/unit/construction/heuristics/metrics_test.rs +++ b/vrp-core/tests/unit/construction/heuristics/metrics_test.rs @@ -1,3 +1,4 @@ +use crate::construction::features::capacity::MaxVehicleLoadTourState; use crate::construction::heuristics::*; use crate::helpers::construction::heuristics::InsertionContextBuilder; use crate::helpers::models::solution::RouteContextBuilder; @@ -15,22 +16,22 @@ fn create_insertion_ctx( ctx } -fn create_route_ctx_with_route_state(key: StateKey, value: f64) -> RouteContext { +fn create_route_ctx_with_route_state(state_fn: impl FnOnce(&mut RouteState)) -> RouteContext { let mut ctx = RouteContextBuilder::default().build(); - ctx.state_mut().put_route_state(key, value); + state_fn(ctx.state_mut()); ctx } #[test] fn can_get_max_load_variance() { - let insertion_ctx = create_insertion_ctx(4, &|problem, idx| { + let insertion_ctx = create_insertion_ctx(4, &|_, idx| { let value = match idx { 0 => 5., 1 => 3., 2 => 0., _ => 7., }; - create_route_ctx_with_route_state(problem.extras.get_capacity_keys().unwrap().max_load, value) + create_route_ctx_with_route_state(|state| state.set_max_vehicle_load(value)) }); let variance = get_max_load_variance(&insertion_ctx); @@ -46,7 +47,9 @@ fn can_get_duration_mean() { 1 => 2., _ => 7., }; - create_route_ctx_with_route_state(problem.extras.get_schedule_keys().unwrap().total_duration, value) + create_route_ctx_with_route_state(|state| { + state.put_route_state(problem.extras.get_schedule_keys().unwrap().total_duration, value) + }) }); let mean = get_duration_mean(&insertion_ctx); @@ -62,7 +65,9 @@ fn can_get_distance_mean() { 1 => 2., _ => 11., }; - create_route_ctx_with_route_state(problem.extras.get_schedule_keys().unwrap().total_distance, value) + create_route_ctx_with_route_state(|state| { + state.put_route_state(problem.extras.get_schedule_keys().unwrap().total_distance, value) + }) }); let mean = get_distance_mean(&insertion_ctx); diff --git a/vrp-core/tests/unit/construction/probing/repair_solution_test.rs b/vrp-core/tests/unit/construction/probing/repair_solution_test.rs index b3dcb628b..2d634bfb9 100644 --- a/vrp-core/tests/unit/construction/probing/repair_solution_test.rs +++ b/vrp-core/tests/unit/construction/probing/repair_solution_test.rs @@ -1,6 +1,7 @@ use super::*; +use crate::construction::features::capacity::create_capacity_limit_feature; use crate::construction::features::*; -use crate::helpers::construction::features::{create_simple_demand, create_simple_dynamic_demand, TestCapacityAspects}; +use crate::helpers::construction::features::{create_simple_demand, create_simple_dynamic_demand}; use crate::helpers::models::domain::TestGoalContextBuilder; use crate::helpers::models::problem::*; use crate::models::common::*; @@ -93,13 +94,7 @@ fn create_test_problem( .unwrap(), ) .add_feature(create_locked_jobs_feature("locked_jobs", &fleet, locks.as_slice(), 4).unwrap()) - .add_feature( - create_capacity_limit_feature::( - "capacity", - TestCapacityAspects::new(extras.get_capacity_keys().cloned().unwrap(), 5), - ) - .unwrap(), - ) + .add_feature(create_capacity_limit_feature::("capacity", 5).unwrap()) .with_objectives(&["transport"]) .build(); diff --git a/vrp-core/tests/unit/models/goal_test.rs b/vrp-core/tests/unit/models/goal_test.rs index 2719fce05..33720843f 100644 --- a/vrp-core/tests/unit/models/goal_test.rs +++ b/vrp-core/tests/unit/models/goal_test.rs @@ -1,7 +1,7 @@ use super::*; +use crate::construction::features::capacity::create_capacity_limit_feature; use crate::construction::features::*; -use crate::helpers::construction::features::TestCapacityAspects; -use crate::helpers::construction::heuristics::{create_capacity_keys, InsertionContextBuilder}; +use crate::helpers::construction::heuristics::InsertionContextBuilder; use crate::helpers::models::domain::TestGoalContextBuilder; use crate::helpers::models::solution::{test_actor, ActivityBuilder}; use crate::models::common::SingleDimLoad; @@ -81,11 +81,7 @@ pub fn can_create_goal_context_with_objective() -> GenericResult<()> { #[test] pub fn cannot_create_goal_context_without_objectives() -> GenericResult<()> { - let features = vec![create_capacity_limit_feature::( - "capacity", - TestCapacityAspects::new(create_capacity_keys(), 0), - ) - .unwrap()]; + let features = vec![create_capacity_limit_feature::("capacity", 0).unwrap()]; assert!(GoalContextBuilder::with_features(features)?.build().is_err()); Ok(()) diff --git a/vrp-pragmatic/src/format/problem/aspects.rs b/vrp-pragmatic/src/format/problem/aspects.rs index dadf0faed..0ac2902e8 100644 --- a/vrp-pragmatic/src/format/problem/aspects.rs +++ b/vrp-pragmatic/src/format/problem/aspects.rs @@ -1,6 +1,6 @@ use crate::format::*; use std::collections::HashSet; -use std::marker::PhantomData; +use vrp_core::construction::features::capacity::JobDemandDimension; use vrp_core::construction::features::*; use vrp_core::construction::heuristics::{RouteContext, StateKey}; use vrp_core::models::common::*; @@ -33,42 +33,6 @@ impl BreakAspects for PragmaticBreakAspects { } } -/// Provides a way to use capacity feature. -pub struct PragmaticCapacityAspects { - state_keys: CapacityKeys, - violation_code: ViolationCode, - phantom: PhantomData, -} - -impl PragmaticCapacityAspects { - /// Creates a new instance of `PragmaticCapacityAspects`. - pub fn new(state_keys: CapacityKeys, violation_code: ViolationCode) -> Self { - Self { state_keys, violation_code, phantom: Default::default() } - } -} - -impl CapacityAspects for PragmaticCapacityAspects { - fn get_capacity<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a T> { - vehicle.dimens.get_capacity() - } - - fn get_demand<'a>(&self, single: &'a Single) -> Option<&'a Demand> { - single.dimens.get_demand() - } - - fn set_demand(&self, single: &mut Single, demand: Demand) { - single.dimens.set_demand(demand); - } - - fn get_state_keys(&self) -> &CapacityKeys { - &self.state_keys - } - - fn get_violation_code(&self) -> ViolationCode { - self.violation_code - } -} - /// Provides a way to use compatibility feature. #[derive(Clone)] pub struct PragmaticCompatibilityAspects { @@ -114,8 +78,8 @@ impl FastServiceAspects for PragmaticFastServiceAspects { } fn get_demand_type(&self, single: &Single) -> Option { - let demand_single: Option<&Demand> = single.dimens.get_demand(); - let demand_multi: Option<&Demand> = single.dimens.get_demand(); + let demand_single: Option<&Demand> = single.dimens.get_job_demand(); + let demand_multi: Option<&Demand> = single.dimens.get_job_demand(); demand_single.map(|d| d.get_type()).or_else(|| demand_multi.map(|d| d.get_type())) } @@ -193,11 +157,9 @@ impl RechargeAspects for PragmaticRechargeAspects { /// Provides a way to use reload feature. #[derive(Clone, Default)] -pub struct PragmaticReloadAspects { - phantom: PhantomData, -} +pub struct PragmaticReloadAspects {} -impl ReloadAspects for PragmaticReloadAspects { +impl ReloadAspects for PragmaticReloadAspects { fn belongs_to_route(&self, route: &Route, job: &Job) -> bool { job.as_single() .map_or(false, |single| self.is_reload_single(single.as_ref()) && is_correct_vehicle(route, single)) @@ -206,14 +168,6 @@ impl ReloadAspects for PragmaticReloadAspects { fn is_reload_single(&self, single: &Single) -> bool { single.dimens.get_job_type().map_or(false, |job_type| job_type == "reload") } - - fn get_capacity<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a T> { - vehicle.dimens.get_capacity() - } - - fn get_demand<'a>(&self, single: &'a Single) -> Option<&'a Demand> { - single.dimens.get_demand() - } } #[derive(Clone)] diff --git a/vrp-pragmatic/src/format/problem/fleet_reader.rs b/vrp-pragmatic/src/format/problem/fleet_reader.rs index 84729b245..848dd3a80 100644 --- a/vrp-pragmatic/src/format/problem/fleet_reader.rs +++ b/vrp-pragmatic/src/format/problem/fleet_reader.rs @@ -10,6 +10,7 @@ use crate::Location as ApiLocation; use std::cmp::Ordering; use std::collections::HashSet; use vrp_core::construction::enablers::create_typed_actor_groups; +use vrp_core::construction::features::capacity::VehicleCapacityDimension; use vrp_core::models::common::*; use vrp_core::models::problem::*; @@ -148,9 +149,9 @@ pub(super) fn read_fleet(api_problem: &ApiProblem, props: &ProblemProperties, co } if props.has_multi_dimen_capacity { - dimens.set_capacity(MultiDimLoad::new(vehicle.capacity.clone())); + dimens.set_vehicle_capacity(MultiDimLoad::new(vehicle.capacity.clone())); } else { - dimens.set_capacity(SingleDimLoad::new(*vehicle.capacity.first().unwrap())); + dimens.set_vehicle_capacity(SingleDimLoad::new(*vehicle.capacity.first().unwrap())); } if let Some(skills) = vehicle.skills.as_ref() { diff --git a/vrp-pragmatic/src/format/problem/goal_reader.rs b/vrp-pragmatic/src/format/problem/goal_reader.rs index 157368141..ba401ac38 100644 --- a/vrp-pragmatic/src/format/problem/goal_reader.rs +++ b/vrp-pragmatic/src/format/problem/goal_reader.rs @@ -3,9 +3,10 @@ use crate::format::problem::aspects::*; use std::collections::HashSet; use vrp_core::construction::clustering::vicinity::ClusterDimension; use vrp_core::construction::enablers::{FeatureCombinator, RouteIntervals, ScheduleKeys}; +use vrp_core::construction::features::capacity::*; use vrp_core::construction::features::*; use vrp_core::construction::heuristics::{StateKey, StateKeyRegistry}; -use vrp_core::models::common::{CapacityDimension, LoadOps, MultiDimLoad, SingleDimLoad}; +use vrp_core::models::common::{LoadOps, MultiDimLoad, SingleDimLoad}; use vrp_core::models::problem::{Actor, Single, TransportCost}; use vrp_core::models::{CoreStateKeys, Extras, Feature, GoalContext, GoalContextBuilder}; @@ -180,7 +181,6 @@ fn get_objective_features( Objective::BalanceMaxLoad { options } => { let load_balance_keys = LoadBalanceKeys { reload_interval: state_context.get_reload_intervals_key(), - max_future_capacity: state_context.capacity_keys.max_future_capacity, balance_max_load: state_context.next_key(), }; if props.has_multi_dimen_capacity { @@ -199,7 +199,7 @@ fn get_objective_features( max_ratio }), Arc::new(|vehicle| { - vehicle.dimens.get_capacity().expect("vehicle has no capacity defined") + vehicle.dimens.get_vehicle_capacity().expect("vehicle has no capacity defined") }), ) } else { @@ -209,7 +209,7 @@ fn get_objective_features( load_balance_keys, Arc::new(|loaded, capacity| loaded.value as f64 / capacity.value as f64), Arc::new(|vehicle| { - vehicle.dimens.get_capacity().expect("vehicle has no capacity defined") + vehicle.dimens.get_vehicle_capacity().expect("vehicle has no capacity defined") }), ) } @@ -291,7 +291,6 @@ fn get_capacity_feature( props: &ProblemProperties, state_context: &mut StateKeyContext, ) -> Result { - let capacity_keys = state_context.capacity_keys.clone(); if props.has_reloads { if props.has_multi_dimen_capacity { get_capacity_with_reload_feature::( @@ -313,15 +312,9 @@ fn get_capacity_feature( ) } } else if props.has_multi_dimen_capacity { - create_capacity_limit_feature::( - name, - PragmaticCapacityAspects::new(capacity_keys, CAPACITY_CONSTRAINT_CODE), - ) + create_capacity_limit_feature::(name, CAPACITY_CONSTRAINT_CODE) } else { - create_capacity_limit_feature::( - name, - PragmaticCapacityAspects::new(capacity_keys, CAPACITY_CONSTRAINT_CODE), - ) + create_capacity_limit_feature::(name, CAPACITY_CONSTRAINT_CODE) } } @@ -372,15 +365,10 @@ fn get_capacity_with_reload_feature( capacity_map: fn(Vec) -> T, load_schedule_threshold_fn: LoadScheduleThresholdFn, ) -> Result { - let capacity_keys = state_context.capacity_keys.clone(); let job_index = blocks.job_index.as_ref().ok_or("misconfiguration in goal reader: job index is not set")?; let reload_resources = get_reload_resources(api_problem, job_index, capacity_map); let capacity_feature_factory: CapacityFeatureFactoryFn = Box::new(move |name, route_intervals| { - create_capacity_limit_with_multi_trip_feature::( - name, - route_intervals, - PragmaticCapacityAspects::new(capacity_keys, CAPACITY_CONSTRAINT_CODE), - ) + create_capacity_limit_with_multi_trip_feature::(name, route_intervals, CAPACITY_CONSTRAINT_CODE) }); let reload_keys = state_context.get_reload_keys(); @@ -602,15 +590,13 @@ struct StateKeyContext<'a> { state_registry: &'a mut StateKeyRegistry, reload_intervals: Option, schedule_keys: ScheduleKeys, - capacity_keys: CapacityKeys, } impl<'a> StateKeyContext<'a> { pub fn new(extras: &Extras, state_registry: &'a mut StateKeyRegistry) -> Self { let schedule_keys = extras.get_schedule_keys().cloned().expect("schedule keys are missing in extras"); - let capacity_keys = extras.get_capacity_keys().cloned().expect("capacity keys are missing in extras"); - Self { state_registry, reload_intervals: None, capacity_keys, schedule_keys } + Self { state_registry, reload_intervals: None, schedule_keys } } pub fn next_key(&mut self) -> StateKey { @@ -626,6 +612,6 @@ impl<'a> StateKeyContext<'a> { } pub fn get_reload_keys(&mut self) -> ReloadKeys { - ReloadKeys { intervals: self.get_reload_intervals_key(), capacity_keys: self.capacity_keys.clone() } + ReloadKeys { intervals: self.get_reload_intervals_key() } } } diff --git a/vrp-pragmatic/src/format/problem/job_reader.rs b/vrp-pragmatic/src/format/problem/job_reader.rs index 4554ae7c0..70d4c0a17 100644 --- a/vrp-pragmatic/src/format/problem/job_reader.rs +++ b/vrp-pragmatic/src/format/problem/job_reader.rs @@ -5,6 +5,7 @@ use crate::format::{JobIndex, Location}; use crate::utils::VariableJobPermutation; use std::collections::HashMap; use std::sync::Arc; +use vrp_core::construction::features::capacity::JobDemandDimension; use vrp_core::construction::features::BreakPolicy; use vrp_core::construction::features::JobSkills as FeatureJobSkills; use vrp_core::models::common::*; @@ -392,9 +393,9 @@ fn get_single_with_dimens( let dimens = &mut single.dimens; if has_multi_dimens { - dimens.set_demand(demand) + dimens.set_job_demand(demand) } else { - dimens.set_demand(Demand { + dimens.set_job_demand(Demand { pickup: (SingleDimLoad::new(demand.pickup.0.load[0]), SingleDimLoad::new(demand.pickup.1.load[0])), delivery: (SingleDimLoad::new(demand.delivery.0.load[0]), SingleDimLoad::new(demand.delivery.1.load[0])), }) diff --git a/vrp-pragmatic/src/format/solution/solution_writer.rs b/vrp-pragmatic/src/format/solution/solution_writer.rs index d0c3d68bc..598b3dc04 100644 --- a/vrp-pragmatic/src/format/solution/solution_writer.rs +++ b/vrp-pragmatic/src/format/solution/solution_writer.rs @@ -7,6 +7,7 @@ use crate::format::solution::model::Timing; use crate::format::solution::*; use crate::format::CoordIndex; use vrp_core::construction::enablers::{get_route_intervals, ReservedTimesIndex}; +use vrp_core::construction::features::capacity::JobDemandDimension; use vrp_core::construction::heuristics::UnassignmentInfo; use vrp_core::models::common::*; use vrp_core::models::problem::{JobIdDimension, Multi, TravelTime, VehicleIdDimension}; @@ -405,7 +406,7 @@ fn get_activity_type(activity: &Activity) -> Option<&String> { fn get_capacity(dimens: &Dimensions) -> Option> { // NOTE: try to detect whether dimensions stores multidimensional demand - let demand: Option> = dimens.get_demand().cloned(); + let demand: Option> = dimens.get_job_demand().cloned(); if let Some(demand) = demand { return Some(demand); } @@ -417,7 +418,7 @@ fn get_capacity(dimens: &Dimensions) -> Option> { MultiDimLoad::new(vec![capacity.value]) } }; - dimens.get_demand().map(|demand: &Demand| Demand { + dimens.get_job_demand().map(|demand: &Demand| Demand { pickup: (create_capacity(demand.pickup.0), create_capacity(demand.pickup.1)), delivery: (create_capacity(demand.delivery.0), create_capacity(demand.delivery.1)), }) diff --git a/vrp-pragmatic/tests/unit/format/problem/reader_test.rs b/vrp-pragmatic/tests/unit/format/problem/reader_test.rs index 4b3434174..8ac28d1e0 100644 --- a/vrp-pragmatic/tests/unit/format/problem/reader_test.rs +++ b/vrp-pragmatic/tests/unit/format/problem/reader_test.rs @@ -3,6 +3,7 @@ use crate::helpers::*; use std::collections::HashSet; use std::iter::FromIterator; use std::sync::Arc; +use vrp_core::construction::features::capacity::JobDemandDimension; use vrp_core::models::common::*; use vrp_core::models::problem::{JobIdDimension, Jobs, Multi, Place, Single, VehicleIdDimension}; @@ -191,7 +192,7 @@ fn can_read_complex_problem() { assert_eq!(place.duration, 100.); assert_eq!(place.location.unwrap(), 0); assert_demand( - job.dimens.get_demand().expect("cannot get demand"), + job.dimens.get_job_demand().expect("cannot get demand"), &Demand { pickup: (MultiDimLoad::default(), MultiDimLoad::default()), delivery: (MultiDimLoad::new(vec![0, 1]), MultiDimLoad::default()), @@ -209,14 +210,14 @@ fn can_read_complex_problem() { let place = get_single_place(pickup.as_ref()); assert_eq!(place.duration, 110.); assert_eq!(place.location.unwrap(), 1); - assert_demand(pickup.dimens.get_demand().unwrap(), &single_demand_as_multi((0, 2), (0, 0))); + assert_demand(pickup.dimens.get_job_demand().unwrap(), &single_demand_as_multi((0, 2), (0, 0))); assert_time_spans(&place.times, vec![(10., 30.)]); let delivery = job.jobs.last().unwrap().clone(); let place = get_single_place(delivery.as_ref()); assert_eq!(place.duration, 120.); assert_eq!(place.location.unwrap(), 0); - assert_demand(delivery.dimens.get_demand().unwrap(), &single_demand_as_multi((0, 0), (0, 2))); + assert_demand(delivery.dimens.get_job_demand().unwrap(), &single_demand_as_multi((0, 0), (0, 2))); assert_time_spans(&place.times, vec![(50., 60.)]); // pickup @@ -225,7 +226,7 @@ fn can_read_complex_problem() { assert_eq!(job.dimens.get_job_id().unwrap(), "pickup_job"); assert_eq!(place.duration, 90.); assert_eq!(place.location.unwrap(), 2); - assert_demand(job.dimens.get_demand().unwrap(), &single_demand_as_multi((3, 0), (0, 0))); + assert_demand(job.dimens.get_job_demand().unwrap(), &single_demand_as_multi((3, 0), (0, 0))); assert_time_spans(&place.times, vec![(10., 70.)]); assert_job_skills(&job.dimens, Some(vec!["unique2".to_string()])); diff --git a/vrp-scientific/src/common/aspects.rs b/vrp-scientific/src/common/aspects.rs deleted file mode 100644 index 6e3dbc22f..000000000 --- a/vrp-scientific/src/common/aspects.rs +++ /dev/null @@ -1,39 +0,0 @@ -use vrp_core::construction::features::{CapacityAspects, CapacityKeys}; -use vrp_core::models::common::{CapacityDimension, Demand, DemandDimension, SingleDimLoad}; -use vrp_core::models::problem::{Single, Vehicle}; -use vrp_core::models::ViolationCode; - -/// Provides a way to use capacity feature. -pub struct ScientificCapacityAspects { - state_keys: CapacityKeys, - violation_code: ViolationCode, -} - -impl ScientificCapacityAspects { - /// Creates a new instance of `ScientificCapacityAspects`. - pub fn new(state_keys: CapacityKeys, violation_code: ViolationCode) -> Self { - Self { state_keys, violation_code } - } -} - -impl CapacityAspects for ScientificCapacityAspects { - fn get_capacity<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a SingleDimLoad> { - vehicle.dimens.get_capacity() - } - - fn get_demand<'a>(&self, single: &'a Single) -> Option<&'a Demand> { - single.dimens.get_demand() - } - - fn set_demand(&self, single: &mut Single, demand: Demand) { - single.dimens.set_demand(demand); - } - - fn get_state_keys(&self) -> &CapacityKeys { - &self.state_keys - } - - fn get_violation_code(&self) -> ViolationCode { - self.violation_code - } -} diff --git a/vrp-scientific/src/common/mod.rs b/vrp-scientific/src/common/mod.rs index b31bce144..6772533be 100644 --- a/vrp-scientific/src/common/mod.rs +++ b/vrp-scientific/src/common/mod.rs @@ -1,6 +1,5 @@ //! Contains common text reading and writing functionality. -mod aspects; mod text_reader; pub(crate) use self::text_reader::*; diff --git a/vrp-scientific/src/common/text_reader.rs b/vrp-scientific/src/common/text_reader.rs index f7c8917d0..4ac91c7de 100644 --- a/vrp-scientific/src/common/text_reader.rs +++ b/vrp-scientific/src/common/text_reader.rs @@ -1,7 +1,7 @@ -use crate::common::aspects::ScientificCapacityAspects; use std::io::prelude::*; use std::io::{BufReader, Read}; use std::sync::Arc; +use vrp_core::construction::features::capacity::{create_capacity_limit_feature, VehicleCapacityDimension}; use vrp_core::construction::features::*; use vrp_core::models::common::*; use vrp_core::models::problem::*; @@ -67,7 +67,7 @@ pub(crate) fn create_fleet_with_distance_costs( let mut dimens = create_dimens_with_id("v", &i.to_string(), |id, dimens| { dimens.set_vehicle_id(id); }); - dimens.set_capacity(SingleDimLoad::new(capacity as i32)); + dimens.set_vehicle_capacity(SingleDimLoad::new(capacity as i32)); Arc::new(Vehicle { profile: Profile::default(), costs: Costs { @@ -138,15 +138,12 @@ fn get_essential_features( ) -> Result, GenericError> { let schedule_keys = extras.get_schedule_keys().cloned().ok_or_else(|| GenericError::from("missing schedule keys set in extras"))?; - let capacity_keys = - extras.get_capacity_keys().cloned().ok_or_else(|| GenericError::from("missing capacity keys set in extras"))?; - let capacity_aspects = ScientificCapacityAspects::new(capacity_keys, 2); Ok(vec![ create_minimize_unassigned_jobs_feature("min_unassigned", Arc::new(|_, _| 1.))?, create_minimize_tours_feature("min_tours")?, create_minimize_distance_feature("min_distance", transport, activity, schedule_keys, 1)?, - create_capacity_limit_feature::("capacity", capacity_aspects)?, + create_capacity_limit_feature::("capacity", 2)?, ]) } diff --git a/vrp-scientific/src/lilim/reader.rs b/vrp-scientific/src/lilim/reader.rs index aff4b3a8c..c221ac368 100644 --- a/vrp-scientific/src/lilim/reader.rs +++ b/vrp-scientific/src/lilim/reader.rs @@ -6,6 +6,7 @@ use crate::common::*; use std::collections::HashMap; use std::io::{BufReader, Read}; use std::sync::Arc; +use vrp_core::construction::features::capacity::JobDemandDimension; use vrp_core::models::common::*; use vrp_core::models::problem::*; use vrp_core::models::*; @@ -136,7 +137,7 @@ impl LilimReader { let mut dimens = create_dimens_with_id("c", &customer.id.to_string(), |id, dimens| { dimens.set_job_id(id); }); - dimens.set_demand(if customer.demand > 0 { + dimens.set_job_demand(if customer.demand > 0 { Demand:: { pickup: (SingleDimLoad::default(), SingleDimLoad::new(customer.demand)), delivery: (SingleDimLoad::default(), SingleDimLoad::default()), diff --git a/vrp-scientific/src/solomon/reader.rs b/vrp-scientific/src/solomon/reader.rs index 3bb844c5c..2baa02273 100644 --- a/vrp-scientific/src/solomon/reader.rs +++ b/vrp-scientific/src/solomon/reader.rs @@ -5,6 +5,7 @@ mod reader_test; use crate::common::*; use std::io::{BufReader, Read}; use std::sync::Arc; +use vrp_core::construction::features::capacity::JobDemandDimension; use vrp_core::models::common::*; use vrp_core::models::problem::*; use vrp_core::models::*; @@ -98,7 +99,7 @@ impl SolomonReader { match self.read_customer() { Ok(customer) => { let mut dimens = Dimensions::default(); - dimens.set_job_id(customer.id.to_string().as_str()).set_demand(Demand:: { + dimens.set_job_id(customer.id.to_string().as_str()).set_job_demand(Demand:: { pickup: (SingleDimLoad::default(), SingleDimLoad::default()), delivery: (SingleDimLoad::new(customer.demand as i32), SingleDimLoad::default()), }); diff --git a/vrp-scientific/src/tsplib/reader.rs b/vrp-scientific/src/tsplib/reader.rs index ee6901d0b..9783603ec 100644 --- a/vrp-scientific/src/tsplib/reader.rs +++ b/vrp-scientific/src/tsplib/reader.rs @@ -6,6 +6,7 @@ use crate::common::*; use std::collections::HashMap; use std::io::{BufReader, Read}; use std::sync::Arc; +use vrp_core::construction::features::capacity::JobDemandDimension; use vrp_core::models::common::*; use vrp_core::models::problem::*; use vrp_core::models::*; @@ -210,7 +211,7 @@ impl TsplibReader { fn create_job(&mut self, id: &str, location: (i32, i32), demand: i32) -> Job { let mut dimens = Dimensions::default(); - dimens.set_job_id(id).set_demand(Demand:: { + dimens.set_job_id(id).set_job_demand(Demand:: { pickup: (SingleDimLoad::default(), SingleDimLoad::default()), delivery: (SingleDimLoad::new(demand), SingleDimLoad::default()), }); diff --git a/vrp-scientific/tests/helpers/analysis.rs b/vrp-scientific/tests/helpers/analysis.rs index f4c6e5332..7312cfa49 100644 --- a/vrp-scientific/tests/helpers/analysis.rs +++ b/vrp-scientific/tests/helpers/analysis.rs @@ -1,3 +1,4 @@ +use vrp_core::construction::features::capacity::{JobDemandDimension, VehicleCapacityDimension}; use vrp_core::construction::heuristics::InsertionContext; use vrp_core::models::common::*; use vrp_core::models::problem::{Job, JobIdDimension}; @@ -36,7 +37,7 @@ pub fn get_customer_ids_from_routes(insertion_ctx: &InsertionContext) -> Vec i32 { - let capacity: &SingleDimLoad = problem.fleet.vehicles.first().unwrap().dimens.get_capacity().unwrap(); + let capacity: &SingleDimLoad = problem.fleet.vehicles.first().unwrap().dimens.get_vehicle_capacity().unwrap(); capacity.value } @@ -83,6 +84,6 @@ pub fn get_job_simple_demand(job: &Job) -> &Demand { Job::Single(single) => &single.dimens, Job::Multi(multi) => &multi.dimens, } - .get_demand() + .get_job_demand() .unwrap() }