Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
routing_constraints.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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
15
16#include <algorithm>
17#include <cstdint>
18#include <functional>
19#include <limits>
20#include <string>
21#include <utility>
22#include <vector>
23
24#include "absl/container/flat_hash_set.h"
25#include "absl/log/check.h"
33
34namespace operations_research {
35
36namespace {
37// Constraint which ensures that var != values.
38class DifferentFromValues : public Constraint {
39 public:
40 DifferentFromValues(Solver* solver, IntVar* var, std::vector<int64_t> values)
41 : Constraint(solver), var_(var), values_(std::move(values)) {}
42 void Post() override {}
43 void InitialPropagate() override { var_->RemoveValues(values_); }
44 std::string DebugString() const override { return "DifferentFromValues"; }
45 void Accept(ModelVisitor* const visitor) const override {
46 visitor->BeginVisitConstraint(RoutingModelVisitor::kRemoveValues, this);
47 visitor->VisitIntegerVariableArrayArgument(ModelVisitor::kVarsArgument,
48 {var_});
49 visitor->VisitIntegerArrayArgument(ModelVisitor::kValuesArgument, values_);
50 visitor->EndVisitConstraint(RoutingModelVisitor::kRemoveValues, this);
51 }
52
53 private:
54 IntVar* const var_;
55 const std::vector<int64_t> values_;
56};
57} // namespace
58
60 std::vector<int64_t> values) {
61 return solver->RevAlloc(
62 new DifferentFromValues(solver, var, std::move(values)));
63}
64
65namespace {
66// For each vehicle, computes information on the partially fixed start/end
67// chains (based on bound NextVar values):
68// - For every 'end_node', the last node of a start chain of a vehicle,
69// vehicle_index_of_start_chain_end[end_node] contains the corresponding
70// vehicle index. Contains -1 for other nodes.
71// - For every vehicle 'v', end_chain_starts[v] contains the first node of the
72// end chain of that vehicle.
73void ComputeVehicleChainStartEndInfo(
74 const RoutingModel& model, std::vector<int64_t>* end_chain_starts,
75 std::vector<int>* vehicle_index_of_start_chain_end) {
76 vehicle_index_of_start_chain_end->resize(model.Size() + model.vehicles(), -1);
77
78 for (int vehicle = 0; vehicle < model.vehicles(); ++vehicle) {
79 int64_t node = model.Start(vehicle);
80 while (!model.IsEnd(node) && model.NextVar(node)->Bound()) {
81 node = model.NextVar(node)->Value();
82 }
83 vehicle_index_of_start_chain_end->at(node) = vehicle;
84 }
85
86 *end_chain_starts = ComputeVehicleEndChainStarts(model);
87}
88
89class ResourceAssignmentConstraint : public Constraint {
90 public:
91 ResourceAssignmentConstraint(
92 const RoutingModel::ResourceGroup* resource_group,
93 const std::vector<IntVar*>* vehicle_resource_vars, RoutingModel* model)
94 : Constraint(model->solver()),
95 model_(*model),
96 resource_group_(*resource_group),
97 vehicle_resource_vars_(*vehicle_resource_vars) {
98 DCHECK_EQ(vehicle_resource_vars_.size(), model_.vehicles());
99
100 const std::vector<RoutingDimension*>& dimensions = model_.GetDimensions();
101 for (int v = 0; v < model_.vehicles(); v++) {
102 IntVar* const resource_var = vehicle_resource_vars_[v];
103 model->AddToAssignment(resource_var);
104 // The resource variable must be fixed by the search.
105 model->AddVariableTargetToFinalizer(resource_var, -1);
106
107 if (!resource_group_.VehicleRequiresAResource(v)) {
108 continue;
109 }
110 for (const RoutingModel::DimensionIndex d :
111 resource_group_.GetAffectedDimensionIndices()) {
112 const RoutingDimension* const dim = dimensions[d.value()];
113 // The vehicle's start/end cumuls must be fixed by the search.
114 model->AddVariableMinimizedByFinalizer(dim->CumulVar(model_.End(v)));
115 model->AddVariableMaximizedByFinalizer(dim->CumulVar(model_.Start(v)));
116 }
117 }
118 }
119
120 void Post() override {}
121
122 void InitialPropagate() override {
123 if (!AllResourceAssignmentsFeasible()) {
124 solver()->Fail();
125 }
126 SetupResourceConstraints();
127 }
128
129 private:
130 bool AllResourceAssignmentsFeasible() {
131 DCHECK(!model_.GetResourceGroups().empty());
132
133 std::vector<int64_t> end_chain_starts;
134 std::vector<int> vehicle_index_of_start_chain_end;
135 ComputeVehicleChainStartEndInfo(model_, &end_chain_starts,
136 &vehicle_index_of_start_chain_end);
137 const auto next = [&model = model_, &end_chain_starts,
138 &vehicle_index_of_start_chain_end](int64_t node) {
139 if (model.NextVar(node)->Bound()) return model.NextVar(node)->Value();
140 const int vehicle = vehicle_index_of_start_chain_end[node];
141 if (vehicle < 0) {
142 // The node isn't the last node of a route start chain and is considered
143 // as unperformed and ignored when evaluating the feasibility of the
144 // resource assignment.
145 return node;
146 }
147 return end_chain_starts[vehicle];
148 };
149
150 const std::vector<RoutingDimension*>& dimensions = model_.GetDimensions();
151 for (RoutingModel::DimensionIndex d :
152 resource_group_.GetAffectedDimensionIndices()) {
153 if (!ResourceAssignmentFeasibleForDimension(*dimensions[d.value()],
154 next)) {
155 return false;
156 }
157 }
158 return true;
159 }
160
161 bool ResourceAssignmentFeasibleForDimension(
162 const RoutingDimension& dimension,
163 const std::function<int64_t(int64_t)>& next) {
164 LocalDimensionCumulOptimizer* const optimizer =
165 model_.GetMutableLocalCumulLPOptimizer(dimension);
166
167 if (optimizer == nullptr) return true;
168
169 LocalDimensionCumulOptimizer* const mp_optimizer =
170 model_.GetMutableLocalCumulMPOptimizer(dimension);
171 DCHECK_NE(mp_optimizer, nullptr);
172 const auto transit = [&dimension](int64_t node, int64_t /*next*/) {
173 // TODO(user): Get rid of this max() by only allowing resources on
174 // dimensions with positive transits (model.AreVehicleTransitsPositive()).
175 // TODO(user): The transit lower bounds have not necessarily been
176 // propagated at this point. Add demons to check the resource assignment
177 // feasibility after the transit ranges have been propagated.
178 return std::max<int64_t>(dimension.FixedTransitVar(node)->Min(), 0);
179 };
180
181 using RCIndex = RoutingModel::ResourceClassIndex;
183 ignored_resources_per_class(resource_group_.GetResourceClassesCount());
184 std::vector<std::vector<int64_t>> assignment_costs(model_.vehicles());
185 for (int v : resource_group_.GetVehiclesRequiringAResource()) {
187 v, resource_group_, ignored_resources_per_class, next, transit,
188 /*optimize_vehicle_costs*/ false,
189 model_.GetMutableLocalCumulLPOptimizer(dimension),
190 model_.GetMutableLocalCumulMPOptimizer(dimension),
191 &assignment_costs[v], nullptr, nullptr)) {
192 return false;
193 }
194 }
195 // TODO(user): Replace this call with a more efficient max-flow, instead
196 // of running the full min-cost flow.
198 resource_group_.GetVehiclesRequiringAResource(),
199 resource_group_.GetResourceIndicesPerClass(),
200 ignored_resources_per_class,
201 [&assignment_costs](int v) { return &assignment_costs[v]; },
202 nullptr) >= 0;
203 }
204
205 void SetupResourceConstraints() {
206 Solver* const s = solver();
207 // Resources cannot be shared, so assigned resources must all be different
208 // (note that resource_var == -1 means no resource assigned).
209 s->AddConstraint(s->MakeAllDifferentExcept(vehicle_resource_vars_, -1));
210 for (int v = 0; v < model_.vehicles(); v++) {
211 IntVar* const resource_var = vehicle_resource_vars_[v];
212 if (!resource_group_.VehicleRequiresAResource(v)) {
213 resource_var->SetValue(-1);
214 continue;
215 }
216 // vehicle_route_considered_[v] <--> vehicle_res_vars[v] != -1.
217 s->AddConstraint(
218 s->MakeEquality(model_.VehicleRouteConsideredVar(v),
219 s->MakeIsDifferentCstVar(resource_var, -1)));
220
221 // Reduce domain of resource_var.
222 const absl::flat_hash_set<int>& resources_marked_allowed =
223 resource_group_.GetResourcesMarkedAllowedForVehicle(v);
224 if (!resources_marked_allowed.empty()) {
225 std::vector<int> allowed_resources(resources_marked_allowed.begin(),
226 resources_marked_allowed.end());
227 allowed_resources.push_back(-1);
228 s->AddConstraint(s->MakeMemberCt(resource_var, allowed_resources));
229 }
230
231 if (resource_var->Bound()) {
232 ResourceBound(v);
233 } else {
234 Demon* const demon = MakeConstraintDemon1(
235 s, this, &ResourceAssignmentConstraint::ResourceBound,
236 "ResourceBound", v);
237 resource_var->WhenBound(demon);
238 }
239 }
240 }
241 void ResourceBound(int vehicle) {
242 const int64_t resource = vehicle_resource_vars_[vehicle]->Value();
243 if (resource < 0) return;
244 for (const RoutingModel::DimensionIndex d :
245 resource_group_.GetAffectedDimensionIndices()) {
246 const RoutingDimension* const dim = model_.GetDimensions()[d.value()];
247 const RoutingModel::ResourceGroup::Attributes& attributes =
248 resource_group_.GetResources()[resource].GetDimensionAttributes(dim);
249 // resource_start_lb <= cumul[start(vehicle)] <= resource_start_ub
250 // resource_end_lb <= cumul[end(vehicle)] <= resource_end_ub
251 dim->CumulVar(model_.Start(vehicle))
252 ->SetRange(attributes.start_domain().Min(),
253 attributes.start_domain().Max());
254 dim->CumulVar(model_.End(vehicle))
255 ->SetRange(attributes.end_domain().Min(),
256 attributes.end_domain().Max());
257 }
258 }
259
260 const RoutingModel& model_;
261 const RoutingModel::ResourceGroup& resource_group_;
262 const std::vector<IntVar*>& vehicle_resource_vars_;
263};
264} // namespace
265
267 const RoutingModel::ResourceGroup* resource_group,
268 const std::vector<IntVar*>* vehicle_resource_vars, RoutingModel* model) {
269 return model->solver()->RevAlloc(new ResourceAssignmentConstraint(
270 resource_group, vehicle_resource_vars, model));
271}
272
273namespace {
274class PathSpansAndTotalSlacks : public Constraint {
275 public:
276 PathSpansAndTotalSlacks(const RoutingModel* model,
277 const RoutingDimension* dimension,
278 std::vector<IntVar*> spans,
279 std::vector<IntVar*> total_slacks)
280 : Constraint(model->solver()),
281 model_(model),
282 dimension_(dimension),
283 spans_(std::move(spans)),
284 total_slacks_(std::move(total_slacks)) {
285 CHECK_EQ(spans_.size(), model_->vehicles());
286 CHECK_EQ(total_slacks_.size(), model_->vehicles());
287 vehicle_demons_.resize(model_->vehicles());
288 }
289
290 std::string DebugString() const override { return "PathSpansAndTotalSlacks"; }
291
292 void Post() override {
293 const int num_nodes = model_->VehicleVars().size();
294 const int num_transits = model_->Nexts().size();
295 for (int node = 0; node < num_nodes; ++node) {
296 auto* demon = MakeConstraintDemon1(
297 model_->solver(), this, &PathSpansAndTotalSlacks::PropagateNode,
298 "PathSpansAndTotalSlacks::PropagateNode", node);
299 dimension_->CumulVar(node)->WhenRange(demon);
300 model_->VehicleVar(node)->WhenBound(demon);
301 if (node < num_transits) {
302 dimension_->TransitVar(node)->WhenRange(demon);
303 dimension_->FixedTransitVar(node)->WhenBound(demon);
304 model_->NextVar(node)->WhenBound(demon);
305 }
306 }
307 for (int vehicle = 0; vehicle < spans_.size(); ++vehicle) {
308 if (!spans_[vehicle] && !total_slacks_[vehicle]) continue;
309 auto* demon = MakeDelayedConstraintDemon1(
310 solver(), this, &PathSpansAndTotalSlacks::PropagateVehicle,
311 "PathSpansAndTotalSlacks::PropagateVehicle", vehicle);
312 vehicle_demons_[vehicle] = demon;
313 if (spans_[vehicle]) spans_[vehicle]->WhenRange(demon);
314 if (total_slacks_[vehicle]) total_slacks_[vehicle]->WhenRange(demon);
315 if (dimension_->HasBreakConstraints()) {
316 for (IntervalVar* b : dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
317 b->WhenAnything(demon);
318 }
319 }
320 }
321 }
322
323 // Call propagator on all vehicles.
324 void InitialPropagate() override {
325 for (int vehicle = 0; vehicle < spans_.size(); ++vehicle) {
326 if (!spans_[vehicle] && !total_slacks_[vehicle]) continue;
327 PropagateVehicle(vehicle);
328 }
329 }
330
331 private:
332 // Called when a path/dimension variables of the node changes,
333 // this delays propagator calls until path variables (Next and VehicleVar)
334 // are instantiated, which saves fruitless and multiple identical calls.
335 void PropagateNode(int node) {
336 if (!model_->VehicleVar(node)->Bound()) return;
337 const int vehicle = model_->VehicleVar(node)->Min();
338 if (vehicle < 0 || vehicle_demons_[vehicle] == nullptr) return;
339 EnqueueDelayedDemon(vehicle_demons_[vehicle]);
340 }
341
342 // In order to make reasoning on span and total_slack of a vehicle uniform,
343 // we rely on the fact that span == sum_fixed_transits + total_slack
344 // to present both span and total_slack in terms of span and fixed transit.
345 // This allows to use the same code whether there actually are variables
346 // for span and total_slack or not.
347 int64_t SpanMin(int vehicle, int64_t sum_fixed_transits) {
348 DCHECK_GE(sum_fixed_transits, 0);
349 const int64_t span_min = spans_[vehicle]
350 ? spans_[vehicle]->Min()
351 : std::numeric_limits<int64_t>::max();
352 const int64_t total_slack_min = total_slacks_[vehicle]
353 ? total_slacks_[vehicle]->Min()
354 : std::numeric_limits<int64_t>::max();
355 return std::min(span_min, CapAdd(total_slack_min, sum_fixed_transits));
356 }
357 int64_t SpanMax(int vehicle, int64_t sum_fixed_transits) {
358 DCHECK_GE(sum_fixed_transits, 0);
359 const int64_t span_max = spans_[vehicle]
360 ? spans_[vehicle]->Max()
361 : std::numeric_limits<int64_t>::min();
362 const int64_t total_slack_max = total_slacks_[vehicle]
363 ? total_slacks_[vehicle]->Max()
364 : std::numeric_limits<int64_t>::min();
365 return std::max(span_max, CapAdd(total_slack_max, sum_fixed_transits));
366 }
367 void SetSpanMin(int vehicle, int64_t min, int64_t sum_fixed_transits) {
368 DCHECK_GE(sum_fixed_transits, 0);
369 if (spans_[vehicle]) {
370 spans_[vehicle]->SetMin(min);
371 }
372 if (total_slacks_[vehicle]) {
373 total_slacks_[vehicle]->SetMin(CapSub(min, sum_fixed_transits));
374 }
375 }
376 void SetSpanMax(int vehicle, int64_t max, int64_t sum_fixed_transits) {
377 DCHECK_GE(sum_fixed_transits, 0);
378 if (spans_[vehicle]) {
379 spans_[vehicle]->SetMax(max);
380 }
381 if (total_slacks_[vehicle]) {
382 total_slacks_[vehicle]->SetMax(CapSub(max, sum_fixed_transits));
383 }
384 }
385 // Propagates span == sum_fixed_transits + total_slack.
386 // This should be called at least once during PropagateVehicle().
387 void SynchronizeSpanAndTotalSlack(int vehicle, int64_t sum_fixed_transits) {
388 DCHECK_GE(sum_fixed_transits, 0);
389 IntVar* span = spans_[vehicle];
390 IntVar* total_slack = total_slacks_[vehicle];
391 if (!span || !total_slack) return;
392 span->SetMin(CapAdd(total_slack->Min(), sum_fixed_transits));
393 span->SetMax(CapAdd(total_slack->Max(), sum_fixed_transits));
394 total_slack->SetMin(CapSub(span->Min(), sum_fixed_transits));
395 total_slack->SetMax(CapSub(span->Max(), sum_fixed_transits));
396 }
397
398 void PropagateVehicle(int vehicle) {
399 DCHECK(spans_[vehicle] || total_slacks_[vehicle]);
400 const int start = model_->Start(vehicle);
401 const int end = model_->End(vehicle);
402 // If transits are positive, the domain of the span variable can be reduced
403 // to cumul(end) - cumul(start).
404 if (spans_[vehicle] != nullptr &&
405 dimension_->AreVehicleTransitsPositive(vehicle)) {
406 spans_[vehicle]->SetRange(CapSub(dimension_->CumulVar(end)->Min(),
407 dimension_->CumulVar(start)->Max()),
408 CapSub(dimension_->CumulVar(end)->Max(),
409 dimension_->CumulVar(start)->Min()));
410 }
411 // Record path, if it is not fixed from start to end, stop here.
412 // TRICKY: do not put end node yet, we look only at transits in the next
413 // reasonings, we will append the end when we look at cumuls.
414 {
415 path_.clear();
416 int curr_node = start;
417 while (!model_->IsEnd(curr_node)) {
418 const IntVar* next_var = model_->NextVar(curr_node);
419 if (!next_var->Bound()) return;
420 path_.push_back(curr_node);
421 curr_node = next_var->Value();
422 }
423 }
424 // Compute the sum of fixed transits. Fixed transit variables should all be
425 // fixed, otherwise we wait to get called later when propagation does it.
426 int64_t sum_fixed_transits = 0;
427 for (const int node : path_) {
428 const IntVar* fixed_transit_var = dimension_->FixedTransitVar(node);
429 if (!fixed_transit_var->Bound()) return;
430 sum_fixed_transits =
431 CapAdd(sum_fixed_transits, fixed_transit_var->Value());
432 }
433
434 SynchronizeSpanAndTotalSlack(vehicle, sum_fixed_transits);
435
436 // The amount of break time that must occur during the route must be smaller
437 // than span max - sum_fixed_transits. A break must occur on the route if it
438 // must be after the route's start and before the route's end.
439 // Propagate lower bound on span, then filter out values
440 // that would force more breaks in route than possible.
441 if (dimension_->HasBreakConstraints() &&
442 !dimension_->GetBreakIntervalsOfVehicle(vehicle).empty()) {
443 const int64_t vehicle_start_max = dimension_->CumulVar(start)->Max();
444 const int64_t vehicle_end_min = dimension_->CumulVar(end)->Min();
445 // Compute and propagate lower bound.
446 int64_t min_break_duration = 0;
447 for (IntervalVar* br : dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
448 if (!br->MustBePerformed()) continue;
449 if (vehicle_start_max < br->EndMin() &&
450 br->StartMax() < vehicle_end_min) {
451 min_break_duration = CapAdd(min_break_duration, br->DurationMin());
452 }
453 }
454 SetSpanMin(vehicle, CapAdd(min_break_duration, sum_fixed_transits),
455 sum_fixed_transits);
456 // If a break that is not inside the route may violate slack_max,
457 // we can propagate in some cases: when the break must be before or
458 // must be after the route.
459 // In the other cases, we cannot deduce a better bound on a CumulVar or
460 // on a break, so we do nothing.
461 const int64_t slack_max =
462 CapSub(SpanMax(vehicle, sum_fixed_transits), sum_fixed_transits);
463 const int64_t max_additional_slack =
464 CapSub(slack_max, min_break_duration);
465 for (IntervalVar* br : dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
466 if (!br->MustBePerformed()) continue;
467 // Break must be before end, detect whether it must be before start.
468 if (vehicle_start_max >= br->EndMin() &&
469 br->StartMax() < vehicle_end_min) {
470 if (br->DurationMin() > max_additional_slack) {
471 // Having the break inside would violate max_additional_slack..
472 // Thus, it must be outside the route, in this case, before.
473 br->SetEndMax(vehicle_start_max);
474 dimension_->CumulVar(start)->SetMin(br->EndMin());
475 }
476 }
477 // Break must be after start, detect whether it must be after end.
478 // Same reasoning, in the case where the break is after.
479 if (vehicle_start_max < br->EndMin() &&
480 br->StartMax() >= vehicle_end_min) {
481 if (br->DurationMin() > max_additional_slack) {
482 br->SetStartMin(vehicle_end_min);
483 dimension_->CumulVar(end)->SetMax(br->StartMax());
484 }
485 }
486 }
487 }
488
489 // Propagate span == cumul(end) - cumul(start).
490 {
491 IntVar* start_cumul = dimension_->CumulVar(start);
492 IntVar* end_cumul = dimension_->CumulVar(end);
493 const int64_t start_min = start_cumul->Min();
494 const int64_t start_max = start_cumul->Max();
495 const int64_t end_min = end_cumul->Min();
496 const int64_t end_max = end_cumul->Max();
497 // Propagate from cumuls to span.
498 const int64_t span_lb = CapSub(end_min, start_max);
499 SetSpanMin(vehicle, span_lb, sum_fixed_transits);
500 const int64_t span_ub = CapSub(end_max, start_min);
501 SetSpanMax(vehicle, span_ub, sum_fixed_transits);
502 // Propagate from span to cumuls.
503 const int64_t span_min = SpanMin(vehicle, sum_fixed_transits);
504 const int64_t span_max = SpanMax(vehicle, sum_fixed_transits);
505 const int64_t slack_from_lb = CapSub(span_max, span_lb);
506 const int64_t slack_from_ub = CapSub(span_ub, span_min);
507 // start >= start_max - (span_max - span_lb).
508 start_cumul->SetMin(CapSub(start_max, slack_from_lb));
509 // end <= end_min + (span_max - span_lb).
510 end_cumul->SetMax(CapAdd(end_min, slack_from_lb));
511 // // start <= start_min + (span_ub - span_min)
512 start_cumul->SetMax(CapAdd(start_min, slack_from_ub));
513 // // end >= end_max - (span_ub - span_min)
514 end_cumul->SetMin(CapSub(end_max, slack_from_ub));
515 }
516
517 // Propagate sum transits == span.
518 {
519 // Propagate from transits to span.
520 int64_t span_lb = 0;
521 int64_t span_ub = 0;
522 for (const int node : path_) {
523 span_lb = CapAdd(span_lb, dimension_->TransitVar(node)->Min());
524 span_ub = CapAdd(span_ub, dimension_->TransitVar(node)->Max());
525 }
526 SetSpanMin(vehicle, span_lb, sum_fixed_transits);
527 SetSpanMax(vehicle, span_ub, sum_fixed_transits);
528 // Propagate from span to transits.
529 // transit[i] <= transit_i_min + (span_max - span_lb)
530 // transit[i] >= transit_i_max - (span_ub - span_min)
531 const int64_t span_min = SpanMin(vehicle, sum_fixed_transits);
532 const int64_t span_max = SpanMax(vehicle, sum_fixed_transits);
533 const int64_t slack_from_lb = CapSub(span_max, span_lb);
534 const int64_t slack_from_ub =
535 span_ub < std::numeric_limits<int64_t>::max()
536 ? CapSub(span_ub, span_min)
537 : std::numeric_limits<int64_t>::max();
538 for (const int node : path_) {
539 IntVar* transit_var = dimension_->TransitVar(node);
540 const int64_t transit_i_min = transit_var->Min();
541 const int64_t transit_i_max = transit_var->Max();
542 // TRICKY: the first propagation might change transit_var->Max(),
543 // but we must use the same value of transit_i_max in the computation
544 // of transit[i]'s lower bound that was used for span_ub.
545 transit_var->SetMax(CapAdd(transit_i_min, slack_from_lb));
546 transit_var->SetMin(CapSub(transit_i_max, slack_from_ub));
547 }
548 }
549
550 // TRICKY: add end node now, we will look at cumuls.
551 path_.push_back(end);
552
553 // A stronger bound: from start min of the route, go to node i+1 with time
554 // max(cumul[i] + fixed_transit, cumul[i+1].Min()).
555 // Record arrival time (should be the same as end cumul min).
556 // Then do the reverse route, going to time
557 // min(cumul[i+1] - fixed_transit, cumul[i].Max())
558 // Record final time as departure time.
559 // Then arrival time - departure time is a valid lower bound of span.
560 // First reasoning: start - end - start
561 {
562 // At each iteration, arrival time is a lower bound of path[i]'s cumul,
563 // so we opportunistically tighten the variable.
564 // This helps reduce the amount of inter-constraint propagation.
565 int64_t arrival_time = dimension_->CumulVar(start)->Min();
566 for (int i = 1; i < path_.size(); ++i) {
567 arrival_time =
568 std::max(CapAdd(arrival_time,
569 dimension_->FixedTransitVar(path_[i - 1])->Min()),
570 dimension_->CumulVar(path_[i])->Min());
571 dimension_->CumulVar(path_[i])->SetMin(arrival_time);
572 }
573 // At each iteration, departure_time is the latest time at each the
574 // vehicle can leave to reach the earliest feasible vehicle end. Thus it
575 // is not an upper bound of the cumul, we cannot tighten the variable.
576 int64_t departure_time = arrival_time;
577 for (int i = path_.size() - 2; i >= 0; --i) {
578 departure_time =
579 std::min(CapSub(departure_time,
580 dimension_->FixedTransitVar(path_[i])->Min()),
581 dimension_->CumulVar(path_[i])->Max());
582 }
583 const int64_t span_lb = CapSub(arrival_time, departure_time);
584 SetSpanMin(vehicle, span_lb, sum_fixed_transits);
585 const int64_t maximum_deviation =
586 CapSub(SpanMax(vehicle, sum_fixed_transits), span_lb);
587 const int64_t start_lb = CapSub(departure_time, maximum_deviation);
588 dimension_->CumulVar(start)->SetMin(start_lb);
589 }
590 // Second reasoning: end - start - end
591 {
592 // At each iteration, use departure time to tighten opportunistically.
593 int64_t departure_time = dimension_->CumulVar(end)->Max();
594 for (int i = path_.size() - 2; i >= 0; --i) {
595 departure_time =
596 std::min(CapSub(departure_time,
597 dimension_->FixedTransitVar(path_[i])->Min()),
598 dimension_->CumulVar(path_[i])->Max());
599 dimension_->CumulVar(path_[i])->SetMax(departure_time);
600 }
601 // Symmetrically to the first reasoning, arrival_time is the earliest
602 // possible arrival for the latest departure of vehicle start.
603 // It cannot be used to tighten the successive cumul variables.
604 int arrival_time = departure_time;
605 for (int i = 1; i < path_.size(); ++i) {
606 arrival_time =
607 std::max(CapAdd(arrival_time,
608 dimension_->FixedTransitVar(path_[i - 1])->Min()),
609 dimension_->CumulVar(path_[i])->Min());
610 }
611 const int64_t span_lb = CapSub(arrival_time, departure_time);
612 SetSpanMin(vehicle, span_lb, sum_fixed_transits);
613 const int64_t maximum_deviation =
614 CapSub(SpanMax(vehicle, sum_fixed_transits), span_lb);
615 dimension_->CumulVar(end)->SetMax(
616 CapAdd(arrival_time, maximum_deviation));
617 }
618 }
619
620 const RoutingModel* const model_;
621 const RoutingDimension* const dimension_;
622 std::vector<IntVar*> spans_;
623 std::vector<IntVar*> total_slacks_;
624 std::vector<int> path_;
625 std::vector<Demon*> vehicle_demons_;
626};
627} // namespace
628
629Constraint* MakePathSpansAndTotalSlacks(const RoutingDimension* dimension,
630 std::vector<IntVar*> spans,
631 std::vector<IntVar*> total_slacks) {
632 RoutingModel* const model = dimension->model();
633 CHECK_EQ(model->vehicles(), spans.size());
634 CHECK_EQ(model->vehicles(), total_slacks.size());
635 return model->solver()->RevAlloc(
636 new PathSpansAndTotalSlacks(model, dimension, spans, total_slacks));
637}
638
639namespace {
640// Very light version of the RangeLessOrEqual constraint (see ./range_cst.cc).
641// Only performs initial propagation and then checks the compatibility of the
642// variable domains without domain pruning.
643// This is useful when to avoid ping-pong effects with costly constraints
644// such as the PathCumul constraint.
645// This constraint has not been added to the cp library (in range_cst.cc) given
646// it only does checking and no propagation (except the initial propagation)
647// and is only fit for local search, in particular in the context of vehicle
648// routing.
649class LightRangeLessOrEqual : public Constraint {
650 public:
651 LightRangeLessOrEqual(Solver* s, IntExpr* l, IntExpr* r);
652 ~LightRangeLessOrEqual() override {}
653 void Post() override;
654 void InitialPropagate() override;
655 std::string DebugString() const override;
656 IntVar* Var() override {
657 return solver()->MakeIsLessOrEqualVar(left_, right_);
658 }
659 // TODO(user): introduce a kLightLessOrEqual tag.
660 void Accept(ModelVisitor* const visitor) const override {
661 visitor->BeginVisitConstraint(ModelVisitor::kLessOrEqual, this);
662 visitor->VisitIntegerExpressionArgument(ModelVisitor::kLeftArgument, left_);
663 visitor->VisitIntegerExpressionArgument(ModelVisitor::kRightArgument,
664 right_);
665 visitor->EndVisitConstraint(ModelVisitor::kLessOrEqual, this);
666 }
667
668 private:
669 void CheckRange();
670
671 IntExpr* const left_;
672 IntExpr* const right_;
673 Demon* demon_;
674};
675
676LightRangeLessOrEqual::LightRangeLessOrEqual(Solver* const s, IntExpr* const l,
677 IntExpr* const r)
678 : Constraint(s), left_(l), right_(r), demon_(nullptr) {}
679
680void LightRangeLessOrEqual::Post() {
681 demon_ = MakeConstraintDemon0(
682 solver(), this, &LightRangeLessOrEqual::CheckRange, "CheckRange");
683 left_->WhenRange(demon_);
684 right_->WhenRange(demon_);
685}
686
687void LightRangeLessOrEqual::InitialPropagate() {
688 left_->SetMax(right_->Max());
689 right_->SetMin(left_->Min());
690 if (left_->Max() <= right_->Min()) {
691 demon_->inhibit(solver());
692 }
693}
694
695void LightRangeLessOrEqual::CheckRange() {
696 if (left_->Min() > right_->Max()) {
697 solver()->Fail();
698 }
699 if (left_->Max() <= right_->Min()) {
700 demon_->inhibit(solver());
701 }
702}
703
704std::string LightRangeLessOrEqual::DebugString() const {
705 return left_->DebugString() + " < " + right_->DebugString();
706}
707} // namespace
708
709} // namespace operations_research
int64_t max
int64_t min
std::vector< int > dimensions
For the time being, Solver is neither MT_SAFE nor MT_HOT.
int64_t b
Definition table.cc:45
Block * next
IntVar * var
GRBmodel * model
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapAdd(int64_t x, int64_t y)
std::vector< int64_t > ComputeVehicleEndChainStarts(const RoutingModel &model)
int64_t CapSub(int64_t x, int64_t y)
bool ComputeVehicleToResourceClassAssignmentCosts(int v, const RoutingModel::ResourceGroup &resource_group, const util_intops::StrongVector< RoutingModel::ResourceClassIndex, absl::flat_hash_set< int > > &ignored_resources_per_class, const std::function< int64_t(int64_t)> &next_accessor, const std::function< int64_t(int64_t, int64_t)> &transit_accessor, bool optimize_vehicle_costs, LocalDimensionCumulOptimizer *lp_optimizer, LocalDimensionCumulOptimizer *mp_optimizer, std::vector< int64_t > *assignment_costs, std::vector< std::vector< int64_t > > *cumul_values, std::vector< std::vector< int64_t > > *break_values)
Constraint * MakePathSpansAndTotalSlacks(const RoutingDimension *dimension, std::vector< IntVar * > spans, std::vector< IntVar * > total_slacks)
Demon * MakeConstraintDemon0(Solver *const s, T *const ct, void(T::*method)(), const std::string &name)
Constraint * MakeResourceConstraint(const RoutingModel::ResourceGroup *resource_group, const std::vector< IntVar * > *vehicle_resource_vars, RoutingModel *model)
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)
Constraint * MakeDifferentFromValues(Solver *solver, IntVar *var, std::vector< int64_t > values)
int64_t ComputeBestVehicleToResourceAssignment(const std::vector< int > &vehicles, const util_intops::StrongVector< RoutingModel::ResourceClassIndex, std::vector< int > > &resource_indices_per_class, const util_intops::StrongVector< RoutingModel::ResourceClassIndex, absl::flat_hash_set< int > > &ignored_resources_per_class, std::function< const std::vector< int64_t > *(int)> vehicle_to_resource_class_assignment_costs, std::vector< int > *resource_indices)
STL namespace.
std::vector< int64_t > assignment_costs
Rev< int64_t > start_max
Rev< int64_t > end_max
Rev< int64_t > start_min
Rev< int64_t > end_min
std::optional< int64_t > end
int64_t start