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