vrp_pragmatic/format/
mod.rs

1//! This module defines logic to serialize/deserialize problem and routing matrix in pragmatic
2//! format from json input and create and write pragmatic solution.
3//!
4
5extern crate serde_json;
6
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::Arc;
10use vrp_core::construction::enablers::ReservedTimesIndex;
11use vrp_core::models::common::{Distance, Duration};
12use vrp_core::models::problem::{Job as CoreJob, Single, VehicleIdDimension};
13use vrp_core::models::solution::Route;
14use vrp_core::models::{Extras as CoreExtras, Problem as CoreProblem, ViolationCode};
15use vrp_core::prelude::{Float, GenericError};
16
17mod coord_index;
18pub use self::coord_index::CoordIndex;
19
20mod dimensions;
21pub use self::dimensions::*;
22
23mod location_fallback;
24pub use self::location_fallback::*;
25
26pub mod problem;
27pub mod solution;
28
29/// Represents a location type.
30#[derive(Clone, Debug, Deserialize, Serialize)]
31#[serde(untagged)]
32pub enum Location {
33    /// A location type represented by geocoordinate with latitude and longitude.
34    Coordinate {
35        /// Latitude.
36        lat: f64,
37        /// Longitude.
38        lng: f64,
39    },
40
41    /// A location type represented by index reference in routing matrix.
42    Reference {
43        /// An index in routing matrix.
44        index: usize,
45    },
46
47    /// A custom location type with no reference in matrix.
48    Custom {
49        /// Specifies a custom location type.
50        r#type: CustomLocationType,
51    },
52}
53
54impl Location {
55    /// Creates a new [`Location`] as coordinate.
56    pub fn new_coordinate(lat: f64, lng: f64) -> Self {
57        Self::Coordinate { lat, lng }
58    }
59
60    /// Creates a new [`Location`] as index reference.
61    pub fn new_reference(index: usize) -> Self {
62        Self::Reference { index }
63    }
64
65    /// Creates a new [`Location`] as custom unknown type.
66    pub fn new_unknown() -> Self {
67        Self::Custom { r#type: CustomLocationType::Unknown }
68    }
69
70    /// Returns lat lng if location is coordinate, panics otherwise.
71    pub fn to_lat_lng(&self) -> (f64, f64) {
72        match self {
73            Self::Coordinate { lat, lng } => (*lat, *lng),
74            _ => unreachable!("expect coordinate"),
75        }
76    }
77}
78
79impl std::fmt::Display for Location {
80    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
81        match *self {
82            Location::Coordinate { lat, lng } => write!(f, "lat={lat}, lng={lng}"),
83            Location::Reference { index } => write!(f, "index={index}"),
84            Location::Custom { r#type } => {
85                let value = match r#type {
86                    CustomLocationType::Unknown => "unknown",
87                };
88                write!(f, "custom={value}")
89            }
90        }
91    }
92}
93
94/// A custom location type which has no reference to matrix.
95#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
96pub enum CustomLocationType {
97    /// Unknown location type which has a zero distance/duration to any other location.
98    #[serde(rename(deserialize = "unknown", serialize = "unknown"))]
99    Unknown,
100}
101
102/// A format error.
103#[derive(Clone, Debug, Serialize)]
104pub struct FormatError {
105    /// An error code in registry.
106    pub code: String,
107    /// A possible error cause.
108    pub cause: String,
109    /// An action to take in order to recover from error.
110    pub action: String,
111    /// A details about exception.
112    pub details: Option<String>,
113}
114
115impl FormatError {
116    /// Creates a new instance of `FormatError` action without details.
117    pub fn new(code: String, cause: String, action: String) -> Self {
118        Self { code, cause, action, details: None }
119    }
120
121    /// Creates a new instance of `FormatError` action.
122    pub fn new_with_details(code: String, cause: String, action: String, details: String) -> Self {
123        Self { code, cause, action, details: Some(details) }
124    }
125
126    /// Serializes error into json string.
127    pub fn to_json(&self) -> String {
128        serde_json::to_string_pretty(&self).unwrap()
129    }
130}
131
132impl std::error::Error for FormatError {}
133
134impl std::fmt::Display for FormatError {
135    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
136        write!(f, "{}, cause: '{}', action: '{}'.", self.code, self.cause, self.action)
137    }
138}
139
140/// Keeps track of multiple `FormatError`.
141#[derive(Debug)]
142pub struct MultiFormatError {
143    /// Inner errors.
144    pub errors: Vec<FormatError>,
145}
146
147impl MultiFormatError {
148    /// Formats multiple format errors into json string.
149    pub fn to_json(&self) -> String {
150        serde_json::to_string_pretty(&self.errors).unwrap()
151    }
152}
153
154impl std::error::Error for MultiFormatError {}
155
156impl std::fmt::Display for MultiFormatError {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        write!(f, "{}", self.errors.iter().map(|err| err.to_string()).collect::<Vec<_>>().join("\n"))
159    }
160}
161
162impl From<Vec<FormatError>> for MultiFormatError {
163    fn from(errors: Vec<FormatError>) -> Self {
164        MultiFormatError { errors }
165    }
166}
167
168impl From<MultiFormatError> for GenericError {
169    fn from(value: MultiFormatError) -> Self {
170        value.to_string().into()
171    }
172}
173
174impl IntoIterator for MultiFormatError {
175    type Item = FormatError;
176    type IntoIter = <Vec<FormatError> as IntoIterator>::IntoIter;
177
178    fn into_iter(self) -> Self::IntoIter {
179        self.errors.into_iter()
180    }
181}
182
183const TIME_CONSTRAINT_CODE: ViolationCode = ViolationCode(1);
184const DISTANCE_LIMIT_CONSTRAINT_CODE: ViolationCode = ViolationCode(2);
185const DURATION_LIMIT_CONSTRAINT_CODE: ViolationCode = ViolationCode(3);
186const CAPACITY_CONSTRAINT_CODE: ViolationCode = ViolationCode(4);
187const BREAK_CONSTRAINT_CODE: ViolationCode = ViolationCode(5);
188const SKILL_CONSTRAINT_CODE: ViolationCode = ViolationCode(6);
189const LOCKING_CONSTRAINT_CODE: ViolationCode = ViolationCode(7);
190const REACHABLE_CONSTRAINT_CODE: ViolationCode = ViolationCode(8);
191const AREA_CONSTRAINT_CODE: ViolationCode = ViolationCode(9);
192const TOUR_SIZE_CONSTRAINT_CODE: ViolationCode = ViolationCode(10);
193const TOUR_ORDER_CONSTRAINT_CODE: ViolationCode = ViolationCode(11);
194const GROUP_CONSTRAINT_CODE: ViolationCode = ViolationCode(12);
195const COMPATIBILITY_CONSTRAINT_CODE: ViolationCode = ViolationCode(13);
196const RELOAD_RESOURCE_CONSTRAINT_CODE: ViolationCode = ViolationCode(14);
197const RECHARGE_CONSTRAINT_CODE: ViolationCode = ViolationCode(15);
198
199/// An job id to job index.
200pub type JobIndex = HashMap<String, CoreJob>;
201
202pub use self::properties::{CoordIndexExtraProperty, JobIndexExtraProperty};
203
204mod properties {
205    use crate::format::{CoordIndex, JobIndex};
206    use vrp_core::custom_extra_property;
207    use vrp_core::models::Extras;
208
209    custom_extra_property!(JobIndex typeof JobIndex);
210    custom_extra_property!(CoordIndex typeof CoordIndex);
211}
212
213/// Get job and coord indices from extras
214pub fn get_indices(extras: &CoreExtras) -> Result<(Arc<JobIndex>, Arc<CoordIndex>), GenericError> {
215    let job_index = extras.get_job_index().ok_or_else(|| GenericError::from("cannot get job index"))?;
216    let coord_index = extras.get_coord_index().ok_or_else(|| GenericError::from("cannot get coord index"))?;
217
218    Ok((job_index, coord_index))
219}
220
221/// Checks whether the given single job can be assigned to the given route taking into consideration
222/// its id and shift index.
223pub(crate) fn is_correct_vehicle(route: &Route, single: &Single) -> bool {
224    let job_vehicle_id = single.dimens.get_vehicle_id();
225    let job_shift_idx = single.dimens.get_shift_index();
226
227    let vehicle = &route.actor.vehicle;
228    let vehicle_id = vehicle.dimens.get_vehicle_id();
229    let vehicle_shift_idx = vehicle.dimens.get_shift_index();
230
231    job_vehicle_id == vehicle_id && job_shift_idx == vehicle_shift_idx
232}