Skip to main content

vrp_core/models/
domain.rs

1use crate::construction::heuristics::UnassignmentInfo;
2use crate::models::common::{Cost, Location};
3use crate::models::problem::*;
4use crate::models::solution::{Registry, Route};
5use crate::models::*;
6use rosomaxa::evolution::TelemetryMetrics;
7use rosomaxa::prelude::*;
8use std::fmt::{Debug, Formatter};
9use std::sync::Arc;
10
11/// Defines a VRP problem. You can use a [`ProblemBuilder`] to create the one.
12pub struct Problem {
13    /// Specifies used fleet.
14    pub fleet: Arc<Fleet>,
15
16    /// Specifies all jobs.
17    pub jobs: Arc<Jobs>,
18
19    /// Specifies jobs which preassigned to specific vehicles and/or drivers.
20    pub locks: Vec<Arc<Lock>>,
21
22    /// Specifies optimization goal with the corresponding global/local objectives and invariants.
23    pub goal: Arc<GoalContext>,
24
25    /// Specifies activity costs.
26    pub activity: Arc<dyn ActivityCost>,
27
28    /// Specifies transport costs.
29    pub transport: Arc<dyn TransportCost>,
30
31    /// Specifies index for storing extra parameters of arbitrary type.
32    pub extras: Arc<Extras>,
33}
34
35impl Debug for Problem {
36    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
37        f.debug_struct(short_type_name::<Self>())
38            .field("fleet", &self.fleet)
39            .field("jobs", &self.jobs.size())
40            .field("locks", &self.locks.len())
41            .field("goal", self.goal.as_ref())
42            .finish_non_exhaustive()
43    }
44}
45
46/// Represents a VRP solution.
47pub struct Solution {
48    /// A total solution cost.
49    /// Definition of the cost depends on VRP variant.
50    pub cost: Cost,
51
52    /// Actor's registry.
53    pub registry: Registry,
54
55    /// List of assigned routes.
56    pub routes: Vec<Route>,
57
58    /// List of unassigned jobs within reason code.
59    pub unassigned: Vec<(Job, UnassignmentInfo)>,
60
61    /// An optional telemetry metrics if available.
62    pub telemetry: Option<TelemetryMetrics>,
63}
64
65/// An enumeration which specifies how jobs should be ordered in tour.
66pub enum LockOrder {
67    /// Jobs can be reshuffled in any order.
68    Any,
69    /// Jobs cannot be reshuffled, but new job can be inserted in between.
70    Sequence,
71    /// Jobs cannot be reshuffled and no jobs can be inserted in between.
72    Strict,
73}
74
75/// An enumeration which specifies how other jobs can be inserted in tour.
76#[derive(Clone)]
77pub enum LockPosition {
78    /// No specific position.
79    Any,
80    /// First job follows departure.
81    Departure,
82    /// Last job is before arrival.
83    Arrival,
84    /// First and last jobs should be between departure and arrival.
85    Fixed,
86}
87
88/// Specifies lock details.
89pub struct LockDetail {
90    /// Lock order.
91    pub order: LockOrder,
92    /// Lock position.
93    pub position: LockPosition,
94    /// Jobs affected by the lock.
95    pub jobs: Vec<Job>,
96}
97
98/// Contains information about jobs locked to specific actors.
99pub struct Lock {
100    /// Specifies condition when locked jobs can be assigned to a specific actor
101    pub condition_fn: Arc<dyn Fn(&Actor) -> bool + Sync + Send>,
102    /// Specifies lock details.
103    pub details: Vec<LockDetail>,
104    /// Specifies whether route is created or not in solution from the beginning.
105    /// True means that route is not created till evaluation.
106    pub is_lazy: bool,
107}
108
109impl LockDetail {
110    /// Creates a new instance of `LockDetail`.
111    pub fn new(order: LockOrder, position: LockPosition, jobs: Vec<Job>) -> Self {
112        Self { order, position, jobs }
113    }
114}
115
116impl Lock {
117    /// Creates a new instance of `Lock`.
118    pub fn new(condition: Arc<dyn Fn(&Actor) -> bool + Sync + Send>, details: Vec<LockDetail>, is_lazy: bool) -> Self {
119        Self { condition_fn: condition, details, is_lazy }
120    }
121}
122
123/// Specifies a function to group actors based on their similarity.
124pub type FleetGroupKeyFn = dyn Fn(&Actor) -> usize + Send + Sync;
125
126/// Provides way to build a VRP definition.
127#[derive(Default)]
128pub struct ProblemBuilder {
129    jobs: Vec<Job>,
130    vehicles: Vec<Vehicle>,
131    #[allow(clippy::type_complexity)]
132    group_key_fn: Option<Box<dyn Fn(&[Arc<Actor>]) -> Box<FleetGroupKeyFn>>>,
133    goal: Option<Arc<GoalContext>>,
134    activity: Option<Arc<dyn ActivityCost>>,
135    transport: Option<Arc<dyn TransportCost>>,
136    extras: Option<Arc<Extras>>,
137    logger: Option<InfoLogger>,
138}
139
140impl ProblemBuilder {
141    /// Adds a job to the collection of the things to be done.
142    pub fn add_job(mut self, job: Job) -> Self {
143        self.jobs.push(job);
144        self
145    }
146
147    /// Adds multiple jobs to the collection of the things to be done.
148    pub fn add_jobs(mut self, jobs: impl Iterator<Item = Job>) -> Self {
149        self.jobs.extend(jobs);
150        self
151    }
152
153    /// Add a vehicle to the fleet.
154    /// At least one has to be provided.
155    pub fn add_vehicle(mut self, vehicle: Vehicle) -> Self {
156        self.vehicles.push(vehicle);
157        self
158    }
159
160    /// Add multiple vehicles to the fleet.
161    /// At least one has to be provided.
162    pub fn add_vehicles(mut self, vehicles: impl Iterator<Item = Vehicle>) -> Self {
163        self.vehicles.extend(vehicles);
164        self
165    }
166
167    /// Sets a vehicle similarity function which allows grouping of similar vehicles together.
168    /// That helps the solver to take more effective decisions job-vehicle assignment.
169    /// Optional: when omitted, only vehicles with the same `profile.index` are grouped together.
170    pub fn with_vehicle_similarity(
171        mut self,
172        group_key_fn: impl Fn(&[Arc<Actor>]) -> Box<FleetGroupKeyFn> + 'static,
173    ) -> Self {
174        self.group_key_fn = Some(Box::new(group_key_fn));
175        self
176    }
177
178    /// Adds a goal of optimization. Use [GoalContextBuilder] to create the one.
179    /// A required field.
180    pub fn with_goal(mut self, goal: GoalContext) -> Self {
181        self.goal = Some(Arc::new(goal));
182        self
183    }
184
185    /// Adds a transport distance/duration estimation logic. A typical implementation will normally
186    /// wrap routing distance/duration matrices.
187    /// A required field.
188    pub fn with_transport_cost(mut self, transport: Arc<dyn TransportCost>) -> Self {
189        self.transport = Some(transport);
190        self
191    }
192
193    /// Adds an activity service time estimation logic.
194    /// An optional field: [SimpleActivityCost] will be used by default.
195    pub fn with_activity_cost(mut self, activity: Arc<dyn ActivityCost>) -> Self {
196        self.activity = Some(activity);
197        self
198    }
199
200    /// Adds an extras: an extension mechanism to pass arbitrary properties associated within
201    /// the problem definition.
202    /// An optional field.
203    pub fn with_extras(mut self, extras: Extras) -> Self {
204        self.extras = Some(Arc::new(extras));
205        self
206    }
207
208    /// Adds a logger to the problem definition.
209    pub fn with_logger(mut self, logger: InfoLogger) -> Self {
210        self.logger = Some(logger);
211        self
212    }
213
214    /// Builds a problem definition.
215    /// Returns [Err] in case of an invalid configuration.
216    pub fn build(mut self) -> GenericResult<Problem> {
217        if self.jobs.is_empty() {
218            return Err("empty list of jobs: specify at least one job".into());
219        }
220
221        if self.vehicles.is_empty() {
222            return Err("empty list of vehicles: specify at least one vehicle".into());
223        }
224
225        // analyze user input
226        let transport = self.transport.take().ok_or_else(|| {
227            GenericError::from("no information about routing data: use 'with_transport_cost' method to specify it")
228        })?;
229        let activity = self.activity.take().unwrap_or_else(|| Arc::new(SimpleActivityCost::default()));
230        let goal = self
231            .goal
232            .take()
233            .ok_or_else(|| GenericError::from("unknown goal of optimization: use 'with_goal' method to set it"))?;
234        let extras = self.extras.take().unwrap_or_else(|| Arc::new(Extras::default()));
235
236        // setup fleet
237        // NOTE: driver concept is not fully supported yet, but we must provide at least one.
238        let driver = Arc::new(Driver::empty());
239        let vehicles = self.vehicles.into_iter().map(Arc::new).collect();
240        let group_key = self.group_key_fn.take().unwrap_or_else(|| Box::new(|_| Box::new(|a| a.vehicle.profile.index)));
241        let fleet = Arc::new(Fleet::new(vec![driver], vehicles, group_key));
242
243        let logger = self.logger.unwrap_or_else(|| Arc::new(|msg| println!("{}", msg)));
244
245        // setup jobs
246        let jobs = Arc::new(Jobs::new(fleet.as_ref(), self.jobs, transport.as_ref(), &logger)?);
247
248        Ok(Problem { fleet, jobs, locks: vec![], goal, activity, transport, extras })
249    }
250}
251
252impl Solution {
253    /// Iterates through all tours and returns locations of each activity in the order they are visited.
254    pub fn get_locations(&self) -> impl Iterator<Item = impl Iterator<Item = Location> + '_> + '_ {
255        self.routes.iter().map(|route| route.tour.all_activities().map(|activity| activity.place.location))
256    }
257}