Google OR-Tools v9.15
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/algorithm/container.h"
26#include "absl/container/flat_hash_set.h"
27#include "absl/functional/any_invocable.h"
28#include "absl/log/check.h"
29#include "absl/types/span.h"
40
41namespace operations_research {
42
43namespace {
44// Constraint which ensures that var != values.
45class DifferentFromValues : public Constraint {
46 public:
47 DifferentFromValues(Solver* solver, IntVar* var, std::vector<int64_t> values)
48 : Constraint(solver), var_(var), values_(std::move(values)) {}
49 void Post() override {}
50 void InitialPropagate() override { var_->RemoveValues(values_); }
51 std::string DebugString() const override { return "DifferentFromValues"; }
52 void Accept(ModelVisitor* const visitor) const override {
53 visitor->BeginVisitConstraint(RoutingModelVisitor::kRemoveValues, this);
54 visitor->VisitIntegerVariableArrayArgument(ModelVisitor::kVarsArgument,
55 {var_});
56 visitor->VisitIntegerArrayArgument(ModelVisitor::kValuesArgument, values_);
57 visitor->EndVisitConstraint(RoutingModelVisitor::kRemoveValues, this);
58 }
59
60 private:
61 IntVar* const var_;
62 const std::vector<int64_t> values_;
63};
64} // namespace
65
67 std::vector<int64_t> values) {
68 return solver->RevAlloc(
69 new DifferentFromValues(solver, var, std::move(values)));
70}
71
72namespace {
73// For each vehicle, computes information on the partially fixed start/end
74// chains (based on bound NextVar values):
75// - For every 'end_node', the last node of a start chain of a vehicle,
76// vehicle_index_of_start_chain_end[end_node] contains the corresponding
77// vehicle index. Contains -1 for other nodes.
78// - For every vehicle 'v', end_chain_starts[v] contains the first node of the
79// end chain of that vehicle.
80void ComputeVehicleChainStartEndInfo(
81 const RoutingModel& model, std::vector<int64_t>* end_chain_starts,
82 std::vector<int>* vehicle_index_of_start_chain_end) {
83 vehicle_index_of_start_chain_end->resize(model.Size() + model.vehicles(), -1);
84
85 for (int vehicle = 0; vehicle < model.vehicles(); ++vehicle) {
86 int64_t node = model.Start(vehicle);
87 while (!model.IsEnd(node) && model.NextVar(node)->Bound()) {
88 node = model.NextVar(node)->Value();
89 }
90 vehicle_index_of_start_chain_end->at(node) = vehicle;
91 }
92
93 *end_chain_starts = ComputeVehicleEndChainStarts(model);
94}
95
96class ResourceAssignmentConstraint : public Constraint {
97 public:
98 ResourceAssignmentConstraint(
99 const RoutingModel::ResourceGroup* resource_group,
100 const std::vector<IntVar*>* vehicle_resource_vars, RoutingModel* model)
101 : Constraint(model->solver()),
102 model_(*model),
103 resource_group_(*resource_group),
104 vehicle_resource_vars_(*vehicle_resource_vars) {
105 DCHECK_EQ(vehicle_resource_vars_.size(), model_.vehicles());
106
107 const std::vector<RoutingDimension*>& dimensions = model_.GetDimensions();
108 for (int v = 0; v < model_.vehicles(); v++) {
109 IntVar* const resource_var = vehicle_resource_vars_[v];
110 model->AddToAssignment(resource_var);
111 // The resource variable must be fixed by the search.
112 model->AddVariableTargetToFinalizer(resource_var, -1);
113
114 if (!resource_group_.VehicleRequiresAResource(v)) {
115 continue;
116 }
117 for (const RoutingModel::DimensionIndex d :
118 resource_group_.GetAffectedDimensionIndices()) {
119 const RoutingDimension* const dim = dimensions[d.value()];
120 // The vehicle's start/end cumuls must be fixed by the search.
121 model->AddVariableMinimizedByFinalizer(dim->CumulVar(model_.End(v)));
122 model->AddVariableMaximizedByFinalizer(dim->CumulVar(model_.Start(v)));
123 }
124 }
125 }
126
127 void Post() override {}
128
129 void InitialPropagate() override {
130 if (!AllResourceAssignmentsFeasible()) {
131 solver()->Fail();
132 }
133 SetupResourceConstraints();
134 }
135
136 private:
137 bool AllResourceAssignmentsFeasible() {
138 DCHECK(!model_.GetResourceGroups().empty());
139
140 std::vector<int64_t> end_chain_starts;
141 std::vector<int> vehicle_index_of_start_chain_end;
142 ComputeVehicleChainStartEndInfo(model_, &end_chain_starts,
143 &vehicle_index_of_start_chain_end);
144 const auto next = [&model = model_, &end_chain_starts,
145 &vehicle_index_of_start_chain_end](int64_t node) {
146 if (model.NextVar(node)->Bound()) return model.NextVar(node)->Value();
147 const int vehicle = vehicle_index_of_start_chain_end[node];
148 if (vehicle < 0) {
149 // The node isn't the last node of a route start chain and is considered
150 // as unperformed and ignored when evaluating the feasibility of the
151 // resource assignment.
152 return node;
153 }
154 return end_chain_starts[vehicle];
155 };
156
157 const std::vector<RoutingDimension*>& dimensions = model_.GetDimensions();
159 resource_group_.GetAffectedDimensionIndices()) {
160 if (!ResourceAssignmentFeasibleForDimension(*dimensions[d.value()],
161 next)) {
162 return false;
163 }
164 }
165 return true;
166 }
167
168 bool ResourceAssignmentFeasibleForDimension(
169 const RoutingDimension& dimension,
170 const std::function<int64_t(int64_t)>& next) {
171 LocalDimensionCumulOptimizer* const optimizer =
172 model_.GetMutableLocalCumulLPOptimizer(dimension);
173
174 if (optimizer == nullptr) return true;
175
176 LocalDimensionCumulOptimizer* const mp_optimizer =
177 model_.GetMutableLocalCumulMPOptimizer(dimension);
178 DCHECK_NE(mp_optimizer, nullptr);
179 const auto transit = [&dimension](int64_t node, int64_t /*next*/) {
180 // TODO(user): Get rid of this max() by only allowing resources on
181 // dimensions with positive transits (model.AreVehicleTransitsPositive()).
182 // TODO(user): The transit lower bounds have not necessarily been
183 // propagated at this point. Add demons to check the resource assignment
184 // feasibility after the transit ranges have been propagated.
185 return std::max<int64_t>(dimension.FixedTransitVar(node)->Min(), 0);
186 };
187
188 using RCIndex = RoutingModel::ResourceClassIndex;
189 const util_intops::StrongVector<RCIndex, absl::flat_hash_set<int>>
190 ignored_resources_per_class(resource_group_.GetResourceClassesCount());
191 std::vector<std::vector<int64_t>> assignment_costs(model_.vehicles());
192 // TODO(user): Adjust the 'solve_duration_ratio' parameter.
193 for (int v : resource_group_.GetVehiclesRequiringAResource()) {
195 v, /*solve_duration_ratio=*/1.0, resource_group_,
196 ignored_resources_per_class, next, transit,
197 /*optimize_vehicle_costs*/ false,
198 model_.GetMutableLocalCumulLPOptimizer(dimension),
199 model_.GetMutableLocalCumulMPOptimizer(dimension),
200 &assignment_costs[v], nullptr, nullptr)) {
201 return false;
202 }
203 }
204 // TODO(user): Replace this call with a more efficient max-flow, instead
205 // of running the full min-cost flow.
207 resource_group_.GetVehiclesRequiringAResource(),
208 resource_group_.GetResourceIndicesPerClass(),
209 ignored_resources_per_class,
210 [&assignment_costs](int v) { return &assignment_costs[v]; },
211 nullptr) >= 0;
212 }
213
214 void SetupResourceConstraints() {
215 Solver* const s = solver();
216 // Resources cannot be shared, so assigned resources must all be different
217 // (note that resource_var == -1 means no resource assigned).
218 s->AddConstraint(s->MakeAllDifferentExcept(vehicle_resource_vars_, -1));
219 for (int v = 0; v < model_.vehicles(); v++) {
220 IntVar* const resource_var = vehicle_resource_vars_[v];
221 if (!resource_group_.VehicleRequiresAResource(v)) {
222 resource_var->SetValue(-1);
223 continue;
224 }
225 // vehicle_route_considered_[v] <--> vehicle_res_vars[v] != -1.
226 s->AddConstraint(
227 s->MakeEquality(model_.VehicleRouteConsideredVar(v),
228 s->MakeIsDifferentCstVar(resource_var, -1)));
229
230 // Reduce domain of resource_var.
231 const absl::flat_hash_set<int>& resources_marked_allowed =
232 resource_group_.GetResourcesMarkedAllowedForVehicle(v);
233 if (!resources_marked_allowed.empty()) {
234 std::vector<int> allowed_resources(resources_marked_allowed.begin(),
235 resources_marked_allowed.end());
236 allowed_resources.push_back(-1);
237 s->AddConstraint(s->MakeMemberCt(resource_var, allowed_resources));
238 }
239
240 if (resource_var->Bound()) {
241 ResourceBound(v);
242 } else {
243 Demon* const demon = MakeConstraintDemon1(
244 s, this, &ResourceAssignmentConstraint::ResourceBound,
245 "ResourceBound", v);
246 resource_var->WhenBound(demon);
247 }
248 }
249 }
250 void ResourceBound(int vehicle) {
251 const int64_t resource = vehicle_resource_vars_[vehicle]->Value();
252 if (resource < 0) return;
253 for (const RoutingModel::DimensionIndex d :
254 resource_group_.GetAffectedDimensionIndices()) {
255 const RoutingDimension* const dim = model_.GetDimensions()[d.value()];
256 const RoutingModel::ResourceGroup::Attributes& attributes =
257 resource_group_.GetResources()[resource].GetDimensionAttributes(d);
258 // resource_start_lb <= cumul[start(vehicle)] <= resource_start_ub
259 // resource_end_lb <= cumul[end(vehicle)] <= resource_end_ub
260 dim->CumulVar(model_.Start(vehicle))
261 ->SetRange(attributes.start_domain().Min(),
262 attributes.start_domain().Max());
263 dim->CumulVar(model_.End(vehicle))
264 ->SetRange(attributes.end_domain().Min(),
265 attributes.end_domain().Max());
266 }
267 }
268
269 const RoutingModel& model_;
270 const RoutingModel::ResourceGroup& resource_group_;
271 const std::vector<IntVar*>& vehicle_resource_vars_;
272};
273} // namespace
274
276 const RoutingModel::ResourceGroup* resource_group,
277 const std::vector<IntVar*>* vehicle_resource_vars, RoutingModel* model) {
278 return model->solver()->RevAlloc(new ResourceAssignmentConstraint(
279 resource_group, vehicle_resource_vars, model));
280}
281
282namespace {
283class PathSpansAndTotalSlacks : public Constraint {
284 public:
285 PathSpansAndTotalSlacks(const RoutingModel* model,
286 const RoutingDimension* dimension,
287 std::vector<IntVar*> spans,
288 std::vector<IntVar*> total_slacks)
289 : Constraint(model->solver()),
290 model_(model),
291 dimension_(dimension),
292 spans_(std::move(spans)),
293 total_slacks_(std::move(total_slacks)) {
294 CHECK_EQ(spans_.size(), model_->vehicles());
295 CHECK_EQ(total_slacks_.size(), model_->vehicles());
296 vehicle_demons_.resize(model_->vehicles());
297 }
298
299 std::string DebugString() const override { return "PathSpansAndTotalSlacks"; }
300
301 void Post() override {
302 const int num_nodes = model_->VehicleVars().size();
303 const int num_transits = model_->Nexts().size();
304 for (int node = 0; node < num_nodes; ++node) {
305 auto* demon = MakeConstraintDemon1(
306 model_->solver(), this, &PathSpansAndTotalSlacks::PropagateNode,
307 "PathSpansAndTotalSlacks::PropagateNode", node);
308 dimension_->CumulVar(node)->WhenRange(demon);
309 model_->VehicleVar(node)->WhenBound(demon);
310 if (node < num_transits) {
311 dimension_->TransitVar(node)->WhenRange(demon);
312 dimension_->FixedTransitVar(node)->WhenBound(demon);
313 model_->NextVar(node)->WhenBound(demon);
314 }
315 }
316 for (int vehicle = 0; vehicle < spans_.size(); ++vehicle) {
317 if (!spans_[vehicle] && !total_slacks_[vehicle]) continue;
318 auto* demon = MakeDelayedConstraintDemon1(
319 solver(), this, &PathSpansAndTotalSlacks::PropagateVehicle,
320 "PathSpansAndTotalSlacks::PropagateVehicle", vehicle);
321 vehicle_demons_[vehicle] = demon;
322 if (spans_[vehicle]) spans_[vehicle]->WhenRange(demon);
323 if (total_slacks_[vehicle]) total_slacks_[vehicle]->WhenRange(demon);
324 if (dimension_->HasBreakConstraints()) {
325 for (IntervalVar* b : dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
326 b->WhenAnything(demon);
327 }
328 }
329 }
330 }
331
332 // Call propagator on all vehicles.
333 void InitialPropagate() override {
334 for (int vehicle = 0; vehicle < spans_.size(); ++vehicle) {
335 if (!spans_[vehicle] && !total_slacks_[vehicle]) continue;
336 PropagateVehicle(vehicle);
337 }
338 }
339
340 private:
341 // Called when a path/dimension variables of the node changes,
342 // this delays propagator calls until path variables (Next and VehicleVar)
343 // are instantiated, which saves fruitless and multiple identical calls.
344 void PropagateNode(int node) {
345 if (!model_->VehicleVar(node)->Bound()) return;
346 const int vehicle = model_->VehicleVar(node)->Min();
347 if (vehicle < 0 || vehicle_demons_[vehicle] == nullptr) return;
348 EnqueueDelayedDemon(vehicle_demons_[vehicle]);
349 }
350
351 // In order to make reasoning on span and total_slack of a vehicle uniform,
352 // we rely on the fact that span == sum_fixed_transits + total_slack
353 // to present both span and total_slack in terms of span and fixed transit.
354 // This allows to use the same code whether there actually are variables
355 // for span and total_slack or not.
356 int64_t SpanMin(int vehicle, int64_t sum_fixed_transits) {
357 DCHECK_GE(sum_fixed_transits, 0);
358 const int64_t span_min = spans_[vehicle]
359 ? spans_[vehicle]->Min()
360 : std::numeric_limits<int64_t>::max();
361 const int64_t total_slack_min = total_slacks_[vehicle]
362 ? total_slacks_[vehicle]->Min()
363 : std::numeric_limits<int64_t>::max();
364 return std::min(span_min, CapAdd(total_slack_min, sum_fixed_transits));
365 }
366 int64_t SpanMax(int vehicle, int64_t sum_fixed_transits) {
367 DCHECK_GE(sum_fixed_transits, 0);
368 const int64_t span_max = spans_[vehicle]
369 ? spans_[vehicle]->Max()
370 : std::numeric_limits<int64_t>::min();
371 const int64_t total_slack_max = total_slacks_[vehicle]
372 ? total_slacks_[vehicle]->Max()
373 : std::numeric_limits<int64_t>::min();
374 return std::max(span_max, CapAdd(total_slack_max, sum_fixed_transits));
375 }
376 void SetSpanMin(int vehicle, int64_t min, int64_t sum_fixed_transits) {
377 DCHECK_GE(sum_fixed_transits, 0);
378 if (spans_[vehicle]) {
379 spans_[vehicle]->SetMin(min);
380 }
381 if (total_slacks_[vehicle]) {
382 total_slacks_[vehicle]->SetMin(CapSub(min, sum_fixed_transits));
383 }
384 }
385 void SetSpanMax(int vehicle, int64_t max, int64_t sum_fixed_transits) {
386 DCHECK_GE(sum_fixed_transits, 0);
387 if (spans_[vehicle]) {
388 spans_[vehicle]->SetMax(max);
389 }
390 if (total_slacks_[vehicle]) {
391 total_slacks_[vehicle]->SetMax(CapSub(max, sum_fixed_transits));
392 }
393 }
394 // Propagates span == sum_fixed_transits + total_slack.
395 // This should be called at least once during PropagateVehicle().
396 void SynchronizeSpanAndTotalSlack(int vehicle, int64_t sum_fixed_transits) {
397 DCHECK_GE(sum_fixed_transits, 0);
398 IntVar* span = spans_[vehicle];
399 IntVar* total_slack = total_slacks_[vehicle];
400 if (!span || !total_slack) return;
401 span->SetMin(CapAdd(total_slack->Min(), sum_fixed_transits));
402 span->SetMax(CapAdd(total_slack->Max(), sum_fixed_transits));
403 total_slack->SetMin(CapSub(span->Min(), sum_fixed_transits));
404 total_slack->SetMax(CapSub(span->Max(), sum_fixed_transits));
405 }
406
407 void PropagateVehicle(int vehicle) {
408 DCHECK(spans_[vehicle] || total_slacks_[vehicle]);
409 const int start = model_->Start(vehicle);
410 const int end = model_->End(vehicle);
411 // If transits are positive, the domain of the span variable can be reduced
412 // to cumul(end) - cumul(start).
413 if (spans_[vehicle] != nullptr &&
414 dimension_->AreVehicleTransitsPositive(vehicle)) {
415 spans_[vehicle]->SetRange(CapSub(dimension_->CumulVar(end)->Min(),
416 dimension_->CumulVar(start)->Max()),
417 CapSub(dimension_->CumulVar(end)->Max(),
418 dimension_->CumulVar(start)->Min()));
419 }
420 // Record path, if it is not fixed from start to end, stop here.
421 // TRICKY: do not put end node yet, we look only at transits in the next
422 // reasonings, we will append the end when we look at cumuls.
423 {
424 path_.clear();
425 int curr_node = start;
426 while (!model_->IsEnd(curr_node)) {
427 const IntVar* next_var = model_->NextVar(curr_node);
428 if (!next_var->Bound()) return;
429 path_.push_back(curr_node);
430 curr_node = next_var->Value();
431 }
432 }
433 // Compute the sum of fixed transits. Fixed transit variables should all be
434 // fixed, otherwise we wait to get called later when propagation does it.
435 int64_t sum_fixed_transits = 0;
436 for (const int node : path_) {
437 const IntVar* fixed_transit_var = dimension_->FixedTransitVar(node);
438 if (!fixed_transit_var->Bound()) return;
439 sum_fixed_transits =
440 CapAdd(sum_fixed_transits, fixed_transit_var->Value());
441 }
442
443 SynchronizeSpanAndTotalSlack(vehicle, sum_fixed_transits);
444
445 // The amount of break time that must occur during the route must be smaller
446 // than span max - sum_fixed_transits. A break must occur on the route if it
447 // must be after the route's start and before the route's end.
448 // Propagate lower bound on span, then filter out values
449 // that would force more breaks in route than possible.
450 if (dimension_->HasBreakConstraints() &&
451 !dimension_->GetBreakIntervalsOfVehicle(vehicle).empty()) {
452 const int64_t vehicle_start_max = dimension_->CumulVar(start)->Max();
453 const int64_t vehicle_end_min = dimension_->CumulVar(end)->Min();
454 // Compute and propagate lower bound.
455 int64_t min_break_duration = 0;
456 for (IntervalVar* br : dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
457 if (!br->MustBePerformed()) continue;
458 if (vehicle_start_max < br->EndMin() &&
459 br->StartMax() < vehicle_end_min) {
460 min_break_duration = CapAdd(min_break_duration, br->DurationMin());
461 }
462 }
463 SetSpanMin(vehicle, CapAdd(min_break_duration, sum_fixed_transits),
464 sum_fixed_transits);
465 // If a break that is not inside the route may violate slack_max,
466 // we can propagate in some cases: when the break must be before or
467 // must be after the route.
468 // In the other cases, we cannot deduce a better bound on a CumulVar or
469 // on a break, so we do nothing.
470 const int64_t slack_max =
471 CapSub(SpanMax(vehicle, sum_fixed_transits), sum_fixed_transits);
472 const int64_t max_additional_slack =
473 CapSub(slack_max, min_break_duration);
474 for (IntervalVar* br : dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
475 if (!br->MustBePerformed()) continue;
476 // Break must be before end, detect whether it must be before start.
477 if (vehicle_start_max >= br->EndMin() &&
478 br->StartMax() < vehicle_end_min) {
479 if (br->DurationMin() > max_additional_slack) {
480 // Having the break inside would violate max_additional_slack..
481 // Thus, it must be outside the route, in this case, before.
482 br->SetEndMax(vehicle_start_max);
483 dimension_->CumulVar(start)->SetMin(br->EndMin());
484 }
485 }
486 // Break must be after start, detect whether it must be after end.
487 // Same reasoning, in the case where the break is after.
488 if (vehicle_start_max < br->EndMin() &&
489 br->StartMax() >= vehicle_end_min) {
490 if (br->DurationMin() > max_additional_slack) {
491 br->SetStartMin(vehicle_end_min);
492 dimension_->CumulVar(end)->SetMax(br->StartMax());
493 }
494 }
495 }
496 }
497
498 // Propagate span == cumul(end) - cumul(start).
499 {
500 IntVar* start_cumul = dimension_->CumulVar(start);
501 IntVar* end_cumul = dimension_->CumulVar(end);
502 const int64_t start_min = start_cumul->Min();
503 const int64_t start_max = start_cumul->Max();
504 const int64_t end_min = end_cumul->Min();
505 const int64_t end_max = end_cumul->Max();
506 // Propagate from cumuls to span.
507 const int64_t span_lb = CapSub(end_min, start_max);
508 SetSpanMin(vehicle, span_lb, sum_fixed_transits);
509 const int64_t span_ub = CapSub(end_max, start_min);
510 SetSpanMax(vehicle, span_ub, sum_fixed_transits);
511 // Propagate from span to cumuls.
512 const int64_t span_min = SpanMin(vehicle, sum_fixed_transits);
513 const int64_t span_max = SpanMax(vehicle, sum_fixed_transits);
514 const int64_t slack_from_lb = CapSub(span_max, span_lb);
515 const int64_t slack_from_ub = CapSub(span_ub, span_min);
516 // start >= start_max - (span_max - span_lb).
517 start_cumul->SetMin(CapSub(start_max, slack_from_lb));
518 // end <= end_min + (span_max - span_lb).
519 end_cumul->SetMax(CapAdd(end_min, slack_from_lb));
520 // // start <= start_min + (span_ub - span_min)
521 start_cumul->SetMax(CapAdd(start_min, slack_from_ub));
522 // // end >= end_max - (span_ub - span_min)
523 end_cumul->SetMin(CapSub(end_max, slack_from_ub));
524 }
525
526 // Propagate sum transits == span.
527 {
528 // Propagate from transits to span.
529 int64_t span_lb = 0;
530 int64_t span_ub = 0;
531 for (const int node : path_) {
532 span_lb = CapAdd(span_lb, dimension_->TransitVar(node)->Min());
533 span_ub = CapAdd(span_ub, dimension_->TransitVar(node)->Max());
534 }
535 SetSpanMin(vehicle, span_lb, sum_fixed_transits);
536 SetSpanMax(vehicle, span_ub, sum_fixed_transits);
537 // Propagate from span to transits.
538 // transit[i] <= transit_i_min + (span_max - span_lb)
539 // transit[i] >= transit_i_max - (span_ub - span_min)
540 const int64_t span_min = SpanMin(vehicle, sum_fixed_transits);
541 const int64_t span_max = SpanMax(vehicle, sum_fixed_transits);
542 const int64_t slack_from_lb = CapSub(span_max, span_lb);
543 const int64_t slack_from_ub =
544 span_ub < std::numeric_limits<int64_t>::max()
545 ? CapSub(span_ub, span_min)
546 : std::numeric_limits<int64_t>::max();
547 for (const int node : path_) {
548 IntVar* transit_var = dimension_->TransitVar(node);
549 const int64_t transit_i_min = transit_var->Min();
550 const int64_t transit_i_max = transit_var->Max();
551 // TRICKY: the first propagation might change transit_var->Max(),
552 // but we must use the same value of transit_i_max in the computation
553 // of transit[i]'s lower bound that was used for span_ub.
554 transit_var->SetMax(CapAdd(transit_i_min, slack_from_lb));
555 transit_var->SetMin(CapSub(transit_i_max, slack_from_ub));
556 }
557 }
558
559 // TRICKY: add end node now, we will look at cumuls.
560 path_.push_back(end);
561
562 // A stronger bound: from start min of the route, go to node i+1 with time
563 // max(cumul[i] + fixed_transit, cumul[i+1].Min()).
564 // Record arrival time (should be the same as end cumul min).
565 // Then do the reverse route, going to time
566 // min(cumul[i+1] - fixed_transit, cumul[i].Max())
567 // Record final time as departure time.
568 // Then arrival time - departure time is a valid lower bound of span.
569 // First reasoning: start - end - start
570 {
571 // At each iteration, arrival time is a lower bound of path[i]'s cumul,
572 // so we opportunistically tighten the variable.
573 // This helps reduce the amount of inter-constraint propagation.
574 int64_t arrival_time = dimension_->CumulVar(start)->Min();
575 for (int i = 1; i < path_.size(); ++i) {
576 arrival_time =
577 std::max(CapAdd(arrival_time,
578 dimension_->FixedTransitVar(path_[i - 1])->Min()),
579 dimension_->CumulVar(path_[i])->Min());
580 dimension_->CumulVar(path_[i])->SetMin(arrival_time);
581 }
582 // At each iteration, departure_time is the latest time at each the
583 // vehicle can leave to reach the earliest feasible vehicle end. Thus it
584 // is not an upper bound of the cumul, we cannot tighten the variable.
585 int64_t departure_time = arrival_time;
586 for (int i = path_.size() - 2; i >= 0; --i) {
587 departure_time =
588 std::min(CapSub(departure_time,
589 dimension_->FixedTransitVar(path_[i])->Min()),
590 dimension_->CumulVar(path_[i])->Max());
591 }
592 const int64_t span_lb = CapSub(arrival_time, departure_time);
593 SetSpanMin(vehicle, span_lb, sum_fixed_transits);
594 const int64_t maximum_deviation =
595 CapSub(SpanMax(vehicle, sum_fixed_transits), span_lb);
596 const int64_t start_lb = CapSub(departure_time, maximum_deviation);
597 dimension_->CumulVar(start)->SetMin(start_lb);
598 }
599 // Second reasoning: end - start - end
600 {
601 // At each iteration, use departure time to tighten opportunistically.
602 int64_t departure_time = dimension_->CumulVar(end)->Max();
603 for (int i = path_.size() - 2; i >= 0; --i) {
604 departure_time =
605 std::min(CapSub(departure_time,
606 dimension_->FixedTransitVar(path_[i])->Min()),
607 dimension_->CumulVar(path_[i])->Max());
608 dimension_->CumulVar(path_[i])->SetMax(departure_time);
609 }
610 // Symmetrically to the first reasoning, arrival_time is the earliest
611 // possible arrival for the latest departure of vehicle start.
612 // It cannot be used to tighten the successive cumul variables.
613 int arrival_time = departure_time;
614 for (int i = 1; i < path_.size(); ++i) {
615 arrival_time =
616 std::max(CapAdd(arrival_time,
617 dimension_->FixedTransitVar(path_[i - 1])->Min()),
618 dimension_->CumulVar(path_[i])->Min());
619 }
620 const int64_t span_lb = CapSub(arrival_time, departure_time);
621 SetSpanMin(vehicle, span_lb, sum_fixed_transits);
622 const int64_t maximum_deviation =
623 CapSub(SpanMax(vehicle, sum_fixed_transits), span_lb);
624 dimension_->CumulVar(end)->SetMax(
625 CapAdd(arrival_time, maximum_deviation));
626 }
627 }
628
629 const RoutingModel* const model_;
630 const RoutingDimension* const dimension_;
631 std::vector<IntVar*> spans_;
632 std::vector<IntVar*> total_slacks_;
633 std::vector<int> path_;
634 std::vector<Demon*> vehicle_demons_;
635};
636} // namespace
637
639 std::vector<IntVar*> spans,
640 std::vector<IntVar*> total_slacks) {
641 RoutingModel* const model = dimension->model();
642 CHECK_EQ(model->vehicles(), spans.size());
643 CHECK_EQ(model->vehicles(), total_slacks.size());
644 return model->solver()->RevAlloc(new PathSpansAndTotalSlacks(
645 model, dimension, std::move(spans), std::move(total_slacks)));
646}
647
648namespace {
649// Very light version of the RangeLessOrEqual constraint (see ./range_cst.cc).
650// Only performs initial propagation and then checks the compatibility of the
651// variable domains without domain pruning.
652// This is useful when to avoid ping-pong effects with costly constraints
653// such as the PathCumul constraint.
654// This constraint has not been added to the cp library (in range_cst.cc) given
655// it only does checking and no propagation (except the initial propagation)
656// and is only fit for local search, in particular in the context of vehicle
657// routing.
658class LightRangeLessOrEqual : public Constraint {
659 public:
660 LightRangeLessOrEqual(Solver* s, IntExpr* l, IntExpr* r);
661 ~LightRangeLessOrEqual() override {}
662 void Post() override;
663 void InitialPropagate() override;
664 std::string DebugString() const override;
665 IntVar* Var() override {
666 return solver()->MakeIsLessOrEqualVar(left_, right_);
667 }
668 // TODO(user): introduce a kLightLessOrEqual tag.
669 void Accept(ModelVisitor* const visitor) const override {
670 visitor->BeginVisitConstraint(ModelVisitor::kLessOrEqual, this);
671 visitor->VisitIntegerExpressionArgument(ModelVisitor::kLeftArgument, left_);
672 visitor->VisitIntegerExpressionArgument(ModelVisitor::kRightArgument,
673 right_);
674 visitor->EndVisitConstraint(ModelVisitor::kLessOrEqual, this);
675 }
676
677 private:
678 void CheckRange();
679
680 IntExpr* const left_;
681 IntExpr* const right_;
682 Demon* demon_;
683};
684
685LightRangeLessOrEqual::LightRangeLessOrEqual(Solver* const s, IntExpr* const l,
686 IntExpr* const r)
687 : Constraint(s), left_(l), right_(r), demon_(nullptr) {}
688
689void LightRangeLessOrEqual::Post() {
690 demon_ = MakeConstraintDemon0(
691 solver(), this, &LightRangeLessOrEqual::CheckRange, "CheckRange");
692 left_->WhenRange(demon_);
693 right_->WhenRange(demon_);
694}
695
696void LightRangeLessOrEqual::InitialPropagate() {
697 left_->SetMax(right_->Max());
698 right_->SetMin(left_->Min());
699 if (left_->Max() <= right_->Min()) {
700 demon_->inhibit(solver());
701 }
702}
703
704void LightRangeLessOrEqual::CheckRange() {
705 if (left_->Min() > right_->Max()) {
706 solver()->Fail();
707 }
708 if (left_->Max() <= right_->Min()) {
709 demon_->inhibit(solver());
710 }
711}
712
713std::string LightRangeLessOrEqual::DebugString() const {
714 return left_->DebugString() + " < " + right_->DebugString();
715}
716} // namespace
717
718namespace {
719
720class RouteConstraint : public Constraint {
721 public:
722 RouteConstraint(
723 RoutingModel* model, std::vector<IntVar*> route_cost_vars,
724 std::function<std::optional<int64_t>(const std::vector<int64_t>&)>
725 route_evaluator)
726 : Constraint(model->solver()),
727 model_(model),
728 route_cost_vars_(std::move(route_cost_vars)),
729 route_evaluator_(std::move(route_evaluator)),
730 starts_(model->Size() + model->vehicles(), -1),
731 ends_(model->Size() + model->vehicles(), -1) {
732 const int size = model_->Size() + model_->vehicles();
733 for (int i = 0; i < size; ++i) {
734 starts_.SetValue(solver(), i, i);
735 ends_.SetValue(solver(), i, i);
736 }
737 }
738 ~RouteConstraint() override {}
739 void Post() override {
740 const std::vector<IntVar*> nexts = model_->Nexts();
741 for (int i = 0; i < nexts.size(); ++i) {
742 if (!nexts[i]->Bound()) {
743 auto* demon = MakeConstraintDemon2(
744 model_->solver(), this, &RouteConstraint::AddLink,
745 "RouteConstraint::AddLink", i, nexts[i]);
746 nexts[i]->WhenBound(demon);
747 }
748 }
749 }
750 void InitialPropagate() override {
751 const std::vector<IntVar*> nexts = model_->Nexts();
752 for (int i = 0; i < nexts.size(); ++i) {
753 if (nexts[i]->Bound()) {
754 AddLink(i, nexts[i]);
755 }
756 }
757 }
758 std::string DebugString() const override { return "RouteConstraint"; }
759
760 private:
761 void AddLink(int index, IntVar* next) {
762 DCHECK(next->Bound());
763 const int64_t chain_start = starts_.Value(index);
764 const int64_t index_next = next->Min();
765 const int64_t chain_end = ends_.Value(index_next);
766 starts_.SetValue(solver(), chain_end, chain_start);
767 ends_.SetValue(solver(), chain_start, chain_end);
768 if (model_->IsStart(chain_start) && model_->IsEnd(chain_end)) {
769 CheckRoute(chain_start, chain_end);
770 }
771 }
772 void CheckRoute(int64_t start, int64_t end) {
773 route_.clear();
774 for (int64_t node = start; node != end;
775 node = model_->NextVar(node)->Min()) {
776 route_.push_back(node);
777 }
778 route_.push_back(end);
779 std::optional<int64_t> cost = route_evaluator_(route_);
780 if (!cost.has_value()) {
781 solver()->Fail();
782 }
783 route_cost_vars_[model_->VehicleIndex(start)]->SetValue(cost.value());
784 }
785
786 RoutingModel* const model_;
787 std::vector<IntVar*> route_cost_vars_;
788 std::function<std::optional<int64_t>(const std::vector<int64_t>&)>
789 route_evaluator_;
790 RevArray<int> starts_;
791 RevArray<int> ends_;
792 std::vector<int64_t> route_;
793};
794} // namespace
795
797 RoutingModel* model, std::vector<IntVar*> route_cost_vars,
798 std::function<std::optional<int64_t>(const std::vector<int64_t>&)>
799 route_evaluator) {
800 return model->solver()->RevAlloc(new RouteConstraint(
801 model, std::move(route_cost_vars), std::move(route_evaluator)));
802}
803
804namespace {
805
816class GlobalVehicleBreaksConstraint : public Constraint {
817 public:
818 explicit GlobalVehicleBreaksConstraint(const RoutingDimension* dimension);
819 std::string DebugString() const override {
820 return "GlobalVehicleBreaksConstraint";
821 }
822
823 void Post() override;
824 void InitialPropagate() override;
825
826 private:
827 void PropagateNode(int node);
828 void PropagateVehicle(int vehicle);
829
830 const RoutingModel* model_;
831 const RoutingDimension* const dimension_;
832 std::vector<Demon*> vehicle_demons_;
833
834 DimensionValues dimension_values_;
835 PrePostVisitValues visits_;
836 std::vector<DimensionValues::Interval> cumul_intervals_;
837 std::vector<DimensionValues::Interval> slack_intervals_;
838 BreakPropagator break_propagator_;
839};
840
841GlobalVehicleBreaksConstraint::GlobalVehicleBreaksConstraint(
842 const RoutingDimension* dimension)
843 : Constraint(dimension->model()->solver()),
844 model_(dimension->model()),
845 dimension_(dimension),
846 dimension_values_(dimension->model()->vehicles(),
847 dimension->cumuls().size()),
848 visits_(dimension->model()->vehicles(), dimension->cumuls().size()),
849 cumul_intervals_(dimension->cumuls().size()),
850 slack_intervals_(dimension->cumuls().size()),
851 break_propagator_(dimension->cumuls().size()) {
852 vehicle_demons_.resize(model_->vehicles());
853}
854
855void GlobalVehicleBreaksConstraint::Post() {
856 for (int vehicle = 0; vehicle < model_->vehicles(); vehicle++) {
857 if (dimension_->GetBreakIntervalsOfVehicle(vehicle).empty() &&
858 dimension_->GetBreakDistanceDurationOfVehicle(vehicle).empty()) {
859 continue;
860 }
861 vehicle_demons_[vehicle] = MakeDelayedConstraintDemon1(
862 solver(), this, &GlobalVehicleBreaksConstraint::PropagateVehicle,
863 "PropagateVehicle", vehicle);
864 for (IntervalVar* interval :
865 dimension_->GetBreakIntervalsOfVehicle(vehicle)) {
866 interval->WhenAnything(vehicle_demons_[vehicle]);
867 }
868 }
869 const int num_cumuls = dimension_->cumuls().size();
870 const int num_nexts = model_->Nexts().size();
871 for (int node = 0; node < num_cumuls; node++) {
872 Demon* dimension_demon = MakeConstraintDemon1(
873 solver(), this, &GlobalVehicleBreaksConstraint::PropagateNode,
874 "PropagateNode", node);
875 if (node < num_nexts) {
876 model_->NextVar(node)->WhenBound(dimension_demon);
877 dimension_->SlackVar(node)->WhenRange(dimension_demon);
878 }
879 model_->VehicleVar(node)->WhenBound(dimension_demon);
880 dimension_->CumulVar(node)->WhenRange(dimension_demon);
881 }
882}
883
884void GlobalVehicleBreaksConstraint::InitialPropagate() {
885 for (int vehicle = 0; vehicle < model_->vehicles(); vehicle++) {
886 if (!dimension_->GetBreakIntervalsOfVehicle(vehicle).empty() ||
887 !dimension_->GetBreakDistanceDurationOfVehicle(vehicle).empty()) {
888 PropagateVehicle(vehicle);
889 }
890 }
891}
892
893// This dispatches node events to the right vehicle propagator.
894// It also filters out a part of uninteresting events, on which the vehicle
895// propagator will not find anything new.
896void GlobalVehicleBreaksConstraint::PropagateNode(int node) {
897 if (!model_->VehicleVar(node)->Bound()) return;
898 const int vehicle = model_->VehicleVar(node)->Min();
899 if (vehicle < 0 || vehicle_demons_[vehicle] == nullptr) return;
900 EnqueueDelayedDemon(vehicle_demons_[vehicle]);
901}
902
903// First, perform energy-based reasoning on intervals and cumul variables.
904// Then, perform reasoning on slack variables.
905void GlobalVehicleBreaksConstraint::PropagateVehicle(int vehicle) {
906 dimension_values_.Revert();
907 visits_.Revert();
908
909 // Fill dimension_values_ from the path.
910 // If the path is not a complete start -> end, return.
911 // This leverages travel caching in FillDimensionValuesFromRoutingDimension().
912 int node = model_->Start(vehicle);
913 while (!model_->IsEnd(node)) {
914 dimension_values_.PushNode(node);
915 if (model_->NextVar(node)->Bound()) {
916 node = model_->NextVar(node)->Min();
917 } else {
918 return;
919 }
920 }
921 dimension_values_.PushNode(node);
922 dimension_values_.MakePathFromNewNodes(vehicle);
923 // Translate CP variables to Intervals, and fill dimension_values_.
924 const auto& cp_cumuls = dimension_->cumuls();
925 const auto& cp_slacks = dimension_->slacks();
926 for (const int node : dimension_values_.Nodes(vehicle)) {
927 cumul_intervals_[node] = {.min = cp_cumuls[node]->Min(),
928 .max = cp_cumuls[node]->Max()};
929 if (dimension_->model()->IsEnd(node)) {
930 slack_intervals_[node] = {.min = 0, .max = 0};
931 } else {
932 slack_intervals_[node] = {.min = cp_slacks[node]->Min(),
933 .max = cp_slacks[node]->Max()};
934 }
935 }
937 vehicle, dimension_->vehicle_capacities()[vehicle],
938 dimension_->vehicle_span_upper_bounds()[vehicle], cumul_intervals_,
939 slack_intervals_, dimension_->transit_evaluator(vehicle),
940 dimension_values_)) {
941 solver()->Fail();
942 }
943 if (!PropagateTransitAndSpan(vehicle, dimension_values_)) {
944 solver()->Fail();
945 }
946 // Extract pre/post visit data.
947 auto any_invocable = [this](int evaluator_index)
948 -> std::optional<absl::AnyInvocable<int64_t(int64_t, int64_t) const>> {
949 const auto& evaluator =
950 evaluator_index == -1
951 ? nullptr
952 : dimension_->model()->TransitCallback(evaluator_index);
953 if (evaluator == nullptr) return std::nullopt;
954 return evaluator;
955 };
957 vehicle, dimension_values_,
958 any_invocable(dimension_->GetPreTravelEvaluatorOfVehicle(vehicle)),
959 any_invocable(dimension_->GetPostTravelEvaluatorOfVehicle(vehicle)),
960 visits_);
961 // Copy break data into dimension_values_.
962 using VehicleBreak = DimensionValues::VehicleBreak;
963 const std::vector<IntervalVar*>& cp_breaks =
964 dimension_->GetBreakIntervalsOfVehicle(vehicle);
965 std::vector<VehicleBreak>& dv_breaks =
966 dimension_values_.MutableVehicleBreaks(vehicle);
967 dv_breaks.clear();
968 for (const IntervalVar* cp_break : cp_breaks) {
969 if (cp_break->MayBePerformed()) {
970 dv_breaks.push_back(
971 {.start = {.min = cp_break->StartMin(), .max = cp_break->StartMax()},
972 .end = {.min = cp_break->EndMin(), .max = cp_break->EndMax()},
973 .duration = {.min = cp_break->DurationMin(),
974 .max = cp_break->DurationMax()},
975 .is_performed = {.min = cp_break->MustBePerformed(), .max = 1}});
976 } else {
977 dv_breaks.push_back({.start = {.min = 0, .max = 0},
978 .end = {.min = 0, .max = 0},
979 .duration = {.min = 0, .max = 0},
980 .is_performed = {.min = 0, .max = 0}});
981 }
982 }
983 // Propagate inside dimension_values_, fail if infeasible.
984 if (break_propagator_.FastPropagations(vehicle, dimension_values_, visits_) ==
985 BreakPropagator::kInfeasible) {
986 solver()->Fail();
987 }
988 const auto& interbreaks =
989 dimension_->GetBreakDistanceDurationOfVehicle(vehicle);
990 if (break_propagator_.PropagateInterbreak(vehicle, dimension_values_,
991 interbreaks) ==
992 BreakPropagator::kInfeasible) {
993 solver()->Fail();
994 }
995 if (!PropagateTransitAndSpan(vehicle, dimension_values_)) {
996 solver()->Fail();
997 }
998 // Copy changes back to CP variables.
999 using Interval = DimensionValues::Interval;
1000 const int num_nodes = dimension_values_.NumNodes(vehicle);
1001 const absl::Span<const int> nodes = dimension_values_.Nodes(vehicle);
1002 const absl::Span<const Interval> dv_cumuls =
1003 dimension_values_.Cumuls(vehicle);
1004 for (int r = 0; r < num_nodes; ++r) {
1005 const int node = nodes[r];
1006 cp_cumuls[node]->SetRange(dv_cumuls[r].min, dv_cumuls[r].max);
1007 }
1008 const int num_breaks = cp_breaks.size();
1009 for (int b = 0; b < num_breaks; ++b) {
1010 IntervalVar* cp_break = cp_breaks[b];
1011 if (!cp_break->MayBePerformed()) continue;
1012 const VehicleBreak& dv_break = dv_breaks[b];
1013 cp_break->SetStartRange(dv_break.start.min, dv_break.start.max);
1014 cp_break->SetEndRange(dv_break.end.min, dv_break.end.max);
1015 cp_break->SetDurationRange(dv_break.duration.min, dv_break.duration.max);
1016 if (dv_break.is_performed.min == 1) {
1017 cp_break->SetPerformed(true);
1018 } else if (dv_break.is_performed.max == 0) {
1019 cp_break->SetPerformed(false);
1020 }
1021 }
1022 // If everything went fine, we can save dimension state.
1023 // Saving is only done for caching reasons, this allows subsequent calls to
1024 // FillDimensionValuesFromRoutingDimension() to re-use travel evaluations.
1025 dimension_values_.Commit();
1026 visits_.Commit();
1027}
1028
1029} // namespace
1030
1032 Solver* solver, const RoutingDimension* dimension) {
1033 return solver->RevAlloc(new GlobalVehicleBreaksConstraint(dimension));
1034}
1035
1036namespace {
1037
1038// TODO(user): Make this a real constraint with demons on transit and active
1039// variables.
1040class NumActiveVehiclesCapacityConstraint : public Constraint {
1041 public:
1042 NumActiveVehiclesCapacityConstraint(Solver* solver,
1043 std::vector<IntVar*> transit_vars,
1044 std::vector<IntVar*> active_vars,
1045 std::vector<IntVar*> vehicle_active_vars,
1046 std::vector<int64_t> vehicle_capacities,
1047 int max_active_vehicles,
1048 bool enforce_active_vehicles)
1049 : Constraint(solver),
1050 transit_vars_(std::move(transit_vars)),
1051 active_vars_(std::move(active_vars)),
1052 vehicle_active_vars_(std::move(vehicle_active_vars)),
1053 vehicle_capacities_(std::move(vehicle_capacities)),
1054 max_active_vehicles_(
1055 std::min(max_active_vehicles,
1056 static_cast<int>(vehicle_active_vars_.size()))),
1057 enforce_active_vehicles_(enforce_active_vehicles) {
1058 DCHECK_EQ(transit_vars_.size(), active_vars_.size());
1059 DCHECK_EQ(vehicle_capacities_.size(), vehicle_active_vars_.size());
1060 }
1061 std::string DebugString() const override {
1062 return "NumActiveVehiclesCapacityConstraint";
1063 }
1064 void Post() override {
1065 int64_t remaining_demand = 0;
1066 for (int i = 0; i < transit_vars_.size(); ++i) {
1067 if (active_vars_[i]->Min() == 1) {
1068 CapAddTo(transit_vars_[i]->Min(), &remaining_demand);
1069 }
1070 }
1071 sorted_by_capacity_vehicles_.clear();
1072 sorted_by_capacity_vehicles_.reserve(vehicle_capacities_.size());
1073 for (int v = 0; v < vehicle_active_vars_.size(); ++v) {
1074 if (vehicle_active_vars_[v]->Max() == 0) continue;
1075 sorted_by_capacity_vehicles_.push_back(v);
1076 }
1077 const int updated_max_active_vehicles = std::min<int>(
1078 max_active_vehicles_, sorted_by_capacity_vehicles_.size());
1079 absl::c_sort(sorted_by_capacity_vehicles_, [this](int a, int b) {
1080 return vehicle_capacities_[a] > vehicle_capacities_[b];
1081 });
1082 for (int i = 0; i < updated_max_active_vehicles; ++i) {
1083 CapSubFrom(vehicle_capacities_[sorted_by_capacity_vehicles_[i]],
1084 &remaining_demand);
1085 }
1086 if (remaining_demand > 0) solver()->Fail();
1087
1088 // Check vehicles that need to be forced to be active.
1089 if (enforce_active_vehicles_) {
1090 int64_t extended_capacity = 0;
1091 if (updated_max_active_vehicles < sorted_by_capacity_vehicles_.size()) {
1092 extended_capacity = vehicle_capacities_
1093 [sorted_by_capacity_vehicles_[updated_max_active_vehicles]];
1094 }
1095 for (int i = 0; i < updated_max_active_vehicles; ++i) {
1096 const int vehicle = sorted_by_capacity_vehicles_[i];
1097 if (CapAdd(remaining_demand, vehicle_capacities_[vehicle]) >
1098 extended_capacity) {
1099 vehicle_active_vars_[vehicle]->SetValue(1);
1100 } else {
1101 break;
1102 }
1103 }
1104 }
1105
1106 // Check remaining vehicles and make inactive the ones which do not have
1107 // enough capacity.
1108 if (updated_max_active_vehicles > 0 &&
1109 updated_max_active_vehicles - 1 < sorted_by_capacity_vehicles_.size()) {
1110 CapAddTo(
1111 vehicle_capacities_
1112 [sorted_by_capacity_vehicles_[updated_max_active_vehicles - 1]],
1113 &remaining_demand);
1114 }
1115 for (int i = updated_max_active_vehicles;
1116 i < sorted_by_capacity_vehicles_.size(); ++i) {
1117 const int vehicle = sorted_by_capacity_vehicles_[i];
1118 if (vehicle_capacities_[vehicle] < remaining_demand ||
1119 updated_max_active_vehicles == 0) {
1120 vehicle_active_vars_[vehicle]->SetValue(0);
1121 }
1122 }
1123 }
1124 void InitialPropagate() override {}
1125
1126 private:
1127 const std::vector<IntVar*> transit_vars_;
1128 const std::vector<IntVar*> active_vars_;
1129 const std::vector<IntVar*> vehicle_active_vars_;
1130 const std::vector<int64_t> vehicle_capacities_;
1131 const int max_active_vehicles_;
1132 const bool enforce_active_vehicles_;
1133 std::vector<int> sorted_by_capacity_vehicles_;
1134};
1135
1136} // namespace
1137
1139 Solver* solver, std::vector<IntVar*> transit_vars,
1140 std::vector<IntVar*> active_vars, std::vector<IntVar*> vehicle_active_vars,
1141 std::vector<int64_t> vehicle_capacities, int max_active_vehicles,
1142 bool enforce_active_vehicles) {
1143 return solver->RevAlloc(new NumActiveVehiclesCapacityConstraint(
1144 solver, std::move(transit_vars), std::move(active_vars),
1145 std::move(vehicle_active_vars), std::move(vehicle_capacities),
1146 max_active_vehicles, enforce_active_vehicles));
1147}
1148
1149} // namespace operations_research
static Iterator End(ClosedInterval interval)
RoutingModel * model() const
Returns the model on which the dimension was created.
Definition routing.h:3102
RoutingDimensionIndex DimensionIndex
Definition routing.h:270
int vehicles() const
Returns the number of vehicle routes in the model.
Definition routing.h:2102
RoutingResourceClassIndex ResourceClassIndex
Definition routing.h:273
OR-Tools root namespace.
int64_t CapAdd(int64_t x, int64_t y)
bool FillDimensionValuesFromRoutingDimension(int path, int64_t capacity, int64_t span_upper_bound, absl::Span< const DimensionValues::Interval > cumul_of_node, absl::Span< const DimensionValues::Interval > slack_of_node, absl::AnyInvocable< int64_t(int64_t, int64_t) const > evaluator, DimensionValues &dimension_values)
void CapAddTo(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)
void CapSubFrom(int64_t amount, int64_t *target)
Constraint * MakeGlobalVehicleBreaksConstraint(Solver *solver, const RoutingDimension *dimension)
Demon * MakeConstraintDemon2(Solver *const s, T *const ct, void(T::*method)(P, Q), const std::string &name, P param1, Q param2)
ClosedInterval::Iterator end(ClosedInterval interval)
bool PropagateTransitAndSpan(int path, DimensionValues &dimension_values)
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)
void FillPrePostVisitValues(int path, const DimensionValues &dimension_values, std::optional< absl::AnyInvocable< int64_t(int64_t, int64_t) const > > pre_travel_evaluator, std::optional< absl::AnyInvocable< int64_t(int64_t, int64_t) const > > post_travel_evaluator, PrePostVisitValues &visit_values)
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)
Constraint * MakeNumActiveVehiclesCapacityConstraint(Solver *solver, std::vector< IntVar * > transit_vars, std::vector< IntVar * > active_vars, std::vector< IntVar * > vehicle_active_vars, std::vector< int64_t > vehicle_capacities, int max_active_vehicles, bool enforce_active_vehicles)
STL namespace.
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...
std::string DebugString() const
Definition model.cc:805