vrp_core/models/problem/
builders.rs

1//! Provides a way to build some of the core models using the builder pattern.
2
3use crate::construction::features::{JobDemandDimension, VehicleCapacityDimension};
4use crate::models::common::{Cost, Demand, Dimensions, Duration, LoadOps, Location, TimeSpan, TimeWindow, Timestamp};
5use crate::models::problem::{
6    Costs, Job, JobIdDimension, JobPermutation, Multi, Place, Single, Vehicle, VehicleDetail, VehicleIdDimension,
7    VehiclePlace,
8};
9use rosomaxa::prelude::{GenericError, GenericResult};
10use std::sync::Arc;
11
12/// Provides a way to build a [Single] job using the builder pattern.
13#[derive(Debug)]
14pub struct SingleBuilder(Single);
15
16impl Default for SingleBuilder {
17    fn default() -> Self {
18        Self(Single { places: vec![], dimens: Default::default() })
19    }
20}
21
22impl SingleBuilder {
23    /// Adds a new place to single job's `places` collection. Use this api to add multiple places
24    /// which are used as alternative places (e.g. locations) to serve the job.
25    pub fn add_place(mut self, place: Place) -> Self {
26        self.0.places.push(place);
27        self
28    }
29
30    /// Adds new places to single job's `places` collection.
31    pub fn add_places(mut self, places: impl Iterator<Item = Place>) -> Self {
32        self.0.places.extend(places);
33        self
34    }
35
36    /// Sets a job id dimension.
37    pub fn id(mut self, id: &str) -> Self {
38        self.0.dimens.set_job_id(id.to_string());
39        self
40    }
41
42    /// A simple api to set a single job's demand.
43    pub fn demand<T: LoadOps>(mut self, demand: Demand<T>) -> Self {
44        self.0.dimens.set_job_demand(demand);
45        self
46    }
47
48    /// A simple api to associate arbitrary property within the job.
49    pub fn dimension(mut self, func: impl FnOnce(&mut Dimensions)) -> Self {
50        func(&mut self.0.dimens);
51        self
52    }
53
54    /// A simple api to set location of the first place.
55    /// Normally, location is represented as an index in routing matrix.
56    /// Fails if used with more than one place, creates a new place if no places are specified.
57    pub fn location(mut self, location: Location) -> GenericResult<Self> {
58        self.ensure_single_place()?.location = Some(location);
59        Ok(self)
60    }
61
62    /// A simple api to set duration of the first place.
63    /// Fails if used with more than one place, creates a new place if no places are specified.
64    pub fn duration(mut self, duration: Duration) -> GenericResult<Self> {
65        self.ensure_single_place()?.duration = duration;
66        Ok(self)
67    }
68
69    /// A simple api to set time windows of the first place.
70    /// Fails if used with more than one place, creates a new place if no places are specified.
71    pub fn times(mut self, times: Vec<TimeWindow>) -> GenericResult<Self> {
72        self.ensure_single_place()?.times = times.into_iter().map(TimeSpan::Window).collect();
73        Ok(self)
74    }
75
76    /// Builds a [Single] job.
77    pub fn build(self) -> GenericResult<Single> {
78        Ok(self.0)
79    }
80
81    /// Builds a [Job].
82    pub fn build_as_job(self) -> GenericResult<Job> {
83        Ok(Job::Single(Arc::new(self.0)))
84    }
85
86    fn ensure_single_place(&mut self) -> GenericResult<&mut Place> {
87        if self.0.places.len() > 1 {
88            return Err("cannot use the simple api with multiple places, use `SingleBuilder::add_place` and `JobPlaceBuilder` instead".into());
89        }
90
91        if self.0.places.is_empty() {
92            self.0.places.push(empty_place());
93        }
94
95        self.0.places.first_mut().ok_or_else(|| GenericError::from("no places"))
96    }
97}
98
99/// Provides a way to build a [Place] used internally by [Single] job.
100pub struct JobPlaceBuilder(Place);
101
102impl Default for JobPlaceBuilder {
103    fn default() -> Self {
104        Self(empty_place())
105    }
106}
107
108impl JobPlaceBuilder {
109    /// Sets place's location.
110    pub fn location(mut self, loc: Option<Location>) -> Self {
111        self.0.location = loc;
112        self
113    }
114
115    /// Sets place's duration.
116    pub fn duration(mut self, duration: Duration) -> Self {
117        self.0.duration = duration;
118        self
119    }
120
121    /// Sets place's time windows.
122    pub fn times(mut self, times: Vec<TimeWindow>) -> Self {
123        self.0.times = times.into_iter().map(TimeSpan::Window).collect();
124        self
125    }
126
127    /// Builds a job [Place].
128    pub fn build(self) -> GenericResult<Place> {
129        Ok(self.0)
130    }
131}
132
133/// Provides a way to build a [Multi] job using the builder pattern.
134#[derive(Default)]
135pub struct MultiBuilder {
136    jobs: Vec<Arc<Single>>,
137    dimens: Dimensions,
138    permutator: Option<Box<dyn JobPermutation>>,
139}
140
141impl MultiBuilder {
142    /// Sets a job id dimension.
143    pub fn id(mut self, id: &str) -> Self {
144        self.dimens.set_job_id(id.to_string());
145        self
146    }
147
148    /// Adds a [Single] as sub-job.
149    pub fn add_job(mut self, single: Single) -> Self {
150        self.jobs.push(Arc::new(single));
151        self
152    }
153
154    /// A simple api to associate arbitrary property within the job.
155    pub fn dimension(mut self, func: impl FnOnce(&mut Dimensions)) -> Self {
156        func(&mut self.dimens);
157        self
158    }
159
160    /// Sets a permutation logic which tells allowed order of sub-jobs assignment.
161    /// If omitted, sub-jobs can be assigned only in the order of addition.
162    pub fn permutation(mut self, permutation: impl JobPermutation + 'static) -> Self {
163        self.permutator = Some(Box::new(permutation));
164        self
165    }
166
167    /// Builds [Multi] job as shared reference.
168    pub fn build(self) -> GenericResult<Arc<Multi>> {
169        if self.jobs.len() < 2 {
170            return Err("the number of sub-jobs must be 2 or more".into());
171        }
172
173        Ok(if let Some(permutator) = self.permutator {
174            Multi::new_shared_with_permutator(self.jobs, self.dimens, permutator)
175        } else {
176            Multi::new_shared(self.jobs, self.dimens)
177        })
178    }
179
180    /// Builds a [Job].
181    pub fn build_as_job(self) -> GenericResult<Job> {
182        Ok(Job::Multi(self.build()?))
183    }
184}
185
186fn empty_place() -> Place {
187    // NOTE a time window must be present as it is expected in evaluator logic.
188    Place { location: None, duration: 0.0, times: vec![TimeSpan::Window(TimeWindow::max())] }
189}
190
191/// Provides a way to build a [Vehicle].
192pub struct VehicleBuilder(Vehicle);
193
194impl Default for VehicleBuilder {
195    fn default() -> Self {
196        Self(Vehicle {
197            profile: Default::default(),
198            costs: Costs {
199                fixed: 0.0,
200                per_distance: 1.,
201                per_driving_time: 0.0,
202                per_waiting_time: 0.0,
203                per_service_time: 0.0,
204            },
205            dimens: Default::default(),
206            details: vec![],
207        })
208    }
209}
210
211impl VehicleBuilder {
212    /// Sets a vehicle id dimension.
213    pub fn id(mut self, id: &str) -> Self {
214        self.0.dimens.set_vehicle_id(id.to_string());
215        self
216    }
217
218    /// Adds a vehicle detail which specifies start/end location, time, etc.
219    /// Use [VehicleDetailBuilder] to construct one.
220    pub fn add_detail(mut self, detail: VehicleDetail) -> Self {
221        self.0.details.push(detail);
222        self
223    }
224
225    /// Sets routing profile index which is used to configure which routing data to use within the vehicle.
226    pub fn set_profile_idx(mut self, idx: usize) -> Self {
227        self.0.profile.index = idx;
228        self
229    }
230
231    /// Sets a cost per distance unit.
232    pub fn set_distance_cost(mut self, cost: Cost) -> Self {
233        self.0.costs.per_distance = cost;
234        self
235    }
236
237    /// Sets a cost per duration unit.
238    pub fn set_duration_cost(mut self, cost: Cost) -> Self {
239        self.0.costs.per_driving_time = cost;
240        self.0.costs.per_service_time = cost;
241        self.0.costs.per_waiting_time = cost;
242        self
243    }
244
245    /// Sets a vehicle capacity dimension.
246    pub fn capacity<T: LoadOps>(mut self, value: T) -> Self {
247        self.0.dimens.set_vehicle_capacity(value);
248        self
249    }
250
251    /// A simple api to associate arbitrary property within the vehicle.
252    pub fn dimension(mut self, func: impl FnOnce(&mut Dimensions)) -> Self {
253        func(&mut self.0.dimens);
254        self
255    }
256
257    /// Builds a [Vehicle].
258    pub fn build(self) -> GenericResult<Vehicle> {
259        if self.0.details.is_empty() {
260            Err("at least one vehicle detail needs to be added, use `VehicleDetailBuilder` and `add_detail` function"
261                .into())
262        } else {
263            Ok(self.0)
264        }
265    }
266}
267
268/// Provides a way to build [VehicleDetail].
269pub struct VehicleDetailBuilder(VehicleDetail);
270
271impl Default for VehicleDetailBuilder {
272    fn default() -> Self {
273        Self(VehicleDetail { start: None, end: None })
274    }
275}
276
277impl VehicleDetailBuilder {
278    /// Sets start location.
279    pub fn set_start_location(mut self, location: Location) -> Self {
280        self.ensure_start().location = location;
281        self
282    }
283
284    /// Sets earliest departure time for start location.
285    pub fn set_start_time(mut self, earliest: Timestamp) -> Self {
286        self.ensure_start().time.earliest = Some(earliest);
287        // NOTE disable departure time optimization
288        self.ensure_start().time.latest = Some(earliest);
289        self
290    }
291
292    /// Sets a latest departure time to enable departure time optimization (disabled implicitly with `set_start_time` call).
293    pub fn set_start_time_latest(mut self, latest: Timestamp) -> Self {
294        self.ensure_start().time.latest = Some(latest);
295        self
296    }
297
298    /// Sets end location.
299    pub fn set_end_location(mut self, location: Location) -> Self {
300        self.ensure_end().location = location;
301        self
302    }
303
304    /// Sets the latest arrival time for end location.
305    pub fn set_end_time(mut self, latest: Timestamp) -> Self {
306        self.ensure_end().time.latest = Some(latest);
307        self
308    }
309
310    fn ensure_start(&mut self) -> &mut VehiclePlace {
311        if self.0.start.is_none() {
312            self.0.start = Some(VehiclePlace { location: 0, time: Default::default() });
313        }
314        self.0.start.as_mut().unwrap()
315    }
316
317    fn ensure_end(&mut self) -> &mut VehiclePlace {
318        if self.0.end.is_none() {
319            self.0.end = Some(VehiclePlace { location: 0, time: Default::default() });
320        }
321        self.0.end.as_mut().unwrap()
322    }
323
324    /// Builds vehicle detail.
325    pub fn build(self) -> GenericResult<VehicleDetail> {
326        if self.0.start.is_none() {
327            Err("start place must be defined for vehicle detail".into())
328        } else {
329            Ok(self.0)
330        }
331    }
332}