I got solution 2 to work, but it still hast one big problem:
Break Intervals do not distinguish between original arc cost and slack.
While there are cases, where one would want to consider slack as "working hours" and therefore include it in break calculation, this is not always desired.
For example, there is a clear distinction between driving hours and working hours. These two are regulated differently by, for instance, EU law.
Waiting at a stop to be served would be considered "ready time" and not contribute to working or breaks.
This is, however, just semantics. It gets problematic when you realize that break intervals generate slack aswell. So by taking a break, you increase the dimension slack and could accidentally trigger the next break.
Take these breaks for instance:
breaks = [
solver.FixedDurationIntervalVar(
16200, # break start min (after total 4.5 driving hrs)
16200, # break start max
2700, # break duration: 45 mins
False, # optional: no
f'Break for vehicle {v} at 4,5 hrs'),
solver.FixedDurationIntervalVar(
32400, # break start min (after total 9 driving hrs)
32400, # break start max
39600, # break duration: 11 hrs
False, # optional: no
f'Break for vehicle {v} at 9 hrs'),
solver.FixedDurationIntervalVar(
48600, # break start min (after total 13.5 driving hrs)
48600, # break start max
2700, # break duration: 45 mins
False, # optional: no
f'Break for vehicle {v} at 4.5 hrs after long 11hrs break')
]
In this naive case the second break will generate enough slack to instantly trigger the third break aswell.
This is why my routes where deemed unreachable the moment I added more than one break. The breaks simply started triggering themselves and
pushing my transit time over the limits set by time windows.
The fix is simple:
Include the planned break duration in the min and max values:
breaks = [
solver.FixedDurationIntervalVar(
16200, # break start min (after total 4.5 driving hrs)
16200, # break start max
2700, # break duration: 45 mins
False, # optional: no
f'Break for vehicle {v} at 4,5 hrs'),
solver.FixedDurationIntervalVar(
32400 + 2700, # break start min (after total 9 driving hrs including breaks)
32400 + 2700, # break start max
39600, # break duration: 11 hrs
False, # optional: no
f'Break for vehicle {v} at 9 hrs'),
solver.FixedDurationIntervalVar(
48600 + 2700 + 39600, # break start min (after total 13.5 driving hrs including breaks)
48600 + 2700 + 39600, # break start max
2700, # break duration: 45 mins
False, # optional: no
f'Break for vehicle {v} at 4.5 hrs after long 11hrs break')
]
But the slack problem is not completely gone. Slack generated by waiting around for time windows will still contribute to breaks.
But I guess I will have to live with that.