vrp_core/construction/heuristics/
context.rs

1#[cfg(test)]
2#[path = "../../../tests/unit/construction/heuristics/context_test.rs"]
3mod context_test;
4
5use crate::construction::enablers::{TotalDistanceTourState, TotalDurationTourState};
6use crate::construction::heuristics::factories::*;
7use crate::models::common::Cost;
8use crate::models::problem::*;
9use crate::models::solution::*;
10use crate::models::GoalContext;
11use crate::models::{Problem, Solution};
12use crate::prelude::ViolationCode;
13use rosomaxa::evolution::TelemetryMetrics;
14use rosomaxa::prelude::*;
15use rustc_hash::FxHasher;
16use std::any::{Any, TypeId};
17use std::collections::{HashMap, HashSet};
18use std::fmt::{Debug, Formatter};
19use std::hash::BuildHasherDefault;
20use std::ops::Deref;
21use std::sync::Arc;
22
23/// A context which contains information needed for heuristic and metaheuristic.
24pub struct InsertionContext {
25    /// Original problem.
26    pub problem: Arc<Problem>,
27
28    /// Solution context: discovered solution.
29    pub solution: SolutionContext,
30
31    /// Information about environment.
32    pub environment: Arc<Environment>,
33}
34
35impl InsertionContext {
36    /// Creates insertion context for given problem with unassigned jobs.
37    pub fn new(problem: Arc<Problem>, environment: Arc<Environment>) -> Self {
38        create_insertion_context(problem, environment)
39    }
40
41    /// Creates insertion context for given problem with empty solution.
42    pub fn new_empty(problem: Arc<Problem>, environment: Arc<Environment>) -> Self {
43        create_empty_insertion_context(problem, environment)
44    }
45
46    /// Creates insertion context from existing solution.
47    pub fn new_from_solution(
48        problem: Arc<Problem>,
49        solution: (Solution, Option<Cost>),
50        environment: Arc<Environment>,
51    ) -> Self {
52        let mut ctx = create_insertion_context_from_solution(problem, solution, environment);
53        ctx.restore();
54
55        ctx
56    }
57
58    /// Gets total cost of the solution.
59    ///
60    /// Returns None if cost cannot be calculate as the context is in non-consistent state.
61    pub fn get_total_cost(&self) -> Option<Cost> {
62        let get_cost = |costs: &Costs, distance: Float, duration: Float| {
63            costs.fixed
64                + costs.per_distance * distance
65                // NOTE this is incorrect when timing costs are different: fitness value will be
66                // different from actual cost. However we accept this so far as it is simpler for
67                // implementation and pragmatic format does not expose this feature
68                // .
69                // TODO calculate actual cost
70                + costs.per_driving_time.max(costs.per_service_time).max(costs.per_waiting_time) * duration
71        };
72
73        self.solution.routes.iter().try_fold(Cost::default(), |acc, route_ctx| {
74            let actor = &route_ctx.route.actor;
75            let distance = route_ctx.state.get_total_distance();
76            let duration = route_ctx.state.get_total_duration();
77
78            distance.zip(duration).map(|(&distance, &duration)| {
79                acc + get_cost(&actor.vehicle.costs, distance, duration)
80                    + get_cost(&actor.driver.costs, distance, duration)
81            })
82        })
83    }
84
85    /// Restores valid context state.
86    pub fn restore(&mut self) {
87        self.problem.goal.accept_solution_state(&mut self.solution);
88        self.solution.remove_empty_routes();
89    }
90}
91
92impl HeuristicSolution for InsertionContext {
93    fn fitness(&self) -> impl Iterator<Item = Float> {
94        self.problem.goal.fitness(self)
95    }
96
97    fn deep_copy(&self) -> Self {
98        InsertionContext {
99            problem: self.problem.clone(),
100            solution: self.solution.deep_copy(),
101            environment: self.environment.clone(),
102        }
103    }
104}
105
106impl Debug for InsertionContext {
107    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
108        f.debug_struct(short_type_name::<Self>())
109            .field("problem", &self.problem)
110            .field("solution", &self.solution)
111            .finish_non_exhaustive()
112    }
113}
114
115/// Keeps information about unassigned reason code.
116#[derive(Clone, Debug)]
117pub enum UnassignmentInfo {
118    /// No code is available.
119    Unknown,
120    /// Only single code is available.
121    Simple(ViolationCode),
122    /// A collection of actor-code pairs is available.
123    Detailed(Vec<(Arc<Actor>, ViolationCode)>),
124}
125
126/// Contains information regarding discovered solution.
127pub struct SolutionContext {
128    /// List of jobs which require permanent assignment.
129    pub required: Vec<Job>,
130
131    /// List of jobs which at the moment does not require assignment and might be ignored.
132    pub ignored: Vec<Job>,
133
134    /// Map of jobs which cannot be assigned and within reason code.
135    pub unassigned: HashMap<Job, UnassignmentInfo>,
136
137    /// Specifies jobs which should not be affected by ruin.
138    pub locked: HashSet<Job>,
139
140    /// Set of routes within their state.
141    pub routes: Vec<RouteContext>,
142
143    /// Keeps track of used routes and resources.
144    pub registry: RegistryContext,
145
146    /// A collection of data associated with a solution.
147    pub state: SolutionState,
148}
149
150impl SolutionContext {
151    /// Returns number of jobs considered by solution context.
152    /// NOTE: the amount can be different for a partially solved problem from an original problem.
153    pub fn get_jobs_amount(&self) -> usize {
154        let assigned = self.routes.iter().map(|route_ctx| route_ctx.route().tour.job_count()).sum::<usize>();
155
156        let required = self.required.iter().filter(|job| !self.unassigned.contains_key(*job)).count();
157
158        self.unassigned.len() + required + self.ignored.len() + assigned
159    }
160
161    /// Keep routes for which given predicate returns true.
162    pub fn keep_routes(&mut self, predicate: &dyn Fn(&RouteContext) -> bool) {
163        // as for 1.68, drain_filter is not yet stable (see https://github.com/rust-lang/rust/issues/43244)
164        let (keep, remove): (Vec<_>, Vec<_>) = self.routes.drain(0..).partition(predicate);
165
166        remove.into_iter().for_each(|route_ctx| {
167            assert!(self.registry.free_route(route_ctx));
168        });
169
170        self.routes = keep;
171    }
172
173    /// Removes empty routes from solution context.
174    pub(crate) fn remove_empty_routes(&mut self) {
175        self.keep_routes(&|route_ctx| route_ctx.route().tour.has_jobs())
176    }
177
178    /// Creates a deep copy of `SolutionContext`.
179    pub fn deep_copy(&self) -> Self {
180        Self {
181            required: self.required.clone(),
182            ignored: self.ignored.clone(),
183            unassigned: self.unassigned.clone(),
184            locked: self.locked.clone(),
185            routes: self.routes.iter().map(|rc| rc.deep_copy()).collect(),
186            registry: self.registry.deep_copy(),
187            state: self.state.clone(),
188        }
189    }
190}
191
192impl Debug for SolutionContext {
193    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
194        f.debug_struct(short_type_name::<Self>())
195            .field("required", &self.required.len())
196            .field("locked", &self.locked.len())
197            .field("routes", &self.routes)
198            .field("unassigned", &self.unassigned)
199            .finish_non_exhaustive()
200    }
201}
202
203impl From<InsertionContext> for Solution {
204    fn from(insertion_ctx: InsertionContext) -> Self {
205        (insertion_ctx, None).into()
206    }
207}
208
209impl From<(InsertionContext, Option<TelemetryMetrics>)> for Solution {
210    fn from(value: (InsertionContext, Option<TelemetryMetrics>)) -> Self {
211        let (insertion_ctx, telemetry) = value;
212        let cost = insertion_ctx.get_total_cost().unwrap_or_default();
213        let solution_ctx = insertion_ctx.solution;
214
215        Solution {
216            cost,
217            registry: solution_ctx.registry.resources().deep_copy(),
218            routes: solution_ctx.routes.iter().map(|rc| rc.route.deep_copy()).collect(),
219            unassigned: solution_ctx
220                .unassigned
221                .iter()
222                .map(|(job, code)| (job.clone(), code.clone()))
223                .chain(solution_ctx.required.iter().map(|job| (job.clone(), UnassignmentInfo::Unknown)))
224                .collect(),
225            telemetry,
226        }
227    }
228}
229
230/// Keeps track of some solution state values.
231#[derive(Clone, Default)]
232pub struct SolutionState {
233    index: HashMap<TypeId, Arc<dyn Any + Send + Sync>, BuildHasherDefault<FxHasher>>,
234}
235
236impl SolutionState {
237    /// Gets the value from solution state using the key type provided.
238    pub fn get_value<K: 'static, V: Send + Sync + 'static>(&self) -> Option<&V> {
239        self.index.get(&TypeId::of::<K>()).and_then(|any| any.downcast_ref::<V>())
240    }
241    /// Sets the value to solution state using the key type provided.
242    pub fn set_value<K: 'static, V: 'static + Sync + Send>(&mut self, value: V) {
243        self.index.insert(TypeId::of::<K>(), Arc::new(value));
244    }
245}
246
247/// Specifies insertion context for route.
248pub struct RouteContext {
249    route: Route,
250    state: RouteState,
251    cache: RouteCache,
252}
253
254/// Provides a way to associate arbitrary data within route or/and activity.
255/// NOTE: do not put any state that is not refreshed after `accept_route_state` call: it will be
256/// wiped out at some point.
257#[derive(Clone)]
258pub struct RouteState {
259    index: HashMap<TypeId, Arc<dyn Any + Send + Sync>, BuildHasherDefault<FxHasher>>,
260}
261
262impl RouteContext {
263    /// Creates a new instance of `RouteContext`.
264    pub fn new(actor: Arc<Actor>) -> Self {
265        let tour = Tour::new(&actor);
266        Self::new_with_state(Route { actor, tour }, RouteState::default())
267    }
268
269    /// Creates a new instance of `RouteContext` with arguments provided.
270    pub fn new_with_state(route: Route, state: RouteState) -> Self {
271        RouteContext { route, state, cache: RouteCache { is_stale: true } }
272    }
273
274    /// Creates a deep copy of `RouteContext`.
275    pub fn deep_copy(&self) -> Self {
276        let new_route = Route { actor: self.route.actor.clone(), tour: self.route.tour.deep_copy() };
277        let new_state = self.state.clone();
278
279        RouteContext { route: new_route, state: new_state, cache: RouteCache { is_stale: self.cache.is_stale } }
280    }
281
282    /// Returns a reference to route.
283    pub fn route(&self) -> &Route {
284        &self.route
285    }
286
287    /// Returns a reference to state.
288    pub fn state(&self) -> &RouteState {
289        &self.state
290    }
291
292    /// Unwraps given `RouteContext` as pair of mutable references.
293    /// Marks context as stale.
294    pub fn as_mut(&mut self) -> (&mut Route, &mut RouteState) {
295        self.mark_stale(true);
296        (&mut self.route, &mut self.state)
297    }
298
299    /// Returns mutable reference to used `Route`.
300    /// Marks context as stale.
301    pub fn route_mut(&mut self) -> &mut Route {
302        self.mark_stale(true);
303        &mut self.route
304    }
305
306    /// Returns mutable reference to used `RouteState`.
307    /// Marks context as stale.
308    pub fn state_mut(&mut self) -> &mut RouteState {
309        self.mark_stale(true);
310        &mut self.state
311    }
312
313    /// Returns true if context is stale. Context is marked stale when it is accessed by `mut`
314    /// methods. A general motivation of the flag is to avoid recalculating non-changed states.
315    pub fn is_stale(&self) -> bool {
316        self.cache.is_stale
317    }
318
319    /// Marks context stale or resets the flag.
320    pub(crate) fn mark_stale(&mut self, is_stale: bool) {
321        self.cache.is_stale = is_stale;
322    }
323}
324
325impl PartialEq<RouteContext> for RouteContext {
326    fn eq(&self, other: &RouteContext) -> bool {
327        std::ptr::eq(self.route.actor.deref(), other.route.actor.deref())
328    }
329}
330
331impl Eq for RouteContext {}
332
333impl Debug for RouteContext {
334    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
335        f.debug_struct(short_type_name::<Self>())
336            .field("route", &self.route)
337            .field("is_stale", &self.is_stale())
338            .finish_non_exhaustive()
339    }
340}
341
342impl Default for RouteState {
343    fn default() -> RouteState {
344        RouteState { index: HashMap::with_capacity_and_hasher(4, BuildHasherDefault::<FxHasher>::default()) }
345    }
346}
347
348impl RouteState {
349    /// Gets a value associated with the tour using `K` type as a key.
350    pub fn get_tour_state<K: 'static, V: Send + Sync + 'static>(&self) -> Option<&V> {
351        self.index.get(&TypeId::of::<K>()).and_then(|any| any.downcast_ref::<V>())
352    }
353
354    /// Sets the value associated with the tour using `K` type as a key.
355    pub fn set_tour_state<K: 'static, V: Send + Sync + 'static>(&mut self, value: V) {
356        self.index.insert(TypeId::of::<K>(), Arc::new(value));
357    }
358
359    /// Removes the value associated with the tour using `K` type as a key. Returns true if the
360    /// value was present.
361    pub fn remove_tour_state<K: 'static>(&mut self) -> bool {
362        self.index.remove(&TypeId::of::<K>()).is_some()
363    }
364
365    /// Gets value associated with a key converted to a given type.
366    pub fn get_activity_state<K: 'static, V: Send + Sync + 'static>(&self, activity_idx: usize) -> Option<&V> {
367        self.index
368            .get(&TypeId::of::<K>())
369            .and_then(|s| s.downcast_ref::<Vec<V>>())
370            .and_then(|activity_states| activity_states.get(activity_idx))
371    }
372
373    /// Gets values associated with key and activities.
374    pub fn get_activity_states<K: 'static, V: Send + Sync + 'static>(&self) -> Option<&Vec<V>> {
375        self.index.get(&TypeId::of::<K>()).and_then(|s| s.downcast_ref::<Vec<V>>())
376    }
377
378    /// Adds values associated with activities.
379    pub fn set_activity_states<K: 'static, V: Send + Sync + 'static>(&mut self, values: Vec<V>) {
380        self.index.insert(TypeId::of::<K>(), Arc::new(values));
381    }
382
383    /// Clear all states.
384    pub fn clear(&mut self) {
385        self.index.clear();
386    }
387}
388
389struct RouteCache {
390    is_stale: bool,
391}
392
393/// Keeps track on how routes are used.
394pub struct RegistryContext {
395    registry: Registry,
396    /// Index keeps track of actor mapping to empty route prototypes.
397    index: HashMap<Arc<Actor>, Arc<RouteContext>>,
398}
399
400impl RegistryContext {
401    /// Creates a new instance of `RouteRegistry`.
402    pub fn new(goal: &GoalContext, registry: Registry) -> Self {
403        let index = registry
404            .all()
405            .map(|actor| {
406                let mut route_ctx = RouteContext::new(actor.clone());
407                // NOTE: need to initialize empty route with states
408                goal.accept_route_state(&mut route_ctx);
409
410                (actor, Arc::new(route_ctx))
411            })
412            .collect();
413        Self { registry, index }
414    }
415
416    /// Returns underlying registry.
417    pub fn resources(&self) -> &Registry {
418        &self.registry
419    }
420
421    /// Returns next route available for insertion.
422    pub fn next_route(&self) -> impl Iterator<Item = &RouteContext> {
423        self.registry.next().map(move |actor| self.index[&actor].as_ref())
424    }
425
426    /// Gets route for given actor and marks it as used.
427    /// Returns None if actor is already in use.
428    /// NOTE: you need to call free route to make it to be available again.
429    pub fn get_route(&mut self, actor: &Arc<Actor>) -> Option<RouteContext> {
430        self.registry.use_actor(actor).then(|| self.index.get(actor).map(|route_ctx| route_ctx.deep_copy())).flatten()
431    }
432
433    /// Marks route as used.
434    /// Returns true if route was not used before.
435    pub fn use_route(&mut self, route_ctx: &RouteContext) -> bool {
436        self.registry.use_actor(&route_ctx.route.actor)
437    }
438
439    /// Return back route to be reused again.
440    /// Returns whether the route was not present in the registry.
441    pub fn free_route(&mut self, route: RouteContext) -> bool {
442        self.registry.free_actor(&route.route.actor)
443    }
444
445    /// Creates a deep copy of `RegistryContext`.
446    pub fn deep_copy(&self) -> Self {
447        Self {
448            registry: self.registry.deep_copy(),
449            index: self.index.iter().map(|(actor, route_ctx)| (actor.clone(), route_ctx.clone())).collect(),
450        }
451    }
452
453    /// Creates a deep sliced copy of `RegistryContext` keeping only specific actors data.
454    pub fn deep_slice(&self, filter: impl Fn(&Actor) -> bool) -> Self {
455        let index = self
456            .index
457            .iter()
458            .filter(|(actor, _)| filter(actor.as_ref()))
459            .map(|(actor, route_ctx)| (actor.clone(), route_ctx.clone()))
460            .collect();
461        Self { registry: self.registry.deep_slice(filter), index }
462    }
463}
464
465/// Specifies insertion context for activity.
466pub struct ActivityContext<'a> {
467    /// Activity insertion index.
468    pub index: usize,
469
470    /// Previous activity.
471    pub prev: &'a Activity,
472
473    /// Target activity.
474    pub target: &'a Activity,
475
476    /// Next activity. Absent if tour is open and target activity inserted last.
477    pub next: Option<&'a Activity>,
478}
479
480/// A local move context.
481pub enum MoveContext<'a> {
482    /// Evaluation of job insertion into the given route.
483    Route {
484        /// A solution context.
485        solution_ctx: &'a SolutionContext,
486        /// A route context where job supposed to be inserted.
487        route_ctx: &'a RouteContext,
488        /// A job which being evaluated.
489        job: &'a Job,
490    },
491    /// Evaluation of activity insertion into the given position.
492    Activity {
493        /// A route context where activity supposed to be inserted.
494        route_ctx: &'a RouteContext,
495        /// An activity context.
496        activity_ctx: &'a ActivityContext<'a>,
497    },
498}
499
500impl<'a> MoveContext<'a> {
501    /// Creates a route variant for `MoveContext`.
502    pub fn route(solution_ctx: &'a SolutionContext, route_ctx: &'a RouteContext, job: &'a Job) -> MoveContext<'a> {
503        MoveContext::Route { solution_ctx, route_ctx, job }
504    }
505
506    /// Creates a route variant for `MoveContext`.
507    pub fn activity(route_ctx: &'a RouteContext, activity_ctx: &'a ActivityContext) -> MoveContext<'a> {
508        MoveContext::Activity { route_ctx, activity_ctx }
509    }
510}