-
Notifications
You must be signed in to change notification settings - Fork 1
feat(shifts): add shift time from first job param #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "permissions": { | ||
| "allow": [ | ||
| "Bash(git stash *)" | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,11 +59,20 @@ fn check_shift_limits(context: &CheckerContext) -> GenericResult<()> { | |
| fn check_shift_time(context: &CheckerContext) -> GenericResult<()> { | ||
| context.solution.tours.iter().try_for_each::<_, GenericResult<_>>(|tour| { | ||
| let vehicle = context.get_vehicle(&tour.vehicle_id)?; | ||
| let allow_out_of_hours_depot_travel = | ||
| vehicle.limits.as_ref().and_then(|limits| limits.allow_out_of_hours_depot_travel).unwrap_or(false); | ||
|
|
||
| let (start, end) = tour.stops.first().zip(tour.stops.last()).ok_or("empty tour")?; | ||
|
|
||
| let departure = parse_time(&start.schedule().departure); | ||
| let arrival = parse_time(&end.schedule().arrival); | ||
| // With `allow_out_of_hours_depot_travel`, the shift start applies to the first-job arrival | ||
| // rather than to the depot departure. The end side is NOT relaxed (depot return must | ||
| // still sit within the shift end). | ||
| let effective_start = if allow_out_of_hours_depot_travel && tour.stops.len() > 2 { | ||
| parse_time(&tour.stops[1].schedule().arrival) | ||
| } else { | ||
| parse_time(&start.schedule().departure) | ||
| }; | ||
|
Comment on lines
+70
to
+74
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This validates the solution after we have solved. Check effective start on either the 1st or 2nd stop depending on if the flag is enabled |
||
| let effective_end = parse_time(&end.schedule().arrival); | ||
|
|
||
| let has_match = vehicle | ||
| .shifts | ||
|
|
@@ -74,7 +83,7 @@ fn check_shift_time(context: &CheckerContext) -> GenericResult<()> { | |
|
|
||
| (start, end) | ||
| }) | ||
| .any(|(start, end)| departure >= start && arrival <= end); | ||
| .any(|(start, end)| effective_start >= start && effective_end <= end); | ||
|
|
||
| if !has_match { | ||
| Err(format!( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,7 @@ use crate::format::UnknownLocationFallback; | |
| use crate::get_unique_locations; | ||
| use crate::utils::get_approx_transportation; | ||
| use std::collections::HashSet; | ||
| use vrp_core::construction::enablers::create_typed_actor_groups; | ||
| use vrp_core::construction::enablers::{FirstJobArrivalFloorDimension, create_typed_actor_groups}; | ||
| use vrp_core::construction::features::{VehicleCapacityDimension, VehicleSkillsDimension}; | ||
| use vrp_core::models::common::*; | ||
| use vrp_core::models::problem::*; | ||
|
|
@@ -111,31 +111,36 @@ pub(super) fn read_fleet(api_problem: &ApiProblem, props: &ProblemProperties, co | |
| let index = *profile_indices.get(&vehicle.profile.matrix).unwrap(); | ||
| let profile = Profile::new(index, vehicle.profile.scale); | ||
|
|
||
| let tour_size = vehicle.limits.as_ref().and_then(|l| l.tour_size); | ||
| let tour_size = vehicle.limits.as_ref().and_then(|limits| limits.tour_size); | ||
|
|
||
| let allow_out_of_hours_depot_travel = | ||
| vehicle.limits.as_ref().and_then(|limits| limits.allow_out_of_hours_depot_travel).unwrap_or(false); | ||
|
|
||
| for (shift_index, shift) in vehicle.shifts.iter().enumerate() { | ||
| let start = { | ||
| let location = coord_index.get_by_loc(&shift.start.location).unwrap(); | ||
| let earliest = parse_time(&shift.start.earliest); | ||
| let latest = shift.start.latest.as_ref().map(|time| parse_time(time)); | ||
| (location, earliest, latest) | ||
| let shift_start_earliest = parse_time(&shift.start.earliest); | ||
| let shift_start_latest = shift.start.latest.as_ref().map(|time| parse_time(time)); | ||
|
|
||
| // When `allow_out_of_hours_depot_travel` is set, the shift start applies to the first | ||
| // job's arrival rather than to the depot departure. Relax the depot start window; | ||
| // the floor is re-applied in `apply_first_job_arrival_floor` (via `FirstJobArrivalFloor` | ||
| // dim) and `start.latest` is enforced in the travel-limit constraint. The end side | ||
| // is NOT relaxed — the back-propagated `latest_arrival` bounds the last-job departure. | ||
| let start_time = if allow_out_of_hours_depot_travel { | ||
| TimeInterval { earliest: None, latest: None } | ||
| } else { | ||
| TimeInterval { earliest: Some(shift_start_earliest), latest: shift_start_latest } | ||
| }; | ||
|
Comment on lines
+128
to
132
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This removes the depots time window if we allow_out_of_hours_travel as the solver no longer enforces the shift window at the depo stop. The insertions and departure calculation handle this instead now |
||
| let start_location = coord_index.get_by_loc(&shift.start.location).unwrap(); | ||
|
|
||
| let end = shift.end.as_ref().map(|end| { | ||
| let location = coord_index.get_by_loc(&end.location).unwrap(); | ||
| let time = parse_time(&end.latest); | ||
| (location, time) | ||
| let time = TimeInterval { earliest: None, latest: Some(parse_time(&end.latest)) }; | ||
| VehiclePlace { location, time } | ||
| }); | ||
|
|
||
| let details = vec![VehicleDetail { | ||
| start: Some(VehiclePlace { | ||
| location: start.0, | ||
| time: TimeInterval { earliest: Some(start.1), latest: start.2 }, | ||
| }), | ||
| end: end.map(|(location, time)| VehiclePlace { | ||
| location, | ||
| time: TimeInterval { earliest: None, latest: Some(time) }, | ||
| }), | ||
| start: Some(VehiclePlace { location: start_location, time: start_time }), | ||
| end, | ||
| }]; | ||
|
|
||
| vehicle.vehicle_ids.iter().for_each(|vehicle_id| { | ||
|
|
@@ -150,6 +155,13 @@ pub(super) fn read_fleet(api_problem: &ApiProblem, props: &ProblemProperties, co | |
| dimens.set_tour_size(tour_size); | ||
| } | ||
|
|
||
| if allow_out_of_hours_depot_travel { | ||
| dimens.set_first_job_arrival_floor(shift_start_earliest); | ||
| if let Some(latest) = shift_start_latest { | ||
| dimens.set_shift_start_latest(latest); | ||
| } | ||
| } | ||
|
|
||
| if props.has_multi_dimen_capacity { | ||
| dimens.set_vehicle_capacity(MultiDimLoad::new(vehicle.capacity.clone())); | ||
| } else { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This ensures we can't insert jobs that would cause us to start too late