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