From 72821323a5a04037b71a26130019f3426a907760 Mon Sep 17 00:00:00 2001 From: reinterpretcat Date: Fri, 14 Jun 2024 22:58:40 +0200 Subject: [PATCH] Refactor dimensions to avoid using string as a key --- CHANGELOG.md | 1 + vrp-cli/src/extensions/analyze/clusters.rs | 5 +- .../construction/clustering/vicinity/mod.rs | 9 +- .../src/construction/heuristics/insertions.rs | 6 +- vrp-core/src/models/common/dimens.rs | 26 ++ vrp-core/src/models/common/domain.rs | 49 +--- vrp-core/src/models/common/load.rs | 14 +- vrp-core/src/models/common/mod.rs | 3 + vrp-core/src/models/examples.rs | 8 +- vrp-core/src/models/extras.rs | 27 +- vrp-core/src/models/problem/fleet.rs | 23 +- vrp-core/src/models/problem/jobs.rs | 32 ++- vrp-core/src/models/solution/tour.rs | 6 +- vrp-core/src/solver/heuristic.rs | 1 - .../processing/reschedule_reserved_time.rs | 1 - .../solver/processing/vicinity_clustering.rs | 2 +- .../construction/clustering/vicinity.rs | 32 ++- .../tests/helpers/construction/features.rs | 8 +- vrp-core/tests/helpers/models/domain.rs | 5 +- .../tests/helpers/models/problem/fleet.rs | 10 +- vrp-core/tests/helpers/models/problem/jobs.rs | 18 +- vrp-core/tests/helpers/solver/mod.rs | 4 +- vrp-core/tests/helpers/solver/mutation.rs | 11 +- .../clustering/vicinity/estimations_test.rs | 6 +- .../unit/construction/features/breaks_test.rs | 15 +- .../features/compatibility_test.rs | 9 +- .../features/fast_service_test.rs | 2 +- .../unit/construction/features/groups_test.rs | 10 +- .../construction/features/recharge_test.rs | 13 +- .../construction/features/reloads_test.rs | 17 +- .../features/shared_resource_test.rs | 9 +- .../unit/construction/features/skills_test.rs | 24 +- .../construction/features/total_value_test.rs | 6 +- .../construction/features/tour_order_test.rs | 15 +- .../probing/repair_solution_test.rs | 8 +- .../processing/unassignment_reason_test.rs | 10 +- .../processing/vicinity_clustering_test.rs | 5 +- vrp-pragmatic/Cargo.toml | 1 + vrp-pragmatic/src/format/entities.rs | 267 +++++------------- vrp-pragmatic/src/format/mod.rs | 2 +- vrp-pragmatic/src/format/problem/aspects.rs | 12 +- .../src/format/problem/clustering_reader.rs | 2 +- .../src/format/problem/fleet_reader.rs | 4 +- .../src/format/problem/goal_reader.rs | 11 +- .../src/format/problem/job_reader.rs | 63 +++-- .../src/format/problem/problem_reader.rs | 3 +- .../src/format/solution/activity_matcher.rs | 4 +- .../src/format/solution/initial_reader.rs | 6 +- .../src/format/solution/solution_writer.rs | 9 +- vrp-pragmatic/tests/helpers/core.rs | 6 +- .../tests/unit/format/problem/reader_test.rs | 5 +- vrp-scientific/src/common/aspects.rs | 8 +- vrp-scientific/src/common/initial_reader.rs | 2 +- vrp-scientific/src/common/routing.rs | 2 +- vrp-scientific/src/common/text_reader.rs | 14 +- vrp-scientific/src/common/text_writer.rs | 4 +- vrp-scientific/src/lilim/reader.rs | 8 +- vrp-scientific/src/solomon/reader.rs | 4 +- vrp-scientific/src/tsplib/reader.rs | 4 +- vrp-scientific/tests/helpers/analysis.rs | 4 +- .../tests/integration/known_problems_test.rs | 7 +- 61 files changed, 434 insertions(+), 468 deletions(-) create mode 100644 vrp-core/src/models/common/dimens.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ca63807e..2d2e6e575 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ are already published. So, I stick to it for now. * tweak infeasible search heuristic * refactor route intervals and multi trip enablers * refactor feature objective +* refactor dimensions approach ## [v1.23.0]- 2023-12-22 diff --git a/vrp-cli/src/extensions/analyze/clusters.rs b/vrp-cli/src/extensions/analyze/clusters.rs index 769c95d8c..76ec34f53 100644 --- a/vrp-cli/src/extensions/analyze/clusters.rs +++ b/vrp-cli/src/extensions/analyze/clusters.rs @@ -5,8 +5,7 @@ mod clusters_test; use std::io::{BufReader, BufWriter, Read}; use std::sync::Arc; use vrp_core::construction::clustering::dbscan::create_job_clusters; -use vrp_core::models::common::IdDimension; -use vrp_core::models::problem::get_job_locations; +use vrp_core::models::problem::{get_job_locations, JobIdDimension}; use vrp_core::models::Problem; use vrp_core::prelude::GenericError; use vrp_core::utils::Environment; @@ -35,7 +34,7 @@ pub fn get_clusters( .flat_map(|(cluster_idx, jobs)| { jobs.iter() .filter_map(move |job| { - job.dimens().get_id().cloned().map(|job_id| { + job.dimens().get_job_id().cloned().map(|job_id| { get_job_locations(job) .flatten() .filter_map(move |l_idx| coord_index.get_by_idx(l_idx)) diff --git a/vrp-core/src/construction/clustering/vicinity/mod.rs b/vrp-core/src/construction/clustering/vicinity/mod.rs index b9b632ddb..7b4e6f094 100644 --- a/vrp-core/src/construction/clustering/vicinity/mod.rs +++ b/vrp-core/src/construction/clustering/vicinity/mod.rs @@ -5,8 +5,8 @@ mod vicinity_test; use crate::construction::heuristics::*; +use crate::models::common::Dimensions; use crate::models::common::*; -use crate::models::common::{Dimensions, ValueDimension}; use crate::models::problem::{Actor, Job}; use crate::models::Problem; use hashbrown::HashSet; @@ -19,8 +19,6 @@ mod estimations; use self::estimations::*; use crate::models::solution::Commute; -const CLUSTER_DIMENSION_KEY: &str = "cls"; - /// A trait to get or set cluster info. pub trait ClusterDimension { /// Sets cluster. @@ -29,14 +27,15 @@ pub trait ClusterDimension { fn get_cluster(&self) -> Option<&Vec>; } +struct ClusterDimensionKey; impl ClusterDimension for Dimensions { fn set_cluster(&mut self, jobs: Vec) -> &mut Self { - self.set_value(CLUSTER_DIMENSION_KEY, jobs); + self.set_value::(jobs); self } fn get_cluster(&self) -> Option<&Vec> { - self.get_value(CLUSTER_DIMENSION_KEY) + self.get_value::() } } diff --git a/vrp-core/src/construction/heuristics/insertions.rs b/vrp-core/src/construction/heuristics/insertions.rs index 58b62a285..8afae0a78 100644 --- a/vrp-core/src/construction/heuristics/insertions.rs +++ b/vrp-core/src/construction/heuristics/insertions.rs @@ -3,8 +3,8 @@ mod insertions_test; use crate::construction::heuristics::*; -use crate::models::common::{Cost, IdDimension}; -use crate::models::problem::{Actor, Job}; +use crate::models::common::Cost; +use crate::models::problem::{Actor, Job, JobIdDimension}; use crate::models::solution::Activity; use crate::models::ViolationCode; use rosomaxa::prelude::*; @@ -52,7 +52,7 @@ impl Debug for InsertionSuccess { .map(|(a, idx)| { ( a.retrieve_job() - .and_then(|job| job.dimens().get_id().cloned()) + .and_then(|job| job.dimens().get_job_id().cloned()) .unwrap_or("undef".to_string()), *idx, ) diff --git a/vrp-core/src/models/common/dimens.rs b/vrp-core/src/models/common/dimens.rs new file mode 100644 index 000000000..8cc43d225 --- /dev/null +++ b/vrp-core/src/models/common/dimens.rs @@ -0,0 +1,26 @@ +use hashbrown::HashMap; +use rustc_hash::FxHasher; +use std::any::{Any, TypeId}; +use std::hash::BuildHasherDefault; +use std::sync::Arc; + +/// Multiple named dimensions which can contain anything: +/// * unit of measure, e.g. volume, mass, size, etc. +/// * set of skills +/// * tag. +#[derive(Clone, Debug, Default)] +pub struct Dimensions { + index: HashMap, BuildHasherDefault>, +} + +impl Dimensions { + /// Gets a value using type provided. + pub fn get_value(&self) -> Option<&V> { + self.index.get(&TypeId::of::()).and_then(|any| any.downcast_ref::()) + } + + /// Sets the value using type provided. + pub fn set_value(&mut self, value: V) { + self.index.insert(TypeId::of::(), Arc::new(value)); + } +} diff --git a/vrp-core/src/models/common/domain.rs b/vrp-core/src/models/common/domain.rs index 67ee8c29f..3f103904c 100644 --- a/vrp-core/src/models/common/domain.rs +++ b/vrp-core/src/models/common/domain.rs @@ -3,13 +3,9 @@ mod domain_test; use crate::models::common::{Duration, Timestamp}; -use hashbrown::HashMap; use rosomaxa::prelude::compare_floats; -use rustc_hash::FxHasher; -use std::any::Any; use std::cmp::Ordering; -use std::hash::{BuildHasherDefault, Hash, Hasher}; -use std::sync::Arc; +use std::hash::{Hash, Hasher}; /// Specifies location type. pub type Location = usize; @@ -217,49 +213,6 @@ impl PartialEq for Schedule { impl Eq for Schedule {} -/// Multiple named dimensions which can contain anything: -/// * unit of measure, e.g. volume, mass, size, etc. -/// * set of skills -/// * tag. -pub type Dimensions = HashMap, BuildHasherDefault>; - -/// A trait to return arbitrary typed value by its key. -pub trait ValueDimension { - /// Gets value from dimension with given key. - fn get_value(&self, key: &str) -> Option<&T>; - /// Sets value in dimension with given key and value. - fn set_value(&mut self, key: &str, value: T); -} - -impl ValueDimension for Dimensions { - fn get_value(&self, key: &str) -> Option<&T> { - self.get(key).and_then(|any| any.downcast_ref::()) - } - - fn set_value(&mut self, key: &str, value: T) { - self.insert(key.to_owned(), Arc::new(value)); - } -} - -/// A trait to get or set id. -pub trait IdDimension { - /// Sets value as id. - fn set_id(&mut self, id: &str) -> &mut Self; - /// Gets id value if present. - fn get_id(&self) -> Option<&String>; -} - -impl IdDimension for Dimensions { - fn set_id(&mut self, id: &str) -> &mut Self { - self.set_value("id", id.to_string()); - self - } - - fn get_id(&self) -> Option<&String> { - self.get_value("id") - } -} - impl Hash for TimeInterval { fn hash(&self, state: &mut H) { let earliest = self.earliest.unwrap_or(0.).to_bits() as i64; diff --git a/vrp-core/src/models/common/load.rs b/vrp-core/src/models/common/load.rs index 4f22083fb..ce0b1e9f5 100644 --- a/vrp-core/src/models/common/load.rs +++ b/vrp-core/src/models/common/load.rs @@ -2,15 +2,13 @@ #[path = "../../../tests/unit/models/common/load_test.rs"] mod load_test; -use crate::models::common::{Dimensions, ValueDimension}; +use crate::models::common::Dimensions; use rosomaxa::prelude::UnwrapValue; use std::cmp::Ordering; use std::fmt::{Debug, Display, Formatter}; use std::iter::Sum; use std::ops::{Add, ControlFlow, Mul, Sub}; -const CAPACITY_DIMENSION_KEY: &str = "capacity"; -const DEMAND_DIMENSION_KEY: &str = "demand"; const LOAD_DIMENSION_SIZE: usize = 8; /// Represents a load type used to represent customer's demand or vehicle's load. @@ -114,25 +112,27 @@ impl Add for Demand { } } +struct CapacityDimenKey; impl CapacityDimension for Dimensions { fn set_capacity(&mut self, demand: T) -> &mut Self { - self.set_value(CAPACITY_DIMENSION_KEY, demand); + self.set_value::(demand); self } fn get_capacity(&self) -> Option<&T> { - self.get_value(CAPACITY_DIMENSION_KEY) + self.get_value::() } } +struct DemandDimenKey; impl DemandDimension for Dimensions { fn set_demand(&mut self, demand: Demand) -> &mut Self { - self.set_value(DEMAND_DIMENSION_KEY, demand); + self.set_value::(demand); self } fn get_demand(&self) -> Option<&Demand> { - self.get_value(DEMAND_DIMENSION_KEY) + self.get_value::() } } diff --git a/vrp-core/src/models/common/mod.rs b/vrp-core/src/models/common/mod.rs index 1d293e2f1..a89a760b3 100644 --- a/vrp-core/src/models/common/mod.rs +++ b/vrp-core/src/models/common/mod.rs @@ -1,5 +1,8 @@ //! Common models. +mod dimens; +pub use self::dimens::*; + mod domain; pub use self::domain::*; diff --git a/vrp-core/src/models/examples.rs b/vrp-core/src/models/examples.rs index 55ded03e0..ec202a5a0 100644 --- a/vrp-core/src/models/examples.rs +++ b/vrp-core/src/models/examples.rs @@ -52,7 +52,7 @@ fn create_example_fleet() -> Arc { details: vec![], })]; let mut vehicle_dimens = Dimensions::default(); - vehicle_dimens.set_id("v1"); + vehicle_dimens.set_vehicle_id("v1"); let vehicles = vec![Arc::new(Vehicle { profile: Profile::default(), costs: Costs { fixed: 0., per_distance: 1., per_driving_time: 0., per_waiting_time: 0., per_service_time: 0. }, @@ -80,15 +80,15 @@ struct ExampleCapacityAspects { impl CapacityAspects for ExampleCapacityAspects { fn get_capacity<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a SingleDimLoad> { - vehicle.dimens.get_value("capacity") + vehicle.dimens.get_capacity() } fn get_demand<'a>(&self, single: &'a Single) -> Option<&'a Demand> { - single.dimens.get_value("demand") + single.dimens.get_demand() } fn set_demand(&self, single: &mut Single, demand: Demand) { - single.dimens.set_value("demand", demand); + single.dimens.set_demand(demand); } fn get_state_keys(&self) -> &CapacityKeys { diff --git a/vrp-core/src/models/extras.rs b/vrp-core/src/models/extras.rs index 02a9a533e..c7dc32dd9 100644 --- a/vrp-core/src/models/extras.rs +++ b/vrp-core/src/models/extras.rs @@ -1,17 +1,30 @@ use crate::construction::enablers::ScheduleKeys; use crate::construction::features::CapacityKeys; use crate::construction::heuristics::StateKeyRegistry; -use crate::models::common::{Dimensions, ValueDimension}; use crate::solver::HeuristicKeys; +use hashbrown::HashMap; use rosomaxa::prelude::GenericError; +use rustc_hash::FxHasher; +use std::any::Any; +use std::hash::BuildHasherDefault; use std::sync::Arc; /// Specifies a type used to store any values regarding problem configuration. pub struct Extras { - index: Dimensions, + index: HashMap, BuildHasherDefault>, } impl Extras { + /// Gets value for the key if it is stored and has `T` type. + pub fn get_value(&self, key: &str) -> Option<&T> { + self.index.get(key).and_then(|any| any.downcast_ref::()) + } + + /// Sets value using the given key. + pub fn set_value(&mut self, key: &str, value: T) { + self.index.insert(key.to_string(), Arc::new(value)); + } + /// Returns a shared reference for the value under the given key. pub fn get_value_raw(&self, key: &str) -> Option> { self.index.get(key).cloned().and_then(|any| any.downcast::().ok()) @@ -118,13 +131,3 @@ impl CoreStateKeys for Extras { self.get_value("heuristic_keys") } } - -impl ValueDimension for Extras { - fn get_value(&self, key: &str) -> Option<&T> { - self.index.get_value(key) - } - - fn set_value(&mut self, key: &str, value: T) { - self.index.set_value(key, value) - } -} diff --git a/vrp-core/src/models/problem/fleet.rs b/vrp-core/src/models/problem/fleet.rs index efeffafef..e0f548809 100644 --- a/vrp-core/src/models/problem/fleet.rs +++ b/vrp-core/src/models/problem/fleet.rs @@ -105,7 +105,7 @@ pub struct Actor { impl Debug for Actor { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct(short_type_name::()) - .field("vehicle", &self.vehicle.dimens.get_id().map(|id| id.as_str()).unwrap_or("undef")) + .field("vehicle", &self.vehicle.dimens.get_vehicle_id().map(|id| id.as_str()).unwrap_or("undef")) .finish_non_exhaustive() } } @@ -203,3 +203,24 @@ impl Hash for Actor { address.hash(state); } } + +/// A trait to get or set vehicle id. +pub trait VehicleIdDimension { + /// Sets value as vehicle id. + fn set_vehicle_id(&mut self, value: &str) -> &mut Self; + + /// Gets vehicle id value if present. + fn get_vehicle_id(&self) -> Option<&String>; +} + +struct VehicleIdDimensionKey; +impl VehicleIdDimension for Dimensions { + fn set_vehicle_id(&mut self, id: &str) -> &mut Self { + self.set_value::(id.to_string()); + self + } + + fn get_vehicle_id(&self) -> Option<&String> { + self.get_value::() + } +} diff --git a/vrp-core/src/models/problem/jobs.rs b/vrp-core/src/models/problem/jobs.rs index b9ac6ae43..4c307c410 100644 --- a/vrp-core/src/models/problem/jobs.rs +++ b/vrp-core/src/models/problem/jobs.rs @@ -96,7 +96,7 @@ pub struct Single { impl Debug for Single { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct(short_type_name::()) - .field("id", &self.dimens.get_id().map(|id| id.as_str()).unwrap_or("undef")) + .field("id", &self.dimens.get_job_id().map(|id| id.as_str()).unwrap_or("undef")) .finish_non_exhaustive() } } @@ -116,7 +116,7 @@ pub struct Multi { impl Debug for Multi { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct(short_type_name::()) - .field("id", &self.dimens.get_id().map(|id| id.as_str()).unwrap_or("undef")) + .field("id", &self.dimens.get_job_id().map(|id| id.as_str()).unwrap_or("undef")) .field("jobs", &self.jobs.len()) .finish_non_exhaustive() } @@ -157,6 +157,27 @@ impl JobPermutation for FixedJobPermutation { } } +/// A trait to get or set id. +pub trait JobIdDimension { + /// Sets value as id. + fn set_job_id(&mut self, id: &str) -> &mut Self; + + /// Gets id value if present. + fn get_job_id(&self) -> Option<&String>; +} + +struct JobIdDimensionKey; +impl JobIdDimension for Dimensions { + fn set_job_id(&mut self, id: &str) -> &mut Self { + self.set_value::(id.to_string()); + self + } + + fn get_job_id(&self) -> Option<&String> { + self.get_value::() + } +} + impl Multi { /// Creates a new multi job from given 'dimens' and `jobs` assuming that jobs has to be /// inserted in order they specified. @@ -190,7 +211,7 @@ impl Multi { /// Returns parent multi job for given sub-job. pub fn roots(single: &Single) -> Option> { - single.dimens.get_value::>("rf").and_then(|w| w.upgrade()) + single.dimens.get_value::>().and_then(|w| w.upgrade()) } /// Wraps given multi job into [`Arc`] adding reference to it from all sub-jobs. @@ -200,7 +221,7 @@ impl Multi { Arc::get_mut(single) .expect("Single from Multi should not be shared before binding") .dimens - .set_value("rf", weak_multi.clone()); + .set_value::(weak_multi.clone()); }); multi @@ -208,6 +229,9 @@ impl Multi { } } +/// A private type to get/set link between multi job and its children single jobs. +struct JobLink; + /// Floating type wit less precision, but lower impact on memory footprint. type LowPrecisionCost = f32; type JobIndex = HashMap, LowPrecisionCost)>; diff --git a/vrp-core/src/models/solution/tour.rs b/vrp-core/src/models/solution/tour.rs index 2365a1adc..20a7c49ac 100644 --- a/vrp-core/src/models/solution/tour.rs +++ b/vrp-core/src/models/solution/tour.rs @@ -2,8 +2,8 @@ #[path = "../../../tests/unit/models/solution/tour_test.rs"] mod tour_test; -use crate::models::common::{IdDimension, Schedule}; -use crate::models::problem::{Actor, Job}; +use crate::models::common::Schedule; +use crate::models::problem::{Actor, Job, JobIdDimension}; use crate::models::solution::{Activity, Place}; use crate::models::OP_START_MSG; use crate::utils::{short_type_name, Either}; @@ -239,7 +239,7 @@ impl Debug for Tour { idx if self.is_closed && idx == self.activities.len() - 1 => "arrival".to_string(), _ => activity .retrieve_job() - .and_then(|job| job.dimens().get_id().cloned()) + .and_then(|job| job.dimens().get_job_id().cloned()) .unwrap_or("undef".to_string()), }) .collect::>(), diff --git a/vrp-core/src/solver/heuristic.rs b/vrp-core/src/solver/heuristic.rs index 156e3b867..f942806d5 100644 --- a/vrp-core/src/solver/heuristic.rs +++ b/vrp-core/src/solver/heuristic.rs @@ -1,7 +1,6 @@ use super::*; use crate::construction::enablers::ScheduleKeys; use crate::construction::heuristics::*; -use crate::models::common::ValueDimension; use crate::models::{CoreStateKeys, Extras, GoalContext}; use crate::rosomaxa::get_default_selection_size; use crate::solver::search::*; diff --git a/vrp-core/src/solver/processing/reschedule_reserved_time.rs b/vrp-core/src/solver/processing/reschedule_reserved_time.rs index 3fd3e13e0..c5d07c033 100644 --- a/vrp-core/src/solver/processing/reschedule_reserved_time.rs +++ b/vrp-core/src/solver/processing/reschedule_reserved_time.rs @@ -1,6 +1,5 @@ use super::*; use crate::construction::enablers::*; -use crate::models::common::ValueDimension; use crate::models::Extras; /// A trait to get or set reserved times index. diff --git a/vrp-core/src/solver/processing/vicinity_clustering.rs b/vrp-core/src/solver/processing/vicinity_clustering.rs index 8322df0df..6cd5041d0 100644 --- a/vrp-core/src/solver/processing/vicinity_clustering.rs +++ b/vrp-core/src/solver/processing/vicinity_clustering.rs @@ -4,7 +4,7 @@ mod vicinity_clustering_test; use super::*; use crate::construction::clustering::vicinity::*; -use crate::models::common::{Schedule, ValueDimension}; +use crate::models::common::Schedule; use crate::models::problem::Jobs; use crate::models::solution::{Activity, Place}; use crate::models::{Extras, ExtrasBuilder, GoalContext, Problem}; diff --git a/vrp-core/tests/helpers/construction/clustering/vicinity.rs b/vrp-core/tests/helpers/construction/clustering/vicinity.rs index c1143672e..482d25108 100644 --- a/vrp-core/tests/helpers/construction/clustering/vicinity.rs +++ b/vrp-core/tests/helpers/construction/clustering/vicinity.rs @@ -2,18 +2,34 @@ use crate::construction::clustering::vicinity::*; use crate::construction::heuristics::*; use crate::helpers::models::domain::TestGoalContextBuilder; use crate::helpers::models::problem::{get_job_id, SingleBuilder}; -use crate::models::common::{Duration, IdDimension, Location, Profile, ValueDimension}; -use crate::models::problem::Job; +use crate::models::common::{Dimensions, Duration, Location, Profile}; +use crate::models::problem::{Job, JobIdDimension}; use crate::models::*; use hashbrown::HashSet; use rosomaxa::prelude::compare_floats; use std::cmp::Ordering; use std::sync::Arc; -pub const MERGED_KEY: &str = "merged"; - pub type JobPlaces = Vec<(Option, Duration, Vec<(f64, f64)>)>; +/// Provides the way to set clustered jobs. +pub trait ClusteredJob { + fn get_clustered_jobs(&self) -> Option<&Vec>; + + fn set_clustered_jobs(&mut self, jobs: Vec); +} + +struct ClusteredJobDimenKey; +impl ClusteredJob for Dimensions { + fn get_clustered_jobs(&self) -> Option<&Vec> { + self.get_value::() + } + + fn set_clustered_jobs(&mut self, jobs: Vec) { + self.set_value::(jobs); + } +} + struct VicinityTestFeatureConstraint { disallow_merge_list: HashSet, } @@ -22,7 +38,7 @@ impl FeatureConstraint for VicinityTestFeatureConstraint { fn evaluate(&self, move_ctx: &MoveContext<'_>) -> Option { match move_ctx { MoveContext::Route { job, .. } => { - if self.disallow_merge_list.contains(job.dimens().get_id().unwrap()) { + if self.disallow_merge_list.contains(job.dimens().get_job_id().unwrap()) { ConstraintViolation::fail(1) } else { None @@ -33,7 +49,7 @@ impl FeatureConstraint for VicinityTestFeatureConstraint { } fn merge(&self, source: Job, candidate: Job) -> Result { - if self.disallow_merge_list.contains(candidate.dimens().get_id().unwrap()) { + if self.disallow_merge_list.contains(candidate.dimens().get_job_id().unwrap()) { Err(1) } else { let source = source.to_single(); @@ -54,9 +70,9 @@ impl FeatureConstraint for VicinityTestFeatureConstraint { ); let mut dimens = source.dimens.clone(); - let mut merged = dimens.get_value::>(MERGED_KEY).cloned().unwrap_or_default(); + let mut merged = dimens.get_clustered_jobs().cloned().unwrap_or_default(); merged.push(candidate); - dimens.set_value(MERGED_KEY, merged); + dimens.set_clustered_jobs(merged); Ok(SingleBuilder::default().dimens(dimens).places(vec![place]).build_as_job_ref()) } diff --git a/vrp-core/tests/helpers/construction/features.rs b/vrp-core/tests/helpers/construction/features.rs index e453791a5..df42dcfe7 100644 --- a/vrp-core/tests/helpers/construction/features.rs +++ b/vrp-core/tests/helpers/construction/features.rs @@ -1,5 +1,5 @@ use crate::construction::features::{CapacityAspects, CapacityKeys}; -use crate::models::common::{Demand, LoadOps, MultiDimLoad, SingleDimLoad, ValueDimension}; +use crate::models::common::{CapacityDimension, Demand, DemandDimension, LoadOps, MultiDimLoad, SingleDimLoad}; use crate::models::problem::{Single, Vehicle}; use crate::models::ViolationCode; use std::marker::PhantomData; @@ -60,15 +60,15 @@ impl TestCapacityAspects { impl CapacityAspects for TestCapacityAspects { fn get_capacity<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a T> { - vehicle.dimens.get_value("capacity") + vehicle.dimens.get_capacity() } fn get_demand<'a>(&self, single: &'a Single) -> Option<&'a Demand> { - single.dimens.get_value("demand") + single.dimens.get_demand() } fn set_demand(&self, single: &mut Single, demand: Demand) { - single.dimens.set_value("demand", demand); + single.dimens.set_demand(demand); } fn get_state_keys(&self) -> &CapacityKeys { diff --git a/vrp-core/tests/helpers/models/domain.rs b/vrp-core/tests/helpers/models/domain.rs index 9891c9aab..405d3aead 100644 --- a/vrp-core/tests/helpers/models/domain.rs +++ b/vrp-core/tests/helpers/models/domain.rs @@ -2,8 +2,7 @@ use crate::construction::enablers::ScheduleKeys; use crate::construction::features::create_minimize_transport_costs_feature; use crate::construction::heuristics::*; use crate::helpers::models::problem::{test_fleet, TestActivityCost, TestTransportCost}; -use crate::models::common::IdDimension; -use crate::models::problem::{Fleet, Job, Jobs}; +use crate::models::problem::{Fleet, Job, JobIdDimension, Jobs}; use crate::models::{ExtrasBuilder, Feature, GoalContext, GoalContextBuilder, Problem}; use rosomaxa::utils::{DefaultRandom, Random}; use std::sync::Arc; @@ -144,7 +143,7 @@ pub fn get_customer_ids_from_unassigned(insertion_ctx: &InsertionContext) -> Vec } pub fn get_customer_id(job: &Job) -> String { - job.dimens().get_id().unwrap().clone() + job.dimens().get_job_id().unwrap().clone() } fn create_empty_problem() -> Problem { diff --git a/vrp-core/tests/helpers/models/problem/fleet.rs b/vrp-core/tests/helpers/models/problem/fleet.rs index f94136b8d..6ce224356 100644 --- a/vrp-core/tests/helpers/models/problem/fleet.rs +++ b/vrp-core/tests/helpers/models/problem/fleet.rs @@ -60,13 +60,13 @@ pub fn test_fleet() -> Fleet { pub fn test_vehicle_with_id(id: &str) -> Vehicle { let mut dimens = Dimensions::default(); - dimens.set_id(id); + dimens.set_vehicle_id(id); Vehicle { profile: Profile::default(), costs: test_costs(), dimens, details: vec![test_vehicle_detail()] } } pub fn get_vehicle_id(vehicle: &Vehicle) -> &String { - vehicle.dimens.get_id().unwrap() + vehicle.dimens.get_vehicle_id().unwrap() } pub fn get_test_actor_from_fleet(fleet: &Fleet, vehicle_id: &str) -> Arc { @@ -83,7 +83,7 @@ impl Default for VehicleBuilder { impl VehicleBuilder { pub fn id(&mut self, id: &str) -> &mut VehicleBuilder { - self.0.dimens.set_id(id); + self.0.dimens.set_vehicle_id(id); self } @@ -112,8 +112,8 @@ impl VehicleBuilder { self } - pub fn property(&mut self, key: &str, value: T) -> &mut Self { - self.0.dimens.insert(key.to_string(), Arc::new(value)); + pub fn property(&mut self, value: T) -> &mut Self { + self.0.dimens.set_value::(value); self } diff --git a/vrp-core/tests/helpers/models/problem/jobs.rs b/vrp-core/tests/helpers/models/problem/jobs.rs index 51c4d8811..a6fe811c2 100644 --- a/vrp-core/tests/helpers/models/problem/jobs.rs +++ b/vrp-core/tests/helpers/models/problem/jobs.rs @@ -1,5 +1,5 @@ use crate::models::common::*; -use crate::models::problem::{FixedJobPermutation, Job, Multi, Place, Single}; +use crate::models::problem::{FixedJobPermutation, Job, JobIdDimension, Multi, Place, Single}; use std::sync::Arc; pub const DEFAULT_JOB_LOCATION: Location = 0; @@ -11,7 +11,7 @@ pub type TestPlace = (Option, Duration, Vec<(f64, f64)>); pub fn test_multi_with_id(id: &str, jobs: Vec>) -> Arc { let mut dimens = Dimensions::default(); - dimens.set_id(id); + dimens.set_job_id(id); Multi::new_shared(jobs, dimens) } @@ -23,13 +23,13 @@ pub fn test_multi_job_with_locations(locations: Vec>>) -> A pub fn test_multi_with_permutations(id: &str, jobs: Vec>, permutations: Vec>) -> Arc { let mut dimens = Dimensions::default(); - dimens.set_id(id); + dimens.set_job_id(id); Multi::new_shared_with_permutator(jobs, dimens, Box::new(FixedJobPermutation::new(permutations))) } pub fn get_job_id(job: &Job) -> &String { - job.dimens().get_id().unwrap() + job.dimens().get_job_id().expect("no job id") } pub struct SingleBuilder(Single); @@ -46,18 +46,18 @@ impl SingleBuilder { places: locations.into_iter().map(test_place_with_location).collect(), dimens: Default::default(), }; - single.dimens.set_id("single"); + single.dimens.set_job_id("single"); Self(single) } pub fn id(&mut self, id: &str) -> &mut Self { - self.0.dimens.set_id(id); + self.0.dimens.set_job_id(id); self } - pub fn property(&mut self, key: &str, value: T) -> &mut Self { - self.0.dimens.insert(key.to_string(), Arc::new(value)); + pub fn property(&mut self, value: T) -> &mut Self { + self.0.dimens.set_value::(value); self } @@ -115,7 +115,7 @@ impl SingleBuilder { fn test_single() -> Single { let mut single = Single { places: vec![test_place_with_location(Some(DEFAULT_JOB_LOCATION))], dimens: Default::default() }; - single.dimens.set_id("single"); + single.dimens.set_job_id("single"); single } diff --git a/vrp-core/tests/helpers/solver/mod.rs b/vrp-core/tests/helpers/solver/mod.rs index 06e05814c..b53be6351 100644 --- a/vrp-core/tests/helpers/solver/mod.rs +++ b/vrp-core/tests/helpers/solver/mod.rs @@ -4,7 +4,7 @@ use crate::construction::heuristics::MoveContext; use crate::helpers::models::domain::{test_random, TestGoalContextBuilder}; use crate::helpers::models::problem::*; use crate::helpers::models::solution::{ActivityBuilder, RouteBuilder}; -use crate::models::common::{Cost, IdDimension, Location}; +use crate::models::common::{Cost, Location}; use crate::models::problem::*; use crate::models::solution::{Activity, Registry, Route}; use crate::models::*; @@ -211,7 +211,7 @@ impl FeatureConstraint for LegFeatureConstraint { let retrieve_job_id = |activity: Option<&Activity>| { activity.as_ref().and_then(|next| { next.retrieve_job() - .and_then(|job| job.dimens().get_id().cloned()) + .and_then(|job| job.dimens().get_job_id().cloned()) .or_else(|| Some(self.ignore.clone())) }) }; diff --git a/vrp-core/tests/helpers/solver/mutation.rs b/vrp-core/tests/helpers/solver/mutation.rs index f6fc9d124..79077f1ec 100644 --- a/vrp-core/tests/helpers/solver/mutation.rs +++ b/vrp-core/tests/helpers/solver/mutation.rs @@ -1,12 +1,13 @@ use super::*; use crate::construction::heuristics::*; -use crate::models::common::{IdDimension, Schedule}; +use crate::models::common::Schedule; use crate::models::solution::{Activity, Place}; use hashbrown::HashMap; /// Promotes given job ids to locked in given context. pub fn promote_to_locked(mut insertion_ctx: InsertionContext, job_ids: &[&str]) -> InsertionContext { - let ids = insertion_ctx.problem.jobs.all().filter(|job| job_ids.contains(&job.dimens().get_id().unwrap().as_str())); + let ids = + insertion_ctx.problem.jobs.all().filter(|job| job_ids.contains(&job.dimens().get_job_id().unwrap().as_str())); insertion_ctx.solution.locked.extend(ids); insertion_ctx @@ -38,7 +39,7 @@ pub fn get_jobs_by_ids(insertion_ctx: &InsertionContext, job_ids: &[&str]) -> Ve .jobs .all() .filter_map(|job| { - let job_id = job.dimens().get_id().unwrap().clone(); + let job_id = job.dimens().get_job_id().unwrap().clone(); if job_ids.contains(&job_id.as_str()) { Some((job_id, job)) } else { @@ -57,7 +58,7 @@ pub fn get_jobs_by_ids(insertion_ctx: &InsertionContext, job_ids: &[&str]) -> Ve /// Returns a job by its id. pub fn get_job_by_id(insertion_ctx: &InsertionContext, job_id: &str) -> Option { - insertion_ctx.problem.jobs.all().find(|job| job.dimens().get_id().map_or(false, |id| id == job_id)) + insertion_ctx.problem.jobs.all().find(|job| job.dimens().get_job_id().map_or(false, |id| id == job_id)) } /// Gets all jobs with their id in a map. @@ -67,7 +68,7 @@ pub fn get_jobs_map_by_ids(insertion_ctx: &InsertionContext) -> HashMap) -> Arc { let disallow_insertion_list = disallow_insertion_list.into_iter().map(|id| id.to_string()).collect::>(); Arc::new(move |job| { - let job_to_check = - job.dimens().get_value::>(MERGED_KEY).and_then(|merged| merged.last()).unwrap_or(job); - let id = job_to_check.dimens().get_id(); + let job_to_check = job.dimens().get_clustered_jobs().and_then(|merged| merged.last()).unwrap_or(job); + let id = job_to_check.dimens().get_job_id(); if id.map_or(false, |id| disallow_insertion_list.contains(id)) { Err(-1) diff --git a/vrp-core/tests/unit/construction/features/breaks_test.rs b/vrp-core/tests/unit/construction/features/breaks_test.rs index 52c07d4f6..108b1d8d1 100644 --- a/vrp-core/tests/unit/construction/features/breaks_test.rs +++ b/vrp-core/tests/unit/construction/features/breaks_test.rs @@ -13,6 +13,9 @@ const VIOLATION_CODE: ViolationCode = 1; #[derive(Clone)] struct TestBreakAspects; +struct JobTypeDimenKey; +struct VehicleIdDimenKey; + impl BreakAspects for TestBreakAspects { fn belongs_to_route(&self, route_ctx: &RouteContext, candidate: BreakCandidate<'_>) -> bool { if !self.is_break_job(candidate) { @@ -21,8 +24,8 @@ impl BreakAspects for TestBreakAspects { let Some(single) = candidate.as_single() else { return false }; - let job_vehicle_id = single.dimens.get_value::("vehicle_id"); - let vehicle_id = route_ctx.route().actor.vehicle.dimens.get_id(); + let job_vehicle_id = single.dimens.get_value::(); + let vehicle_id = route_ctx.route().actor.vehicle.dimens.get_vehicle_id(); job_vehicle_id.zip(vehicle_id).map_or(false, |(a, b)| a == b) } @@ -30,7 +33,7 @@ impl BreakAspects for TestBreakAspects { fn is_break_job(&self, candidate: BreakCandidate<'_>) -> bool { candidate .as_single() - .and_then(|break_single| break_single.dimens.get_value::("type")) + .and_then(|break_single| break_single.dimens.get_value::()) .map_or(false, |job_type| job_type == "break") } @@ -48,8 +51,8 @@ fn create_break(vehicle_id: &str, location: Option) -> Arc { .id("break") .location(location) .duration(3600.) - .property("type", "break".to_string()) - .property("vehicle_id", vehicle_id.to_string()) + .property::("break".to_string()) + .property::(vehicle_id.to_string()) .build_shared() } @@ -86,7 +89,7 @@ fn can_remove_orphan_break_impl(break_job_loc: Option, break_activity_ if break_removed { assert_eq!(solution_ctx.unassigned.len(), 1); - assert_eq!(solution_ctx.unassigned.iter().next().unwrap().0.dimens().get_id().unwrap().clone(), "break"); + assert_eq!(solution_ctx.unassigned.iter().next().unwrap().0.dimens().get_job_id().unwrap().clone(), "break"); } else { assert!(solution_ctx.unassigned.is_empty()); } diff --git a/vrp-core/tests/unit/construction/features/compatibility_test.rs b/vrp-core/tests/unit/construction/features/compatibility_test.rs index 0136f4622..8d31ecd7e 100644 --- a/vrp-core/tests/unit/construction/features/compatibility_test.rs +++ b/vrp-core/tests/unit/construction/features/compatibility_test.rs @@ -12,9 +12,11 @@ struct TestCompatibilityAspects { state_key: StateKey, } +struct JobCompatDimenKey; + impl CompatibilityAspects for TestCompatibilityAspects { fn get_job_compatibility<'a>(&self, job: &'a Job) -> Option<&'a String> { - job.dimens().get_value("compat") + job.dimens().get_value::() } fn get_state_key(&self) -> StateKey { @@ -34,7 +36,7 @@ fn create_test_single(compatibility: Option) -> Arc { let mut builder = SingleBuilder::default(); if let Some(compatibility) = compatibility { - builder.property("compat", compatibility); + builder.property::(compatibility); } builder.location(Some(DEFAULT_JOB_LOCATION)).build_shared() @@ -124,7 +126,8 @@ fn can_merge_jobs_impl( let candidate = Job::Single(create_test_single(candidate_compat.map(|v| v.to_string()))); let constraint = create_feature(state_key).constraint.unwrap(); - let result = constraint.merge(source, candidate).map(|job| job.dimens().get_value("compat").cloned()); + let result = + constraint.merge(source, candidate).map(|job| job.dimens().get_value::().cloned()); match (result, expected) { (Ok(_), Err(_)) => unreachable!("unexpected err result"), 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 5187ae124..1925c720a 100644 --- a/vrp-core/tests/unit/construction/features/fast_service_test.rs +++ b/vrp-core/tests/unit/construction/features/fast_service_test.rs @@ -13,7 +13,7 @@ impl FastServiceAspects for TestFastServiceAspects { } fn get_demand_type(&self, single: &Single) -> Option { - single.dimens.get_value::>("demand").map(|demand| demand.get_type()) + single.dimens.get_demand().map(|demand: &Demand| demand.get_type()) } } diff --git a/vrp-core/tests/unit/construction/features/groups_test.rs b/vrp-core/tests/unit/construction/features/groups_test.rs index 806068852..32e57f260 100644 --- a/vrp-core/tests/unit/construction/features/groups_test.rs +++ b/vrp-core/tests/unit/construction/features/groups_test.rs @@ -16,9 +16,11 @@ struct TestGroupAspects { state_key: StateKey, } +struct GroupDimenKey; + impl GroupAspects for TestGroupAspects { fn get_job_group<'a>(&self, job: &'a Job) -> Option<&'a String> { - job.dimens().get_value::("group") + job.dimens().get_value::() } fn get_state_key(&self) -> StateKey { @@ -44,7 +46,7 @@ fn create_test_fleet() -> Fleet { .add_vehicle(test_vehicle_with_id("v1")) .add_vehicle(test_vehicle_with_id("v2")) .with_group_key_fn(Box::new(|actors| { - Box::new(create_typed_actor_groups(actors, |a| a.vehicle.dimens.get_id().cloned().unwrap())) + Box::new(create_typed_actor_groups(actors, |a| a.vehicle.dimens.get_vehicle_id().cloned().unwrap())) })) .build() } @@ -53,7 +55,7 @@ fn create_test_single(group: Option<&str>) -> Arc { let mut builder = SingleBuilder::default(); if let Some(group) = group { - builder.property("group", group.to_string()); + builder.property::(group.to_string()); } builder.build_shared() @@ -102,7 +104,7 @@ fn create_test_solution_context( } fn get_actor(fleet: &Fleet, vehicle: &str) -> Arc { - fleet.actors.iter().find(|actor| actor.vehicle.dimens.get_id().unwrap() == vehicle).unwrap().clone() + fleet.actors.iter().find(|actor| actor.vehicle.dimens.get_vehicle_id().unwrap() == vehicle).unwrap().clone() } fn get_actor_groups(solution_ctx: &mut SolutionContext, state_key: StateKey) -> HashMap> { diff --git a/vrp-core/tests/unit/construction/features/recharge_test.rs b/vrp-core/tests/unit/construction/features/recharge_test.rs index 06b091b79..c56f9f283 100644 --- a/vrp-core/tests/unit/construction/features/recharge_test.rs +++ b/vrp-core/tests/unit/construction/features/recharge_test.rs @@ -12,17 +12,20 @@ struct TestRechargeAspects { recharge_distance_limit_fn: RechargeDistanceLimitFn, } +struct VehicleIdDimenKey; +struct JobTypeDimenKey; + impl RechargeAspects for TestRechargeAspects { fn belongs_to_route(&self, route: &Route, job: &Job) -> bool { job.as_single() .filter(|single| self.is_recharge_single(single)) - .and_then(|single| single.dimens.get_value::("vehicle_id")) - .zip(route.actor.vehicle.dimens.get_id()) + .and_then(|single| single.dimens.get_value::()) + .zip(route.actor.vehicle.dimens.get_vehicle_id()) .map_or(false, |(a, b)| a == b) } fn is_recharge_single(&self, single: &Single) -> bool { - single.dimens.get_value::("type").map_or(false, |job_type| job_type == "recharge") + single.dimens.get_value::().map_or(false, |job_type| job_type == "recharge") } fn get_state_keys(&self) -> &RechargeKeys { @@ -43,8 +46,8 @@ fn recharge(location: Location) -> Activity { .job(Some( SingleBuilder::default() .id("recharge") - .property("type", "recharge".to_string()) - .property("vehicle_id", "v1".to_string()) + .property::("recharge".to_string()) + .property::("v1".to_string()) .build_shared(), )) .build() diff --git a/vrp-core/tests/unit/construction/features/reloads_test.rs b/vrp-core/tests/unit/construction/features/reloads_test.rs index 1c1e18abc..a02575207 100644 --- a/vrp-core/tests/unit/construction/features/reloads_test.rs +++ b/vrp-core/tests/unit/construction/features/reloads_test.rs @@ -14,17 +14,20 @@ struct TestReloadAspects { phantom: PhantomData, } +struct VehicleIdDimenKey; +struct JobTypeDimenKey; + 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())) - .and_then(|single| single.dimens.get_value::("vehicle_id")) - .zip(route.actor.vehicle.dimens.get_id()) + .and_then(|single| single.dimens.get_value::()) + .zip(route.actor.vehicle.dimens.get_vehicle_id()) .map_or(false, |(a, b)| a == b) } fn is_reload_single(&self, single: &Single) -> bool { - single.dimens.get_value::("type").map_or(false, |job_type| job_type == "reload") + single.dimens.get_value::().map_or(false, |job_type| job_type == "reload") } fn get_capacity<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a T> { @@ -69,7 +72,7 @@ fn create_activity_with_demand( SingleBuilder::default() .id(job_id) .demand(single_demand_as_multi(pickup, delivery)) - .property("type", activity_type.to_string()) + .property::(activity_type.to_string()) .build_shared(), )) .build() @@ -88,8 +91,8 @@ fn reload(reload_id: &str) -> Activity { .job(Some( SingleBuilder::default() .id(reload_id) - .property("type", "reload".to_string()) - .property("vehicle_id", "v1".to_string()) + .property::("reload".to_string()) + .property::("v1".to_string()) .build_shared(), )) .build() @@ -303,7 +306,7 @@ fn can_remove_trivial_reloads_when_used_from_capacity_constraint_impl( .tour .all_activities() .filter_map(|activity| activity.job.as_ref()) - .filter_map(|job| job.dimens.get_id()) + .filter_map(|job| job.dimens.get_job_id()) .collect::>(), expected ); 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 b97db3a8f..e19c70a68 100644 --- a/vrp-core/tests/unit/construction/features/shared_resource_test.rs +++ b/vrp-core/tests/unit/construction/features/shared_resource_test.rs @@ -10,6 +10,8 @@ use crate::models::problem::Fleet; const VIOLATION_CODE: ViolationCode = 1; +struct ResourceIdDimenKey; + fn create_usage_activity(demand: i32) -> Activity { let demand = create_simple_demand(-demand); let single = SingleBuilder::default().demand(demand).build_shared(); @@ -20,7 +22,7 @@ fn create_usage_activity(demand: i32) -> Activity { fn create_resource_activity(capacity: i32, resource_id: Option) -> Activity { let mut single = SingleBuilder::default().build(); if let Some(resource_id) = resource_id { - single.dimens.set_value("resource_id", resource_id); + single.dimens.set_value::(resource_id); } single.dimens.set_capacity(SingleDimLoad::new(capacity)); @@ -36,7 +38,10 @@ fn create_feature(intervals_key: StateKey, resource_key: StateKey, total_jobs: u create_interval_fn(intervals_key), Arc::new(|activity| { activity.job.as_ref().and_then(|job| { - job.dimens.get_capacity().cloned().zip(job.dimens.get_value::("resource_id").cloned()) + job.dimens + .get_capacity() + .cloned() + .zip(job.dimens.get_value::().cloned()) }) }), create_resource_demand_fn(), diff --git a/vrp-core/tests/unit/construction/features/skills_test.rs b/vrp-core/tests/unit/construction/features/skills_test.rs index 0ddfef319..cf5409a93 100644 --- a/vrp-core/tests/unit/construction/features/skills_test.rs +++ b/vrp-core/tests/unit/construction/features/skills_test.rs @@ -4,7 +4,6 @@ use crate::construction::heuristics::MoveContext; use crate::helpers::construction::heuristics::InsertionContextBuilder; use crate::helpers::models::problem::{test_driver, FleetBuilder, SingleBuilder, VehicleBuilder}; use crate::helpers::models::solution::{RouteBuilder, RouteContextBuilder}; -use crate::models::common::ValueDimension; use crate::models::problem::{Job, Vehicle}; use crate::models::{ConstraintViolation, ViolationCode}; use hashbrown::HashSet; @@ -15,13 +14,16 @@ const VIOLATION_CODE: ViolationCode = 1; #[derive(Clone)] struct TestJobSkillsAspects; +struct JobSkillsDimenKey; +struct VehicleSkillsDimenKey; + impl JobSkillsAspects for TestJobSkillsAspects { fn get_job_skills<'a>(&self, job: &'a Job) -> Option<&'a JobSkills> { - job.dimens().get_value("skills") + job.dimens().get_value::() } fn get_vehicle_skills<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a HashSet> { - vehicle.dimens.get_value("skills") + vehicle.dimens.get_value::() } fn get_violation_code(&self) -> ViolationCode { @@ -31,21 +33,19 @@ impl JobSkillsAspects for TestJobSkillsAspects { fn create_job_with_skills(all_of: Option>, one_of: Option>, none_of: Option>) -> Job { SingleBuilder::default() - .property( - "skills", - JobSkills { - all_of: all_of.map(|skills| skills.iter().map(|s| s.to_string()).collect()), - one_of: one_of.map(|skills| skills.iter().map(|s| s.to_string()).collect()), - none_of: none_of.map(|skills| skills.iter().map(|s| s.to_string()).collect()), - }, - ) + .property::(JobSkills { + all_of: all_of.map(|skills| skills.iter().map(|s| s.to_string()).collect()), + one_of: one_of.map(|skills| skills.iter().map(|s| s.to_string()).collect()), + none_of: none_of.map(|skills| skills.iter().map(|s| s.to_string()).collect()), + }) .build_as_job_ref() } fn create_vehicle_with_skills(skills: Option>) -> Vehicle { let mut builder = VehicleBuilder::default(); if let Some(skills) = skills { - builder.property("skills", HashSet::::from_iter(skills.iter().map(|s| s.to_string()))); + let skills: HashSet = HashSet::from_iter(skills.iter().map(|s| s.to_string())); + builder.property::(skills); } builder.id("v1").build() diff --git a/vrp-core/tests/unit/construction/features/total_value_test.rs b/vrp-core/tests/unit/construction/features/total_value_test.rs index 5d99a1c2e..c556ca14f 100644 --- a/vrp-core/tests/unit/construction/features/total_value_test.rs +++ b/vrp-core/tests/unit/construction/features/total_value_test.rs @@ -2,7 +2,6 @@ use super::*; use crate::helpers::construction::heuristics::InsertionContextBuilder; use crate::helpers::models::problem::{get_job_id, SingleBuilder}; use crate::helpers::models::solution::*; -use crate::models::common::ValueDimension; use crate::models::problem::Single; const VIOLATION_CODE: ViolationCode = 1; @@ -41,6 +40,7 @@ fn can_estimate_job_value_impl(value: f64, expected: f64) { #[test] fn can_merge_value() { + struct ValueDimenKey; let constraint = create_maximize_total_job_value_feature( "value", JobReadValueFn::Left(Arc::new(move |job| match get_job_id(job).as_str() { @@ -51,7 +51,7 @@ fn can_merge_value() { Arc::new(|job, value| { let single = job.to_single(); let mut dimens = single.dimens.clone(); - dimens.set_value("value", value); + dimens.set_value::(value); Job::Single(Arc::new(Single { places: single.places.clone(), dimens })) }), @@ -65,5 +65,5 @@ fn can_merge_value() { let merged = constraint.merge(source, candidate).unwrap(); - assert_eq!(merged.dimens().get_value::("value").cloned(), Some(12.)) + assert_eq!(merged.dimens().get_value::().cloned(), Some(12.)) } diff --git a/vrp-core/tests/unit/construction/features/tour_order_test.rs b/vrp-core/tests/unit/construction/features/tour_order_test.rs index 5731f8bd2..40d421f5d 100644 --- a/vrp-core/tests/unit/construction/features/tour_order_test.rs +++ b/vrp-core/tests/unit/construction/features/tour_order_test.rs @@ -1,17 +1,17 @@ use super::*; use crate::helpers::models::problem::*; use crate::helpers::models::solution::*; -use crate::models::common::{IdDimension, ValueDimension}; use crate::models::solution::Activity; const VIOLATION_CODE: ViolationCode = 1; +struct OrderDimenKey; + fn create_single_with_order(id: &str, order: Option) -> Arc { - let mut single = SingleBuilder::default().build(); - single.dimens.set_id(id); + let mut single = SingleBuilder::default().id(id).build(); if let Some(order) = order { - single.dimens.set_value("order", order); + single.dimens.set_value::(order); } Arc::new(single) @@ -58,15 +58,16 @@ fn can_merge_order_impl(source: Option, candidate: Option, expected: R let source_job = Job::Single(create_single_with_order("source", source)); let candidate_job = Job::Single(create_single_with_order("candidate", candidate)); - let result = - constraint.merge(source_job, candidate_job).map(|merged| merged.dimens().get_value::("order").cloned()); + let result = constraint + .merge(source_job, candidate_job) + .map(|merged| merged.dimens().get_value::().cloned()); assert_eq!(result, expected); } fn get_order_fn() -> TourOrderFn { Either::Left(Arc::new(|single| { - single.map_or(OrderResult::Ignored, |single| match single.dimens.get_value::("order") { + single.map_or(OrderResult::Ignored, |single| match single.dimens.get_value::() { Some(value) => OrderResult::Value(*value), _ => OrderResult::Default, }) 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 1065d23e6..b3dcb628b 100644 --- a/vrp-core/tests/unit/construction/probing/repair_solution_test.rs +++ b/vrp-core/tests/unit/construction/probing/repair_solution_test.rs @@ -69,7 +69,7 @@ fn create_test_problem( .map(|(vehicle_id, order, position, job_ids)| { let vehicle_id = vehicle_id.to_string(); Arc::new(Lock { - condition_fn: Arc::new(move |actor| *actor.vehicle.dimens.get_id().unwrap() == vehicle_id), + condition_fn: Arc::new(move |actor| *actor.vehicle.dimens.get_vehicle_id().unwrap() == vehicle_id), details: vec![LockDetail { order, position, @@ -156,7 +156,7 @@ fn add_routes(insertion_ctx: &mut InsertionContext, routes: Vec<(&str, RouteData fn get_job_by_id>(jobs: T, job_id: &str) -> Job { let mut jobs = jobs; - jobs.find(|job| job.dimens().get_id().unwrap() == job_id).unwrap() + jobs.find(|job| job.dimens().get_job_id().unwrap() == job_id).unwrap() } fn get_as_single(problem: &Problem, job_id: &str, index: usize) -> Arc { @@ -176,13 +176,13 @@ fn get_routes(insertion_ctx: &InsertionContext) -> Vec<(&str, Vec<&str>)> { .iter() .map(|route_ctx| { ( - route_ctx.route().actor.vehicle.dimens.get_id().unwrap().as_str(), + route_ctx.route().actor.vehicle.dimens.get_vehicle_id().unwrap().as_str(), route_ctx .route() .tour .all_activities() .flat_map(|a| a.job.as_ref()) - .map(|single| single.dimens.get_id().unwrap().as_str()) + .map(|single| single.dimens.get_job_id().unwrap().as_str()) .collect(), ) }) diff --git a/vrp-core/tests/unit/solver/processing/unassignment_reason_test.rs b/vrp-core/tests/unit/solver/processing/unassignment_reason_test.rs index 553750a41..0f45cd793 100644 --- a/vrp-core/tests/unit/solver/processing/unassignment_reason_test.rs +++ b/vrp-core/tests/unit/solver/processing/unassignment_reason_test.rs @@ -3,8 +3,8 @@ use crate::helpers::construction::heuristics::{create_schedule_keys, InsertionCo use crate::helpers::models::domain::{ProblemBuilder, TestGoalContextBuilder}; use crate::helpers::models::problem::*; use crate::helpers::models::solution::{RouteBuilder, RouteContextBuilder}; -use crate::models::common::{IdDimension, TimeWindow}; -use crate::models::problem::Job; +use crate::models::common::TimeWindow; +use crate::models::problem::{Job, JobIdDimension, VehicleIdDimension}; use crate::solver::processing::UnassignmentReason; use rosomaxa::evolution::HeuristicSolutionProcessing; @@ -73,14 +73,14 @@ fn can_combine_vehicle_details_impl( assert_eq!(insertion_ctx.solution.unassigned.len(), expected_details.len()); let mut actual_details = insertion_ctx.solution.unassigned.into_iter().collect::>(); - actual_details.sort_by(|(a, _), (b, _)| a.dimens().get_id().cmp(&b.dimens().get_id())); + actual_details.sort_by(|(a, _), (b, _)| a.dimens().get_job_id().cmp(&b.dimens().get_job_id())); actual_details.into_iter().zip(expected_details).for_each(|((job, code), (expected_job_id, expected_details))| { - assert_eq!(job.to_single().dimens.get_id().unwrap(), expected_job_id); + assert_eq!(job.to_single().dimens.get_job_id().unwrap(), expected_job_id); match code { UnassignmentInfo::Detailed(details) => { let details = details .iter() - .map(|(actor, code)| (actor.vehicle.dimens.get_id().unwrap().as_str(), *code)) + .map(|(actor, code)| (actor.vehicle.dimens.get_vehicle_id().unwrap().as_str(), *code)) .collect::>(); assert_eq!(details, expected_details); } diff --git a/vrp-core/tests/unit/solver/processing/vicinity_clustering_test.rs b/vrp-core/tests/unit/solver/processing/vicinity_clustering_test.rs index 1f7b30a2d..6f0837e06 100644 --- a/vrp-core/tests/unit/solver/processing/vicinity_clustering_test.rs +++ b/vrp-core/tests/unit/solver/processing/vicinity_clustering_test.rs @@ -6,8 +6,7 @@ use crate::helpers::models::domain::*; use crate::helpers::models::problem::*; use crate::helpers::models::solution::*; use crate::helpers::solver::create_default_refinement_ctx; -use crate::models::common::IdDimension; -use crate::models::problem::Job; +use crate::models::problem::{Job, JobIdDimension}; use crate::models::solution::{Commute, CommuteInfo}; fn create_test_jobs() -> Vec { @@ -116,7 +115,7 @@ fn can_unwrap_clusters_in_route_on_post_process_impl( let job_activities = route_ctx.route().tour.all_activities().skip(1).take(3).collect::>(); assert_eq!(job_activities.len(), expected.len()); job_activities.into_iter().zip(expected).for_each(|(activity, (id, (arrival, departure)))| { - assert_eq!(activity.job.as_ref().unwrap().dimens.get_id().unwrap(), id); + assert_eq!(activity.job.as_ref().unwrap().dimens.get_job_id().unwrap(), id); assert_eq!(activity.schedule.arrival, arrival); assert_eq!(activity.schedule.departure, departure); }); diff --git a/vrp-pragmatic/Cargo.toml b/vrp-pragmatic/Cargo.toml index 6bfcc6f89..199686ede 100644 --- a/vrp-pragmatic/Cargo.toml +++ b/vrp-pragmatic/Cargo.toml @@ -20,6 +20,7 @@ serde_json.workspace = true rand.workspace = true time = { version = "0.3.36", features = ["parsing", "formatting"] } +paste = "1.0.15" [dev-dependencies] proptest = "1.4.0" diff --git a/vrp-pragmatic/src/format/entities.rs b/vrp-pragmatic/src/format/entities.rs index 47b484436..f0ed76a2e 100644 --- a/vrp-pragmatic/src/format/entities.rs +++ b/vrp-pragmatic/src/format/entities.rs @@ -2,249 +2,130 @@ use hashbrown::HashSet; use vrp_core::construction::features::{BreakPolicy, JobSkills}; -use vrp_core::models::common::{Dimensions, ValueDimension}; -/// Specifies vehicle entity. -pub trait VehicleTie { - /// Gets vehicle's id. - fn get_vehicle_id(&self) -> Option<&String>; - /// Sets vehicle's id. - fn set_vehicle_id(&mut self, id: String) -> &mut Self; +macro_rules! impl_dimension_property { + ($trait_name:ident with $field_name:ident using $type:ty) => { + paste::paste! { + // define a dummy struct type which is used as a key + struct [<$trait_name Key>]; + impl $trait_name for vrp_core::models::common::Dimensions { + fn [](&self) -> Option<&$type> { + self.get_value::<[<$trait_name Key>], _>() + } + + fn [](&mut self, value: $type) -> &mut Self { + self.set_value::<[<$trait_name Key>], _>(value); + self + } + } + } + }; +} +/// Dimension to define a vehicle type property. +pub trait VehicleTypeDimension { /// Gets vehicle's type id. fn get_vehicle_type(&self) -> Option<&String>; /// Sets vehicle's type id. fn set_vehicle_type(&mut self, id: String) -> &mut Self; +} +impl_dimension_property!(VehicleTypeDimension with vehicle_type using String); +/// Dimension to define a vehicle shift index property. +pub trait ShiftIndexDimension { /// Gets vehicle's shift. - fn get_shift_index(&self) -> Option; + fn get_shift_index(&self) -> Option<&usize>; /// Sets vehicle's shift. fn set_shift_index(&mut self, idx: usize) -> &mut Self; +} +impl_dimension_property!(ShiftIndexDimension with shift_index using usize); +/// Dimension to define a vehicle skills property. +pub trait VehicleSkillsDimension { /// Gets vehicle's skills set. fn get_vehicle_skills(&self) -> Option<&HashSet>; /// Sets vehicle's skills set. fn set_vehicle_skills(&mut self, skills: HashSet) -> &mut Self; +} +impl_dimension_property!(VehicleSkillsDimension with vehicle_skills using HashSet); +/// Dimension to define a vehicle tour size property. +pub trait TourSizeDimension { /// Gets vehicle's tour size. - fn get_tour_size(&self) -> Option; + fn get_tour_size(&self) -> Option<&usize>; /// Sets vehicle's tour size. fn set_tour_size(&mut self, tour_size: usize) -> &mut Self; } +impl_dimension_property!(TourSizeDimension with tour_size using usize); -impl VehicleTie for Dimensions { - fn get_vehicle_id(&self) -> Option<&String> { - self.get_value("vehicle_id") - } - - fn set_vehicle_id(&mut self, id: String) -> &mut Self { - self.set_value("vehicle_id", id.clone()); - // NOTE: core internally uses `id` to provide debug output - self.set_value("id", id); - self - } - - fn get_vehicle_type(&self) -> Option<&String> { - self.get_value("vehicle_type") - } - - fn set_vehicle_type(&mut self, id: String) -> &mut Self { - self.set_value("vehicle_type", id); - self - } - - fn get_shift_index(&self) -> Option { - self.get_value("shift_index").cloned() - } - - fn set_shift_index(&mut self, idx: usize) -> &mut Self { - self.set_value("shift_index", idx); - self - } - - fn get_vehicle_skills(&self) -> Option<&HashSet> { - self.get_value("vehicle_skills") - } - - fn set_vehicle_skills(&mut self, skills: HashSet) -> &mut Self { - self.set_value("vehicle_skills", skills); - self - } - - fn get_tour_size(&self) -> Option { - self.get_value("tour_size").cloned() - } - - fn set_tour_size(&mut self, tour_size: usize) -> &mut Self { - self.set_value("tour_size", tour_size); - self - } -} - -/// Specifies job entity. -pub trait JobTie { - /// Gets job id. - fn get_job_id(&self) -> Option<&String>; - /// Sets job id. - fn set_job_id(&mut self, id: String) -> &mut Self; - +/// Dimension to define a job skills property. +pub trait JobSkillsDimension { /// Gets job skills. fn get_job_skills(&self) -> Option<&JobSkills>; /// Sets job skills. - fn set_job_skills(&mut self, skills: Option) -> &mut Self; + fn set_job_skills(&mut self, skills: JobSkills) -> &mut Self; +} +impl_dimension_property!(JobSkillsDimension with job_skills using JobSkills); +/// Dimension to define a job place tags property. +pub trait PlaceTagsDimension { /// Get job place tags. fn get_place_tags(&self) -> Option<&Vec<(usize, String)>>; /// Sets job place tags. - fn set_place_tags(&mut self, tags: Option>) -> &mut Self; + fn set_place_tags(&mut self, tags: Vec<(usize, String)>) -> &mut Self; +} +impl_dimension_property!(PlaceTagsDimension with place_tags using Vec<(usize, String)>); +/// Dimension to define a job order property. +pub trait JobOrderDimension { /// Gets job order. - fn get_job_order(&self) -> Option; + fn get_job_order(&self) -> Option<&i32>; /// Sets job order. - fn set_job_order(&mut self, order: Option) -> &mut Self; + fn set_job_order(&mut self, order: i32) -> &mut Self; +} +impl_dimension_property!(JobOrderDimension with job_order using i32); +/// Dimension to define a job value property. +pub trait JobValueDimension { /// Gets job value. - fn get_job_value(&self) -> Option; + fn get_job_value(&self) -> Option<&f64>; /// Sets job value. - fn set_job_value(&mut self, value: Option) -> &mut Self; + fn set_job_value(&mut self, value: f64) -> &mut Self; +} +impl_dimension_property!(JobValueDimension with job_value using f64); +/// Dimension to define a job group property. +pub trait JobGroupDimension { /// Gets job group. fn get_job_group(&self) -> Option<&String>; /// Sets job group. - fn set_job_group(&mut self, group: Option) -> &mut Self; + fn set_job_group(&mut self, group: String) -> &mut Self; +} +impl_dimension_property!(JobGroupDimension with job_group using String); +/// Dimension to define a job compatibility property. +pub trait JobCompatibilityDimension { /// Gets job compatibility. fn get_job_compatibility(&self) -> Option<&String>; /// Sets job compatibility. - fn set_job_compatibility(&mut self, compatibility: Option) -> &mut Self; + fn set_job_compatibility(&mut self, compatibility: String) -> &mut Self; +} +impl_dimension_property!(JobCompatibilityDimension with job_compatibility using String); +/// Dimension to define a job type property. +pub trait JobTypeDimension { /// Gets job (activity) type. fn get_job_type(&self) -> Option<&String>; /// Sets job (activity) type fn set_job_type(&mut self, job_type: String) -> &mut Self; } +impl_dimension_property!(JobTypeDimension with job_type using String); -impl JobTie for Dimensions { - fn get_job_id(&self) -> Option<&String> { - self.get_value("job_id") - } - - fn set_job_id(&mut self, id: String) -> &mut Self { - self.set_value("job_id", id.clone()); - // NOTE: core internally uses `id` to provide debug output - self.set_value("id", id); - self - } - - fn get_job_skills(&self) -> Option<&JobSkills> { - self.get_value("job_skills") - } - - fn set_job_skills(&mut self, skills: Option) -> &mut Self { - if let Some(skills) = skills { - self.set_value("job_skills", skills); - } else { - self.remove("job_skills"); - } - - self - } - - fn get_place_tags(&self) -> Option<&Vec<(usize, String)>> { - self.get_value("job_tags") - } - - fn set_place_tags(&mut self, tags: Option>) -> &mut Self { - if let Some(tags) = tags { - self.set_value("job_tags", tags); - } else { - self.remove("job_tags"); - } - - self - } - - fn get_job_order(&self) -> Option { - self.get_value("job_order").cloned() - } - - fn set_job_order(&mut self, order: Option) -> &mut Self { - if let Some(order) = order { - self.set_value("job_order", order); - } else { - self.remove("job_order"); - } - - self - } - - fn get_job_value(&self) -> Option { - self.get_value("job_value").cloned() - } - - fn set_job_value(&mut self, value: Option) -> &mut Self { - if let Some(value) = value { - self.set_value("job_value", value); - } else { - self.remove("job_value"); - } - - self - } - - fn get_job_group(&self) -> Option<&String> { - self.get_value("job_group") - } - - fn set_job_group(&mut self, group: Option) -> &mut Self { - if let Some(group) = group { - self.set_value("job_group", group); - } else { - self.remove("job_group"); - } - - self - } - - fn get_job_compatibility(&self) -> Option<&String> { - self.get_value("job_compat") - } - - fn set_job_compatibility(&mut self, compatibility: Option) -> &mut Self { - if let Some(compatibility) = compatibility { - self.set_value("job_compat", compatibility); - } else { - self.remove("job_compat"); - } - - self - } - - fn get_job_type(&self) -> Option<&String> { - self.get_value("job_type") - } - - fn set_job_type(&mut self, job_type: String) -> &mut Self { - self.set_value("job_type", job_type); - self - } -} - -/// Specifies break entity. -pub trait BreakTie { +/// Dimensions to define a break policy property. +pub trait BreakPolicyDimension { /// Gets break policy. - fn get_break_policy(&self) -> Option; + fn get_break_policy(&self) -> Option<&BreakPolicy>; /// Sets break policy. fn set_break_policy(&mut self, policy: BreakPolicy) -> &mut Self; } - -impl BreakTie for Dimensions { - fn get_break_policy(&self) -> Option { - self.get_value("break_policy").cloned() - } - - fn set_break_policy(&mut self, policy: BreakPolicy) -> &mut Self { - self.set_value("break_policy", policy); - self - } -} +impl_dimension_property!(BreakPolicyDimension with break_policy using BreakPolicy); diff --git a/vrp-pragmatic/src/format/mod.rs b/vrp-pragmatic/src/format/mod.rs index 84a82ecbf..94e2cb893 100644 --- a/vrp-pragmatic/src/format/mod.rs +++ b/vrp-pragmatic/src/format/mod.rs @@ -8,7 +8,7 @@ use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use std::sync::Arc; use vrp_core::construction::enablers::ReservedTimesIndex; -use vrp_core::models::common::{Distance, Duration, ValueDimension}; +use vrp_core::models::common::{Distance, Duration}; use vrp_core::models::problem::Job as CoreJob; use vrp_core::models::{Extras as CoreExtras, Problem as CoreProblem}; use vrp_core::prelude::GenericError; diff --git a/vrp-pragmatic/src/format/problem/aspects.rs b/vrp-pragmatic/src/format/problem/aspects.rs index 7e399f21d..be36daea8 100644 --- a/vrp-pragmatic/src/format/problem/aspects.rs +++ b/vrp-pragmatic/src/format/problem/aspects.rs @@ -1,12 +1,10 @@ -use crate::format::{BreakTie, JobTie, VehicleTie}; +use crate::format::*; use hashbrown::HashSet; use std::marker::PhantomData; use vrp_core::construction::features::*; use vrp_core::construction::heuristics::{RouteContext, StateKey}; -use vrp_core::models::common::{ - CapacityDimension, Demand, DemandDimension, DemandType, IdDimension, LoadOps, MultiDimLoad, SingleDimLoad, -}; -use vrp_core::models::problem::{Job, Single, Vehicle}; +use vrp_core::models::common::*; +use vrp_core::models::problem::{Job, Single, Vehicle, VehicleIdDimension}; use vrp_core::models::solution::Route; use vrp_core::models::ViolationCode; @@ -31,7 +29,7 @@ impl BreakAspects for PragmaticBreakAspects { } fn get_policy(&self, candidate: BreakCandidate<'_>) -> Option { - candidate.as_single().and_then(|single| single.dimens.get_break_policy()) + candidate.as_single().and_then(|single| single.dimens.get_break_policy().cloned()) } } @@ -249,7 +247,7 @@ fn is_correct_vehicle(route: &Route, single: &Single) -> bool { let job_shift_idx = single.dimens.get_shift_index(); let vehicle = &route.actor.vehicle; - let vehicle_id = vehicle.dimens.get_id(); + let vehicle_id = vehicle.dimens.get_vehicle_id(); let vehicle_shift_idx = vehicle.dimens.get_shift_index(); job_vehicle_id == vehicle_id && job_shift_idx == vehicle_shift_idx diff --git a/vrp-pragmatic/src/format/problem/clustering_reader.rs b/vrp-pragmatic/src/format/problem/clustering_reader.rs index 7460ce78b..fb75c6c50 100644 --- a/vrp-pragmatic/src/format/problem/clustering_reader.rs +++ b/vrp-pragmatic/src/format/problem/clustering_reader.rs @@ -1,10 +1,10 @@ use super::*; use crate::format::problem::fleet_reader::get_profile_index_map; -use crate::format::JobTie; use hashbrown::HashSet; use std::cmp::Ordering; use vrp_core::construction::clustering::vicinity::*; use vrp_core::models::common::Profile; +use vrp_core::models::problem::JobIdDimension; /// Creates cluster config if it is defined on the api problem. pub(super) fn create_cluster_config(api_problem: &ApiProblem) -> Result, GenericError> { diff --git a/vrp-pragmatic/src/format/problem/fleet_reader.rs b/vrp-pragmatic/src/format/problem/fleet_reader.rs index 932fa100e..b96ed290f 100644 --- a/vrp-pragmatic/src/format/problem/fleet_reader.rs +++ b/vrp-pragmatic/src/format/problem/fleet_reader.rs @@ -3,7 +3,7 @@ mod fleet_reader_test; use super::*; -use crate::format::{UnknownLocationFallback, VehicleTie}; +use crate::format::UnknownLocationFallback; use crate::get_unique_locations; use crate::utils::get_approx_transportation; use crate::Location as ApiLocation; @@ -141,7 +141,7 @@ pub(super) fn read_fleet(api_problem: &ApiProblem, props: &ProblemProperties, co dimens .set_vehicle_type(vehicle.type_id.clone()) .set_shift_index(shift_index) - .set_vehicle_id(vehicle_id.clone()); + .set_vehicle_id(vehicle_id); if let Some(tour_size) = tour_size { dimens.set_tour_size(tour_size); diff --git a/vrp-pragmatic/src/format/problem/goal_reader.rs b/vrp-pragmatic/src/format/problem/goal_reader.rs index c11d28670..e715e3352 100644 --- a/vrp-pragmatic/src/format/problem/goal_reader.rs +++ b/vrp-pragmatic/src/format/problem/goal_reader.rs @@ -1,6 +1,5 @@ use super::*; use crate::format::problem::aspects::*; -use crate::format::{JobTie, VehicleTie}; use hashbrown::HashSet; use vrp_core::construction::clustering::vicinity::ClusterDimension; use vrp_core::construction::enablers::{FeatureCombinator, RouteIntervals, ScheduleKeys}; @@ -82,7 +81,7 @@ pub(super) fn create_goal_context( features.push(create_activity_limit_feature( "activity_limit", TOUR_SIZE_CONSTRAINT_CODE, - Arc::new(|actor| actor.vehicle.dimens.get_tour_size()), + Arc::new(|actor| actor.vehicle.dimens.get_tour_size().copied()), )?); } @@ -136,7 +135,7 @@ fn get_objective_features( JobReadValueFn::Left(Arc::new({ let break_value = *breaks; move |job| { - job.dimens().get_job_value().unwrap_or_else(|| { + job.dimens().get_job_value().copied().unwrap_or_else(|| { job.dimens() .get_job_type() .zip(break_value) @@ -149,7 +148,7 @@ fn get_objective_features( Arc::new(|job, value| match job { CoreJob::Single(single) => { let mut dimens = single.dimens.clone(); - dimens.set_job_value(Some(value)); + dimens.set_job_value(value); CoreJob::Single(Arc::new(Single { places: single.places.clone(), dimens })) } @@ -473,7 +472,7 @@ fn get_recharge_feature( }); let distance_limit_fn: RechargeDistanceLimitFn = Arc::new(move |actor: &Actor| { - actor.vehicle.dimens.get_vehicle_type().zip(actor.vehicle.dimens.get_shift_index()).and_then( + actor.vehicle.dimens.get_vehicle_type().zip(actor.vehicle.dimens.get_shift_index().copied()).and_then( |(type_id, shift_idx)| distance_limit_index.get(type_id).and_then(|idx| idx.get(&shift_idx).copied()), ) }); @@ -583,7 +582,7 @@ fn get_tour_order_fn() -> TourOrderFn { .as_ref() .map(|single| &single.dimens) .map(|dimens| { - dimens.get_job_order().map(|order| OrderResult::Value(order as f64)).unwrap_or_else(|| { + dimens.get_job_order().copied().map(|order| OrderResult::Value(order as f64)).unwrap_or_else(|| { dimens.get_job_type().map_or(OrderResult::Default, |v| { match v.as_str() { "break" | "reload" => OrderResult::Ignored, diff --git a/vrp-pragmatic/src/format/problem/job_reader.rs b/vrp-pragmatic/src/format/problem/job_reader.rs index 4d58d9c62..c4d1f93ef 100644 --- a/vrp-pragmatic/src/format/problem/job_reader.rs +++ b/vrp-pragmatic/src/format/problem/job_reader.rs @@ -1,14 +1,16 @@ use crate::format::coord_index::CoordIndex; use crate::format::problem::JobSkills as ApiJobSkills; use crate::format::problem::*; -use crate::format::{BreakTie, JobIndex, JobTie, Location, VehicleTie}; +use crate::format::{JobIndex, Location}; use crate::utils::VariableJobPermutation; use hashbrown::HashMap; use std::sync::Arc; use vrp_core::construction::features::BreakPolicy; use vrp_core::construction::features::JobSkills as FeatureJobSkills; use vrp_core::models::common::*; -use vrp_core::models::problem::{Actor, Fleet, Job, Jobs, Multi, Place, Single, TransportCost}; +use vrp_core::models::problem::{ + Actor, Fleet, Job, JobIdDimension, Jobs, Multi, Place, Single, TransportCost, VehicleIdDimension, +}; use vrp_core::models::{Lock, LockDetail, LockOrder, LockPosition}; // TODO configure sample size @@ -126,7 +128,7 @@ fn read_required_jobs( .map(|p| (Some(p.location.clone()), p.duration, parse_times(&p.times), p.tag.clone())) .collect(); - get_single_with_extras(places, demand, &task.order, activity_type, has_multi_dimens, coord_index) + get_single_with_dimens(places, demand, &task.order, activity_type, has_multi_dimens, coord_index) }; api_problem.plan.jobs.iter().for_each(|job| { @@ -340,10 +342,10 @@ fn get_conditional_job( let mut single = get_single(places, coord_index); single .dimens - .set_job_id(job_id.to_string()) + .set_job_id(job_id) .set_job_type(job_type.to_string()) .set_shift_index(shift_index) - .set_vehicle_id(vehicle_id); + .set_vehicle_id(vehicle_id.as_str()); single } @@ -373,12 +375,12 @@ fn get_single(places: Vec, coord_index: &CoordIndex) -> Single { let mut dimens = Dimensions::default(); - dimens.set_place_tags(Some(tags)); + dimens.set_place_tags(tags); Single { places, dimens } } -fn get_single_with_extras( +fn get_single_with_dimens( places: Vec, demand: Demand, order: &Option, @@ -390,27 +392,45 @@ fn get_single_with_extras( let dimens = &mut single.dimens; if has_multi_dimens { - dimens.set_demand(demand); + dimens.set_demand(demand) } else { dimens.set_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])), - }); + }) + } + .set_job_type(activity_type.to_string()); + + if let Some(order) = order { + dimens.set_job_order(*order); } - dimens.set_job_type(activity_type.to_string()).set_job_order(*order); single } +fn fill_dimens(job: &ApiJob, dimens: &mut Dimensions) { + dimens.set_job_id(job.id.as_str()); + + if let Some(value) = job.value { + dimens.set_job_value(value); + } + + if let Some(group) = job.group.clone() { + dimens.set_job_group(group); + } + + if let Some(compat) = job.compatibility.clone() { + dimens.set_job_compatibility(compat); + } + + if let Some(skills) = get_skills(&job.skills) { + dimens.set_job_skills(skills); + } +} + fn get_single_job(job: &ApiJob, single: Single) -> Job { let mut single = single; - single - .dimens - .set_job_id(job.id.clone()) - .set_job_value(job.value) - .set_job_group(job.group.clone()) - .set_job_compatibility(job.compatibility.clone()) - .set_job_skills(get_skills(&job.skills)); + fill_dimens(job, &mut single.dimens); Job::Single(Arc::new(single)) } @@ -422,12 +442,7 @@ fn get_multi_job( random: &Arc, ) -> Job { let mut dimens: Dimensions = Default::default(); - dimens - .set_job_id(job.id.clone()) - .set_job_value(job.value) - .set_job_group(job.group.clone()) - .set_job_compatibility(job.compatibility.clone()) - .set_job_skills(get_skills(&job.skills)); + fill_dimens(job, &mut dimens); let singles = singles.into_iter().map(Arc::new).collect::>(); @@ -453,7 +468,7 @@ fn get_multi_job( fn create_condition(vehicle_id: String, shift_index: usize) -> Arc bool + Sync + Send> { Arc::new(move |actor: &Actor| { *actor.vehicle.dimens.get_vehicle_id().unwrap() == vehicle_id - && actor.vehicle.dimens.get_shift_index().unwrap() == shift_index + && actor.vehicle.dimens.get_shift_index().copied().unwrap() == shift_index }) } diff --git a/vrp-pragmatic/src/format/problem/problem_reader.rs b/vrp-pragmatic/src/format/problem/problem_reader.rs index f6cdda918..6bba1a891 100644 --- a/vrp-pragmatic/src/format/problem/problem_reader.rs +++ b/vrp-pragmatic/src/format/problem/problem_reader.rs @@ -3,7 +3,6 @@ use crate::format::problem::clustering_reader::create_cluster_config; use crate::format::problem::fleet_reader::*; use crate::format::problem::goal_reader::create_goal_context; use crate::format::problem::job_reader::{read_jobs_with_extra_locks, read_locks}; -use crate::format::VehicleTie; use crate::format::{FormatError, JobIndex}; use crate::validation::ValidationContext; use crate::{parse_time, CoordIndex}; @@ -88,7 +87,7 @@ fn read_reserved_times_index(api_problem: &ApiProblem, fleet: &CoreFleet) -> Res .iter() .filter_map(|actor| { let type_id = actor.vehicle.dimens.get_vehicle_type().unwrap().clone(); - let shift_idx = actor.vehicle.dimens.get_shift_index().unwrap(); + let shift_idx = actor.vehicle.dimens.get_shift_index().copied().unwrap(); let times = breaks_map .get(&(type_id, shift_idx)) diff --git a/vrp-pragmatic/src/format/solution/activity_matcher.rs b/vrp-pragmatic/src/format/solution/activity_matcher.rs index 43aa3fe02..082d4ba2b 100644 --- a/vrp-pragmatic/src/format/solution/activity_matcher.rs +++ b/vrp-pragmatic/src/format/solution/activity_matcher.rs @@ -2,14 +2,14 @@ use crate::format::problem::VehicleBreak; use crate::format::problem::{Problem as FormatProblem, VehicleRequiredBreakTime}; use crate::format::solution::{Activity as FormatActivity, Schedule as FormatSchedule, Tour as FormatTour}; use crate::format::solution::{PointStop, TransitStop}; -use crate::format::{CoordIndex, JobIndex, JobTie}; +use crate::format::{CoordIndex, JobIndex, PlaceTagsDimension}; use crate::parse_time; use hashbrown::HashSet; use std::cmp::Ordering; use std::iter::once; use std::sync::Arc; use vrp_core::models::common::*; -use vrp_core::models::problem::{Job, Single}; +use vrp_core::models::problem::{Job, JobIdDimension, Single}; use vrp_core::models::solution::{Activity, Place}; use vrp_core::prelude::*; diff --git a/vrp-pragmatic/src/format/solution/initial_reader.rs b/vrp-pragmatic/src/format/solution/initial_reader.rs index 56e501a89..a34a458f6 100644 --- a/vrp-pragmatic/src/format/solution/initial_reader.rs +++ b/vrp-pragmatic/src/format/solution/initial_reader.rs @@ -7,14 +7,14 @@ use crate::format::solution::Activity as FormatActivity; use crate::format::solution::Stop as FormatStop; use crate::format::solution::Tour as FormatTour; use crate::format::solution::{deserialize_solution, map_reason_code}; -use crate::format::{get_indices, CoordIndex, JobIndex, JobTie, VehicleTie}; +use crate::format::{get_indices, CoordIndex, JobIndex, ShiftIndexDimension, VehicleTypeDimension}; use crate::parse_time; use hashbrown::{HashMap, HashSet}; use std::io::{BufReader, Read}; use std::sync::Arc; use vrp_core::construction::heuristics::UnassignmentInfo; use vrp_core::models::common::*; -use vrp_core::models::problem::{Actor, Job}; +use vrp_core::models::problem::{Actor, Job, JobIdDimension, VehicleIdDimension}; use vrp_core::models::solution::Tour as CoreTour; use vrp_core::models::solution::{Activity, Registry, Route}; use vrp_core::prelude::*; @@ -143,7 +143,7 @@ fn get_actor_key(actor: &Actor) -> ActorKey { let vehicle_id = dimens.get_vehicle_id().cloned().expect("cannot get vehicle id!"); let type_id = dimens.get_vehicle_type().cloned().expect("cannot get type id!"); - let shift_index = dimens.get_shift_index().expect("cannot get shift index!"); + let shift_index = dimens.get_shift_index().copied().expect("cannot get shift index!"); (vehicle_id, type_id, shift_index) } diff --git a/vrp-pragmatic/src/format/solution/solution_writer.rs b/vrp-pragmatic/src/format/solution/solution_writer.rs index a9f2c2846..82b693446 100644 --- a/vrp-pragmatic/src/format/solution/solution_writer.rs +++ b/vrp-pragmatic/src/format/solution/solution_writer.rs @@ -6,11 +6,10 @@ use crate::format::solution::activity_matcher::get_job_tag; use crate::format::solution::model::Timing; use crate::format::solution::*; use crate::format::CoordIndex; -use crate::format::{JobTie, VehicleTie}; use vrp_core::construction::enablers::{get_route_intervals, ReservedTimesIndex}; use vrp_core::construction::heuristics::UnassignmentInfo; use vrp_core::models::common::*; -use vrp_core::models::problem::{Multi, TravelTime}; +use vrp_core::models::problem::{JobIdDimension, Multi, TravelTime, VehicleIdDimension}; use vrp_core::models::solution::{Activity, Route}; use vrp_core::rosomaxa::evolution::TelemetryMetrics; use vrp_core::solver::processing::{ReservedTimeDimension, VicinityDimension}; @@ -76,7 +75,7 @@ fn create_tour( let mut tour = Tour { vehicle_id: vehicle.dimens.get_vehicle_id().unwrap().clone(), type_id: vehicle.dimens.get_vehicle_type().unwrap().clone(), - shift_index: vehicle.dimens.get_shift_index().unwrap(), + shift_index: vehicle.dimens.get_shift_index().copied().unwrap(), stops: vec![], statistic: Statistic::default(), }; @@ -348,7 +347,7 @@ fn create_unassigned(solution: &DomainSolution) -> Option> { .map(|(actor, _)| { let dimens = &actor.vehicle.dimens; let vehicle_id = dimens.get_vehicle_id().cloned().unwrap(); - let shift_index = dimens.get_shift_index().unwrap(); + let shift_index = dimens.get_shift_index().copied().unwrap(); (vehicle_id, shift_index) }) .collect::>(); @@ -389,7 +388,7 @@ fn create_violations(solution: &DomainSolution) -> Option> { .filter(|(job, _)| job.dimens().get_job_type().map_or(false, |t| t == "break")) .map(|(job, _)| Violation::Break { vehicle_id: job.dimens().get_vehicle_id().expect("vehicle id").clone(), - shift_index: job.dimens().get_shift_index().expect("shift index"), + shift_index: job.dimens().get_shift_index().copied().expect("shift index"), }) .collect::>(); diff --git a/vrp-pragmatic/tests/helpers/core.rs b/vrp-pragmatic/tests/helpers/core.rs index 40c05233c..3ecf86e2e 100644 --- a/vrp-pragmatic/tests/helpers/core.rs +++ b/vrp-pragmatic/tests/helpers/core.rs @@ -1,4 +1,4 @@ -use crate::format::{JobTie, VehicleTie}; +use crate::format::{JobTypeDimension, ShiftIndexDimension, VehicleTypeDimension}; use std::sync::Arc; use vrp_core::construction::enablers::create_typed_actor_groups; use vrp_core::models::common::*; @@ -23,7 +23,7 @@ pub fn test_vehicle(id: &str) -> Vehicle { fn test_vehicle_impl(id: &str, has_open_end: bool) -> Vehicle { let mut dimens = Dimensions::default(); - dimens.set_vehicle_id(id.to_string()).set_vehicle_type(id.to_owned()).set_shift_index(0); + dimens.set_vehicle_id(id).set_vehicle_type(id.to_owned()).set_shift_index(0); Vehicle { profile: Profile::default(), @@ -75,7 +75,7 @@ pub fn create_activity_with_job_at_location(job: Arc, location: Location pub fn create_single(id: &str) -> Arc { let mut single = create_single_with_location(Some(DEFAULT_JOB_LOCATION)); - single.dimens.set_job_id(id.to_string()).set_job_type("delivery".to_string()); + single.dimens.set_job_id(id).set_job_type("delivery".to_string()); Arc::new(single) } diff --git a/vrp-pragmatic/tests/unit/format/problem/reader_test.rs b/vrp-pragmatic/tests/unit/format/problem/reader_test.rs index 62836a986..e7fd03ede 100644 --- a/vrp-pragmatic/tests/unit/format/problem/reader_test.rs +++ b/vrp-pragmatic/tests/unit/format/problem/reader_test.rs @@ -1,11 +1,10 @@ use crate::format::problem::*; -use crate::format::{JobTie, VehicleTie}; use crate::helpers::*; use hashbrown::HashSet; use std::iter::FromIterator; use std::sync::Arc; use vrp_core::models::common::*; -use vrp_core::models::problem::{Jobs, Multi, Place, Single}; +use vrp_core::models::problem::{JobIdDimension, Jobs, Multi, Place, Single, VehicleIdDimension}; fn get_job(index: usize, jobs: &Jobs) -> vrp_core::models::problem::Job { jobs.all().collect::>().get(index).unwrap().clone() @@ -192,7 +191,7 @@ fn can_read_complex_problem() { assert_eq!(place.duration, 100.); assert_eq!(place.location.unwrap(), 0); assert_demand( - job.dimens.get_demand().unwrap(), + job.dimens.get_demand().expect("cannot get demand"), &Demand { pickup: (MultiDimLoad::default(), MultiDimLoad::default()), delivery: (MultiDimLoad::new(vec![0, 1]), MultiDimLoad::default()), diff --git a/vrp-scientific/src/common/aspects.rs b/vrp-scientific/src/common/aspects.rs index ce4645e56..6e3dbc22f 100644 --- a/vrp-scientific/src/common/aspects.rs +++ b/vrp-scientific/src/common/aspects.rs @@ -1,5 +1,5 @@ use vrp_core::construction::features::{CapacityAspects, CapacityKeys}; -use vrp_core::models::common::{Demand, SingleDimLoad, ValueDimension}; +use vrp_core::models::common::{CapacityDimension, Demand, DemandDimension, SingleDimLoad}; use vrp_core::models::problem::{Single, Vehicle}; use vrp_core::models::ViolationCode; @@ -18,15 +18,15 @@ impl ScientificCapacityAspects { impl CapacityAspects for ScientificCapacityAspects { fn get_capacity<'a>(&self, vehicle: &'a Vehicle) -> Option<&'a SingleDimLoad> { - vehicle.dimens.get_value("capacity") + vehicle.dimens.get_capacity() } fn get_demand<'a>(&self, single: &'a Single) -> Option<&'a Demand> { - single.dimens.get_value("demand") + single.dimens.get_demand() } fn set_demand(&self, single: &mut Single, demand: Demand) { - single.dimens.set_value("demand", demand); + single.dimens.set_demand(demand); } fn get_state_keys(&self) -> &CapacityKeys { diff --git a/vrp-scientific/src/common/initial_reader.rs b/vrp-scientific/src/common/initial_reader.rs index 2a56551c6..f38a34e6c 100644 --- a/vrp-scientific/src/common/initial_reader.rs +++ b/vrp-scientific/src/common/initial_reader.rs @@ -38,7 +38,7 @@ pub fn read_init_solution( let id_map = problem.jobs.all().fold(HashMap::>::new(), |mut acc, job| { let single = job.to_single().clone(); - acc.insert(single.dimens.get_id().unwrap().to_string(), single); + acc.insert(single.dimens.get_job_id().unwrap().to_string(), single); acc }); diff --git a/vrp-scientific/src/common/routing.rs b/vrp-scientific/src/common/routing.rs index fb10651bd..948ca2390 100644 --- a/vrp-scientific/src/common/routing.rs +++ b/vrp-scientific/src/common/routing.rs @@ -3,7 +3,7 @@ mod routing_test; use std::sync::Arc; -use vrp_core::models::common::{Location, ValueDimension}; +use vrp_core::models::common::Location; use vrp_core::models::problem::{create_matrix_transport_cost, MatrixData, TransportCost}; use vrp_core::models::Extras; use vrp_core::prelude::GenericError; diff --git a/vrp-scientific/src/common/text_reader.rs b/vrp-scientific/src/common/text_reader.rs index 40364a4c3..f7c8917d0 100644 --- a/vrp-scientific/src/common/text_reader.rs +++ b/vrp-scientific/src/common/text_reader.rs @@ -59,12 +59,14 @@ pub(crate) fn create_fleet_with_distance_costs( per_waiting_time: 0.0, per_service_time: 0.0, }, - dimens: create_dimens_with_id("driver", &0.to_string()), + dimens: Default::default(), details: Default::default(), })], (0..number) .map(|i| { - let mut dimens = create_dimens_with_id("v", &i.to_string()); + 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)); Arc::new(Vehicle { profile: Profile::default(), @@ -93,9 +95,13 @@ pub(crate) fn create_fleet_with_distance_costs( ) } -pub(crate) fn create_dimens_with_id(prefix: &str, id: &str) -> Dimensions { +pub(crate) fn create_dimens_with_id( + prefix: &str, + id: &str, + id_setter_fn: impl Fn(&str, &mut Dimensions), +) -> Dimensions { let mut dimens = Dimensions::default(); - dimens.set_id([prefix.to_string(), id.to_string()].concat().as_str()); + id_setter_fn([prefix.to_string(), id.to_string()].concat().as_str(), &mut dimens); dimens } diff --git a/vrp-scientific/src/common/text_writer.rs b/vrp-scientific/src/common/text_writer.rs index ed4f04b83..534b051f0 100644 --- a/vrp-scientific/src/common/text_writer.rs +++ b/vrp-scientific/src/common/text_writer.rs @@ -3,7 +3,7 @@ mod text_writer_test; use std::io::{BufWriter, Error, ErrorKind, Write}; -use vrp_core::models::common::IdDimension; +use vrp_core::models::problem::JobIdDimension; use vrp_core::models::Solution; pub(crate) fn write_text_solution(solution: &Solution, writer: &mut BufWriter) -> Result<(), Error> { @@ -19,7 +19,7 @@ pub(crate) fn write_text_solution(solution: &Solution, writer: &mut Bu .all_activities() .filter(|a| a.job.is_some()) .map(|a| a.retrieve_job().unwrap()) - .map(|job| job.dimens().get_id().unwrap().clone()) + .map(|job| job.dimens().get_job_id().unwrap().clone()) .collect::>() .join(" "); writer.write_all(format!("Route {i}: {customers}\n").as_bytes()).unwrap(); diff --git a/vrp-scientific/src/lilim/reader.rs b/vrp-scientific/src/lilim/reader.rs index 782d6538d..aff4b3a8c 100644 --- a/vrp-scientific/src/lilim/reader.rs +++ b/vrp-scientific/src/lilim/reader.rs @@ -123,7 +123,9 @@ impl LilimReader { jobs.push(Job::Multi(Multi::new_shared( vec![self.create_single_job(pickup), self.create_single_job(delivery)], - create_dimens_with_id("mlt", &index.to_string()), + create_dimens_with_id("mlt", &index.to_string(), |id, dimens| { + dimens.set_job_id(id); + }), ))); }); @@ -131,7 +133,9 @@ impl LilimReader { } fn create_single_job(&mut self, customer: &JobLine) -> Arc { - let mut dimens = create_dimens_with_id("c", &customer.id.to_string()); + 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 { Demand:: { pickup: (SingleDimLoad::default(), SingleDimLoad::new(customer.demand)), diff --git a/vrp-scientific/src/solomon/reader.rs b/vrp-scientific/src/solomon/reader.rs index 36b6e29d8..3bb844c5c 100644 --- a/vrp-scientific/src/solomon/reader.rs +++ b/vrp-scientific/src/solomon/reader.rs @@ -97,8 +97,8 @@ impl SolomonReader { loop { match self.read_customer() { Ok(customer) => { - let mut dimens = create_dimens_with_id("", &customer.id.to_string()); - dimens.set_demand(Demand:: { + let mut dimens = Dimensions::default(); + dimens.set_job_id(customer.id.to_string().as_str()).set_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 87c5edb20..ee6901d0b 100644 --- a/vrp-scientific/src/tsplib/reader.rs +++ b/vrp-scientific/src/tsplib/reader.rs @@ -209,8 +209,8 @@ impl TsplibReader { } fn create_job(&mut self, id: &str, location: (i32, i32), demand: i32) -> Job { - let mut dimens = create_dimens_with_id("", id); - dimens.set_demand(Demand:: { + let mut dimens = Dimensions::default(); + dimens.set_job_id(id).set_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 09e95d4d8..f4c6e5332 100644 --- a/vrp-scientific/tests/helpers/analysis.rs +++ b/vrp-scientific/tests/helpers/analysis.rs @@ -1,6 +1,6 @@ use vrp_core::construction::heuristics::InsertionContext; use vrp_core::models::common::*; -use vrp_core::models::problem::Job; +use vrp_core::models::problem::{Job, JobIdDimension}; use vrp_core::models::Problem; pub fn get_customer_id(job: &Job) -> String { @@ -8,7 +8,7 @@ pub fn get_customer_id(job: &Job) -> String { } pub fn get_job_id(job: &Job) -> &String { - job.dimens().get_id().unwrap() + job.dimens().get_job_id().unwrap() } pub fn get_customer_ids_from_routes_sorted(insertion_ctx: &InsertionContext) -> Vec> { diff --git a/vrp-scientific/tests/integration/known_problems_test.rs b/vrp-scientific/tests/integration/known_problems_test.rs index ba594f7b2..0e3878ec2 100644 --- a/vrp-scientific/tests/integration/known_problems_test.rs +++ b/vrp-scientific/tests/integration/known_problems_test.rs @@ -2,7 +2,7 @@ use crate::helpers::*; use std::sync::Arc; use vrp_core::construction::heuristics::InsertionContext; use vrp_core::construction::heuristics::*; -use vrp_core::models::common::IdDimension; +use vrp_core::models::problem::JobIdDimension; use vrp_core::models::Problem; use vrp_core::rosomaxa::evolution::TelemetryMode; use vrp_core::solver::create_elitism_population; @@ -15,7 +15,10 @@ struct StableJobSelector {} impl JobSelector for StableJobSelector { fn prepare(&self, insertion_ctx: &mut InsertionContext) { - insertion_ctx.solution.required.sort_by(|a, b| a.dimens().get_id().unwrap().cmp(b.dimens().get_id().unwrap())); + insertion_ctx + .solution + .required + .sort_by(|a, b| a.dimens().get_job_id().unwrap().cmp(b.dimens().get_job_id().unwrap())); } }