Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
routing_breaks.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#include <algorithm>
15#include <cstdint>
16#include <iterator>
17#include <limits>
18#include <map>
19#include <numeric>
20#include <string>
21#include <utility>
22#include <vector>
23
24#include "absl/log/check.h"
25#include "absl/types/span.h"
27#include "ortools/base/types.h"
35
36namespace operations_research {
37
38bool DisjunctivePropagator::Propagate(Tasks* tasks) {
39 DCHECK_LE(tasks->num_chain_tasks, tasks->start_min.size());
40 DCHECK_EQ(tasks->start_min.size(), tasks->start_max.size());
41 DCHECK_EQ(tasks->start_min.size(), tasks->duration_min.size());
42 DCHECK_EQ(tasks->start_min.size(), tasks->duration_max.size());
43 DCHECK_EQ(tasks->start_min.size(), tasks->end_min.size());
44 DCHECK_EQ(tasks->start_min.size(), tasks->end_max.size());
45 DCHECK_EQ(tasks->start_min.size(), tasks->is_preemptible.size());
46 // Do forward deductions, then backward deductions.
47 // All propagators are followed by Precedences(),
48 // except MirrorTasks() after which Precedences() would make no deductions,
49 // and DetectablePrecedencesWithChain() which is stronger than Precedences().
50 // Precedences() is a propagator that does obvious deductions quickly (O(n)),
51 // so interleaving Precedences() speeds up the propagation fixed point.
52 if (!Precedences(tasks) || !EdgeFinding(tasks) || !Precedences(tasks) ||
53 !DetectablePrecedencesWithChain(tasks)) {
54 return false;
55 }
56 if (!tasks->forbidden_intervals.empty()) {
57 if (!ForbiddenIntervals(tasks) || !Precedences(tasks)) return false;
58 }
59 if (!tasks->distance_duration.empty()) {
60 if (!DistanceDuration(tasks) || !Precedences(tasks)) return false;
61 }
62 if (!MirrorTasks(tasks) || !EdgeFinding(tasks) || !Precedences(tasks) ||
63 !DetectablePrecedencesWithChain(tasks) || !MirrorTasks(tasks)) {
64 return false;
65 }
66 return true;
67}
68
69bool DisjunctivePropagator::Precedences(Tasks* tasks) {
70 const int num_chain_tasks = tasks->num_chain_tasks;
71 if (num_chain_tasks > 0) {
72 // Propagate forwards.
73 int64_t time = tasks->start_min[0];
74 for (int task = 0; task < num_chain_tasks; ++task) {
75 time = std::max(tasks->start_min[task], time);
76 tasks->start_min[task] = time;
77 time = CapAdd(time, tasks->duration_min[task]);
78 if (tasks->end_max[task] < time) return false;
79 time = std::max(time, tasks->end_min[task]);
80 tasks->end_min[task] = time;
81 }
82 // Propagate backwards.
83 time = tasks->end_max[num_chain_tasks - 1];
84 for (int task = num_chain_tasks - 1; task >= 0; --task) {
85 time = std::min(tasks->end_max[task], time);
86 tasks->end_max[task] = time;
87 time = CapSub(time, tasks->duration_min[task]);
88 if (time < tasks->start_min[task]) return false;
89 time = std::min(time, tasks->start_max[task]);
90 tasks->start_max[task] = time;
91 }
92 }
93 const int num_tasks = tasks->start_min.size();
94 for (int task = 0; task < num_tasks; ++task) {
95 // Enforce start + duration <= end.
96 tasks->end_min[task] =
97 std::max(tasks->end_min[task],
98 CapAdd(tasks->start_min[task], tasks->duration_min[task]));
99 tasks->start_max[task] =
100 std::min(tasks->start_max[task],
101 CapSub(tasks->end_max[task], tasks->duration_min[task]));
102 tasks->duration_max[task] =
103 std::min(tasks->duration_max[task],
104 CapSub(tasks->end_max[task], tasks->start_min[task]));
105 if (!tasks->is_preemptible[task]) {
106 // Enforce start + duration == end for nonpreemptibles.
107 tasks->end_max[task] =
108 std::min(tasks->end_max[task],
109 CapAdd(tasks->start_max[task], tasks->duration_max[task]));
110 tasks->start_min[task] =
111 std::max(tasks->start_min[task],
112 CapSub(tasks->end_min[task], tasks->duration_max[task]));
113 tasks->duration_min[task] =
114 std::max(tasks->duration_min[task],
115 CapSub(tasks->end_min[task], tasks->start_max[task]));
116 }
117 if (tasks->duration_min[task] > tasks->duration_max[task]) return false;
118 if (tasks->end_min[task] > tasks->end_max[task]) return false;
119 if (tasks->start_min[task] > tasks->start_max[task]) return false;
120 }
121 return true;
122}
123
124bool DisjunctivePropagator::MirrorTasks(Tasks* tasks) {
125 const int num_tasks = tasks->start_min.size();
126 // For all tasks, start_min := -end_max and end_max := -start_min.
127 for (int task = 0; task < num_tasks; ++task) {
128 const int64_t t = -tasks->start_min[task];
129 tasks->start_min[task] = -tasks->end_max[task];
130 tasks->end_max[task] = t;
131 }
132 // For all tasks, start_max := -end_min and end_min := -start_max.
133 for (int task = 0; task < num_tasks; ++task) {
134 const int64_t t = -tasks->start_max[task];
135 tasks->start_max[task] = -tasks->end_min[task];
136 tasks->end_min[task] = t;
137 }
138 // In the mirror problem, tasks linked by precedences are in reversed order.
139 const int num_chain_tasks = tasks->num_chain_tasks;
140 for (const auto it :
141 {tasks->start_min.begin(), tasks->start_max.begin(),
142 tasks->duration_min.begin(), tasks->duration_max.begin(),
143 tasks->end_min.begin(), tasks->end_max.begin()}) {
144 std::reverse(it, it + num_chain_tasks);
145 std::reverse(it + num_chain_tasks, it + num_tasks);
146 }
147 std::reverse(tasks->is_preemptible.begin(),
148 tasks->is_preemptible.begin() + num_chain_tasks);
149 std::reverse(tasks->is_preemptible.begin() + num_chain_tasks,
150 tasks->is_preemptible.begin() + num_tasks);
151 return true;
152}
153
154bool DisjunctivePropagator::EdgeFinding(Tasks* tasks) {
155 const int num_tasks = tasks->start_min.size();
156 // Prepare start_min events for tree.
157 tasks_by_start_min_.resize(num_tasks);
158 std::iota(tasks_by_start_min_.begin(), tasks_by_start_min_.end(), 0);
159 std::sort(
160 tasks_by_start_min_.begin(), tasks_by_start_min_.end(),
161 [&](int i, int j) { return tasks->start_min[i] < tasks->start_min[j]; });
162 event_of_task_.resize(num_tasks);
163 for (int event = 0; event < num_tasks; ++event) {
164 event_of_task_[tasks_by_start_min_[event]] = event;
165 }
166 // Tasks will be browsed according to end_max order.
167 tasks_by_end_max_.resize(num_tasks);
168 std::iota(tasks_by_end_max_.begin(), tasks_by_end_max_.end(), 0);
169 std::sort(
170 tasks_by_end_max_.begin(), tasks_by_end_max_.end(),
171 [&](int i, int j) { return tasks->end_max[i] < tasks->end_max[j]; });
172
173 // Generic overload checking: insert tasks by end_max,
174 // fail if envelope > end_max.
175 theta_lambda_tree_.Reset(num_tasks);
176 for (const int task : tasks_by_end_max_) {
177 theta_lambda_tree_.AddOrUpdateEvent(
178 event_of_task_[task], tasks->start_min[task], tasks->duration_min[task],
179 tasks->duration_min[task]);
180 if (theta_lambda_tree_.GetEnvelope() > tasks->end_max[task]) {
181 return false;
182 }
183 }
184
185 // Generic edge finding: from full set of tasks, at each end_max event in
186 // decreasing order, check lambda feasibility, then move end_max task from
187 // theta to lambda.
188 for (int i = num_tasks - 1; i >= 0; --i) {
189 const int task = tasks_by_end_max_[i];
190 const int64_t envelope = theta_lambda_tree_.GetEnvelope();
191 // If a nonpreemptible optional would overload end_max, push to envelope.
192 while (theta_lambda_tree_.GetOptionalEnvelope() > tasks->end_max[task]) {
193 int critical_event; // Dummy value.
194 int optional_event;
195 int64_t available_energy; // Dummy value.
196 theta_lambda_tree_.GetEventsWithOptionalEnvelopeGreaterThan(
197 tasks->end_max[task], &critical_event, &optional_event,
198 &available_energy);
199 const int optional_task = tasks_by_start_min_[optional_event];
200 tasks->start_min[optional_task] =
201 std::max(tasks->start_min[optional_task], envelope);
202 theta_lambda_tree_.RemoveEvent(optional_event);
203 }
204 if (!tasks->is_preemptible[task]) {
205 theta_lambda_tree_.AddOrUpdateOptionalEvent(event_of_task_[task],
206 tasks->start_min[task],
207 tasks->duration_min[task]);
208 } else {
209 theta_lambda_tree_.RemoveEvent(event_of_task_[task]);
210 }
211 }
212 return true;
213}
214
215bool DisjunctivePropagator::DetectablePrecedencesWithChain(Tasks* tasks) {
216 const int num_tasks = tasks->start_min.size();
217 // Prepare start_min events for tree.
218 tasks_by_start_min_.resize(num_tasks);
219 std::iota(tasks_by_start_min_.begin(), tasks_by_start_min_.end(), 0);
220 std::sort(
221 tasks_by_start_min_.begin(), tasks_by_start_min_.end(),
222 [&](int i, int j) { return tasks->start_min[i] < tasks->start_min[j]; });
223 event_of_task_.resize(num_tasks);
224 for (int event = 0; event < num_tasks; ++event) {
225 event_of_task_[tasks_by_start_min_[event]] = event;
226 }
227 theta_lambda_tree_.Reset(num_tasks);
228
229 // Sort nonchain tasks by start max = end_max - duration_min.
230 const int num_chain_tasks = tasks->num_chain_tasks;
231 nonchain_tasks_by_start_max_.resize(num_tasks - num_chain_tasks);
232 std::iota(nonchain_tasks_by_start_max_.begin(),
233 nonchain_tasks_by_start_max_.end(), num_chain_tasks);
234 std::sort(nonchain_tasks_by_start_max_.begin(),
235 nonchain_tasks_by_start_max_.end(), [&tasks](int i, int j) {
236 return tasks->end_max[i] - tasks->duration_min[i] <
237 tasks->end_max[j] - tasks->duration_min[j];
238 });
239
240 // Detectable precedences, specialized for routes: for every task on route,
241 // put all tasks before it in the tree, then push with envelope.
242 int index_nonchain = 0;
243 for (int i = 0; i < num_chain_tasks; ++i) {
244 if (!tasks->is_preemptible[i]) {
245 // Add all nonchain tasks detected before i.
246 while (index_nonchain < nonchain_tasks_by_start_max_.size()) {
247 const int task = nonchain_tasks_by_start_max_[index_nonchain];
248 if (tasks->end_max[task] - tasks->duration_min[task] >=
249 tasks->start_min[i] + tasks->duration_min[i])
250 break;
251 theta_lambda_tree_.AddOrUpdateEvent(
252 event_of_task_[task], tasks->start_min[task],
253 tasks->duration_min[task], tasks->duration_min[task]);
254 index_nonchain++;
255 }
256 }
257 // All chain and nonchain tasks before i are now in the tree, push i.
258 const int64_t new_start_min = theta_lambda_tree_.GetEnvelope();
259 // Add i to the tree before updating it.
260 theta_lambda_tree_.AddOrUpdateEvent(event_of_task_[i], tasks->start_min[i],
261 tasks->duration_min[i],
262 tasks->duration_min[i]);
263 tasks->start_min[i] = std::max(tasks->start_min[i], new_start_min);
264 }
265 return true;
266}
267
268bool DisjunctivePropagator::ForbiddenIntervals(Tasks* tasks) {
269 if (tasks->forbidden_intervals.empty()) return true;
270 const int num_tasks = tasks->start_min.size();
271 for (int task = 0; task < num_tasks; ++task) {
272 if (tasks->duration_min[task] == 0) continue;
273 if (tasks->forbidden_intervals[task] == nullptr) continue;
274 // If start_min forbidden, push to next feasible value.
275 {
276 const auto& interval =
277 tasks->forbidden_intervals[task]->FirstIntervalGreaterOrEqual(
278 tasks->start_min[task]);
279 if (interval == tasks->forbidden_intervals[task]->end()) continue;
280 if (interval->start <= tasks->start_min[task]) {
281 tasks->start_min[task] = CapAdd(interval->end, 1);
282 }
283 }
284 // If end_max forbidden, push to next feasible value.
285 {
286 const int64_t start_max =
287 CapSub(tasks->end_max[task], tasks->duration_min[task]);
288 const auto& interval =
289 tasks->forbidden_intervals[task]->LastIntervalLessOrEqual(start_max);
290 if (interval == tasks->forbidden_intervals[task]->end()) continue;
291 if (interval->end >= start_max) {
292 tasks->end_max[task] =
293 CapAdd(interval->start, tasks->duration_min[task] - 1);
294 }
295 }
296 if (CapAdd(tasks->start_min[task], tasks->duration_min[task]) >
297 tasks->end_max[task]) {
298 return false;
299 }
300 }
301 return true;
302}
303
304bool DisjunctivePropagator::DistanceDuration(Tasks* tasks) {
305 if (tasks->distance_duration.empty()) return true;
306 if (tasks->num_chain_tasks == 0) return true;
307 const int route_start = 0;
308 const int route_end = tasks->num_chain_tasks - 1;
309 const int num_tasks = tasks->start_min.size();
310 for (int i = 0; i < tasks->distance_duration.size(); ++i) {
311 const int64_t max_distance = tasks->distance_duration[i].first;
312 const int64_t minimum_break_duration = tasks->distance_duration[i].second;
313
314 // This is a sweeping algorithm that looks whether the union of intervals
315 // defined by breaks and route start/end is (-infty, +infty).
316 // Those intervals are:
317 // - route start: (-infty, start_max + distance]
318 // - route end: [end_min, +infty)
319 // - breaks: [start_min, end_max + distance) if their duration_max
320 // is >= min_duration, empty set otherwise.
321 // If sweeping finds that a time point can be covered by only one interval,
322 // it will force the corresponding break or route start/end to cover this
323 // point, which can force a break to be above minimum_break_duration.
324
325 // We suppose break tasks are ordered, so the algorithm supposes that
326 // start_min(task_n) <= start_min(task_{n+1}) and
327 // end_max(task_n) <= end_max(task_{n+1}).
328 for (int task = tasks->num_chain_tasks + 1; task < num_tasks; ++task) {
329 tasks->start_min[task] =
330 std::max(tasks->start_min[task], tasks->start_min[task - 1]);
331 }
332 for (int task = num_tasks - 2; task >= tasks->num_chain_tasks; --task) {
333 tasks->end_max[task] =
334 std::min(tasks->end_max[task], tasks->end_max[task + 1]);
335 }
336 // Skip breaks that cannot be performed after start.
337 int index_break_by_emax = tasks->num_chain_tasks;
338 while (index_break_by_emax < num_tasks &&
339 tasks->end_max[index_break_by_emax] <= tasks->end_min[route_start]) {
340 ++index_break_by_emax;
341 }
342 // Special case: no breaks after start.
343 if (index_break_by_emax == num_tasks) {
344 tasks->end_min[route_start] =
345 std::max(tasks->end_min[route_start],
346 CapSub(tasks->start_min[route_end], max_distance));
347 tasks->start_max[route_end] =
348 std::min(tasks->start_max[route_end],
349 CapAdd(tasks->end_max[route_start], max_distance));
350 continue;
351 }
352 // There will be a break after start, so route_start coverage is tested.
353 // Initial state: start at -inf with route_start in task_set.
354 // Sweep over profile, looking for time points where the number of
355 // covering breaks is <= 1. If it is 0, fail, otherwise force the
356 // unique break to cover it.
357 // Route start and end get a special treatment, not sure generalizing
358 // would be better.
359 int64_t xor_active_tasks = route_start;
360 int num_active_tasks = 1;
361 int64_t previous_time = std::numeric_limits<int64_t>::min();
362 const int64_t route_start_time =
363 CapAdd(tasks->end_max[route_start], max_distance);
364 const int64_t route_end_time = tasks->start_min[route_end];
365 // NOTE: all smin events must be closed by a corresponding emax event,
366 // otherwise num_active_tasks is wrong (too high) and the reasoning misses
367 // some filtering.
368 int index_break_by_smin = index_break_by_emax;
369 while (index_break_by_emax < num_tasks) {
370 // Find next time point among start/end of covering intervals.
371 int64_t current_time =
372 CapAdd(tasks->end_max[index_break_by_emax], max_distance);
373 if (index_break_by_smin < num_tasks) {
374 current_time =
375 std::min(current_time, tasks->start_min[index_break_by_smin]);
376 }
377 if (previous_time < route_start_time && route_start_time < current_time) {
378 current_time = route_start_time;
379 }
380 if (previous_time < route_end_time && route_end_time < current_time) {
381 current_time = route_end_time;
382 }
383 // If num_active_tasks was 1, the unique active task must cover from
384 // previous_time to current_time.
385 if (num_active_tasks == 1) {
386 // xor_active_tasks is the unique task that can cover [previous_time,
387 // current_time).
388 if (xor_active_tasks != route_end) {
389 tasks->end_min[xor_active_tasks] =
390 std::max(tasks->end_min[xor_active_tasks],
391 CapSub(current_time, max_distance));
392 if (xor_active_tasks != route_start) {
393 tasks->duration_min[xor_active_tasks] = std::max(
394 tasks->duration_min[xor_active_tasks],
395 std::max(
396 minimum_break_duration,
397 CapSub(CapSub(current_time, max_distance), previous_time)));
398 }
399 }
400 }
401 // Process covering intervals that start or end at current_time.
402 while (index_break_by_smin < num_tasks &&
403 current_time == tasks->start_min[index_break_by_smin]) {
404 if (tasks->duration_max[index_break_by_smin] >=
405 minimum_break_duration) {
406 xor_active_tasks ^= index_break_by_smin;
407 ++num_active_tasks;
408 }
409 ++index_break_by_smin;
410 }
411 while (index_break_by_emax < num_tasks &&
412 current_time ==
413 CapAdd(tasks->end_max[index_break_by_emax], max_distance)) {
414 if (tasks->duration_max[index_break_by_emax] >=
415 minimum_break_duration) {
416 xor_active_tasks ^= index_break_by_emax;
417 --num_active_tasks;
418 }
419 ++index_break_by_emax;
420 }
421 if (current_time == route_start_time) {
422 xor_active_tasks ^= route_start;
423 --num_active_tasks;
424 }
425 if (current_time == route_end_time) {
426 xor_active_tasks ^= route_end;
427 ++num_active_tasks;
428 }
429 // If num_active_tasks becomes 1, the unique active task must cover from
430 // current_time.
431 if (num_active_tasks <= 0) return false;
432 if (num_active_tasks == 1) {
433 if (xor_active_tasks != route_start) {
434 // xor_active_tasks is the unique task that can cover from
435 // current_time to the next time point.
436 tasks->start_max[xor_active_tasks] =
437 std::min(tasks->start_max[xor_active_tasks], current_time);
438 if (xor_active_tasks != route_end) {
439 tasks->duration_min[xor_active_tasks] = std::max(
440 tasks->duration_min[xor_active_tasks], minimum_break_duration);
441 }
442 }
443 }
444 previous_time = current_time;
445 }
446 }
447 return true;
448}
449
450bool DisjunctivePropagator::ChainSpanMin(Tasks* tasks) {
451 const int num_chain_tasks = tasks->num_chain_tasks;
452 if (num_chain_tasks < 1) return true;
453 // TODO(user): add stronger bounds.
454 // The duration of the chain plus that of nonchain tasks that must be
455 // performed during the chain is a lower bound of the chain span.
456 {
457 int64_t sum_chain_durations = 0;
458 const auto duration_start = tasks->duration_min.begin();
459 const auto duration_end = tasks->duration_min.begin() + num_chain_tasks;
460 for (auto it = duration_start; it != duration_end; ++it) {
461 sum_chain_durations = CapAdd(sum_chain_durations, *it);
462 }
463 int64_t sum_forced_nonchain_durations = 0;
464 for (int i = num_chain_tasks; i < tasks->start_min.size(); ++i) {
465 // Tasks that can be executed before or after are skipped.
466 if (tasks->end_min[i] <= tasks->start_max[0] ||
467 tasks->end_min[num_chain_tasks - 1] <= tasks->start_max[i]) {
468 continue;
469 }
470 sum_forced_nonchain_durations =
471 CapAdd(sum_forced_nonchain_durations, tasks->duration_min[i]);
472 }
473 tasks->span_min =
474 std::max(tasks->span_min,
475 CapAdd(sum_chain_durations, sum_forced_nonchain_durations));
476 }
477 // The difference end of the chain - start of the chain is a lower bound.
478 {
479 const int64_t end_minus_start =
480 CapSub(tasks->end_min[num_chain_tasks - 1], tasks->start_max[0]);
481 tasks->span_min = std::max(tasks->span_min, end_minus_start);
482 }
483
484 return tasks->span_min <= tasks->span_max;
485}
486
487// Computes a lower bound of the span of the chain, taking into account only
488// the first nonchain task.
489// TODO(user): extend to arbitrary number of nonchain tasks.
490bool DisjunctivePropagator::ChainSpanMinDynamic(Tasks* tasks) {
491 // Do nothing if there are no chain tasks or no nonchain tasks.
492 const int num_chain_tasks = tasks->num_chain_tasks;
493 if (num_chain_tasks < 1) return true;
494 if (num_chain_tasks == tasks->start_min.size()) return true;
495 const int task_index = num_chain_tasks;
496 if (!Precedences(tasks)) return false;
497 const int64_t min_possible_chain_end = tasks->end_min[num_chain_tasks - 1];
498 const int64_t max_possible_chain_start = tasks->start_max[0];
499 // For each chain task i, compute cumulated duration of chain tasks before it.
500 int64_t total_duration = 0;
501 {
502 total_duration_before_.resize(num_chain_tasks);
503 for (int i = 0; i < num_chain_tasks; ++i) {
504 total_duration_before_[i] = total_duration;
505 total_duration = CapAdd(total_duration, tasks->duration_min[i]);
506 }
507 }
508 // Estimate span min of chain tasks. Use the schedule that ends at
509 // min_possible_chain_end and starts at smallest of start_max[0] or the
510 // threshold where pushing start[0] later does not make a difference to the
511 // chain span because of chain precedence constraints,
512 // i.e. min_possible_chain_end - total_duration.
513 {
514 const int64_t chain_span_min =
515 min_possible_chain_end -
516 std::min(tasks->start_max[0], min_possible_chain_end - total_duration);
517 if (chain_span_min > tasks->span_max) {
518 return false;
519 } else {
520 tasks->span_min = std::max(tasks->span_min, chain_span_min);
521 }
522 // If task can be performed before or after the chain,
523 // span_min is chain_span_min.
524 if (tasks->end_min[task_index] <= tasks->start_max[0] ||
525 tasks->end_min[num_chain_tasks - 1] <= tasks->start_max[task_index]) {
526 return true;
527 }
528 }
529 // Scan all possible preemption positions of the nontask chain,
530 // keep the one that yields the minimum span.
531 int64_t span_min = std::numeric_limits<int64_t>::max();
532 bool schedule_is_feasible = false;
533 for (int i = 0; i < num_chain_tasks; ++i) {
534 if (!tasks->is_preemptible[i]) continue;
535 // Estimate span min if tasks is performed during i.
536 // For all possible minimal-span schedules, there is a schedule where task i
537 // and nonchain task form a single block. Thus, we only consider those.
538 const int64_t block_start_min =
539 std::max(tasks->start_min[i],
540 tasks->start_min[task_index] - tasks->duration_min[i]);
541 const int64_t block_start_max =
542 std::min(tasks->start_max[task_index],
543 tasks->start_max[i] - tasks->duration_min[task_index]);
544 if (block_start_min > block_start_max) continue;
545
546 // Compute the block start that yields the minimal span.
547 // Given a feasible block start, a chain of minimum span constrained to
548 // this particular block start can be obtained by scheduling all tasks after
549 // the block at their earliest, and all tasks before it at their latest.
550 // The span can be decomposed into two parts: the head, which are the
551 // tasks that are before the block, and the tail, which are the block and
552 // the tasks after it.
553 // When the block start varies, the head length of the optimal schedule
554 // described above decreases as much as the block start decreases, until
555 // an inflection point at which it stays constant. That inflection value
556 // is the one where the precedence constraints force the chain start to
557 // decrease because of durations.
558 const int64_t head_inflection =
559 max_possible_chain_start + total_duration_before_[i];
560 // The map from block start to minimal tail length also has an inflection
561 // point, that additionally depends on the nonchain task's duration.
562 const int64_t tail_inflection =
563 min_possible_chain_end - (total_duration - total_duration_before_[i]) -
564 tasks->duration_min[task_index];
565 // All block start values between these two yield the same minimal span.
566 // Indeed, first, mind that the inflection points might be in any order.
567 // - if head_inflection < tail_inflection, then inside the interval
568 // [head_inflection, tail_inflection], increasing the block start by delta
569 // decreases the tail length by delta and increases the head length by
570 // delta too.
571 // - if tail_inflection < head_inflection, then inside the interval
572 // [tail_inflection, head_inflection], head length is constantly at
573 // total_duration_before_[i], and tail length is also constant.
574 // In both cases, outside of the interval, one part is constant and the
575 // other increases as much as the distance to the interval.
576 // We can abstract inflection point to the interval they form.
577 const int64_t optimal_interval_min_start =
578 std::min(head_inflection, tail_inflection);
579 const int64_t optimal_interval_max_start =
580 std::max(head_inflection, tail_inflection);
581 // If the optimal interval for block start intersects the feasible interval,
582 // we can select any point within it, for instance the earliest one.
583 int64_t block_start = std::max(optimal_interval_min_start, block_start_min);
584 // If the intervals do not intersect, the feasible value closest to the
585 // optimal interval has the minimal span, because the span increases as
586 // much as the distance to the optimal interval.
587 if (optimal_interval_max_start < block_start_min) {
588 // Optimal interval is before feasible interval, closest is feasible min.
589 block_start = block_start_min;
590 } else if (block_start_max < optimal_interval_min_start) {
591 // Optimal interval is after feasible interval, closest is feasible max.
592 block_start = block_start_max;
593 }
594 // Compute span for the chosen block start.
595 const int64_t head_duration =
596 std::max(block_start, head_inflection) - max_possible_chain_start;
597 const int64_t tail_duration =
598 min_possible_chain_end - std::min(block_start, tail_inflection);
599 const int64_t optimal_span_at_i = head_duration + tail_duration;
600 span_min = std::min(span_min, optimal_span_at_i);
601 schedule_is_feasible = true;
602 }
603 if (!schedule_is_feasible || span_min > tasks->span_max) {
604 return false;
605 } else {
606 tasks->span_min = std::max(tasks->span_min, span_min);
607 return true;
608 }
609}
610
611void AppendTasksFromPath(absl::Span<const int64_t> path,
612 const TravelBounds& travel_bounds,
613 const RoutingDimension& dimension,
614 DisjunctivePropagator::Tasks* tasks) {
615 const int num_nodes = path.size();
616 DCHECK_EQ(travel_bounds.pre_travels.size(), num_nodes - 1);
617 DCHECK_EQ(travel_bounds.post_travels.size(), num_nodes - 1);
618 for (int i = 0; i < num_nodes; ++i) {
619 const int64_t cumul_min = dimension.CumulVar(path[i])->Min();
620 const int64_t cumul_max = dimension.CumulVar(path[i])->Max();
621 // Add task associated to visit i.
622 // Visits start at Cumul(path[i]) - before_visit
623 // and end at Cumul(path[i]) + after_visit
624 {
625 const int64_t before_visit =
626 (i == 0) ? 0 : travel_bounds.post_travels[i - 1];
627 const int64_t after_visit =
628 (i == num_nodes - 1) ? 0 : travel_bounds.pre_travels[i];
629
630 tasks->start_min.push_back(CapSub(cumul_min, before_visit));
631 tasks->start_max.push_back(CapSub(cumul_max, before_visit));
632 tasks->duration_min.push_back(CapAdd(before_visit, after_visit));
633 tasks->duration_max.push_back(CapAdd(before_visit, after_visit));
634 tasks->end_min.push_back(CapAdd(cumul_min, after_visit));
635 tasks->end_max.push_back(CapAdd(cumul_max, after_visit));
636 tasks->is_preemptible.push_back(false);
637 }
638 if (i == num_nodes - 1) break;
639
640 // Tasks from travels.
641 // A travel task starts at Cumul(path[i]) + pre_travel,
642 // last for FixedTransitVar(path[i]) - pre_travel - post_travel,
643 // and must end at the latest at Cumul(path[i+1]) - post_travel.
644 {
645 const int64_t pre_travel = travel_bounds.pre_travels[i];
646 const int64_t post_travel = travel_bounds.post_travels[i];
647 tasks->start_min.push_back(CapAdd(cumul_min, pre_travel));
648 tasks->start_max.push_back(CapAdd(cumul_max, pre_travel));
649 tasks->duration_min.push_back(
650 std::max<int64_t>(0, CapSub(travel_bounds.min_travels[i],
651 CapAdd(pre_travel, post_travel))));
652 tasks->duration_max.push_back(
653 travel_bounds.max_travels[i] == std::numeric_limits<int64_t>::max()
654 ? std::numeric_limits<int64_t>::max()
655 : std::max<int64_t>(0, CapSub(travel_bounds.max_travels[i],
656 CapAdd(pre_travel, post_travel))));
657 tasks->end_min.push_back(
658 CapSub(dimension.CumulVar(path[i + 1])->Min(), post_travel));
659 tasks->end_max.push_back(
660 CapSub(dimension.CumulVar(path[i + 1])->Max(), post_travel));
661 tasks->is_preemptible.push_back(true);
662 }
663 }
664}
665
666void FillTravelBoundsOfVehicle(int vehicle, absl::Span<const int64_t> path,
667 const RoutingDimension& dimension,
668 TravelBounds* travel_bounds) {
669 // Fill path and min/max/pre/post travel bounds.
670 FillPathEvaluation(path, dimension.transit_evaluator(vehicle),
671 &travel_bounds->min_travels);
672 const int num_travels = travel_bounds->min_travels.size();
673 travel_bounds->max_travels.assign(num_travels,
674 std::numeric_limits<int64_t>::max());
675 {
676 const int index = dimension.GetPreTravelEvaluatorOfVehicle(vehicle);
677 if (index == -1) {
678 travel_bounds->pre_travels.assign(num_travels, 0);
679 } else {
680 FillPathEvaluation(path, dimension.model()->TransitCallback(index),
681 &travel_bounds->pre_travels);
682 }
683 }
684 {
685 const int index = dimension.GetPostTravelEvaluatorOfVehicle(vehicle);
686 if (index == -1) {
687 travel_bounds->post_travels.assign(num_travels, 0);
688 } else {
689 FillPathEvaluation(path, dimension.model()->TransitCallback(index),
690 &travel_bounds->post_travels);
691 }
692 }
693}
694
695void AppendTasksFromIntervals(const std::vector<IntervalVar*>& intervals,
696 DisjunctivePropagator::Tasks* tasks) {
697 for (IntervalVar* interval : intervals) {
698 if (!interval->MustBePerformed()) continue;
699 tasks->start_min.push_back(interval->StartMin());
700 tasks->start_max.push_back(interval->StartMax());
701 tasks->duration_min.push_back(interval->DurationMin());
702 tasks->duration_max.push_back(interval->DurationMax());
703 tasks->end_min.push_back(interval->EndMin());
704 tasks->end_max.push_back(interval->EndMax());
705 tasks->is_preemptible.push_back(false);
706 }
707}
708
709GlobalVehicleBreaksConstraint::GlobalVehicleBreaksConstraint(
710 const RoutingDimension* dimension)
711 : Constraint(dimension->model()->solver()),
712 model_(dimension->model()),
713 dimension_(dimension) {
714 vehicle_demons_.resize(model_->vehicles());
715}
716
717void GlobalVehicleBreaksConstraint::Post() {
718 for (int vehicle = 0; vehicle < model_->vehicles(); vehicle++) {
719 if (dimension_->GetBreakIntervalsOfVehicle(vehicle).empty() &&
720 dimension_->GetBreakDistanceDurationOfVehicle(vehicle).empty()) {
721 continue;
722 }
723 vehicle_demons_[vehicle] = MakeDelayedConstraintDemon1(
724 solver(), this, &GlobalVehicleBreaksConstraint::PropagateVehicle,
725 "PropagateVehicle", vehicle);
726 for (IntervalVar* interval :
727 dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
728 interval->WhenAnything(vehicle_demons_[vehicle]);
729 }
730 }
731 const int num_cumuls = dimension_->cumuls().size();
732 const int num_nexts = model_->Nexts().size();
733 for (int node = 0; node < num_cumuls; node++) {
734 Demon* dimension_demon = MakeConstraintDemon1(
735 solver(), this, &GlobalVehicleBreaksConstraint::PropagateNode,
736 "PropagateNode", node);
737 if (node < num_nexts) {
738 model_->NextVar(node)->WhenBound(dimension_demon);
739 dimension_->SlackVar(node)->WhenRange(dimension_demon);
740 }
741 model_->VehicleVar(node)->WhenBound(dimension_demon);
742 dimension_->CumulVar(node)->WhenRange(dimension_demon);
743 }
744}
745
746void GlobalVehicleBreaksConstraint::InitialPropagate() {
747 for (int vehicle = 0; vehicle < model_->vehicles(); vehicle++) {
748 if (!dimension_->GetBreakIntervalsOfVehicle(vehicle).empty() ||
749 !dimension_->GetBreakDistanceDurationOfVehicle(vehicle).empty()) {
750 PropagateVehicle(vehicle);
751 }
752 }
753}
754
755// This dispatches node events to the right vehicle propagator.
756// It also filters out a part of uninteresting events, on which the vehicle
757// propagator will not find anything new.
758void GlobalVehicleBreaksConstraint::PropagateNode(int node) {
759 if (!model_->VehicleVar(node)->Bound()) return;
760 const int vehicle = model_->VehicleVar(node)->Min();
761 if (vehicle < 0 || vehicle_demons_[vehicle] == nullptr) return;
762 EnqueueDelayedDemon(vehicle_demons_[vehicle]);
763}
764
765void GlobalVehicleBreaksConstraint::FillPartialPathOfVehicle(int vehicle) {
766 path_.clear();
767 int current = model_->Start(vehicle);
768 while (!model_->IsEnd(current)) {
769 path_.push_back(current);
770 current = model_->NextVar(current)->Bound()
771 ? model_->NextVar(current)->Min()
772 : model_->End(vehicle);
773 }
774 path_.push_back(current);
775}
776
777void GlobalVehicleBreaksConstraint::FillPathTravels(
778 absl::Span<const int64_t> path) {
779 const int num_travels = path.size() - 1;
780 travel_bounds_.min_travels.resize(num_travels);
781 travel_bounds_.max_travels.resize(num_travels);
782 for (int i = 0; i < num_travels; ++i) {
783 travel_bounds_.min_travels[i] = dimension_->FixedTransitVar(path[i])->Min();
784 travel_bounds_.max_travels[i] = dimension_->FixedTransitVar(path[i])->Max();
785 }
786}
787
788// First, perform energy-based reasoning on intervals and cumul variables.
789// Then, perform reasoning on slack variables.
790void GlobalVehicleBreaksConstraint::PropagateVehicle(int vehicle) {
791 // Fill path and pre/post travel information.
792 FillPartialPathOfVehicle(vehicle);
793 const int num_nodes = path_.size();
794 FillPathTravels(path_);
795 {
796 const int index = dimension_->GetPreTravelEvaluatorOfVehicle(vehicle);
797 if (index == -1) {
798 travel_bounds_.pre_travels.assign(num_nodes - 1, 0);
799 } else {
800 FillPathEvaluation(path_, model_->TransitCallback(index),
801 &travel_bounds_.pre_travels);
802 }
803 }
804 {
805 const int index = dimension_->GetPostTravelEvaluatorOfVehicle(vehicle);
806 if (index == -1) {
807 travel_bounds_.post_travels.assign(num_nodes - 1, 0);
808 } else {
809 FillPathEvaluation(path_, model_->TransitCallback(index),
810 &travel_bounds_.post_travels);
811 }
812 }
813 // The last travel might not be fixed: in that case, relax its information.
814 if (!model_->NextVar(path_[num_nodes - 2])->Bound()) {
815 travel_bounds_.min_travels.back() = 0;
816 travel_bounds_.max_travels.back() = std::numeric_limits<int64_t>::max();
817 travel_bounds_.pre_travels.back() = 0;
818 travel_bounds_.post_travels.back() = 0;
819 }
820
821 // Fill tasks from path, break intervals, and break constraints.
822 tasks_.Clear();
823 AppendTasksFromPath(path_, travel_bounds_, *dimension_, &tasks_);
824 tasks_.num_chain_tasks = tasks_.start_min.size();
825 AppendTasksFromIntervals(dimension_->GetBreakIntervalsOfVehicle(vehicle),
826 &tasks_);
827 tasks_.distance_duration =
828 dimension_->GetBreakDistanceDurationOfVehicle(vehicle);
829
830 // Do the actual reasoning, no need to continue if infeasible.
831 if (!disjunctive_propagator_.Propagate(&tasks_)) solver()->Fail();
832
833 // Make task translators to help set new bounds of CP variables.
834 task_translators_.clear();
835 for (int i = 0; i < num_nodes; ++i) {
836 const int64_t before_visit =
837 (i == 0) ? 0 : travel_bounds_.post_travels[i - 1];
838 const int64_t after_visit =
839 (i == num_nodes - 1) ? 0 : travel_bounds_.pre_travels[i];
840 task_translators_.emplace_back(dimension_->CumulVar(path_[i]), before_visit,
841 after_visit);
842 if (i == num_nodes - 1) break;
843 task_translators_.emplace_back(); // Dummy translator for travel tasks.
844 }
845 for (IntervalVar* interval :
846 dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
847 if (!interval->MustBePerformed()) continue;
848 task_translators_.emplace_back(interval);
849 }
850
851 // Push new bounds to CP variables.
852 const int num_tasks = tasks_.start_min.size();
853 for (int task = 0; task < num_tasks; ++task) {
854 task_translators_[task].SetStartMin(tasks_.start_min[task]);
855 task_translators_[task].SetStartMax(tasks_.start_max[task]);
856 task_translators_[task].SetDurationMin(tasks_.duration_min[task]);
857 task_translators_[task].SetEndMin(tasks_.end_min[task]);
858 task_translators_[task].SetEndMax(tasks_.end_max[task]);
859 }
860
861 // Reasoning on slack variables: when intervals must be inside an arc,
862 // that arc's slack must be large enough to accommodate for those.
863 // TODO(user): Make a version more efficient than O(n^2).
864 if (dimension_->GetBreakIntervalsOfVehicle(vehicle).empty()) return;
865 // If the last arc of the path was not bound, do not change slack.
866 const int64_t last_bound_arc =
867 num_nodes - 2 - (model_->NextVar(path_[num_nodes - 2])->Bound() ? 0 : 1);
868 for (int i = 0; i <= last_bound_arc; ++i) {
869 const int64_t arc_start_max =
870 CapSub(dimension_->CumulVar(path_[i])->Max(),
871 i > 0 ? travel_bounds_.post_travels[i - 1] : 0);
872 const int64_t arc_end_min =
873 CapAdd(dimension_->CumulVar(path_[i + 1])->Min(),
874 i < num_nodes - 2 ? travel_bounds_.pre_travels[i + 1] : 0);
875 int64_t total_break_inside_arc = 0;
876 for (IntervalVar* interval :
877 dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
878 if (!interval->MustBePerformed()) continue;
879 const int64_t interval_start_max = interval->StartMax();
880 const int64_t interval_end_min = interval->EndMin();
881 const int64_t interval_duration_min = interval->DurationMin();
882 // If interval cannot end before the arc's from node and
883 // cannot start after the 'to' node, then it must be inside the arc.
884 if (arc_start_max < interval_end_min &&
885 interval_start_max < arc_end_min) {
886 total_break_inside_arc += interval_duration_min;
887 }
888 }
889 dimension_->SlackVar(path_[i])->SetMin(total_break_inside_arc);
890 }
891 // Reasoning on optional intervals.
892 // TODO(user): merge this with energy-based reasoning.
893 // If there is no optional interval, skip the rest of this function.
894 {
895 bool has_optional = false;
896 for (const IntervalVar* interval :
897 dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
898 if (interval->MayBePerformed() && !interval->MustBePerformed()) {
899 has_optional = true;
900 break;
901 }
902 }
903 if (!has_optional) return;
904 }
905 const std::vector<IntervalVar*>& break_intervals =
906 dimension_->GetBreakIntervalsOfVehicle(vehicle);
907 for (int pos = 0; pos < num_nodes - 1; ++pos) {
908 const int64_t current_slack_max = dimension_->SlackVar(path_[pos])->Max();
909 const int64_t visit_start_offset =
910 pos > 0 ? travel_bounds_.post_travels[pos - 1] : 0;
911 const int64_t visit_start_max =
912 CapSub(dimension_->CumulVar(path_[pos])->Max(), visit_start_offset);
913 const int64_t visit_end_offset =
914 (pos < num_nodes - 1) ? travel_bounds_.pre_travels[pos] : 0;
915 const int64_t visit_end_min =
916 CapAdd(dimension_->CumulVar(path_[pos])->Min(), visit_end_offset);
917
918 for (IntervalVar* interval : break_intervals) {
919 if (!interval->MayBePerformed()) continue;
920 const bool interval_is_performed = interval->MustBePerformed();
921 const int64_t interval_start_max = interval->StartMax();
922 const int64_t interval_end_min = interval->EndMin();
923 const int64_t interval_duration_min = interval->DurationMin();
924 // When interval cannot fit inside current arc,
925 // do disjunctive reasoning on full arc.
926 if (pos < num_nodes - 1 && interval_duration_min > current_slack_max) {
927 // The arc lasts from CumulVar(path_[pos]) - post_travel_[pos] to
928 // CumulVar(path_[pos+1]) + pre_travel_[pos+1].
929 const int64_t arc_start_offset =
930 pos > 0 ? travel_bounds_.post_travels[pos - 1] : 0;
931 const int64_t arc_start_max = visit_start_max;
932 const int64_t arc_end_offset =
933 (pos < num_nodes - 2) ? travel_bounds_.pre_travels[pos + 1] : 0;
934 const int64_t arc_end_min =
935 CapAdd(dimension_->CumulVar(path_[pos + 1])->Min(), arc_end_offset);
936 // Interval not before.
937 if (arc_start_max < interval_end_min) {
938 interval->SetStartMin(arc_end_min);
939 if (interval_is_performed) {
940 dimension_->CumulVar(path_[pos + 1])
941 ->SetMax(CapSub(interval_start_max, arc_end_offset));
942 }
943 }
944 // Interval not after.
945 if (interval_start_max < arc_end_min) {
946 interval->SetEndMax(arc_start_max);
947 if (interval_is_performed) {
948 dimension_->CumulVar(path_[pos])
949 ->SetMin(CapAdd(interval_end_min, arc_start_offset));
950 }
951 }
952 continue;
953 }
954 // Interval could fit inside arc: do disjunctive reasoning between
955 // interval and visit.
956 // Interval not before.
957 if (visit_start_max < interval_end_min) {
958 interval->SetStartMin(visit_end_min);
959 if (interval_is_performed) {
960 dimension_->CumulVar(path_[pos])
961 ->SetMax(CapSub(interval_start_max, visit_end_offset));
962 }
963 }
964 // Interval not after.
965 if (interval_start_max < visit_end_min) {
966 interval->SetEndMax(visit_start_max);
967 if (interval_is_performed) {
968 dimension_->CumulVar(path_[pos])
969 ->SetMin(CapAdd(interval_end_min, visit_start_offset));
970 }
971 }
972 }
973 }
974}
975
976namespace {
977class VehicleBreaksFilter : public BasePathFilter {
978 public:
979 VehicleBreaksFilter(const RoutingModel& routing_model,
980 const RoutingDimension& dimension);
981 std::string DebugString() const override { return "VehicleBreaksFilter"; }
982 bool AcceptPath(int64_t path_start, int64_t chain_start,
983 int64_t chain_end) override;
984
985 private:
986 // Fills path_ with the path of vehicle, start to end.
987 void FillPathOfVehicle(int64_t vehicle);
988 std::vector<int64_t> path_;
989 // Handles to model.
990 const RoutingModel& model_;
991 const RoutingDimension& dimension_;
992 // Strong energy-based filtering algorithm.
993 DisjunctivePropagator disjunctive_propagator_;
994 DisjunctivePropagator::Tasks tasks_;
995 // Used to check whether propagation changed a vector.
996 std::vector<int64_t> old_start_min_;
997 std::vector<int64_t> old_start_max_;
998 std::vector<int64_t> old_end_min_;
999 std::vector<int64_t> old_end_max_;
1000
1001 std::vector<int> start_to_vehicle_;
1002 TravelBounds travel_bounds_;
1003};
1004
1005VehicleBreaksFilter::VehicleBreaksFilter(const RoutingModel& routing_model,
1006 const RoutingDimension& dimension)
1007 : BasePathFilter(routing_model.Nexts(),
1008 routing_model.Size() + routing_model.vehicles(),
1009 routing_model.GetPathsMetadata()),
1010 model_(routing_model),
1011 dimension_(dimension) {
1012 DCHECK(dimension_.HasBreakConstraints());
1013 start_to_vehicle_.resize(Size(), -1);
1014 for (int i = 0; i < routing_model.vehicles(); ++i) {
1015 start_to_vehicle_[routing_model.Start(i)] = i;
1016 }
1017}
1018
1019void VehicleBreaksFilter::FillPathOfVehicle(int64_t vehicle) {
1020 path_.clear();
1021 int current = model_.Start(vehicle);
1022 while (!model_.IsEnd(current)) {
1023 path_.push_back(current);
1024 current = GetNext(current);
1025 }
1026 path_.push_back(current);
1027}
1028
1029bool VehicleBreaksFilter::AcceptPath(int64_t path_start, int64_t chain_start,
1030 int64_t chain_end) {
1031 const int vehicle = start_to_vehicle_[path_start];
1032 if (dimension_.GetBreakIntervalsOfVehicle(vehicle).empty() &&
1033 dimension_.GetBreakDistanceDurationOfVehicle(vehicle).empty()) {
1034 return true;
1035 }
1036 // Fill path and pre/post travel information.
1037 FillPathOfVehicle(vehicle);
1038 FillTravelBoundsOfVehicle(vehicle, path_, dimension_, &travel_bounds_);
1039 // Fill tasks from path, forbidden intervals, breaks and break constraints.
1040 tasks_.Clear();
1041 AppendTasksFromPath(path_, travel_bounds_, dimension_, &tasks_);
1042 tasks_.num_chain_tasks = tasks_.start_min.size();
1043 AppendTasksFromIntervals(dimension_.GetBreakIntervalsOfVehicle(vehicle),
1044 &tasks_);
1045 // Add forbidden intervals only if a node has some.
1046 tasks_.forbidden_intervals.clear();
1047 if (std::any_of(path_.begin(), path_.end(), [this](int64_t node) {
1048 return dimension_.forbidden_intervals()[node].NumIntervals() > 0;
1049 })) {
1050 tasks_.forbidden_intervals.assign(tasks_.start_min.size(), nullptr);
1051 for (int i = 0; i < path_.size(); ++i) {
1052 tasks_.forbidden_intervals[2 * i] =
1053 &(dimension_.forbidden_intervals()[path_[i]]);
1054 }
1055 }
1056 // Max distance duration constraint.
1057 tasks_.distance_duration =
1058 dimension_.GetBreakDistanceDurationOfVehicle(vehicle);
1059
1060 // Reduce bounds until failure or fixed point is reached.
1061 // We set a maximum amount of iterations to avoid slow propagation.
1062 bool is_feasible = true;
1063 int maximum_num_iterations = 8;
1064 while (--maximum_num_iterations >= 0) {
1065 old_start_min_ = tasks_.start_min;
1066 old_start_max_ = tasks_.start_max;
1067 old_end_min_ = tasks_.end_min;
1068 old_end_max_ = tasks_.end_max;
1069 is_feasible = disjunctive_propagator_.Propagate(&tasks_);
1070 if (!is_feasible) break;
1071 // If fixed point reached, stop.
1072 if ((old_start_min_ == tasks_.start_min) &&
1073 (old_start_max_ == tasks_.start_max) &&
1074 (old_end_min_ == tasks_.end_min) && (old_end_max_ == tasks_.end_max)) {
1075 break;
1076 }
1077 }
1078 return is_feasible;
1079}
1080
1081} // namespace
1082
1084 const RoutingModel& routing_model, const RoutingDimension& dimension) {
1085 return routing_model.solver()->RevAlloc(
1086 new VehicleBreaksFilter(routing_model, dimension));
1087}
1088
1089} // namespace operations_research
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
IntVarLocalSearchFilter * MakeVehicleBreaksFilter(const RoutingModel &routing_model, const RoutingDimension &dimension)
void FillTravelBoundsOfVehicle(int vehicle, absl::Span< const int64_t > path, const RoutingDimension &dimension, TravelBounds *travel_bounds)
void AppendTasksFromPath(absl::Span< const int64_t > path, const TravelBounds &travel_bounds, const RoutingDimension &dimension, DisjunctivePropagator::Tasks *tasks)
Demon * MakeDelayedConstraintDemon1(Solver *const s, T *const ct, void(T::*method)(P), const std::string &name, P param1)
Demon * MakeConstraintDemon1(Solver *const s, T *const ct, void(T::*method)(P), const std::string &name, P param1)
void FillPathEvaluation(absl::Span< const int64_t > path, const RoutingModel::TransitCallback2 &evaluator, std::vector< int64_t > *values)
Definition routing.cc:6745
void AppendTasksFromIntervals(const std::vector< IntervalVar * > &intervals, DisjunctivePropagator::Tasks *tasks)