Skip to content

Commit

Permalink
Prototype of fast service objective
Browse files Browse the repository at this point in the history
Limited experimental implementation
  • Loading branch information
reinterpretcat committed Oct 31, 2023
1 parent d8c3a77 commit 9702ece
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 0 deletions.
107 changes: 107 additions & 0 deletions vrp-core/src/construction/features/fast_service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use super::*;
use crate::construction::enablers::calculate_travel;
use crate::models::solution::Activity;
use std::marker::PhantomData;

/// Creates a feature to prefer a fast servicing of jobs.
pub fn create_fast_service_feature<T: LoadOps>(
name: &str,
transport: Arc<dyn TransportCost + Send + Sync>,
activity: Arc<dyn ActivityCost + Send + Sync>,
) -> Result<Feature, GenericError> {
FeatureBuilder::default()
.with_name(name)
.with_objective(FastServiceObjective::<T>::new(transport, activity))
.build()
}

struct FastServiceObjective<T> {
transport: Arc<dyn TransportCost + Send + Sync>,
activity: Arc<dyn ActivityCost + Send + Sync>,
phantom: PhantomData<T>,
}

impl<T: LoadOps> Objective for FastServiceObjective<T> {
type Solution = InsertionContext;

fn fitness(&self, solution: &Self::Solution) -> f64 {
solution
.solution
.routes
.iter()
.flat_map(|route_ctx| {
let start_time = route_ctx.route().tour.start().unwrap().schedule.departure;
route_ctx
.route()
.tour
.all_activities()
.filter(|a| self.is_static_delivery(a))
.map(move |a| a.schedule.departure - start_time)
})
.sum::<Duration>() as Cost
}
}

impl<T: LoadOps> FeatureObjective for FastServiceObjective<T> {
fn estimate(&self, move_ctx: &MoveContext<'_>) -> Cost {
if let Some(cost) = self.estimate_job_service(move_ctx) {
cost
} else {
Cost::default()
}
}
}

impl<T: LoadOps> FastServiceObjective<T> {
fn new(transport: Arc<dyn TransportCost + Send + Sync>, activity: Arc<dyn ActivityCost + Send + Sync>) -> Self {
Self { transport, activity, phantom: Default::default() }
}

fn estimate_job_service(&self, move_ctx: &MoveContext<'_>) -> Option<Cost> {
let (route_ctx, activity_ctx) = match move_ctx {
MoveContext::Route { .. } => return None,
MoveContext::Activity { route_ctx, activity_ctx } => (route_ctx, activity_ctx),
};

let (_, (prev_to_tar_dur, tar_to_next_dur)) =
calculate_travel(route_ctx, activity_ctx, self.transport.as_ref());

// TODO add support for:
// - reloads
// - pickup jobs
// - p&d jobs

// handle static delivery only
if self.is_static_delivery(activity_ctx.target) {
let start_time = route_ctx.route().tour.start().unwrap().schedule.departure;

let arrival = activity_ctx.prev.schedule.departure + prev_to_tar_dur;
let departure = self.activity.estimate_departure(route_ctx.route(), activity_ctx.target, arrival);
let target_cost = departure - start_time;

let next_delta = if let Some(next) = activity_ctx.next {
let old_next_cost = next.schedule.arrival - start_time;

let arrival = departure + tar_to_next_dur;
let departure = self.activity.estimate_departure(route_ctx.route(), next, arrival);
let new_next_cost = departure - start_time;

new_next_cost - old_next_cost
} else {
Cost::default()
};

Some(target_cost + next_delta)
} else {
None
}
}

fn is_static_delivery(&self, activity: &Activity) -> bool {
activity
.job
.as_ref()
.and_then::<&Demand<T>, _>(|job| job.dimens.get_demand())
.map_or(false, |demand| demand.delivery.0.is_not_empty())
}
}
3 changes: 3 additions & 0 deletions vrp-core/src/construction/features/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use std::sync::Arc;
mod capacity;
pub use self::capacity::*;

mod fast_service;
pub use self::fast_service::*;

mod fleet_usage;
pub use self::fleet_usage::*;

Expand Down
7 changes: 7 additions & 0 deletions vrp-pragmatic/src/format/problem/goal_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ fn get_objective_features(
}
Objective::TourOrder => {
create_tour_order_soft_feature("tour_order", TOUR_ORDER_KEY, get_tour_order_fn())
},
Objective::FastService => {
if props.has_multi_dimen_capacity {
create_fast_service_feature::<MultiDimLoad>("fast_service", transport.clone(), activity.clone())
} else {
create_fast_service_feature::<SingleDimLoad>("fast_service", transport.clone(), activity.clone())
}
}
})
.collect()
Expand Down
4 changes: 4 additions & 0 deletions vrp-pragmatic/src/format/problem/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,10 @@ pub enum Objective {
/// An objective to control order of job activities in the tour.
#[serde(rename(deserialize = "tour-order", serialize = "tour-order"))]
TourOrder,

/// An objective to prefer jobs to be served as soon as possible.
#[serde(rename(deserialize = "fast-service", serialize = "fast-service"))]
FastService,
}

/// Specifies balance objective options. At the moment, it uses coefficient of variation as
Expand Down
1 change: 1 addition & 0 deletions vrp-pragmatic/src/validation/objectives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ fn check_e1601_duplicate_objectives(objectives: &[&Objective]) -> Result<(), For
BalanceDuration { .. } => acc.entry("balance-duration"),
CompactTour { .. } => acc.entry("compact-tour"),
TourOrder => acc.entry("tour-order"),
FastService => acc.entry("fast-service"),
}
.and_modify(|count| *count += 1)
.or_insert(1_usize);
Expand Down

0 comments on commit 9702ece

Please sign in to comment.