Skip to content

Commit

Permalink
Improve validation rule for time offset features with dep opt (#129)
Browse files Browse the repository at this point in the history
  • Loading branch information
reinterpretcat committed Sep 15, 2023
1 parent ac6a7d3 commit ff90527
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 17 deletions.
28 changes: 22 additions & 6 deletions docs/src/concepts/pragmatic/errors/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,11 +479,11 @@ following rules:
{
"typeId": "vehicle",
"vehicleIds": [
"vehicle_1",
"vehicle_1"
],
"profile": {
"matrix": "car"
},
"profile": {
"matrix": "car"
},
"costs": {
"fixed": 20.0,
/** Error: distance and time are zero **/
Expand All @@ -498,9 +498,25 @@ You can fix the error by defining a small value (e.g. 0.0000001) for duration or

#### E1307

`required break is used with departure rescheduling` is returned when required break is used, but `start.latest` is not
set equal to `start.earliest` in the shift.
`time offset interval for break is used with departure rescheduling` is returned when time offset interval is specified for break,
but `start.latest` is not set equal to `start.earliest` in the shift.

```json
{
"start": {
"earliest": "2019-07-04T09:00:00Z",
/** Error: need to set latest to "2019-07-04T09:00:00Z" explicitely **/
"location": { "lat": 52.5316, "lng": 13.3884 }
},
"breaks": [{
/** Note: offset time is used here **/
"time": [3600, 4000],
"places": [{ "duration": 1800 } ]
}]
}
```

Alternatively, you can switch to time window definition and keep `start.latest` property as you wish.

#### E1308

Expand Down
11 changes: 7 additions & 4 deletions docs/src/concepts/pragmatic/problem/vehicles.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ Each shift can have the following properties:
Check example [here](../../../examples/pragmatic/basics/dispatch.md).
- **breaks** (optional) a list of vehicle breaks. There are two types of breaks:
* __required__: this break is guaranteed to be assigned at cost of flexibility. It has the following properties:
- `time` (required): a fixed time or offset range when the break should happen specified by `earliest` and `latest` properties.
- `time` (required): a fixed time or time offset interval when the break should happen specified by `earliest` and `latest` properties.
The break will be assigned not earlier, and not later than the range specified.
- `duration` (required): duration of the break
* __optional__: although such break is not guaranteed for assignment, it has some advantages over required break:
- aribatry break location is supported
- arbitrary break location is supported
- the algorithm has more flexibility for assignment
It is specified by:
- `time` (required): time window or interval after which a break should happen (e.g. between 3 or 4 hours after start).
- `time` (required): time window or time offset interval after which a break should happen (e.g. between 3 or 4 hours after start).
- `places`: list of alternative places defined by `location` (optional), `duration` (required) and `tag` (optional).
If location of a break is omitted then break is stick to location of a job served before break.
- `policy` (optional): a break skip policy. Possible values:
Expand All @@ -88,6 +88,9 @@ Each shift can have the following properties:
Please note that optional break is a soft constraint and can be unassigned in some cases due to other hard constraints, such
as time windows. You can control its unassignment weight using specific property on `minimize-unassigned` objective.
See example [here](../../../examples/pragmatic/basics/break.md)

Additionally, offset time interval requires departure time optimization to be disabled explicitly (see [E1307](../errors/index.md#e1307)).

- **reloads** (optional) a list of vehicle reloads. A reload is a place where vehicle can load new deliveries and unload
pickups. It can be used to model multi trip routes.
Each reload has optional and required fields:
Expand All @@ -109,5 +112,5 @@ Each shift can have the following properties:
* [E1304 invalid reload time windows in vehicle shift](../errors/index.md#e1304)
* [E1305 invalid dispatch in vehicle shift](../errors/index.md#e1305)
* [E1306 time and duration costs are zeros](../errors/index.md#e1306)
* [E1307 required break is used with departure rescheduling](../errors/index.md#e1307)
* [E1307 time offset interval for break is used with departure rescheduling](../errors/index.md#e1307)
* [E1308 invalid vehicle reload resource](../errors/index.md#e1308)
18 changes: 12 additions & 6 deletions vrp-pragmatic/src/validation/vehicles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,19 +221,25 @@ fn check_e1306_vehicle_has_no_zero_costs(ctx: &ValidationContext) -> Result<(),
}
}

fn check_e1307_vehicle_required_break_rescheduling(ctx: &ValidationContext) -> Result<(), FormatError> {
fn check_e1307_vehicle_offset_break_rescheduling(ctx: &ValidationContext) -> Result<(), FormatError> {
let type_ids = get_invalid_type_ids(
ctx,
Box::new(|_, shift, _| {
shift
.breaks
.as_ref()
.map(|breaks| {
let has_required_break = breaks.iter().any(|br| matches!(br, VehicleBreak::Required { .. }));
let has_time_offset = breaks.iter().any(|br| {
matches!(
br,
VehicleBreak::Required { time: VehicleRequiredBreakTime::OffsetTime { .. }, .. }
| VehicleBreak::Optional { time: VehicleOptionalBreakTime::TimeOffset { .. }, .. }
)
});
let has_rescheduling =
shift.start.latest.as_ref().map_or(true, |latest| *latest != shift.start.earliest);

!(has_required_break && has_rescheduling)
!(has_time_offset && has_rescheduling)
})
.unwrap_or(true)
}),
Expand All @@ -244,8 +250,8 @@ fn check_e1307_vehicle_required_break_rescheduling(ctx: &ValidationContext) -> R
} else {
Err(FormatError::new(
"E1307".to_string(),
"required break is used with departure rescheduling".to_string(),
format!("when required break is used, start.latest should be set equal to start.earliest in the shift, check vehicle type ids: '{}'", type_ids.join(", ")),
"time offset interval for break is used with departure rescheduling".to_string(),
format!("when time offset is used, start.latest should be set equal to start.earliest in the shift, check vehicle type ids: '{}'", type_ids.join(", ")),
))
}
}
Expand Down Expand Up @@ -345,7 +351,7 @@ pub fn validate_vehicles(ctx: &ValidationContext) -> Result<(), MultiFormatError
check_e1304_vehicle_reload_time_is_correct(ctx),
check_e1305_vehicle_dispatch_is_correct(ctx),
check_e1306_vehicle_has_no_zero_costs(ctx),
check_e1307_vehicle_required_break_rescheduling(ctx),
check_e1307_vehicle_offset_break_rescheduling(ctx),
check_e1308_vehicle_reload_resources(ctx),
])
.map_err(|errors| errors.into())
Expand Down
5 changes: 5 additions & 0 deletions vrp-pragmatic/tests/features/breaks/interval_break_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ fn can_assign_interval_break_between_jobs() {
fleet: Fleet {
vehicles: vec![VehicleType {
shifts: vec![VehicleShift {
start: ShiftStart {
earliest: format_time(0.),
latest: Some(format_time(0.)),
location: (0., 0.).to_loc(),
},
breaks: Some(vec![VehicleBreak::Optional {
time: VehicleOptionalBreakTime::TimeOffset(vec![5., 10.]),
places: vec![VehicleOptionalBreakPlace { duration: 2.0, location: None, tag: None }],
Expand Down
2 changes: 1 addition & 1 deletion vrp-pragmatic/tests/unit/validation/vehicles_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ fn can_handle_rescheduling_with_required_break_impl(latest: Option<f64>, expecte
..create_empty_problem()
};

let result = check_e1307_vehicle_required_break_rescheduling(&ValidationContext::new(
let result = check_e1307_vehicle_offset_break_rescheduling(&ValidationContext::new(
&problem,
None,
&CoordIndex::new(&problem),
Expand Down

0 comments on commit ff90527

Please sign in to comment.