Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
routing_decision_builders.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 <string>
21#include <tuple>
22#include <utility>
23#include <vector>
24
25#include "absl/algorithm/container.h"
26#include "absl/container/flat_hash_set.h"
27#include "absl/log/check.h"
28#include "absl/types/span.h"
35
36namespace operations_research {
37
38namespace {
39
40// A decision builder which tries to assign values to variables as close as
41// possible to target values first.
42class SetValuesFromTargets : public DecisionBuilder {
43 public:
44 SetValuesFromTargets(std::vector<IntVar*> variables,
45 std::vector<int64_t> targets)
46 : variables_(std::move(variables)),
47 targets_(std::move(targets)),
48 index_(0),
49 steps_(variables_.size(), 0) {
50 DCHECK_EQ(variables_.size(), targets_.size());
51 }
52 Decision* Next(Solver* solver) override {
53 int index = index_.Value();
54 while (index < variables_.size() && variables_[index]->Bound()) {
55 ++index;
56 }
57 index_.SetValue(solver, index);
58 if (index >= variables_.size()) return nullptr;
59 const int64_t variable_min = variables_[index]->Min();
60 const int64_t variable_max = variables_[index]->Max();
61 // Target can be before, inside, or after the variable range.
62 // We do a trichotomy on this for clarity.
63 if (targets_[index] <= variable_min) {
64 return solver->MakeAssignVariableValue(variables_[index], variable_min);
65 } else if (targets_[index] >= variable_max) {
66 return solver->MakeAssignVariableValue(variables_[index], variable_max);
67 } else {
68 int64_t step = steps_[index];
69 int64_t value = CapAdd(targets_[index], step);
70 // If value is out of variable's range, we can remove the interval of
71 // values already explored (which can make the solver fail) and
72 // recall Next() to get back into the trichotomy above.
73 if (value < variable_min || variable_max < value) {
74 step = GetNextStep(step);
75 value = CapAdd(targets_[index], step);
76 if (step > 0) {
77 // Values in [variable_min, value) were already explored.
78 variables_[index]->SetMin(value);
79 } else {
80 // Values in (value, variable_max] were already explored.
81 variables_[index]->SetMax(value);
82 }
83 return Next(solver);
84 }
85 steps_.SetValue(solver, index, GetNextStep(step));
86 return solver->MakeAssignVariableValueOrDoNothing(variables_[index],
87 value);
88 }
89 }
90
91 private:
92 int64_t GetNextStep(int64_t step) const {
93 return (step > 0) ? -step : CapSub(1, step);
94 }
95 const std::vector<IntVar*> variables_;
96 const std::vector<int64_t> targets_;
97 Rev<int> index_;
98 RevArray<int64_t> steps_;
99};
100
101} // namespace
102
104 std::vector<IntVar*> variables,
105 std::vector<int64_t> targets) {
106 return solver->RevAlloc(
107 new SetValuesFromTargets(std::move(variables), std::move(targets)));
108}
109
110namespace {
111
112bool DimensionFixedTransitsEqualTransitEvaluatorForVehicle(
113 const RoutingDimension& dimension, int vehicle) {
114 const RoutingModel* const model = dimension.model();
115 int node = model->Start(vehicle);
116 while (!model->IsEnd(node)) {
117 if (!model->NextVar(node)->Bound()) {
118 return false;
119 }
120 const int next = model->NextVar(node)->Value();
121 if (dimension.transit_evaluator(vehicle)(node, next) !=
122 dimension.FixedTransitVar(node)->Value()) {
123 return false;
124 }
125 node = next;
126 }
127 return true;
128}
129
130bool DimensionFixedTransitsEqualTransitEvaluators(
131 const RoutingDimension& dimension) {
132 for (int vehicle = 0; vehicle < dimension.model()->vehicles(); vehicle++) {
133 if (!DimensionFixedTransitsEqualTransitEvaluatorForVehicle(dimension,
134 vehicle)) {
135 return false;
136 }
137 }
138 return true;
139}
140
141// Concatenates cumul_values and break_values into 'values', and generates the
142// corresponding 'variables' vector.
143void AppendRouteCumulAndBreakVarAndValues(
144 const RoutingDimension& dimension, int vehicle,
145 absl::Span<const int64_t> cumul_values,
146 absl::Span<const int64_t> break_values, std::vector<IntVar*>* variables,
147 std::vector<int64_t>* values) {
148 auto& vars = *variables;
149 auto& vals = *values;
150 DCHECK_EQ(vars.size(), vals.size());
151 const int old_num_values = vals.size();
152 vals.insert(vals.end(), cumul_values.begin(), cumul_values.end());
153 const RoutingModel& model = *dimension.model();
154 {
155 int current = model.Start(vehicle);
156 while (true) {
157 vars.push_back(dimension.CumulVar(current));
158 if (!model.IsEnd(current)) {
159 current = model.NextVar(current)->Value();
160 } else {
161 break;
162 }
163 }
164 }
165 if (dimension.HasBreakConstraints()) {
166 for (IntervalVar* interval :
167 dimension.GetBreakIntervalsOfVehicle(vehicle)) {
168 vars.push_back(interval->SafeStartExpr(0)->Var());
169 vars.push_back(interval->SafeEndExpr(0)->Var());
170 }
171 vals.insert(vals.end(), break_values.begin(), break_values.end());
172 }
173 DCHECK_EQ(vars.size(), vals.size());
174 int new_num_values = old_num_values;
175 for (int j = old_num_values; j < vals.size(); ++j) {
176 // Value kint64min signals an unoptimized variable, skip setting those.
177 if (vals[j] == std::numeric_limits<int64_t>::min()) continue;
178 // Skip variables that are not bound.
179 if (vars[j]->Bound()) continue;
180 vals[new_num_values] = vals[j];
181 vars[new_num_values] = vars[j];
182 ++new_num_values;
183 }
184 vars.resize(new_num_values);
185 vals.resize(new_num_values);
186}
187
188namespace {
189int GetVehicleRouteSize(const RoutingModel& model, int vehicle) {
190 int route_size = -1;
191 int64_t node = model.Start(vehicle);
192 while (node != model.End(vehicle)) {
193 route_size++;
194 DCHECK(model.NextVar(node)->Bound());
195 node = model.NextVar(node)->Value();
196 }
197 DCHECK_GE(route_size, 0);
198 return route_size;
199}
200} // namespace
201
202class SetCumulsFromLocalDimensionCosts : public DecisionBuilder {
203 public:
204 SetCumulsFromLocalDimensionCosts(
205 LocalDimensionCumulOptimizer* lp_optimizer,
206 LocalDimensionCumulOptimizer* mp_optimizer, bool optimize_and_pack,
207 std::vector<RoutingModel::RouteDimensionTravelInfo>
208 dimension_travel_info_per_route)
209 : model_(*lp_optimizer->dimension()->model()),
210 dimension_(*lp_optimizer->dimension()),
211 lp_optimizer_(lp_optimizer),
212 mp_optimizer_(mp_optimizer),
213 rg_index_(model_.GetDimensionResourceGroupIndices(&dimension_).empty()
214 ? -1
215 : model_.GetDimensionResourceGroupIndex(&dimension_)),
216 resource_group_(rg_index_ >= 0 ? model_.GetResourceGroup(rg_index_)
217 : nullptr),
218 vehicle_resource_class_values_(model_.vehicles()),
219 optimize_and_pack_(optimize_and_pack),
220 dimension_travel_info_per_route_(
221 std::move(dimension_travel_info_per_route)),
222 decision_level_(0) {
223 if (!dimension_travel_info_per_route_.empty()) {
224 DCHECK(optimize_and_pack_);
225 DCHECK_EQ(dimension_travel_info_per_route_.size(), model_.vehicles());
226 }
227 }
228
229 Decision* Next(Solver* solver) override {
230 if (decision_level_.Value() == 2) return nullptr;
231 if (decision_level_.Value() == 1) {
232 Decision* d = set_values_from_targets_->Next(solver);
233 if (d == nullptr) decision_level_.SetValue(solver, 2);
234 return d;
235 }
236 decision_level_.SetValue(solver, 1);
237 if (!FillCPVariablesAndValues(solver)) {
238 solver->Fail();
239 }
240 set_values_from_targets_ =
241 MakeSetValuesFromTargets(solver, cp_variables_, cp_values_);
242 return solver->MakeAssignVariablesValuesOrDoNothing(cp_variables_,
243 cp_values_);
244 }
245
246 private:
247 using Resource = RoutingModel::ResourceGroup::Resource;
248 using RCIndex = RoutingModel::ResourceClassIndex;
249 using RouteDimensionTravelInfo = RoutingModel::RouteDimensionTravelInfo;
250
251 bool FillCPVariablesAndValues(Solver* solver) {
252 DCHECK(DimensionFixedTransitsEqualTransitEvaluators(dimension_));
253 cp_variables_.clear();
254 cp_values_.clear();
255 vehicles_without_resource_assignment_.clear();
256 vehicles_with_resource_assignment_.clear();
257
258 util_intops::StrongVector<RCIndex, absl::flat_hash_set<int>>
259 used_resources_per_class;
260 DetermineVehiclesRequiringResourceAssignment(
261 &vehicles_without_resource_assignment_,
262 &vehicles_with_resource_assignment_, &used_resources_per_class);
263
264 const auto next = [&model = model_](int64_t n) {
265 return model.NextVar(n)->Value();
266 };
267
268 // First look at vehicles that do not need resource assignment (fewer/faster
269 // computations).
270 // NOTE(user): If it ever becomes an issue, we can consider leaving more
271 // 'shares' for the resource assignment calls since they're more expensive.
272 int solve_duration_shares = vehicles_without_resource_assignment_.size() +
273 vehicles_with_resource_assignment_.size();
274 for (int vehicle : vehicles_without_resource_assignment_) {
275 solver->TopPeriodicCheck();
276 cumul_values_.clear();
277 break_start_end_values_.clear();
278 // TODO(user): Distinguish between FEASIBLE and OPTIMAL statuses to
279 // keep track of the FEASIBLE-only cases, and resolve the feasible-only
280 // cases with the remaining time (if any) after all routes have been
281 // scheduled with the initial 'solve_duration_ratio'.
282 if (!ComputeCumulAndBreakValuesForVehicle(
283 vehicle, /*solve_duration_ratio=*/1.0 / solve_duration_shares,
284 next, &cumul_values_, &break_start_end_values_)) {
285 return false;
286 }
287 solve_duration_shares--;
288 AppendRouteCumulAndBreakVarAndValues(dimension_, vehicle, cumul_values_,
289 break_start_end_values_,
290 &cp_variables_, &cp_values_);
291 }
292
293 if (vehicles_with_resource_assignment_.empty()) {
294 return true;
295 }
296
297 // Do resource assignment for the vehicles requiring it and append the
298 // corresponding var and values.
299 resource_indices_.clear();
300 if (!ComputeVehicleResourceClassValuesAndIndices(
301 vehicles_with_resource_assignment_, used_resources_per_class, next,
302 &resource_indices_)) {
303 return false;
304 }
305 DCHECK_EQ(resource_indices_.size(), model_.vehicles());
306 const int num_resource_classes = resource_group_->GetResourceClassesCount();
307 for (int v : vehicles_with_resource_assignment_) {
308 DCHECK(next(model_.Start(v)) != model_.End(v) ||
309 model_.IsVehicleUsedWhenEmpty(v));
310 const auto& [unused, cumul_values, break_values] =
311 vehicle_resource_class_values_[v];
312 const int resource_index = resource_indices_[v];
313 DCHECK_GE(resource_index, 0);
314 DCHECK_EQ(cumul_values.size(), num_resource_classes);
315 DCHECK_EQ(break_values.size(), num_resource_classes);
316 const int rc_index =
317 resource_group_->GetResourceClassIndex(resource_index).value();
318 const std::vector<int64_t>& optimal_cumul_values = cumul_values[rc_index];
319 const std::vector<int64_t>& optimal_break_values = break_values[rc_index];
320 AppendRouteCumulAndBreakVarAndValues(dimension_, v, optimal_cumul_values,
321 optimal_break_values, &cp_variables_,
322 &cp_values_);
323
324 const std::vector<IntVar*>& resource_vars =
325 model_.ResourceVars(rg_index_);
326 DCHECK_EQ(resource_vars.size(), resource_indices_.size());
327 cp_variables_.insert(cp_variables_.end(), resource_vars.begin(),
328 resource_vars.end());
329 cp_values_.insert(cp_values_.end(), resource_indices_.begin(),
330 resource_indices_.end());
331 }
332 return true;
333 }
334
335 inline void DetermineVehiclesRequiringResourceAssignment(
336 std::vector<int>* vehicles_without_resource_assignment,
337 std::vector<int>* vehicles_with_resource_assignment,
338 util_intops::StrongVector<RCIndex, absl::flat_hash_set<int>>*
339 used_resources_per_class) const {
340 DCHECK(vehicles_without_resource_assignment->empty());
341 DCHECK(vehicles_with_resource_assignment->empty());
342 DCHECK(used_resources_per_class->empty());
343 struct VehicleInfo {
344 int vehicle_index;
345 int route_size;
346 bool requires_resource;
347#if __cplusplus < 202002L
348 VehicleInfo(int vi, int rs, bool rr)
349 : vehicle_index(vi), route_size(rs), requires_resource(rr) {}
350#endif
351 bool operator<(const VehicleInfo& other) const {
352 return std::tie(route_size, vehicle_index) <
353 std::tie(other.route_size, other.vehicle_index);
354 }
355 };
356
357 std::vector<VehicleInfo> vehicle_info;
358 vehicle_info.reserve(model_.vehicles());
359 if (rg_index_ < 0) {
360 for (int v = 0; v < model_.vehicles(); ++v) {
361 const int route_size = GetVehicleRouteSize(model_, v);
362 vehicle_info.emplace_back(v, route_size, false);
363 }
364 absl::c_sort(vehicle_info);
365 vehicles_without_resource_assignment->resize(model_.vehicles());
366 absl::c_transform(vehicle_info,
367 vehicles_without_resource_assignment->begin(),
368 [](const VehicleInfo& v) { return v.vehicle_index; });
369 return;
370 }
371
372 DCHECK_NE(resource_group_, nullptr);
373 used_resources_per_class->resize(
374 resource_group_->GetResourceClassesCount());
375 int num_vehicles_with_resource_assignment = 0;
376 for (int v = 0; v < model_.vehicles(); ++v) {
377 bool needs_resource = resource_group_->VehicleRequiresAResource(v);
378 if (needs_resource) {
379 if (model_.NextVar(model_.Start(v))->Value() == model_.End(v) &&
380 !model_.IsVehicleUsedWhenEmpty(v)) {
381 // No resource assignment required for this unused vehicle.
382 // TODO(user): Investigate if we should skip unused vehicles.
383 needs_resource = false;
384 } else if (model_.ResourceVar(v, rg_index_)->Bound()) {
385 needs_resource = false;
386 const int resource_idx = model_.ResourceVar(v, rg_index_)->Value();
387 DCHECK_GE(resource_idx, 0);
388 used_resources_per_class
389 ->at(resource_group_->GetResourceClassIndex(resource_idx))
390 .insert(resource_idx);
391 } else {
392 num_vehicles_with_resource_assignment++;
393 }
394 }
395 vehicle_info.emplace_back(v, GetVehicleRouteSize(model_, v),
396 needs_resource);
397 }
398 absl::c_sort(vehicle_info);
399 vehicles_with_resource_assignment->reserve(
400 num_vehicles_with_resource_assignment);
401 vehicles_without_resource_assignment->reserve(
402 model_.vehicles() - num_vehicles_with_resource_assignment);
403 for (const VehicleInfo& v_info : vehicle_info) {
404 if (v_info.requires_resource) {
405 vehicles_with_resource_assignment->push_back(v_info.vehicle_index);
406 } else {
407 vehicles_without_resource_assignment->push_back(v_info.vehicle_index);
408 }
409 }
410 DCHECK_EQ(vehicles_without_resource_assignment->size() +
411 vehicles_with_resource_assignment->size(),
412 model_.vehicles());
413 }
414
415 bool ComputeCumulAndBreakValuesForVehicle(
416 int vehicle, double solve_duration_ratio,
417 const std::function<int64_t(int64_t)>& next_accessor,
418 std::vector<int64_t>* cumul_values,
419 std::vector<int64_t>* break_start_end_values) {
420 cumul_values->clear();
421 break_start_end_values->clear();
422 const RouteDimensionTravelInfo* const dimension_travel_info =
423 dimension_travel_info_per_route_.empty()
424 ? nullptr
425 : &dimension_travel_info_per_route_[vehicle];
426 const Resource* resource = nullptr;
427 if (rg_index_ >= 0 && model_.ResourceVar(vehicle, rg_index_)->Bound()) {
428 const int resource_index =
429 model_.ResourceVar(vehicle, rg_index_)->Value();
430 if (resource_index >= 0) {
431 resource =
432 &model_.GetResourceGroup(rg_index_)->GetResource(resource_index);
433 }
434 }
435 const bool use_mp_optimizer =
436 dimension_.HasQuadraticCostSoftSpanUpperBounds() ||
437 (dimension_.HasBreakConstraints() &&
438 !dimension_.GetBreakIntervalsOfVehicle(vehicle).empty());
439 LocalDimensionCumulOptimizer* const optimizer =
440 use_mp_optimizer ? mp_optimizer_ : lp_optimizer_;
441 DCHECK_NE(optimizer, nullptr);
443 optimize_and_pack_ ? optimizer->ComputePackedRouteCumuls(
444 vehicle, solve_duration_ratio, next_accessor,
445 dimension_travel_info, resource, cumul_values,
446 break_start_end_values)
447 : optimizer->ComputeRouteCumuls(
448 vehicle, solve_duration_ratio, next_accessor,
449 dimension_travel_info, resource, cumul_values,
450 break_start_end_values);
451 // If relaxation is not feasible, try the MP optimizer.
453 DCHECK(!use_mp_optimizer);
454 DCHECK_NE(mp_optimizer_, nullptr);
455 status = optimize_and_pack_
456 ? mp_optimizer_->ComputePackedRouteCumuls(
457 vehicle, solve_duration_ratio, next_accessor,
458 dimension_travel_info, resource, cumul_values,
459 break_start_end_values)
460 : mp_optimizer_->ComputeRouteCumuls(
461 vehicle, solve_duration_ratio, next_accessor,
462 dimension_travel_info, resource, cumul_values,
463 break_start_end_values);
464 }
466 }
467
468 bool ComputeVehicleResourceClassValuesAndIndices(
469 absl::Span<const int> vehicles_to_assign,
470 const util_intops::StrongVector<RCIndex, absl::flat_hash_set<int>>&
471 used_resources_per_class,
472 const std::function<int64_t(int64_t)>& next_accessor,
473 std::vector<int>* resource_indices) {
474 resource_indices->assign(model_.vehicles(), -1);
475 if (vehicles_to_assign.empty()) return true;
476 DCHECK_NE(resource_group_, nullptr);
477
478 int solve_duration_shares = vehicles_to_assign.size();
479 for (int v : vehicles_to_assign) {
480 DCHECK(resource_group_->VehicleRequiresAResource(v));
481 auto& [assignment_costs, cumul_values, break_values] =
482 vehicle_resource_class_values_[v];
484 v, /*solve_duration_ratio=*/1.0 / solve_duration_shares,
485 *resource_group_, used_resources_per_class, next_accessor,
486 dimension_.transit_evaluator(v),
487 /*optimize_vehicle_costs*/ true, lp_optimizer_, mp_optimizer_,
488 &assignment_costs, &cumul_values, &break_values)) {
489 return false;
490 }
491 solve_duration_shares--;
492 }
493
495 vehicles_to_assign,
496 resource_group_->GetResourceIndicesPerClass(),
497 used_resources_per_class,
498 [&vehicle_rc_values = vehicle_resource_class_values_](int v) {
499 return &vehicle_rc_values[v].assignment_costs;
500 },
501 resource_indices) >= 0;
502 }
503
504 const RoutingModel& model_;
505 const RoutingDimension& dimension_;
506 LocalDimensionCumulOptimizer* lp_optimizer_;
507 LocalDimensionCumulOptimizer* mp_optimizer_;
508 // Stores the resource group index of the lp_/mp_optimizer_'s dimension, if
509 // there is any.
510 const int rg_index_;
511 const RoutingModel::ResourceGroup* const resource_group_;
512 // Stores the information related to assigning a given vehicle to resource
513 // classes. We keep these as class members to avoid unnecessary memory
514 // reallocations.
515 struct VehicleResourceClassValues {
516 std::vector<int64_t> assignment_costs;
517 std::vector<std::vector<int64_t>> cumul_values;
518 std::vector<std::vector<int64_t>> break_values;
519 };
520 std::vector<VehicleResourceClassValues> vehicle_resource_class_values_;
521 const bool optimize_and_pack_;
522 const std::vector<RouteDimensionTravelInfo> dimension_travel_info_per_route_;
523 std::vector<IntVar*> cp_variables_;
524 std::vector<int64_t> cp_values_;
525 // Decision level of this decision builder:
526 // - level 0: set remaining dimension values at once.
527 // - level 1: set remaining dimension values one by one.
528 Rev<int> decision_level_;
529 DecisionBuilder* set_values_from_targets_ = nullptr;
530 // "Local" variables used by FillCPVariablesAndValues(). They can't be defined
531 // as true local variables, because the function may backtrack when a time
532 // limit is reached.
533 std::vector<int> vehicles_without_resource_assignment_;
534 std::vector<int> vehicles_with_resource_assignment_;
535 std::vector<int64_t> cumul_values_;
536 std::vector<int64_t> break_start_end_values_;
537 std::vector<int> resource_indices_;
538};
539
540} // namespace
541
543 Solver* solver, LocalDimensionCumulOptimizer* lp_optimizer,
544 LocalDimensionCumulOptimizer* mp_optimizer, bool optimize_and_pack,
545 std::vector<RoutingModel::RouteDimensionTravelInfo>
546 dimension_travel_info_per_route) {
547 return solver->RevAlloc(new SetCumulsFromLocalDimensionCosts(
548 lp_optimizer, mp_optimizer, optimize_and_pack,
549 std::move(dimension_travel_info_per_route)));
550}
551
552namespace {
553
554class SetCumulsFromGlobalDimensionCosts : public DecisionBuilder {
555 public:
556 SetCumulsFromGlobalDimensionCosts(
557 GlobalDimensionCumulOptimizer* global_optimizer,
558 GlobalDimensionCumulOptimizer* global_mp_optimizer,
559 SearchMonitor* monitor, bool optimize_and_pack,
560 std::vector<RoutingModel::RouteDimensionTravelInfo>
561 dimension_travel_info_per_route)
562 : global_optimizer_(global_optimizer),
563 global_mp_optimizer_(global_mp_optimizer),
564 monitor_(monitor),
565 optimize_and_pack_(optimize_and_pack),
566 dimension_travel_info_per_route_(
567 std::move(dimension_travel_info_per_route)),
568 decision_level_(0) {
569 DCHECK(dimension_travel_info_per_route_.empty() ||
570 dimension_travel_info_per_route_.size() ==
571 global_optimizer_->dimension()->model()->vehicles());
572 // Store the cp variables used to set values on in Next().
573 // NOTE: The order is important as we use the same order to add values
574 // in cp_values_.
575 const RoutingDimension* dimension = global_optimizer_->dimension();
576 const RoutingModel* model = dimension->model();
577 cp_variables_ = dimension->cumuls();
578 if (dimension->HasBreakConstraints()) {
579 for (int vehicle = 0; vehicle < model->vehicles(); ++vehicle) {
580 for (IntervalVar* interval :
581 dimension->GetBreakIntervalsOfVehicle(vehicle)) {
582 cp_variables_.push_back(interval->SafeStartExpr(0)->Var());
583 cp_variables_.push_back(interval->SafeEndExpr(0)->Var());
584 }
585 }
586 }
587 // NOTE: When packing, the resource variables should already have a bound
588 // value which is taken into account by the optimizer, so we don't set them
589 // in MakeSetValuesFromTargets().
590 if (!optimize_and_pack_) {
591 for (int rg_index : model->GetDimensionResourceGroupIndices(dimension)) {
592 const std::vector<IntVar*>& res_vars = model->ResourceVars(rg_index);
593 cp_variables_.insert(cp_variables_.end(), res_vars.begin(),
594 res_vars.end());
595 }
596 }
597 }
598
599 Decision* Next(Solver* solver) override {
600 if (decision_level_.Value() == 2) return nullptr;
601 if (decision_level_.Value() == 1) {
602 Decision* d = set_values_from_targets_->Next(solver);
603 if (d == nullptr) decision_level_.SetValue(solver, 2);
604 return d;
605 }
606 decision_level_.SetValue(solver, 1);
607 if (!FillCPValues()) {
608 solver->Fail();
609 }
610 set_values_from_targets_ =
611 MakeSetValuesFromTargets(solver, cp_variables_, cp_values_);
612 return solver->MakeAssignVariablesValuesOrDoNothing(cp_variables_,
613 cp_values_);
614 }
615
616 private:
617 bool FillCPValues() {
618 const RoutingDimension* dimension = global_optimizer_->dimension();
619 DCHECK(DimensionFixedTransitsEqualTransitEvaluators(*dimension));
620 RoutingModel* const model = dimension->model();
621
622 GlobalDimensionCumulOptimizer* const optimizer =
623 model->GetDimensionResourceGroupIndices(dimension).empty()
624 ? global_optimizer_
625 : global_mp_optimizer_;
626 DimensionSchedulingStatus status = ComputeCumulBreakAndResourceValues(
627 optimizer, &cumul_values_, &break_start_end_values_,
628 &resource_indices_per_group_);
630 // If relaxation is not feasible, try the MILP optimizer.
631 status = ComputeCumulBreakAndResourceValues(
632 global_mp_optimizer_, &cumul_values_, &break_start_end_values_,
633 &resource_indices_per_group_);
634 }
636 return false;
637 }
638 // Concatenate cumul_values_, break_start_end_values_ and all
639 // resource_indices_per_group_ into cp_values_.
640 // NOTE: The order is important as it corresponds to the order of
641 // variables in cp_variables_.
642 cp_values_ = std::move(cumul_values_);
643 if (dimension->HasBreakConstraints()) {
644 cp_values_.insert(cp_values_.end(), break_start_end_values_.begin(),
645 break_start_end_values_.end());
646 }
647 if (optimize_and_pack_) {
648// Resource variables should be bound when packing, so we don't need
649// to restore them again.
650#ifndef NDEBUG
651 for (int rg_index : model->GetDimensionResourceGroupIndices(dimension)) {
652 for (IntVar* res_var : model->ResourceVars(rg_index)) {
653 DCHECK(res_var->Bound());
654 }
655 }
656#endif
657 } else {
658 // Add resource values to cp_values_.
659 for (int rg_index : model->GetDimensionResourceGroupIndices(dimension)) {
660 const std::vector<int>& resource_values =
661 resource_indices_per_group_[rg_index];
662 DCHECK(!resource_values.empty());
663 cp_values_.insert(cp_values_.end(), resource_values.begin(),
664 resource_values.end());
665 }
666 }
667 DCHECK_EQ(cp_variables_.size(), cp_values_.size());
668 // Value kint64min signals an unoptimized variable, set to min instead.
669 for (int j = 0; j < cp_values_.size(); ++j) {
670 if (cp_values_[j] == std::numeric_limits<int64_t>::min()) {
671 cp_values_[j] = cp_variables_[j]->Min();
672 }
673 }
674 return true;
675 }
676
677 DimensionSchedulingStatus ComputeCumulBreakAndResourceValues(
678 GlobalDimensionCumulOptimizer* optimizer,
679 std::vector<int64_t>* cumul_values,
680 std::vector<int64_t>* break_start_end_values,
681 std::vector<std::vector<int>>* resource_indices_per_group) {
682 DCHECK_NE(optimizer, nullptr);
683 cumul_values->clear();
684 break_start_end_values->clear();
685 resource_indices_per_group->clear();
686 RoutingModel* const model = optimizer->dimension()->model();
687 const auto next = [model](int64_t n) { return model->NextVar(n)->Value(); };
688 return optimize_and_pack_
689 ? optimizer->ComputePackedCumuls(
690 next, dimension_travel_info_per_route_, cumul_values,
691 break_start_end_values)
692 : optimizer->ComputeCumuls(
693 next, dimension_travel_info_per_route_, cumul_values,
694 break_start_end_values, resource_indices_per_group);
695 }
696
697 GlobalDimensionCumulOptimizer* const global_optimizer_;
698 GlobalDimensionCumulOptimizer* const global_mp_optimizer_;
699 SearchMonitor* const monitor_;
700 const bool optimize_and_pack_;
701 std::vector<IntVar*> cp_variables_;
702 std::vector<int64_t> cp_values_;
703 // The following 3 members are stored internally to avoid unnecessary memory
704 // reallocations.
705 std::vector<int64_t> cumul_values_;
706 std::vector<int64_t> break_start_end_values_;
707 std::vector<std::vector<int>> resource_indices_per_group_;
708 const std::vector<RoutingModel::RouteDimensionTravelInfo>
709 dimension_travel_info_per_route_;
710 // Decision level of this decision builder:
711 // - level 0: set remaining dimension values at once.
712 // - level 1: set remaining dimension values one by one.
713 Rev<int> decision_level_;
714 DecisionBuilder* set_values_from_targets_ = nullptr;
715};
716
717} // namespace
718
720 Solver* solver, GlobalDimensionCumulOptimizer* global_optimizer,
721 GlobalDimensionCumulOptimizer* global_mp_optimizer, SearchMonitor* monitor,
722 bool optimize_and_pack,
723 std::vector<RoutingModel::RouteDimensionTravelInfo>
724 dimension_travel_info_per_route) {
725 return solver->RevAlloc(new SetCumulsFromGlobalDimensionCosts(
726 global_optimizer, global_mp_optimizer, monitor, optimize_and_pack,
727 std::move(dimension_travel_info_per_route)));
728}
729
730namespace {
731// A decision builder that tries to set variables to their value in the last
732// solution, if their corresponding vehicle path has not changed.
733// This tries to constrain all such variables in one shot in order to speed up
734// instantiation.
735// TODO(user): try to use Assignment instead of MakeAssignment(),
736// try to record and restore the min/max instead of a single value.
737class RestoreDimensionValuesForUnchangedRoutes : public DecisionBuilder {
738 public:
739 explicit RestoreDimensionValuesForUnchangedRoutes(RoutingModel* model)
740 : model_(model) {
741 model_->AddAtSolutionCallback([this]() { AtSolution(); });
742 model_->AddRestoreDimensionValuesResetCallback([this]() { Reset(); });
743 next_last_value_.resize(model_->Nexts().size(), -1);
744 }
745
746 // In a given branch of a search tree, this decision builder only returns
747 // a Decision once, the first time it is called in that branch.
748 Decision* Next(Solver* const s) override {
749 if (!must_return_decision_) return nullptr;
750 s->SaveAndSetValue(&must_return_decision_, false);
751 return MakeDecision(s);
752 }
753
754 void Reset() { next_last_value_.assign(model_->Nexts().size(), -1); }
755
756 private:
757 // Initialize() is lazy to make sure all dimensions have been instantiated
758 // when initialization is done.
759 void Initialize() {
760 is_initialized_ = true;
761 const int num_nodes = model_->VehicleVars().size();
762 node_to_integer_variable_indices_.resize(num_nodes);
763 node_to_interval_variable_indices_.resize(num_nodes);
764 // Search for dimension variables that correspond to input variables.
765 for (const std::string& dimension_name : model_->GetAllDimensionNames()) {
766 const RoutingDimension& dimension =
767 model_->GetDimensionOrDie(dimension_name);
768 // Search among cumuls and slacks, and attach them to corresponding nodes.
769 for (const std::vector<IntVar*>& dimension_variables :
770 {dimension.cumuls(), dimension.slacks()}) {
771 const int num_dimension_variables = dimension_variables.size();
772 DCHECK_LE(num_dimension_variables, num_nodes);
773 for (int node = 0; node < num_dimension_variables; ++node) {
774 node_to_integer_variable_indices_[node].push_back(
775 integer_variables_.size());
776 integer_variables_.push_back(dimension_variables[node]);
777 }
778 }
779 // Search for break start/end variables, attach them to vehicle starts.
780 for (int vehicle = 0; vehicle < model_->vehicles(); ++vehicle) {
781 if (!dimension.HasBreakConstraints()) continue;
782 const int vehicle_start = model_->Start(vehicle);
783 for (IntervalVar* interval :
784 dimension.GetBreakIntervalsOfVehicle(vehicle)) {
785 node_to_interval_variable_indices_[vehicle_start].push_back(
786 interval_variables_.size());
787 interval_variables_.push_back(interval);
788 }
789 }
790 }
791 integer_variables_last_min_.resize(integer_variables_.size());
792 interval_variables_last_start_min_.resize(interval_variables_.size());
793 interval_variables_last_end_max_.resize(interval_variables_.size());
794 }
795
796 Decision* MakeDecision(Solver* const s) {
797 if (!is_initialized_) return nullptr;
798 // Collect vehicles that have not changed.
799 std::vector<int> unchanged_vehicles;
800 const int num_vehicles = model_->vehicles();
801 for (int v = 0; v < num_vehicles; ++v) {
802 bool unchanged = true;
803 for (int current = model_->Start(v); !model_->IsEnd(current);
804 current = next_last_value_[current]) {
805 if (!model_->NextVar(current)->Bound() ||
806 next_last_value_[current] != model_->NextVar(current)->Value()) {
807 unchanged = false;
808 break;
809 }
810 }
811 if (unchanged) unchanged_vehicles.push_back(v);
812 }
813 // If all routes are unchanged, the solver might be trying to do a full
814 // reschedule. Do nothing.
815 if (unchanged_vehicles.size() == num_vehicles) return nullptr;
816
817 // Collect cumuls and slacks of unchanged routes to be assigned a value.
818 std::vector<IntVar*> vars;
819 std::vector<int64_t> values;
820 for (const int vehicle : unchanged_vehicles) {
821 for (int current = model_->Start(vehicle); true;
822 current = next_last_value_[current]) {
823 for (const int index : node_to_integer_variable_indices_[current]) {
824 vars.push_back(integer_variables_[index]);
825 values.push_back(integer_variables_last_min_[index]);
826 }
827 for (const int index : node_to_interval_variable_indices_[current]) {
828 const int64_t start_min = interval_variables_last_start_min_[index];
829 const int64_t end_max = interval_variables_last_end_max_[index];
830 if (start_min < end_max) {
831 vars.push_back(interval_variables_[index]->SafeStartExpr(0)->Var());
832 values.push_back(interval_variables_last_start_min_[index]);
833 vars.push_back(interval_variables_[index]->SafeEndExpr(0)->Var());
834 values.push_back(interval_variables_last_end_max_[index]);
835 } else {
836 vars.push_back(interval_variables_[index]->PerformedExpr()->Var());
837 values.push_back(0);
838 }
839 }
840 if (model_->IsEnd(current)) break;
841 }
842 }
843 return s->MakeAssignVariablesValuesOrDoNothing(vars, values);
844 }
845
846 void AtSolution() {
847 if (!is_initialized_) Initialize();
848 const int num_integers = integer_variables_.size();
849 // Variables may not be fixed at solution time,
850 // the decision builder is fine with the Min() of the unfixed variables.
851 for (int i = 0; i < num_integers; ++i) {
852 integer_variables_last_min_[i] = integer_variables_[i]->Min();
853 }
854 const int num_intervals = interval_variables_.size();
855 for (int i = 0; i < num_intervals; ++i) {
856 const bool is_performed = interval_variables_[i]->MustBePerformed();
857 interval_variables_last_start_min_[i] =
858 is_performed ? interval_variables_[i]->StartMin() : 0;
859 interval_variables_last_end_max_[i] =
860 is_performed ? interval_variables_[i]->EndMax() : -1;
861 }
862 const int num_nodes = next_last_value_.size();
863 for (int node = 0; node < num_nodes; ++node) {
864 if (model_->IsEnd(node)) continue;
865 next_last_value_[node] = model_->NextVar(node)->Value();
866 }
867 }
868
869 // Input data.
870 RoutingModel* const model_;
871
872 // The valuation of the last solution.
873 std::vector<int> next_last_value_;
874 // For every node, the indices of integer_variables_ and interval_variables_
875 // that correspond to that node.
876 std::vector<std::vector<int>> node_to_integer_variable_indices_;
877 std::vector<std::vector<int>> node_to_interval_variable_indices_;
878 // Variables and the value they had in the previous solution.
879 std::vector<IntVar*> integer_variables_;
880 std::vector<int64_t> integer_variables_last_min_;
881 std::vector<IntervalVar*> interval_variables_;
882 std::vector<int64_t> interval_variables_last_start_min_;
883 std::vector<int64_t> interval_variables_last_end_max_;
884
885 bool is_initialized_ = false;
886 bool must_return_decision_ = true;
887};
888} // namespace
889
891 RoutingModel* model) {
892 return model->solver()->RevAlloc(
893 new RestoreDimensionValuesForUnchangedRoutes(model));
894}
895
896// FinalizerVariables
897
899 int64_t cost) {
900 CHECK(var != nullptr);
901 const int index =
902 gtl::LookupOrInsert(&weighted_finalizer_variable_index_, var,
903 weighted_finalizer_variable_targets_.size());
904 if (index < weighted_finalizer_variable_targets_.size()) {
905 auto& [var_target, total_cost] =
906 weighted_finalizer_variable_targets_[index];
907 DCHECK_EQ(var_target.var, var);
908 DCHECK_EQ(var_target.target, target);
909 total_cost = CapAdd(total_cost, cost);
910 } else {
911 DCHECK_EQ(index, weighted_finalizer_variable_targets_.size());
912 weighted_finalizer_variable_targets_.push_back({{var, target}, cost});
913 }
914}
915
917 int64_t cost) {
918 AddWeightedVariableTarget(var, std::numeric_limits<int64_t>::min(), cost);
919}
920
922 int64_t cost) {
923 AddWeightedVariableTarget(var, std::numeric_limits<int64_t>::max(), cost);
924}
925
927 CHECK(var != nullptr);
928 if (finalizer_variable_target_set_.contains(var)) return;
929 finalizer_variable_target_set_.insert(var);
930 finalizer_variable_targets_.push_back({var, target});
931}
932
934 AddVariableTarget(var, std::numeric_limits<int64_t>::max());
935}
936
938 AddVariableTarget(var, std::numeric_limits<int64_t>::min());
939}
940
942 std::stable_sort(weighted_finalizer_variable_targets_.begin(),
943 weighted_finalizer_variable_targets_.end(),
944 [](const std::pair<VarTarget, int64_t>& var_cost1,
945 const std::pair<VarTarget, int64_t>& var_cost2) {
946 return var_cost1.second > var_cost2.second;
947 });
948 const int num_variables = weighted_finalizer_variable_targets_.size() +
949 finalizer_variable_targets_.size();
950 std::vector<IntVar*> variables;
951 std::vector<int64_t> targets;
952 variables.reserve(num_variables);
953 targets.reserve(num_variables);
954 for (const auto& [var_target, cost] : weighted_finalizer_variable_targets_) {
955 variables.push_back(var_target.var);
956 targets.push_back(var_target.target);
957 }
958 for (const auto& [var, target] : finalizer_variable_targets_) {
959 variables.push_back(var);
960 targets.push_back(target);
961 }
962 return MakeSetValuesFromTargets(solver_, std::move(variables),
963 std::move(targets));
964}
965
966} // namespace operations_research
void AddVariableTarget(IntVar *var, int64_t target)
void AddWeightedVariableToMinimize(IntVar *var, int64_t cost)
void AddWeightedVariableTarget(IntVar *var, int64_t target, int64_t cost)
FinalizerVariables.
void AddWeightedVariableToMaximize(IntVar *var, int64_t cost)
A search monitor is a simple set of callbacks to monitor all search events.
For the time being, Solver is neither MT_SAFE nor MT_HOT.
void resize(size_type new_size)
reference at(IntType i)
Collection::value_type::second_type & LookupOrInsert(Collection *const collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
Definition map_util.h:242
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapAdd(int64_t x, int64_t y)
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)
DecisionBuilder * MakeRestoreDimensionValuesForUnchangedRoutes(RoutingModel *model)
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)
DecisionBuilder * MakeSetCumulsFromLocalDimensionCosts(Solver *solver, LocalDimensionCumulOptimizer *lp_optimizer, LocalDimensionCumulOptimizer *mp_optimizer, bool optimize_and_pack, std::vector< RoutingModel::RouteDimensionTravelInfo > dimension_travel_info_per_route)
DecisionBuilder * MakeSetCumulsFromGlobalDimensionCosts(Solver *solver, GlobalDimensionCumulOptimizer *global_optimizer, GlobalDimensionCumulOptimizer *global_mp_optimizer, SearchMonitor *monitor, bool optimize_and_pack, std::vector< RoutingModel::RouteDimensionTravelInfo > dimension_travel_info_per_route)
Variant based on global optimizers, handling all routes together.
DecisionBuilder * MakeSetValuesFromTargets(Solver *solver, std::vector< IntVar * > variables, std::vector< int64_t > targets)
STL namespace.
bool Next()