Skip to main content

vrp_pragmatic/format/solution/
initial_reader.rs

1#[cfg(test)]
2#[path = "../../../tests/unit/format/solution/initial_reader_test.rs"]
3mod initial_reader_test;
4
5use crate::format::solution::activity_matcher::{try_match_point_job, JobInfo};
6use crate::format::solution::Activity as FormatActivity;
7use crate::format::solution::Stop as FormatStop;
8use crate::format::solution::Tour as FormatTour;
9use crate::format::solution::{deserialize_solution, map_reason_code};
10use crate::format::{get_indices, CoordIndex, JobIndex, ShiftIndexDimension, VehicleTypeDimension};
11use crate::parse_time;
12use std::collections::{HashMap, HashSet};
13use std::io::{BufReader, Read};
14use std::sync::Arc;
15use vrp_core::construction::heuristics::UnassignmentInfo;
16use vrp_core::models::common::*;
17use vrp_core::models::problem::{Actor, Job, JobIdDimension, VehicleIdDimension};
18use vrp_core::models::solution::Tour as CoreTour;
19use vrp_core::models::solution::{Activity, Registry, Route};
20use vrp_core::prelude::*;
21
22type ActorKey = (String, String, usize);
23
24/// Reads initial solution from buffer.
25/// NOTE: Solution feasibility is not checked.
26pub fn read_init_solution<R: Read>(
27    solution: BufReader<R>,
28    problem: Arc<Problem>,
29    random: Arc<dyn Random>,
30) -> Result<Solution, GenericError> {
31    let solution = deserialize_solution(solution).map_err(|err| format!("cannot deserialize solution: {err}"))?;
32
33    let mut registry = Registry::new(&problem.fleet, random);
34    let mut added_jobs = HashSet::default();
35
36    let actor_index = registry.all().map(|actor| (get_actor_key(actor.as_ref()), actor)).collect::<HashMap<_, _>>();
37    let (job_index, coord_index) = get_indices(&problem.extras)?;
38
39    let routes =
40        solution.tours.iter().try_fold::<_, _, Result<_, GenericError>>(Vec::<_>::default(), |mut routes, tour| {
41            let actor_key = (tour.vehicle_id.clone(), tour.type_id.clone(), tour.shift_index);
42            let actor =
43                actor_index.get(&actor_key).ok_or_else(|| format!("cannot find vehicle for {actor_key:?}"))?.clone();
44            registry.use_actor(&actor);
45
46            let mut core_route = create_core_route(actor, tour)?;
47
48            tour.stops.iter().try_for_each(|stop| {
49                stop.activities().iter().try_for_each::<_, Result<_, GenericError>>(|activity| {
50                    try_insert_activity(
51                        &mut core_route,
52                        tour,
53                        stop,
54                        activity,
55                        job_index.as_ref(),
56                        coord_index.as_ref(),
57                        &mut added_jobs,
58                    )
59                })
60            })?;
61
62            routes.push(core_route);
63
64            Ok(routes)
65        })?;
66
67    let mut unassigned = solution
68        .unassigned
69        .unwrap_or_default()
70        .iter()
71        .try_fold::<Vec<_>, _, Result<_, GenericError>>(Default::default(), |mut acc, unassigned_job| {
72            let job = job_index
73                .get(&unassigned_job.job_id)
74                .cloned()
75                .ok_or_else(|| format!("cannot get job id for: {unassigned_job:?}"))?;
76            // NOTE we take the first reason only and map it to simple variant
77            let code = unassigned_job
78                .reasons
79                .first()
80                .map(|reason| UnassignmentInfo::Simple(map_reason_code(&reason.code)))
81                .ok_or_else(|| format!("cannot get reason for: {unassigned_job:?}"))?;
82
83            added_jobs.insert(job.clone());
84            acc.push((job, code));
85
86            Ok(acc)
87        })?;
88
89    unassigned.extend(
90        problem
91            .jobs
92            .all()
93            .iter()
94            .filter(|job| !added_jobs.contains(job))
95            .map(|job| (job.clone(), UnassignmentInfo::Unknown)),
96    );
97
98    Ok(Solution { cost: Cost::default(), registry, routes, unassigned, telemetry: None })
99}
100
101fn try_insert_activity(
102    route: &mut Route,
103    tour: &FormatTour,
104    stop: &FormatStop,
105    activity: &FormatActivity,
106    job_index: &JobIndex,
107    coord_index: &CoordIndex,
108    added_jobs: &mut HashSet<Job>,
109) -> Result<(), GenericError> {
110    if activity.commute.is_some() {
111        return Err("commute property in initial solution is not supported".into());
112    }
113
114    let stop = match stop {
115        FormatStop::Transit(_) => return Err("transit property in initial solution is not yet supported".into()),
116        FormatStop::Point(stop) => stop,
117    };
118
119    if let Some(JobInfo(job, single, place, time)) = try_match_point_job(tour, stop, activity, job_index, coord_index)?
120    {
121        let is_inserted = added_jobs.insert(job.clone());
122        if !is_inserted && matches!(job, Job::Single(_)) {
123            return Err(format!(
124                "potential double assignment for single job '{:?}', matched job id: '{:?}'; try to use a different tag as a discriminator",
125                activity.job_id,
126                job.dimens().get_job_id()
127            )
128            .into());
129        }
130
131        route.tour.insert_last(Activity {
132            place,
133            schedule: Schedule { arrival: time.start, departure: time.end },
134            job: Some(single),
135            commute: None,
136        });
137    } else if activity.activity_type != "departure" && activity.activity_type != "arrival" {
138        return Err(
139            format!("cannot match activity with job id '{}' in tour: '{}'", activity.job_id, tour.vehicle_id).into()
140        );
141    }
142
143    Ok(())
144}
145
146fn get_actor_key(actor: &Actor) -> ActorKey {
147    let dimens = &actor.vehicle.dimens;
148
149    let vehicle_id = dimens.get_vehicle_id().cloned().expect("cannot get vehicle id!");
150    let type_id = dimens.get_vehicle_type().cloned().expect("cannot get type id!");
151    let shift_index = dimens.get_shift_index().copied().expect("cannot get shift index!");
152
153    (vehicle_id, type_id, shift_index)
154}
155
156fn create_core_route(actor: Arc<Actor>, format_tour: &FormatTour) -> Result<Route, GenericError> {
157    let mut core_tour = CoreTour::new(&actor);
158
159    // NOTE this is necessary to keep departure time optimization
160    let set_activity_time = |format_stop: &FormatStop,
161                             format_activity: &FormatActivity,
162                             core_activity: &mut Activity|
163     -> Result<(), GenericError> {
164        let time = &format_stop.schedule();
165        let (arrival, departure) = format_activity
166            .time
167            .as_ref()
168            .map_or((&time.arrival, &time.departure), |interval| (&interval.start, &interval.end));
169
170        core_activity.schedule.arrival = parse_time(arrival);
171        core_activity.schedule.departure = parse_time(departure);
172
173        Ok(())
174    };
175
176    let start_stop = format_tour.stops.first().ok_or_else(|| "empty tour in init solution".to_string())?;
177    let start_activity = start_stop.activities().first().ok_or_else(|| "start stop has no activities".to_string())?;
178    let core_start = core_tour.all_activities_mut().next().expect("cannot get start activity from core tour");
179
180    set_activity_time(start_stop, start_activity, core_start)?;
181
182    if core_tour.end().is_some() {
183        let end_stop = format_tour.stops.last().unwrap();
184        let end_activity = end_stop.activities().first().ok_or_else(|| "end stop has no activities".to_string())?;
185        let core_end = core_tour.all_activities_mut().last().unwrap();
186
187        set_activity_time(end_stop, end_activity, core_end)?;
188    }
189
190    Ok(Route { actor, tour: core_tour })
191}