vrp_core/models/common/
load.rs

1#[cfg(test)]
2#[path = "../../../tests/unit/models/common/load_test.rs"]
3mod load_test;
4
5use rosomaxa::prelude::{Float, UnwrapValue};
6use std::cmp::Ordering;
7use std::fmt::{Debug, Display, Formatter};
8use std::iter::Sum;
9use std::ops::{Add, ControlFlow, Mul, Sub};
10
11const LOAD_DIMENSION_SIZE: usize = 8;
12
13/// Represents a load type used to represent customer's demand or vehicle's load.
14pub trait Load: Add + Sub + PartialOrd + Copy + Default + Debug + Send + Sync {
15    /// Returns true if it represents an empty load.
16    fn is_not_empty(&self) -> bool;
17
18    /// Returns max load value.
19    fn max_load(self, other: Self) -> Self;
20
21    /// Returns true if `other` can be loaded into existing capacity.
22    fn can_fit(&self, other: &Self) -> bool;
23
24    /// Returns ratio.
25    fn ratio(&self, other: &Self) -> Float;
26}
27
28/// Specifies constraints on Load operations.
29pub trait LoadOps: Load + Add<Output = Self> + Sub<Output = Self> + 'static
30where
31    Self: Sized,
32{
33}
34
35/// Represents job demand, both static and dynamic.
36pub struct Demand<T: LoadOps> {
37    /// Keeps static and dynamic pickup amount.
38    pub pickup: (T, T),
39    /// Keeps static and dynamic delivery amount.
40    pub delivery: (T, T),
41}
42
43impl<T: LoadOps> Demand<T> {
44    /// Returns demand type.
45    pub fn get_type(&self) -> DemandType {
46        match (self.delivery.0.is_not_empty(), self.pickup.0.is_not_empty()) {
47            (true, false) => DemandType::Delivery,
48            (false, true) => DemandType::Pickup,
49            (true, true) => DemandType::Mixed,
50            (false, false) if self.delivery.1.is_not_empty() && self.pickup.1.is_not_empty() => DemandType::Dynamic,
51            _ => DemandType::Mixed,
52        }
53    }
54}
55
56/// Defines a typical demand types.
57pub enum DemandType {
58    /// A static pickup type models a normal pickup job.
59    Pickup,
60    /// A static delivery type models a normal delivery job,
61    Delivery,
62    /// A dynamic type used to model a pickup and delivery job.
63    Dynamic,
64    /// A mixed type reflects the fact that demand is mixed that has currently no meaning.
65    Mixed,
66}
67
68impl<T: LoadOps> Demand<T> {
69    /// Returns capacity change as difference between pickup and delivery.
70    pub fn change(&self) -> T {
71        self.pickup.0 + self.pickup.1 - self.delivery.0 - self.delivery.1
72    }
73}
74
75impl<T: LoadOps> Default for Demand<T> {
76    fn default() -> Self {
77        Self { pickup: (Default::default(), Default::default()), delivery: (Default::default(), Default::default()) }
78    }
79}
80
81impl<T: LoadOps> Clone for Demand<T> {
82    fn clone(&self) -> Self {
83        Self { pickup: self.pickup, delivery: self.delivery }
84    }
85}
86
87impl<T: LoadOps> Add for Demand<T> {
88    type Output = Self;
89
90    fn add(self, rhs: Self) -> Self::Output {
91        Self {
92            pickup: (self.pickup.0 + rhs.pickup.0, self.pickup.1 + rhs.pickup.1),
93            delivery: (self.delivery.0 + rhs.delivery.0, self.delivery.1 + rhs.delivery.1),
94        }
95    }
96}
97
98impl Demand<SingleDimLoad> {
99    /// Creates a normal (static) pickup demand.
100    pub fn pickup(value: i32) -> Self {
101        Self {
102            pickup: (SingleDimLoad::new(value), SingleDimLoad::default()),
103            delivery: (SingleDimLoad::default(), SingleDimLoad::default()),
104        }
105    }
106
107    /// Creates a PUDO (dynamic) pickup demand.
108    pub fn pudo_pickup(value: i32) -> Self {
109        Self {
110            pickup: (SingleDimLoad::default(), SingleDimLoad::new(value)),
111            delivery: (SingleDimLoad::default(), SingleDimLoad::default()),
112        }
113    }
114
115    /// Creates a normal (static) delivery demand.
116    pub fn delivery(value: i32) -> Self {
117        Self {
118            pickup: (SingleDimLoad::default(), SingleDimLoad::default()),
119            delivery: (SingleDimLoad::new(value), SingleDimLoad::default()),
120        }
121    }
122
123    /// Creates a PUDO (dynamic) delivery demand.
124    pub fn pudo_delivery(value: i32) -> Self {
125        Self {
126            pickup: (SingleDimLoad::default(), SingleDimLoad::default()),
127            delivery: (SingleDimLoad::default(), SingleDimLoad::new(value)),
128        }
129    }
130}
131
132/// Specifies single dimensional load type.
133#[derive(Clone, Copy, Debug, Default)]
134pub struct SingleDimLoad {
135    /// An actual load value.
136    pub value: i32,
137}
138
139impl SingleDimLoad {
140    /// Creates a new instance of `SingleDimLoad`.
141    pub fn new(value: i32) -> Self {
142        Self { value }
143    }
144}
145
146impl LoadOps for SingleDimLoad {}
147
148impl Load for SingleDimLoad {
149    fn is_not_empty(&self) -> bool {
150        self.value != 0
151    }
152
153    fn max_load(self, other: Self) -> Self {
154        let value = self.value.max(other.value);
155        Self { value }
156    }
157
158    fn can_fit(&self, other: &Self) -> bool {
159        self.value >= other.value
160    }
161
162    fn ratio(&self, other: &Self) -> Float {
163        self.value as Float / other.value as Float
164    }
165}
166
167impl Add for SingleDimLoad {
168    type Output = Self;
169
170    fn add(self, rhs: Self) -> Self::Output {
171        let value = self.value + rhs.value;
172        Self { value }
173    }
174}
175
176impl Sub for SingleDimLoad {
177    type Output = Self;
178
179    fn sub(self, rhs: Self) -> Self::Output {
180        let value = self.value - rhs.value;
181        Self { value }
182    }
183}
184
185impl Ord for SingleDimLoad {
186    fn cmp(&self, other: &Self) -> Ordering {
187        self.value.cmp(&other.value)
188    }
189}
190
191impl PartialOrd for SingleDimLoad {
192    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
193        Some(self.cmp(other))
194    }
195}
196
197impl Eq for SingleDimLoad {}
198
199impl PartialEq for SingleDimLoad {
200    fn eq(&self, other: &Self) -> bool {
201        self.cmp(other) == Ordering::Equal
202    }
203}
204
205impl Mul<Float> for SingleDimLoad {
206    type Output = Self;
207
208    fn mul(self, value: Float) -> Self::Output {
209        Self::new((self.value as Float * value).round() as i32)
210    }
211}
212
213impl Display for SingleDimLoad {
214    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
215        write!(f, "{}", self.value)
216    }
217}
218
219/// Specifies multi dimensional load type.
220#[derive(Clone, Copy, Debug)]
221pub struct MultiDimLoad {
222    /// Load data.
223    pub load: [i32; LOAD_DIMENSION_SIZE],
224    /// Actual used size.
225    pub size: usize,
226}
227
228impl MultiDimLoad {
229    /// Creates a new instance of `MultiDimLoad`.
230    pub fn new(data: Vec<i32>) -> Self {
231        assert!(data.len() <= LOAD_DIMENSION_SIZE);
232
233        let mut load = [0; LOAD_DIMENSION_SIZE];
234        for (idx, value) in data.iter().enumerate() {
235            load[idx] = *value;
236        }
237
238        Self { load, size: data.len() }
239    }
240
241    fn get(&self, idx: usize) -> i32 {
242        self.load[idx]
243    }
244
245    /// Converts to vector representation.
246    pub fn as_vec(&self) -> Vec<i32> {
247        if self.size == 0 {
248            vec![0]
249        } else {
250            self.load[..self.size].to_vec()
251        }
252    }
253}
254
255impl Load for MultiDimLoad {
256    fn is_not_empty(&self) -> bool {
257        self.size == 0 || self.load.iter().any(|v| *v != 0)
258    }
259
260    fn max_load(self, other: Self) -> Self {
261        let mut result = self;
262        result.load.iter_mut().zip(other.load.iter()).for_each(|(a, b)| *a = (*a).max(*b));
263
264        result
265    }
266
267    fn can_fit(&self, other: &Self) -> bool {
268        self.load.iter().zip(other.load.iter()).all(|(a, b)| a >= b)
269    }
270
271    fn ratio(&self, other: &Self) -> Float {
272        self.load.iter().zip(other.load.iter()).fold(0., |acc, (a, b)| (*a as Float / *b as Float).max(acc))
273    }
274}
275
276impl LoadOps for MultiDimLoad {}
277
278impl Default for MultiDimLoad {
279    fn default() -> Self {
280        Self { load: [0; LOAD_DIMENSION_SIZE], size: 0 }
281    }
282}
283
284impl Add for MultiDimLoad {
285    type Output = Self;
286
287    fn add(self, rhs: Self) -> Self::Output {
288        fn sum(acc: MultiDimLoad, rhs: &MultiDimLoad) -> MultiDimLoad {
289            let mut dimens = acc;
290
291            for (idx, value) in rhs.load.iter().enumerate() {
292                dimens.load[idx] += *value;
293            }
294
295            dimens.size = dimens.size.max(rhs.size);
296
297            dimens
298        }
299
300        if self.load.len() >= rhs.load.len() {
301            sum(self, &rhs)
302        } else {
303            sum(rhs, &self)
304        }
305    }
306}
307
308impl Sub for MultiDimLoad {
309    type Output = Self;
310
311    fn sub(self, rhs: Self) -> Self::Output {
312        let mut dimens = self;
313
314        for (idx, value) in rhs.load.iter().enumerate() {
315            dimens.load[idx] -= *value;
316        }
317
318        dimens.size = dimens.size.max(rhs.size);
319
320        dimens
321    }
322}
323
324impl PartialOrd for MultiDimLoad {
325    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
326        let size = self.size.max(other.size);
327        (0..size)
328            .try_fold(None, |acc, idx| {
329                let result = self.get(idx).cmp(&other.get(idx));
330                acc.map_or(ControlFlow::Continue(Some(result)), |acc| {
331                    if acc != result {
332                        ControlFlow::Break(None)
333                    } else {
334                        ControlFlow::Continue(Some(result))
335                    }
336                })
337            })
338            .unwrap_value()
339    }
340}
341
342impl Eq for MultiDimLoad {}
343
344impl PartialEq for MultiDimLoad {
345    fn eq(&self, other: &Self) -> bool {
346        self.partial_cmp(other).map_or(false, |ordering| ordering == Ordering::Equal)
347    }
348}
349
350impl Mul<Float> for MultiDimLoad {
351    type Output = Self;
352
353    fn mul(self, value: Float) -> Self::Output {
354        let mut dimens = self;
355
356        dimens.load.iter_mut().for_each(|item| {
357            *item = (*item as Float * value).round() as i32;
358        });
359
360        dimens
361    }
362}
363
364impl Sum for MultiDimLoad {
365    fn sum<I: Iterator<Item = MultiDimLoad>>(iter: I) -> Self {
366        iter.fold(MultiDimLoad::default(), |acc, item| item + acc)
367    }
368}
369
370impl Display for MultiDimLoad {
371    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
372        write!(f, "{:?}", self.load)
373    }
374}