Skip to content

Commit

Permalink
Refine custom location feature implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
reinterpretcat committed Oct 4, 2023
1 parent 1f23987 commit 3089312
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 35 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ All notable changes to this project will be documented in this file.
### Added

* original job place index in activity place to simplify activity-job place matching
* `experimental`: a new type of location in pragmatic format to model zero distance/duration from it to any other location
* `experimental`: a new type of location in pragmatic format to model zero distance/duration from it to any other location.
This could be useful to model optional vehicle start location.

### Changed

Expand Down
2 changes: 1 addition & 1 deletion docs/src/concepts/pragmatic/routing/format.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ If you have already your routing matrix, you can use location indices instead of
## Experimental

Additionally, you can use a custom type of location with `type`=`unknown` to model a zero distance/duration to
any other location.
any other location. This could be useful to model unknown location for vehicle start.
11 changes: 11 additions & 0 deletions vrp-pragmatic/src/checker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,17 @@ impl CheckerContext {
let matrix =
matrices.get(profile.index).ok_or_else(|| format!("cannot find matrix with index {}", profile.index))?;

let max_matrix_idx = self.coord_index.max_matrix_index().unwrap_or(0);
let max_total_idx = max_matrix_idx + self.coord_index.custom_locations_len();

let is_special_from_idx = from_idx > max_matrix_idx && from_idx <= max_total_idx;
let is_special_to_idx = to_idx > max_matrix_idx && to_idx <= max_total_idx;

if is_special_from_idx || is_special_to_idx {
// TODO handle other types of custom locations
return Ok((0, 0));
}

let matrix_size = get_matrix_size(matrices.as_slice());
let matrix_idx = from_idx * matrix_size + to_idx;

Expand Down
8 changes: 3 additions & 5 deletions vrp-pragmatic/src/construction/enablers/location_fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub struct UnknownLocationFallback {
}

impl UnknownLocationFallback {
/// Creates a new instance of [`CoordIndex`];
/// Creates a new instance of [`UnknownLocationFallback`];
pub fn new(coord_index: Arc<CoordIndex>) -> Self {
Self { coord_index }
}
Expand All @@ -18,10 +18,8 @@ impl UnknownLocationFallback {
let (from, to) = (self.coord_index.get_by_idx(from), self.coord_index.get_by_idx(to));

match (from, to) {
(
Some(ApiLocation::Custom { r#type: CustomLocationType::Unknown }),
Some(ApiLocation::Custom { r#type: CustomLocationType::Unknown }),
) => Duration::default(),
(Some(ApiLocation::Custom { r#type: CustomLocationType::Unknown }), _)
| (_, Some(ApiLocation::Custom { r#type: CustomLocationType::Unknown })) => Duration::default(),
_ => panic!("fallback is only for locations of custom unknown type"),
}
}
Expand Down
36 changes: 29 additions & 7 deletions vrp-pragmatic/src/format/coord_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@
use crate::format::problem::{Problem, VehicleBreak};
use crate::format::{CustomLocationType, Location};
use hashbrown::HashMap;
use hashbrown::{HashMap, HashSet};
use std::cmp::Ordering::Less;
use std::hash::{Hash, Hasher};

/// A helper struct which keeps track of coordinate mapping.
pub struct CoordIndex {
direct_index: HashMap<Location, usize>,
reverse_index: HashMap<usize, Location>,
custom_locations: HashSet<Location>,
flags: u8,
}

impl CoordIndex {
/// Creates a new instance of `CoordIndex`.
pub fn new(problem: &Problem) -> Self {
let mut index = Self { direct_index: Default::default(), reverse_index: Default::default(), flags: 0 };
let mut index = Self {
direct_index: Default::default(),
reverse_index: Default::default(),
custom_locations: Default::default(),
flags: 0,
};

// process plan
problem.plan.jobs.iter().for_each(|job| {
Expand Down Expand Up @@ -66,11 +72,20 @@ impl CoordIndex {
});
});

// NOTE promote custom locations to the index to use usize outside
index.custom_locations.iter().for_each(|location| {
debug_assert!(matches!(location, Location::Custom { .. }));

let value = index.direct_index.len();
index.direct_index.insert(location.clone(), value);
index.reverse_index.insert(value, location.clone());
});

index
}

/// Adds location to indices.
pub fn add(&mut self, location: &Location) {
pub(crate) fn add(&mut self, location: &Location) {
if self.direct_index.get(location).is_none() {
let value = match location {
Location::Coordinate { lat: _, lng: _ } => {
Expand All @@ -83,7 +98,9 @@ impl CoordIndex {
}
Location::Custom { r#type: CustomLocationType::Unknown } => {
self.flags |= 0b0100;
self.direct_index.len()
// NOTE do not add custom location in the index yet
self.custom_locations.insert(location.clone());
return;
}
};

Expand All @@ -109,9 +126,14 @@ impl CoordIndex {
sorted_pairs.iter().map(|pair| pair.1.clone()).collect()
}

/// Max location index.
pub fn max_index(&self) -> Option<usize> {
self.reverse_index.keys().max().cloned()
/// Max location index in matrix.
pub(crate) fn max_matrix_index(&self) -> Option<usize> {
self.reverse_index.keys().max().copied().map(|max| max - self.custom_locations_len())
}

/// Returns size of custom locations index.
pub(crate) fn custom_locations_len(&self) -> usize {
self.custom_locations.len()
}

/// Returns true if problem has coordinates.
Expand Down
10 changes: 5 additions & 5 deletions vrp-pragmatic/src/format/problem/fleet_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use super::*;
use crate::construction::enablers::{create_typed_actor_groups, UnknownLocationFallback, VehicleTie};
use crate::get_unique_locations;
use crate::utils::get_approx_transportation;
use crate::Location as ApiLocation;
use hashbrown::HashSet;
use std::cmp::Ordering;
use vrp_core::models::common::*;
Expand Down Expand Up @@ -33,10 +34,6 @@ pub(super) fn create_transport_costs(
return Err("when timestamp is set, all matrices should have profile set".into());
}

if coord_index.has_unknown() && coord_index.has_indices() {
return Err("mixing custom unknown locations with location indices is not yet supported".into());
}

let matrix_profiles = get_profile_index_map(api_problem);
if matrix_profiles.len() > matrices.len() {
return Err(format!(
Expand Down Expand Up @@ -197,7 +194,10 @@ pub fn create_approx_matrices(problem: &ApiProblem) -> Vec<Matrix> {
.collect::<HashSet<u64>>();
let speeds = speeds.into_iter().map(f64::from_bits).collect::<Vec<_>>();

let locations = get_unique_locations(problem);
let locations = get_unique_locations(problem)
.into_iter()
.filter(|location| !matches!(location, ApiLocation::Custom { .. }))
.collect::<Vec<_>>();
let approx_data = get_approx_transportation(&locations, speeds.as_slice());

problem
Expand Down
2 changes: 1 addition & 1 deletion vrp-pragmatic/src/validation/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ fn check_e1503_no_matrix_when_indices_used(
fn check_e1504_index_size_mismatch(ctx: &ValidationContext) -> Result<(), FormatError> {
let (max_index, matrix_size, is_correct_index) = ctx
.coord_index
.max_index()
.max_matrix_index()
.into_iter()
.zip(
ctx.matrices
Expand Down
67 changes: 67 additions & 0 deletions vrp-pragmatic/tests/features/format/location_custom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use crate::format::problem::*;
use crate::format::{CustomLocationType, Location};
use crate::format_time;
use crate::helpers::*;

#[test]
fn can_use_unknown_location() {
let problem = Problem {
plan: Plan {
jobs: vec![
create_delivery_job_with_order("job1", (5., 0.), 1),
create_delivery_job_with_order("job2", (10., 0.), 2),
],
..create_empty_plan()
},
fleet: Fleet {
vehicles: vec![VehicleType {
shifts: vec![VehicleShift {
start: ShiftStart {
earliest: format_time(0.),
latest: None,
location: Location::Custom { r#type: CustomLocationType::Unknown },
},
..create_default_open_vehicle_shift()
}],
..create_default_vehicle_type()
}],
..create_default_fleet()
},
..create_empty_problem()
};
let matrix = Matrix {
profile: Some("car".to_string()),
timestamp: None,
travel_times: vec![0, 5, 5, 0],
distances: vec![0, 5, 5, 0],
error_codes: None,
};

let solution = solve_with_metaheuristic(problem, Some(vec![matrix]));

assert_eq!(
solution,
SolutionBuilder::default()
.tour(
TourBuilder::default()
.stops(vec![
StopBuilder::default().custom_unknown().schedule_stamp(0., 0.).load(vec![2]).build_departure(),
StopBuilder::default()
.coordinate((5., 0.))
.schedule_stamp(0., 1.)
.load(vec![1])
.distance(0)
.build_single("job1", "delivery"),
StopBuilder::default()
.coordinate((10., 0.))
.schedule_stamp(6., 7.)
.load(vec![0])
.distance(5)
.build_single("job2", "delivery"),
])
.statistic(StatisticBuilder::default().driving(5).serving(2).build())
.build()
)
.build()
);
}
20 changes: 6 additions & 14 deletions vrp-pragmatic/tests/features/format/location_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use crate::format::Location;
use crate::format_time;
use crate::helpers::*;

fn create_test_problem() -> Problem {
Problem {
#[test]
fn can_use_location_index() {
let problem = Problem {
plan: Plan {
jobs: vec![create_delivery_job_with_index("job1", 0), create_delivery_job_with_index("job2", 1)],
..create_empty_plan()
Expand All @@ -24,23 +25,14 @@ fn create_test_problem() -> Problem {
..create_default_fleet()
},
..create_empty_problem()
}
}

fn create_test_matrix() -> Matrix {
Matrix {
};
let matrix = Matrix {
profile: Some("car".to_string()),
timestamp: None,
travel_times: vec![0, 3, 3, 1, 0, 3, 3, 2, 0],
distances: vec![0, 3, 3, 1, 0, 3, 3, 2, 0],
error_codes: None,
}
}

#[test]
fn can_use_location_index() {
let problem = create_test_problem();
let matrix = create_test_matrix();
};

let solution = solve_with_metaheuristic(problem, Some(vec![matrix]));

Expand Down
1 change: 1 addition & 0 deletions vrp-pragmatic/tests/features/format/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod location_custom;
mod location_index;
10 changes: 9 additions & 1 deletion vrp-pragmatic/tests/helpers/solution.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Provides helper logic for solution domain, sometimes assuming some defaults used from problem domain helpers.
use crate::format::solution::*;
use crate::format::Location;
use crate::format::{CustomLocationType, Location};
use crate::format_time;
use crate::helpers::ToLocation;
use hashbrown::HashMap;
Expand Down Expand Up @@ -54,6 +54,14 @@ impl StopBuilder {
self
}

pub fn custom_unknown(mut self) -> Self {
let mut stop = self.stop.to_point();
stop.location = Location::Custom { r#type: CustomLocationType::Unknown };
self.stop = Stop::Point(stop);

self
}

pub fn load(mut self, load: Vec<i32>) -> Self {
*self.stop.load_mut() = load;

Expand Down

0 comments on commit 3089312

Please sign in to comment.