Skip to content

Commit

Permalink
Commit progress on fast service objective
Browse files Browse the repository at this point in the history
  • Loading branch information
reinterpretcat committed Dec 12, 2023
1 parent 7705c1e commit 556104a
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 22 deletions.
38 changes: 22 additions & 16 deletions vrp-core/src/construction/features/fast_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ impl<T: LoadOps> Objective for FastServiceObjective<T> {
.routes
.iter()
.flat_map(|route_ctx| {
route_ctx.route().tour.jobs().map(|job| match job {
Job::Single(_) => self.estimate_single_job(route_ctx, job),
Job::Multi(_) => self.estimate_multi_job(route_ctx, job),
})
route_ctx.route().tour.jobs().filter(|job| !self.route_intervals.is_marker_job(job)).map(
|job| match job {
Job::Single(_) => self.estimate_single_job(route_ctx, job),
Job::Multi(_) => self.estimate_multi_job(route_ctx, job),
},
)
})
.sum::<Cost>()
}
Expand All @@ -75,14 +77,15 @@ impl<T: LoadOps> FeatureObjective for FastServiceObjective<T> {

let activity_idx = activity_ctx.index;

let single = if let Some(ref single) = activity_ctx.target.job {
single
} else {
return self.get_departure(route_ctx, activity_ctx) - self.get_start_time(route_ctx, activity_idx);
};
let (single, job) =
if let Some((single, job)) = activity_ctx.target.job.as_ref().zip(activity_ctx.target.retrieve_job()) {
(single, job)
} else {
return self.get_departure(route_ctx, activity_ctx) - self.get_start_time(route_ctx, activity_idx);
};

// NOTE: for simplicity, we ignore impact on already inserted jobs on local objective level
match get_time_interval_type::<T>(single.as_ref()) {
match get_time_interval_type::<T>(&job, single.as_ref()) {
TimeIntervalType::FromStart => {
self.get_departure(route_ctx, activity_ctx) - self.get_start_time(route_ctx, activity_idx)
}
Expand Down Expand Up @@ -173,7 +176,7 @@ impl<T: LoadOps> FastServiceObjective<T> {
let activity_idx = tour.index(job).expect("cannot find index for job");
let activity = &tour[activity_idx];

(match get_time_interval_type::<T>(single) {
(match get_time_interval_type::<T>(job, single) {
TimeIntervalType::FromStart => activity.schedule.departure - self.get_start_time(route_ctx, activity_idx),
TimeIntervalType::ToEnd => self.get_end_time(route_ctx, activity_idx) - activity.schedule.departure,
TimeIntervalType::FromStartToEnd => {
Expand Down Expand Up @@ -244,14 +247,17 @@ impl FeatureState for FastServiceState {
}
}

fn get_time_interval_type<T: LoadOps>(single: &Single) -> TimeIntervalType {
fn get_time_interval_type<T: LoadOps>(job: &Job, single: &Single) -> TimeIntervalType {
if job.as_multi().is_some() {
return TimeIntervalType::FromFirstToLast;
}

let demand: &Demand<T> =
if let Some(demand) = single.dimens.get_demand() { demand } else { return TimeIntervalType::FromStart };

match (demand.delivery.0.is_not_empty(), demand.pickup.0.is_not_empty()) {
(true, true) => TimeIntervalType::FromStartToEnd,
(true, _) => TimeIntervalType::FromStart,
(_, true) => TimeIntervalType::ToEnd,
_ => TimeIntervalType::FromFirstToLast,
(true, false) => TimeIntervalType::FromStart,
(false, true) => TimeIntervalType::ToEnd,
_ => TimeIntervalType::FromStartToEnd,
}
}
16 changes: 16 additions & 0 deletions vrp-core/tests/helpers/models/solution/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,22 @@ impl RouteBuilder {
}
}

#[derive(Default)]
pub struct RouteStateBuilder {
state: RouteState,
}

impl RouteStateBuilder {
pub fn add_route_state<T: Send + Sync + 'static>(&mut self, key: i32, value: T) -> &mut Self {
self.state.put_route_state(key, value);
self
}

pub fn build(&mut self) -> RouteState {
std::mem::replace(&mut self.state, RouteState::default())
}
}

pub struct ActivityBuilder(Activity);

impl Default for ActivityBuilder {
Expand Down
96 changes: 90 additions & 6 deletions vrp-core/tests/unit/construction/features/fast_service_test.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use crate::construction::enablers::NoRouteIntervals;
use crate::helpers::models::problem::*;
use crate::helpers::models::solution::*;

Expand All @@ -15,17 +16,16 @@ fn create_test_feature(route_intervals: Arc<dyn RouteIntervals + Send + Sync>) -
.unwrap()
}

mod estimation {
fn create_test_feature_no_reload() -> Feature {
create_test_feature(Arc::new(NoRouteIntervals::default()))
}

mod local_estimation {
use super::*;
use crate::construction::enablers::NoRouteIntervals;
use crate::helpers::construction::features::{create_simple_demand, create_simple_dynamic_demand};
use crate::models::solution::Activity;
use std::iter::once;

fn create_test_feature_no_reload() -> Feature {
create_test_feature(Arc::new(NoRouteIntervals::default()))
}

fn run_estimation_test_case<T>(test_case: InsertionTestCase<T>, job: Arc<Single>, activities: Vec<Activity>) {
let InsertionTestCase { target_index, target_location, end_time, expected_cost, .. } = test_case;
let (objective, state) = {
Expand Down Expand Up @@ -144,3 +144,87 @@ mod estimation {
drop(multi);
}
}

mod global_estimation {
use super::*;
use crate::construction::enablers::get_route_intervals;
use crate::helpers::models::domain::{create_empty_insertion_context, create_empty_solution_context};
use crate::models::solution::Route;

#[test]
fn can_get_solution_fitness() {
let objective = create_test_feature_no_reload().objective.expect("no objective");
let route_ctx = RouteContextBuilder::default()
.with_route(
RouteBuilder::default()
.add_activity(ActivityBuilder::with_location(10).build())
.add_activity(ActivityBuilder::with_location(20).build())
.build(),
)
.build();
let insertion_ctx = InsertionContext {
solution: SolutionContext { routes: vec![route_ctx], ..create_empty_solution_context() },
..create_empty_insertion_context()
};

let fitness = objective.fitness(&insertion_ctx);

assert_eq!(fitness, 30.)
}

#[test]
fn can_get_solution_fitness_with_reload() {
const STATE_KEY: StateKey = 0;
const INTERVAL_LOCATION: Location = 15;

struct FakeRouteIntervals;

impl RouteIntervals for FakeRouteIntervals {
fn is_marker_job(&self, job: &Job) -> bool {
job.places().any(|p| p.location == Some(INTERVAL_LOCATION))
}

fn is_marker_assignable(&self, _: &Route, _: &Job) -> bool {
unreachable!()
}

fn is_new_interval_needed(&self, _: &RouteContext) -> bool {
unreachable!()
}

fn get_marker_intervals<'a>(&self, route_ctx: &'a RouteContext) -> Option<&'a Vec<(usize, usize)>> {
route_ctx.state().get_route_state::<Vec<(usize, usize)>>(STATE_KEY)
}

fn get_interval_key(&self) -> Option<StateKey> {
unreachable!()
}

fn update_solution_intervals(&self, _: &mut SolutionContext) {
unreachable!()
}
}

let objective = create_test_feature(Arc::new(FakeRouteIntervals)).objective.expect("no objective");
let route = RouteBuilder::default()
.add_activity(ActivityBuilder::with_location(10).build())
.add_activity(ActivityBuilder::with_location(INTERVAL_LOCATION).build())
.add_activity(ActivityBuilder::with_location(20).build())
.build();
let state = RouteStateBuilder::default()
.add_route_state(
STATE_KEY,
get_route_intervals(&route, |activity| activity.place.location == INTERVAL_LOCATION),
)
.build();
let route_ctx = RouteContextBuilder::default().with_route(route).with_state(state).build();
let insertion_ctx = InsertionContext {
solution: SolutionContext { routes: vec![route_ctx], ..create_empty_solution_context() },
..create_empty_insertion_context()
};

let fitness = objective.fitness(&insertion_ctx);

assert_eq!(fitness, 15.)
}
}

0 comments on commit 556104a

Please sign in to comment.