Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
routing_lp_scheduling.h
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
14#ifndef ORTOOLS_CONSTRAINT_SOLVER_ROUTING_LP_SCHEDULING_H_
15#define ORTOOLS_CONSTRAINT_SOLVER_ROUTING_LP_SCHEDULING_H_
16
17#include <algorithm>
18#include <cstdint>
19#include <deque>
20#include <functional>
21#include <limits>
22#include <memory>
23#include <ostream>
24#include <string>
25#include <utility>
26#include <vector>
27
28#include "absl/container/flat_hash_map.h"
29#include "absl/container/flat_hash_set.h"
30#include "absl/log/check.h"
31#include "absl/strings/str_format.h"
32#include "absl/strings/string_view.h"
33#include "absl/time/time.h"
34#include "absl/types/span.h"
46#include "ortools/sat/model.h"
50
51namespace operations_research {
52
53// Classes to solve dimension cumul placement (aka scheduling) problems using
54// linear programming.
55
56// Utility class used in the core optimizer to tighten the cumul bounds as much
57// as possible based on the model precedences.
59 public:
61
62 // Tightens the cumul bounds starting from the current cumul var min/max,
63 // and propagating the precedences resulting from the next_accessor, and the
64 // dimension's precedence rules.
65 // Returns false iff the precedences are infeasible with the given routes.
66 // Otherwise, the user can call CumulMin() and CumulMax() to retrieve the new
67 // bounds of an index.
69 const std::function<int64_t(int64_t)>& next_accessor,
70 int64_t cumul_offset,
71 const std::vector<RoutingModel::RouteDimensionTravelInfo>*
72 dimension_travel_info_per_route = nullptr);
73
74 int64_t CumulMin(int index) const {
75 return propagated_bounds_[PositiveNode(index)];
76 }
77
78 int64_t CumulMax(int index) const {
79 const int64_t negated_upper_bound = propagated_bounds_[NegativeNode(index)];
80 return negated_upper_bound == std::numeric_limits<int64_t>::min()
81 ? std::numeric_limits<int64_t>::max()
82 : -negated_upper_bound;
83 }
84
85 const RoutingDimension& dimension() const { return dimension_; }
86
87 private:
88 // An arc "tail --offset--> head" represents the relation
89 // tail + offset <= head.
90 // As arcs are stored by tail, we don't store it in the struct.
91 struct ArcInfo {
92 int head;
93 int64_t offset;
94 };
95 static const int kNoParent;
96 static const int kParentToBePropagated;
97
98 // Return the node corresponding to the lower bound of the cumul of index and
99 // -index respectively.
100 int PositiveNode(int index) const { return 2 * index; }
101 int NegativeNode(int index) const { return 2 * index + 1; }
102
103 void AddNodeToQueue(int node) {
104 if (!node_in_queue_[node]) {
105 bf_queue_.push_back(node);
106 node_in_queue_[node] = true;
107 }
108 }
109
110 // Adds the relation first_index + offset <= second_index, by adding arcs
111 // first_index --offset--> second_index and
112 // -second_index --offset--> -first_index.
113 void AddArcs(int first_index, int second_index, int64_t offset);
114
115 bool InitializeArcsAndBounds(
116 const std::function<int64_t(int64_t)>& next_accessor,
117 int64_t cumul_offset,
118 const std::vector<RoutingModel::RouteDimensionTravelInfo>*
119 dimension_travel_info_per_route = nullptr);
120
121 bool UpdateCurrentLowerBoundOfNode(int node, int64_t new_lb, int64_t offset);
122
123 bool DisassembleSubtree(int source, int target);
124
125 bool CleanupAndReturnFalse() {
126 // We clean-up node_in_queue_ for future calls, and return false.
127 for (int node_to_cleanup : bf_queue_) {
128 node_in_queue_[node_to_cleanup] = false;
129 }
130 bf_queue_.clear();
131 return false;
132 }
133
134 const RoutingDimension& dimension_;
135 const int64_t num_nodes_;
136
137 // TODO(user): Investigate if all arcs for a given tail can be created
138 // at the same time, in which case outgoing_arcs_ could point to an absl::Span
139 // for each tail index.
140 std::vector<std::vector<ArcInfo>> outgoing_arcs_;
141
142 std::deque<int> bf_queue_;
143 std::vector<bool> node_in_queue_;
144 std::vector<int> tree_parent_node_of_;
145 // After calling PropagateCumulBounds(), for each node index n,
146 // propagated_bounds_[2*n] and -propagated_bounds_[2*n+1] respectively contain
147 // the propagated lower and upper bounds of n's cumul variable.
148 std::vector<int64_t> propagated_bounds_;
149
150 // Vector used in DisassembleSubtree() to avoid memory reallocation.
151 std::vector<int> tmp_dfs_stack_;
152
153 // Used to store the pickup/delivery pairs encountered on the routes.
154 std::vector<std::pair<int64_t, int64_t>>
155 visited_pickup_delivery_indices_for_pair_;
156};
157
159 // An optimal solution was found respecting all constraints.
161 // An optimal solution was found, however constraints which were relaxed were
162 // violated.
164 // Only a feasible solution was found, optimality was not proven.
166 // A solution could not be found.
168};
169
171 public:
172 static const int kNoConstraint = -1;
173
175 : search_stats_(search_stats) {}
176 virtual ~RoutingLinearSolverWrapper() = default;
177 virtual void Clear() = 0;
178 virtual int CreateNewPositiveVariable() = 0;
179 virtual void SetVariableName(int index, absl::string_view name) = 0;
180 virtual bool SetVariableBounds(int index, int64_t lower_bound,
181 int64_t upper_bound) = 0;
182 virtual void SetVariableDisjointBounds(int index,
183 const std::vector<int64_t>& starts,
184 const std::vector<int64_t>& ends) = 0;
185 virtual int64_t GetVariableLowerBound(int index) const = 0;
186 virtual int64_t GetVariableUpperBound(int index) const = 0;
187 virtual void SetObjectiveCoefficient(int index, double coefficient) = 0;
188 virtual double GetObjectiveCoefficient(int index) const = 0;
189 virtual void ClearObjective() = 0;
190 virtual int NumVariables() const = 0;
191 virtual int CreateNewConstraint(int64_t lower_bound, int64_t upper_bound) = 0;
192 virtual void SetCoefficient(int ct, int index, double coefficient) = 0;
193 virtual bool IsCPSATSolver() = 0;
194 virtual void AddObjectiveConstraint() = 0;
195 virtual void AddMaximumConstraint(int max_var, std::vector<int> vars) = 0;
196 virtual void AddProductConstraint(int product_var, std::vector<int> vars) = 0;
197 virtual void SetEnforcementLiteral(int ct, int condition) = 0;
198 virtual DimensionSchedulingStatus Solve(absl::Duration duration_limit) = 0;
199 virtual int64_t GetObjectiveValue() const = 0;
200 virtual int64_t GetVariableValue(int index) const = 0;
201 virtual bool SolutionIsInteger() const = 0;
202
203 // This function is meant to override the parameters of the solver.
204 virtual void SetParameters(const std::string& parameters) = 0;
205 // When solving a scheduling problem, this can be called to add hints that
206 // help the underlying solver:
207 // - nodes is the sequence of nodes in a route represented in the model.
208 // - schedule_variables is the sequence of variables used to represent the
209 // time at which each node is scheduled.
210 virtual void AddRoute(absl::Span<const int64_t> nodes,
211 absl::Span<const int> schedule_variables) = 0;
212
213 // Returns if the model is empty or not.
214 virtual bool ModelIsEmpty() const { return true; }
215
216 // Prints an understandable view of the model.
217 virtual std::string PrintModel() const = 0;
218
219 // Adds a variable with bounds [lower_bound, upper_bound].
220 int AddVariable(int64_t lower_bound, int64_t upper_bound) {
221 CHECK_LE(lower_bound, upper_bound);
222 const int variable = CreateNewPositiveVariable();
223 SetVariableBounds(variable, lower_bound, upper_bound);
224 return variable;
225 }
226 // Adds a linear constraint, enforcing
227 // lower_bound <= sum variable * coeff <= upper_bound,
228 // and returns the identifier of that constraint.
230 int64_t lower_bound, int64_t upper_bound,
231 absl::Span<const std::pair<int, double>> variable_coeffs) {
232 CHECK_LE(lower_bound, upper_bound);
233 const int ct = CreateNewConstraint(lower_bound, upper_bound);
234 for (const auto& variable_coeff : variable_coeffs) {
235 SetCoefficient(ct, variable_coeff.first, variable_coeff.second);
236 }
237 return ct;
238 }
239 // Adds a linear constraint and a 0/1 variable that is true iff
240 // lower_bound <= sum variable * coeff <= upper_bound,
241 // and returns the identifier of that variable.
243 int64_t lower_bound, int64_t upper_bound,
244 absl::Span<const std::pair<int, double>> weighted_variables) {
245 const int reification_ct = AddLinearConstraint(1, 1, {});
246 if (std::numeric_limits<int64_t>::min() < lower_bound) {
247 const int under_lower_bound = AddVariable(0, 1);
248#ifndef NDEBUG
249 SetVariableName(under_lower_bound, "under_lower_bound");
250#endif
251 SetCoefficient(reification_ct, under_lower_bound, 1);
252 const int under_lower_bound_ct =
253 AddLinearConstraint(std::numeric_limits<int64_t>::min(),
254 lower_bound - 1, weighted_variables);
255 SetEnforcementLiteral(under_lower_bound_ct, under_lower_bound);
256 }
257 if (upper_bound < std::numeric_limits<int64_t>::max()) {
258 const int above_upper_bound = AddVariable(0, 1);
259#ifndef NDEBUG
260 SetVariableName(above_upper_bound, "above_upper_bound");
261#endif
262 SetCoefficient(reification_ct, above_upper_bound, 1);
263 const int above_upper_bound_ct = AddLinearConstraint(
264 upper_bound + 1, std::numeric_limits<int64_t>::max(),
265 weighted_variables);
266 SetEnforcementLiteral(above_upper_bound_ct, above_upper_bound);
267 }
268 const int within_bounds = AddVariable(0, 1);
269#ifndef NDEBUG
270 SetVariableName(within_bounds, "within_bounds");
271#endif
272 SetCoefficient(reification_ct, within_bounds, 1);
273 const int within_bounds_ct =
274 AddLinearConstraint(lower_bound, upper_bound, weighted_variables);
275 SetEnforcementLiteral(within_bounds_ct, within_bounds);
276 return within_bounds;
277 }
278
279 protected:
281};
282
284 public:
285 RoutingGlopWrapper(bool is_relaxation, const glop::GlopParameters& parameters,
286 RoutingSearchStats* search_stats)
287 : RoutingLinearSolverWrapper(search_stats),
288 is_relaxation_(is_relaxation) {
289 lp_solver_.SetParameters(parameters);
290 linear_program_.SetMaximizationProblem(false);
291 }
292 void Clear() override {
293 linear_program_.Clear();
294 linear_program_.SetMaximizationProblem(false);
295 allowed_intervals_.clear();
296 }
298 return linear_program_.CreateNewVariable().value();
299 }
300 void SetVariableName(int index, absl::string_view name) override {
301 linear_program_.SetVariableName(glop::ColIndex(index), name);
302 }
303 bool SetVariableBounds(int index, int64_t lower_bound,
304 int64_t upper_bound) override {
305 DCHECK_GE(lower_bound, 0);
306 // When variable upper bounds are greater than this threshold, precision
307 // issues arise in GLOP. In this case we are just going to suppose that
308 // these high bound values are infinite and not set the upper bound.
309 const int64_t kMaxValue = 1e10;
310 const double lp_min = lower_bound;
311 const double lp_max =
312 (upper_bound > kMaxValue) ? glop::kInfinity : upper_bound;
313 if (lp_min <= lp_max) {
314 linear_program_.SetVariableBounds(glop::ColIndex(index), lp_min, lp_max);
315 return true;
316 }
317 // The linear_program would not be feasible, and it cannot handle the
318 // lp_min > lp_max case, so we must detect infeasibility here.
319 return false;
320 }
321 void SetVariableDisjointBounds(int index, const std::vector<int64_t>& starts,
322 const std::vector<int64_t>& ends) override {
323 // TODO(user): Investigate if we can avoid rebuilding the interval list
324 // each time (we could keep a reference to the forbidden interval list in
325 // RoutingDimension but we would need to store cumul offsets and use them
326 // when checking intervals).
327 allowed_intervals_[index] =
328 std::make_unique<SortedDisjointIntervalList>(starts, ends);
329 }
330 int64_t GetVariableLowerBound(int index) const override {
331 return linear_program_.variable_lower_bounds()[glop::ColIndex(index)];
332 }
333 int64_t GetVariableUpperBound(int index) const override {
334 const double upper_bound =
335 linear_program_.variable_upper_bounds()[glop::ColIndex(index)];
336 DCHECK_GE(upper_bound, 0);
337 return upper_bound == glop::kInfinity ? std::numeric_limits<int64_t>::max()
338 : static_cast<int64_t>(upper_bound);
339 }
340 void SetObjectiveCoefficient(int index, double coefficient) override {
341 linear_program_.SetObjectiveCoefficient(glop::ColIndex(index), coefficient);
342 }
343 double GetObjectiveCoefficient(int index) const override {
344 return linear_program_.objective_coefficients()[glop::ColIndex(index)];
345 }
346 void ClearObjective() override {
347 for (glop::ColIndex i(0); i < linear_program_.num_variables(); ++i) {
348 linear_program_.SetObjectiveCoefficient(i, 0);
349 }
350 }
351 int NumVariables() const override {
352 return linear_program_.num_variables().value();
353 }
354 int CreateNewConstraint(int64_t lower_bound, int64_t upper_bound) override {
355 const glop::RowIndex ct = linear_program_.CreateNewConstraint();
356 linear_program_.SetConstraintBounds(
357 ct,
358 (lower_bound == std::numeric_limits<int64_t>::min()) ? -glop::kInfinity
359 : lower_bound,
360 (upper_bound == std::numeric_limits<int64_t>::max()) ? glop::kInfinity
361 : upper_bound);
362 return ct.value();
363 }
364 void SetCoefficient(int ct, int index, double coefficient) override {
365 // Necessary to keep the model clean
366 // (cf. glop::LinearProgram::NotifyThatColumnsAreClean).
367 if (coefficient == 0.0) return;
368 linear_program_.SetCoefficient(glop::RowIndex(ct), glop::ColIndex(index),
369 coefficient);
370 }
371 bool IsCPSATSolver() override { return false; }
372 void AddObjectiveConstraint() override {
373 double max_coefficient = 0;
374 for (int variable = 0; variable < NumVariables(); variable++) {
375 const double coefficient = GetObjectiveCoefficient(variable);
376 max_coefficient = std::max(MathUtil::Abs(coefficient), max_coefficient);
377 }
378 DCHECK_GE(max_coefficient, 0);
379 if (max_coefficient == 0) {
380 // There are no terms in the objective.
381 return;
382 }
383 const glop::RowIndex ct = linear_program_.CreateNewConstraint();
384 double normalized_objective_value = 0;
385 for (int variable = 0; variable < NumVariables(); variable++) {
386 const double coefficient = GetObjectiveCoefficient(variable);
387 if (coefficient != 0) {
388 const double normalized_coeff = coefficient / max_coefficient;
389 SetCoefficient(ct.value(), variable, normalized_coeff);
390 normalized_objective_value +=
391 normalized_coeff * GetValueDouble(glop::ColIndex(variable));
392 }
393 }
394 normalized_objective_value = std::max(
395 normalized_objective_value, GetObjectiveValue() / max_coefficient);
396 linear_program_.SetConstraintBounds(ct, -glop::kInfinity,
397 normalized_objective_value);
398 }
399 void AddMaximumConstraint(int /*max_var*/,
400 std::vector<int> /*vars*/) override {}
401 void AddProductConstraint(int /*product_var*/,
402 std::vector<int> /*vars*/) override {}
403 void SetEnforcementLiteral(int /*ct*/, int /*condition*/) override {};
404 void AddRoute(absl::Span<const int64_t>, absl::Span<const int>) override{};
405 DimensionSchedulingStatus Solve(absl::Duration duration_limit) override {
406 lp_solver_.GetMutableParameters()->set_max_time_in_seconds(
407 absl::ToDoubleSeconds(duration_limit));
408
409 // Because we construct the lp one constraint at a time and we never call
410 // SetCoefficient() on the same variable twice for a constraint, we know
411 // that the columns do not contain duplicates and are already ordered by
412 // constraint so we do not need to call linear_program->CleanUp() which can
413 // be costly. Note that the assumptions are DCHECKed() in the call below.
414 linear_program_.NotifyThatColumnsAreClean();
415 VLOG(2) << linear_program_.Dump();
416 const glop::ProblemStatus status = lp_solver_.Solve(linear_program_);
417 if (search_stats_) search_stats_->num_glop_calls_in_lp_scheduling++;
418 const bool feasible_only = status == glop::ProblemStatus::PRIMAL_FEASIBLE;
419 if (status != glop::ProblemStatus::OPTIMAL &&
420 status != glop::ProblemStatus::IMPRECISE && !feasible_only) {
422 }
423 if (is_relaxation_) {
425 }
426 for (const auto& allowed_interval : allowed_intervals_) {
427 const int64_t value = GetVariableValue(allowed_interval.first);
428 const SortedDisjointIntervalList* const interval_list =
429 allowed_interval.second.get();
430 const auto it = interval_list->FirstIntervalGreaterOrEqual(value);
431 if (it == interval_list->end() || value < it->start) {
433 }
434 }
435 if (feasible_only && !linear_program_.objective_coefficients().empty()) {
437 }
439 }
440 int64_t GetObjectiveValue() const override {
441 return MathUtil::Round<int64_t>(lp_solver_.GetObjectiveValue());
442 }
443 int64_t GetVariableValue(int index) const override {
444 const double value_double = GetValueDouble(glop::ColIndex(index));
445 return (value_double >= std::numeric_limits<int64_t>::max())
446 ? std::numeric_limits<int64_t>::max()
447 : MathUtil::Round<int64_t>(value_double);
448 }
449 bool SolutionIsInteger() const override {
450 return linear_program_.SolutionIsInteger(lp_solver_.variable_values(),
451 /*absolute_tolerance*/ 1e-3);
452 }
453
454 void SetParameters(const std::string& parameters) override {
456 const bool status = params.ParseFromString(parameters);
457 DCHECK(status);
458 lp_solver_.SetParameters(params);
459 }
460
461 // Prints an understandable view of the model
462 // TODO(user): Improve output readability.
463 std::string PrintModel() const override { return linear_program_.Dump(); }
464
465 private:
466 double GetValueDouble(glop::ColIndex index) const {
467 return lp_solver_.variable_values()[index];
468 }
469
470 const bool is_relaxation_;
471 glop::LinearProgram linear_program_;
472 glop::LPSolver lp_solver_;
473 absl::flat_hash_map<int, std::unique_ptr<SortedDisjointIntervalList>>
474 allowed_intervals_;
475};
476
478 public:
479 explicit RoutingCPSatWrapper(RoutingSearchStats* const search_stats)
480 : RoutingLinearSolverWrapper(search_stats) {
481 parameters_.set_num_workers(1);
482 // Keeping presolve but with 1 iteration; as of 10/2023 it is
483 // significantly faster than both full presolve and no presolve.
484 parameters_.set_cp_model_presolve(true);
485 parameters_.set_max_presolve_iterations(1);
486 parameters_.set_cp_model_probing_level(0);
487 parameters_.set_use_sat_inprocessing(false);
488 parameters_.set_symmetry_level(0);
489 parameters_.set_catch_sigint_signal(false);
490 parameters_.set_mip_max_bound(1e8);
491 parameters_.set_search_branching(sat::SatParameters::PORTFOLIO_SEARCH);
492 parameters_.set_linearization_level(2);
493 parameters_.set_cut_level(0);
494 parameters_.set_add_lp_constraints_lazily(false);
495 parameters_.set_use_absl_random(false);
496 parameters_.set_alternative_pool_size(0);
497 parameters_.set_transitive_precedences_work_limit(0);
498 }
500 void Clear() override {
501 model_.Clear();
502 response_.Clear();
503 objective_coefficients_.clear();
504 schedule_variables_.clear();
505 }
507 const int index = model_.variables_size();
508 sat::IntegerVariableProto* const variable = model_.add_variables();
509 variable->add_domain(0);
510 variable->add_domain(static_cast<int64_t>(parameters_.mip_max_bound()));
511 return index;
512 }
513 void SetVariableName(int index, absl::string_view name) override {
514 model_.mutable_variables(index)->set_name(name.data());
515 }
516 bool SetVariableBounds(int index, int64_t lower_bound,
517 int64_t upper_bound) override {
518 DCHECK_GE(lower_bound, 0);
519 const int64_t capped_upper_bound =
520 std::min<int64_t>(upper_bound, parameters_.mip_max_bound());
521 if (lower_bound > capped_upper_bound) return false;
522 sat::IntegerVariableProto* const variable = model_.mutable_variables(index);
523 variable->set_domain(0, lower_bound);
524 variable->set_domain(1, capped_upper_bound);
525 return true;
526 }
527 void SetVariableDisjointBounds(int index, const std::vector<int64_t>& starts,
528 const std::vector<int64_t>& ends) override {
529 DCHECK_EQ(starts.size(), ends.size());
530 const int ct = CreateNewConstraint(1, 1);
531 for (int i = 0; i < starts.size(); ++i) {
532 const int variable = CreateNewPositiveVariable();
533#ifndef NDEBUG
534 SetVariableName(variable,
535 absl::StrFormat("disjoint(%ld, %ld)", index, i));
536#endif
537 SetVariableBounds(variable, 0, 1);
538 SetCoefficient(ct, variable, 1);
539 const int window_ct = CreateNewConstraint(starts[i], ends[i]);
540 SetCoefficient(window_ct, index, 1);
541 model_.mutable_constraints(window_ct)->add_enforcement_literal(variable);
542 }
543 }
544 int64_t GetVariableLowerBound(int index) const override {
545 return model_.variables(index).domain(0);
546 }
547 int64_t GetVariableUpperBound(int index) const override {
548 const auto& domain = model_.variables(index).domain();
549 return domain[domain.size() - 1];
550 }
551 void SetObjectiveCoefficient(int index, double coefficient) override {
552 if (index >= objective_coefficients_.size()) {
553 objective_coefficients_.resize(index + 1, 0);
554 }
555 objective_coefficients_[index] = coefficient;
556 sat::FloatObjectiveProto* const objective =
557 model_.mutable_floating_point_objective();
558 objective->add_vars(index);
559 objective->add_coeffs(coefficient);
560 }
561 double GetObjectiveCoefficient(int index) const override {
562 return (index < objective_coefficients_.size())
563 ? objective_coefficients_[index]
564 : 0;
565 }
566 void ClearObjective() override {
567 model_.mutable_floating_point_objective()->Clear();
568 }
569 int NumVariables() const override { return model_.variables_size(); }
570 int CreateNewConstraint(int64_t lower_bound, int64_t upper_bound) override {
572 model_.add_constraints()->mutable_linear();
573 ct->add_domain(lower_bound);
574 ct->add_domain(upper_bound);
575 return model_.constraints_size() - 1;
576 }
577 void SetCoefficient(int ct_index, int index, double coefficient) override {
579 model_.mutable_constraints(ct_index)->mutable_linear();
580 ct->add_vars(index);
581 const int64_t integer_coefficient = coefficient;
582 ct->add_coeffs(integer_coefficient);
583 }
584 bool IsCPSATSolver() override { return true; }
585 void AddObjectiveConstraint() override {
586 const sat::CpObjectiveProto& objective = response_.integer_objective();
587 int64_t activity = 0;
588 for (int i = 0; i < objective.vars_size(); ++i) {
589 activity += response_.solution(objective.vars(i)) * objective.coeffs(i);
590 }
591 const int ct =
592 CreateNewConstraint(std::numeric_limits<int64_t>::min(), activity);
593 for (int i = 0; i < objective.vars_size(); ++i) {
594 SetCoefficient(ct, objective.vars(i), objective.coeffs(i));
595 }
596 model_.clear_objective();
597 }
598 void AddMaximumConstraint(int max_var, std::vector<int> vars) override {
599 sat::LinearArgumentProto* const ct =
600 model_.add_constraints()->mutable_lin_max();
601 ct->mutable_target()->add_vars(max_var);
602 ct->mutable_target()->add_coeffs(1);
603 for (const int var : vars) {
604 sat::LinearExpressionProto* const expr = ct->add_exprs();
605 expr->add_vars(var);
606 expr->add_coeffs(1);
607 }
608 }
609 void AddProductConstraint(int product_var, std::vector<int> vars) override {
610 sat::LinearArgumentProto* const ct =
611 model_.add_constraints()->mutable_int_prod();
612 ct->mutable_target()->add_vars(product_var);
613 ct->mutable_target()->add_coeffs(1);
614 for (const int var : vars) {
616 expr->add_vars(var);
617 expr->add_coeffs(1);
618 }
619 }
620 void SetEnforcementLiteral(int ct, int condition) override {
621 DCHECK_LT(ct, model_.constraints_size());
622 model_.mutable_constraints(ct)->add_enforcement_literal(condition);
623 }
624 void AddRoute(absl::Span<const int64_t> nodes,
625 absl::Span<const int> schedule_variables) override {
626 DCHECK_EQ(nodes.size(), schedule_variables.size());
627 for (const int64_t node : nodes) {
628 if (node >= schedule_hint_.size()) schedule_hint_.resize(node + 1, 0);
629 }
630 for (int n = 0; n < nodes.size(); ++n) {
631 schedule_variables_.push_back(
632 {.node = nodes[n], .cumul = schedule_variables[n]});
633 }
634 }
635 DimensionSchedulingStatus Solve(absl::Duration duration_limit) override {
636 const double max_time = absl::ToDoubleSeconds(duration_limit);
637 if (max_time <= 0.0) return DimensionSchedulingStatus::INFEASIBLE;
638 parameters_.set_max_time_in_seconds(max_time);
639 VLOG(2) << ProtobufDebugString(model_);
640 auto record_hint = [this]() {
641 hint_.Clear();
642 for (int i = 0; i < response_.solution_size(); ++i) {
643 hint_.add_vars(i);
644 hint_.add_values(response_.solution(i));
645 }
646 for (const auto& [node, cumul] : schedule_variables_) {
647 schedule_hint_[node] = response_.solution(cumul);
648 }
649 };
650 model_.clear_solution_hint();
651 auto* hint = model_.mutable_solution_hint();
652 if (hint_.vars_size() == model_.variables_size()) {
653 *hint = hint_;
654 } else {
655 for (const auto& [node, cumul] : schedule_variables_) {
656 if (schedule_hint_[node] == 0) continue;
657 hint->add_vars(cumul);
658 hint->add_values(schedule_hint_[node]);
659 }
660 }
661 sat::Model model;
662 model.Add(sat::NewSatParameters(parameters_));
663 response_ = sat::SolveCpModel(model_, &model);
664 if (search_stats_) search_stats_->num_cp_sat_calls_in_lp_scheduling++;
665 VLOG(2) << response_;
666 DCHECK_NE(response_.status(), sat::CpSolverStatus::MODEL_INVALID);
667 if (response_.status() == sat::CpSolverStatus::OPTIMAL ||
668 (response_.status() == sat::CpSolverStatus::FEASIBLE &&
669 !model_.has_floating_point_objective())) {
670 record_hint();
672 } else if (response_.status() == sat::CpSolverStatus::FEASIBLE) {
673 // TODO(user): Consider storing "feasible" solutions in a separate
674 // cache we use as hint when the "optimal" cache is empty.
676 }
678 }
679 int64_t GetObjectiveValue() const override {
680 return MathUtil::Round<int64_t>(response_.objective_value());
681 }
682 int64_t GetVariableValue(int index) const override {
683 return response_.solution(index);
684 }
685 bool SolutionIsInteger() const override { return true; }
686
687 // NOTE: This function is not implemented for the CP-SAT solver.
688 void SetParameters(const std::string& /*parameters*/) override {
689 DCHECK(false);
690 }
691
692 bool ModelIsEmpty() const override { return model_.ByteSizeLong() == 0; }
693
694 // Prints an understandable view of the model
695 std::string PrintModel() const override;
696
697 private:
698 sat::CpModelProto model_;
699 sat::CpSolverResponse response_;
700 sat::SatParameters parameters_;
701 std::vector<double> objective_coefficients_;
703 struct NodeAndCumul {
704 int64_t node;
705 int cumul;
706 };
707 // Stores node/cumul pairs of the routes in the current model.
708 std::vector<NodeAndCumul> schedule_variables_;
709 // Maps node to its last known value in any optimal solution.
710 std::vector<int64_t> schedule_hint_;
711};
712
713// Utility class used in Local/GlobalDimensionCumulOptimizer to set the linear
714// solver constraints and solve the problem.
716 using RouteDimensionTravelInfo = RoutingModel::RouteDimensionTravelInfo;
718
719 public:
721 bool use_precedence_propagator);
722
723 // Finds an optimal (or just feasible) solution for the route for the given
724 // resource. If the resource is null, it is simply ignored.
726 int vehicle, double solve_duration_ratio,
727 const std::function<int64_t(int64_t)>& next_accessor,
728 const RouteDimensionTravelInfo* dimension_travel_info,
729 const Resource* resource, bool optimize_vehicle_costs,
730 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
731 std::vector<int64_t>* break_values, int64_t* cost_without_transit,
732 int64_t* transit_cost, bool clear_lp = true);
733
734 // Given some cumuls and breaks, computes the solution cost by solving the
735 // same model as in OptimizeSingleRouteWithResource() with the addition of
736 // constraints for cumuls and breaks.
738 int vehicle, double solve_duration_ratio,
739 const std::function<int64_t(int64_t)>& next_accessor,
740 const RouteDimensionTravelInfo* dimension_travel_info,
742 absl::Span<const int64_t> solution_cumul_values,
743 absl::Span<const int64_t> solution_break_values,
744 int64_t* cost_without_transits, int64_t* cost_offset = nullptr,
745 bool reuse_previous_model_if_possible = true, bool clear_lp = false,
746 bool clear_solution_constraints = true,
747 absl::Duration* solve_duration = nullptr);
748
749 // Computes the optimal scheduling solution(s) for the route for each resource
750 // in 'resources' with an index in 'resource_indices'.
751 // If both 'resources' and 'resource_indices' are empty, computes the optimal
752 // solution for the route itself (without added resource constraints).
753 std::vector<DimensionSchedulingStatus> OptimizeSingleRouteWithResources(
754 int vehicle, double solve_duration_ratio,
755 const std::function<int64_t(int64_t)>& next_accessor,
756 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
757 const RouteDimensionTravelInfo* dimension_travel_info,
758 absl::Span<const Resource> resources,
759 absl::Span<const int> resource_indices, bool optimize_vehicle_costs,
761 std::vector<std::vector<int64_t>>* cumul_values,
762 std::vector<std::vector<int64_t>>* break_values,
763 std::vector<int64_t>* costs_without_transits, int64_t* transit_cost,
764 bool clear_lp = true);
765
766 // In the Optimize() method, if both 'cumul_values' and 'cost' parameters are
767 // null, we don't optimize the cost and stop at the first feasible solution in
768 // the linear solver (since in this case only feasibility is of interest).
769 // When 'optimize_resource_assignment' is false, the resource var values are
770 // used to constrain the vehicle routes according to their assigned resource.
772 const std::function<int64_t(int64_t)>& next_accessor,
773 const std::vector<RouteDimensionTravelInfo>&
774 dimension_travel_info_per_route,
775 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
776 std::vector<int64_t>* break_values,
777 std::vector<std::vector<int>>* resource_indices_per_group,
778 int64_t* cost_without_transits, int64_t* transit_cost,
779 bool clear_lp = true, bool optimize_resource_assignment = true);
780
782 const std::function<int64_t(int64_t)>& next_accessor,
783 const std::vector<RouteDimensionTravelInfo>&
784 dimension_travel_info_per_route,
785 RoutingLinearSolverWrapper* solver, std::vector<int64_t>* cumul_values,
786 std::vector<int64_t>* break_values);
787
789 int vehicle, double solve_duration_ratio,
790 const std::function<int64_t(int64_t)>& next_accessor,
791 const RouteDimensionTravelInfo* dimension_travel_info,
792 const Resource* resource, RoutingLinearSolverWrapper* solver,
793 std::vector<int64_t>* cumul_values, std::vector<int64_t>* break_values);
794
801 int vehicle, double solve_duration_ratio,
802 const std::function<int64_t(int64_t)>& next_accessor,
803 absl::Span<const int64_t> transit_targets,
804 TransitTargetCost transit_target_cost, RoutingLinearSolverWrapper* solver,
805 std::vector<int64_t>* optimal_transits,
806 std::vector<int64_t>* optimal_cumuls,
807 std::vector<int64_t>* optimal_breaks);
808
809 const RoutingDimension* dimension() const { return dimension_; }
810
811 private:
812 // Initializes the containers and given solver. Must be called prior to
813 // setting any constraints and solving.
814 void InitOptimizer(RoutingLinearSolverWrapper* solver);
815
816 // Computes the minimum/maximum of cumuls for nodes on "route", and sets them
817 // in current_route_[min|max]_cumuls_ respectively.
818 bool ExtractRouteCumulBounds(absl::Span<const int64_t> route,
819 int64_t cumul_offset);
820
821 // Tighten the minimum/maximum of cumuls for nodes on "route"
822 // If the propagator_ is not null, uses the bounds tightened by the
823 // propagator. Otherwise, the minimum transits are used to tighten them.
824 bool TightenRouteCumulBounds(absl::Span<const int64_t> route,
825 absl::Span<const int64_t> min_transits,
826 int64_t cumul_offset);
827
828 // Sets the constraints for all nodes on "vehicle"'s route according to
829 // "next_accessor". If optimize_costs is true, also sets the objective
830 // coefficients for the LP.
831 // Returns false if some infeasibility was detected, true otherwise.
832 bool SetRouteCumulConstraints(
833 int vehicle, const std::function<int64_t(int64_t)>& next_accessor,
834 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
835 absl::Span<const int64_t> transit_targets,
836 const RouteDimensionTravelInfo* dimension_travel_info,
837 int64_t cumul_offset, bool optimize_costs,
838 RoutingLinearSolverWrapper* solver, int64_t* route_transit_cost,
839 int64_t* route_cost_offset);
840
841 // Sets the objective coefficients related to the cumuls and transits of the
842 // route in the solver. Supposes that the current_route_cumul_variables_ and
843 // current_route_nodes_ have correctly been initialized prior to calling this
844 // method.
845 void SetRouteCumulCosts(int vehicle, int64_t cumul_offset,
846 int64_t total_fixed_transit,
848 int64_t* route_transit_cost,
849 int64_t* route_cost_offset);
850
851 // Sets the constraints for all variables related to travel. Handles
852 // static or time-dependent travel values.
853 // Returns false if some infeasibility was detected, true otherwise.
854 bool SetRouteTravelConstraints(
855 const RouteDimensionTravelInfo* dimension_travel_info,
856 absl::Span<const int> lp_slacks, absl::Span<const int64_t> fixed_transits,
857 absl::Span<const int64_t> transit_targets,
859
860 // Sets the global constraints on the dimension, and adds global objective
861 // cost coefficients if optimize_costs is true.
862 // NOTE: When called, the call to this function MUST come after
863 // SetRouteCumulConstraints() has been called on all routes, so that
864 // index_to_cumul_variable_ and min_start/max_end_cumul_ are correctly
865 // initialized.
866 // Returns false if some infeasibility was detected, true otherwise.
867 bool SetGlobalConstraints(
868 const std::function<int64_t(int64_t)>& next_accessor,
869 int64_t cumul_offset, bool optimize_costs,
870 bool optimize_resource_assignment, RoutingLinearSolverWrapper* solver);
871
872 bool SetGlobalConstraintsForResourceAssignment(
873 const std::function<int64_t(int64_t)>& next_accessor,
874 int64_t cumul_offset, RoutingLinearSolverWrapper* solver);
875
876 void SetValuesFromLP(absl::Span<const int> lp_variables, int64_t offset,
877 int64_t default_value,
879 std::vector<int64_t>* lp_values) const;
880
881 void SetResourceIndices(
883 std::vector<std::vector<int>>* resource_indices_per_group) const;
884
885 // This function packs the routes of the given vehicles while keeping the cost
886 // of the LP lower than its current (supposed optimal) objective value.
887 // It does so by setting the current objective variables' coefficient to 0 and
888 // setting the coefficient of the route ends to 1, to first minimize the route
889 // ends' cumuls, and then maximizes the starts' cumuls without increasing the
890 // ends.
891 DimensionSchedulingStatus PackRoutes(
892 std::vector<int> vehicles, double solve_duration_ratio,
894 const glop::GlopParameters& packing_parameters);
895
896 std::unique_ptr<CumulBoundsPropagator> propagator_;
897 std::vector<int64_t> current_route_min_cumuls_;
898 std::vector<int64_t> current_route_max_cumuls_;
899 // Stores the nodes on the current route.
900 std::vector<int64_t> current_route_nodes_;
901 const RoutingDimension* const dimension_;
902 // Scheduler variables for current route cumuls and for all nodes cumuls.
903 std::vector<int> current_route_cumul_variables_;
904 std::vector<int> index_to_cumul_variable_;
905 // Scheduler variables for current route transits.
906 std::vector<int> current_route_variable_transit_variables_;
907 // Scheduler variables for current route breaks and all vehicle breaks.
908 // There are two variables for each break: start and end.
909 // current_route_break_variables_ has variables corresponding to
910 // break[0] start, break[0] end, break[1] start, break[1] end, etc.
911 std::vector<int> current_route_break_variables_;
912 // Vector all_break_variables contains the break variables of all vehicles,
913 // in the same format as current_route_break_variables.
914 // It is the concatenation of break variables of vehicles in [0, #vehicles).
915 std::vector<int> all_break_variables_;
916 // Allows to retrieve break variables of a given vehicle: those go from
917 // all_break_variables_[vehicle_to_all_break_variables_offset_[vehicle]] to
918 // all_break_variables[vehicle_to_all_break_variables_offset_[vehicle+1]-1].
919 std::vector<int> vehicle_to_all_break_variables_offset_;
920 // The following vector contains indices of resource-class-to-vehicle
921 // assignment variables. For every resource group, stores indices of
922 // num_resource_classes*num_vehicles boolean variables indicating whether
923 // resource class #rc is assigned to vehicle #v.
924 std::vector<std::vector<int>>
925 resource_class_to_vehicle_assignment_variables_per_group_;
926 // The following vector keeps track of the resources ignored during resource
927 // assignment because they're pre-assigned to a specific vehicle.
928 std::vector<std::vector<absl::flat_hash_set<int>>>
929 resource_class_ignored_resources_per_group_;
930
931 int max_end_cumul_;
932 int min_start_cumul_;
933 std::vector<std::pair<int64_t, int64_t>>
934 visited_pickup_delivery_indices_for_pair_;
935};
936
937// Class used to compute optimal values for dimension cumuls of routes,
938// minimizing cumul soft lower and upper bound costs, and vehicle span costs of
939// a route.
940// In its methods, next_accessor is a callback returning the next node of a
941// given node on a route.
943 public:
947 RoutingSearchStats* search_stats);
948
949 // If feasible, computes the optimal cost of the route performed by a vehicle,
950 // minimizing cumul soft lower and upper bound costs and vehicle span costs,
951 // and stores it in "optimal_cost" (if not null).
952 // Returns true iff the route respects all constraints.
954 int vehicle, double solve_duration_ratio,
955 const std::function<int64_t(int64_t)>& next_accessor,
956 int64_t* optimal_cost);
957
958 // Same as ComputeRouteCumulCost, but the cost computed does not contain
959 // the part of the vehicle span cost due to fixed transits.
961 int vehicle, double solve_duration_ratio,
962 const std::function<int64_t(int64_t)>& next_accessor,
964 int64_t* optimal_cost_without_transits);
965
966 std::vector<DimensionSchedulingStatus>
968 int vehicle, double solve_duration_ratio,
969 const std::function<int64_t(int64_t)>& next_accessor,
970 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
971 absl::Span<const RoutingModel::ResourceGroup::Resource> resources,
972 absl::Span<const int> resource_indices, bool optimize_vehicle_costs,
973 std::vector<int64_t>* optimal_costs_without_transits,
974 std::vector<std::vector<int64_t>>* optimal_cumuls,
975 std::vector<std::vector<int64_t>>* optimal_breaks);
976
977 // If feasible, computes the optimal values for cumul and break variables
978 // of the route performed by a vehicle, minimizing cumul soft lower, upper
979 // bound costs and vehicle span costs, stores them in "optimal_cumuls"
980 // (if not null), and optimal_breaks, and returns true.
981 // Returns false if the route is not feasible.
983 int vehicle, double solve_duration_ratio,
984 const std::function<int64_t(int64_t)>& next_accessor,
985 const RoutingModel::RouteDimensionTravelInfo* dimension_travel_info,
987 std::vector<int64_t>* optimal_cumuls,
988 std::vector<int64_t>* optimal_breaks);
989
990 // Simple combination of ComputeRouteCumulCostWithoutFixedTransits() and
991 // ComputeRouteCumuls().
993 int vehicle, double solve_duration_ratio,
994 const std::function<int64_t(int64_t)>& next_accessor,
995 const RoutingModel::RouteDimensionTravelInfo* dimension_travel_info,
996 std::vector<int64_t>* optimal_cumuls,
997 std::vector<int64_t>* optimal_breaks,
998 int64_t* optimal_cost_without_transits);
999
1000 // If feasible, computes the cost of a given route performed by a vehicle
1001 // defined by its cumuls and breaks.
1003 int vehicle, double solve_duration_ratio,
1004 const std::function<int64_t(int64_t)>& next_accessor,
1005 const RoutingModel::RouteDimensionTravelInfo* dimension_travel_info,
1006 absl::Span<const int64_t> solution_cumul_values,
1007 absl::Span<const int64_t> solution_break_values, int64_t* solution_cost,
1008 int64_t* cost_offset = nullptr,
1009 bool reuse_previous_model_if_possible = false, bool clear_lp = true,
1010 absl::Duration* solve_duration = nullptr);
1011
1012 // Similar to ComputeRouteCumuls, but also tries to pack the cumul values on
1013 // the route, such that the cost remains the same, the cumul of route end is
1014 // minimized, and then the cumul of the start of the route is maximized.
1015 // If 'resource' is non-null, the packed route must also respect its start/end
1016 // time window.
1018 int vehicle, double solve_duration_ratio,
1019 const std::function<int64_t(int64_t)>& next_accessor,
1020 const RoutingModel::RouteDimensionTravelInfo* dimension_travel_info,
1022 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks);
1023
1024 // TODO(user): Add a "resource" to the method.
1025 // TODO(user): Also pack the route at the end of the optimization.
1026 // --> Merge with the "packing" method ?
1028 int vehicle, double solve_duration_ratio,
1029 const std::function<int64_t(int64_t)>& next_accessor,
1030 absl::Span<const int64_t> transit_targets,
1032 std::vector<int64_t>* optimal_transits,
1033 std::vector<int64_t>* optimal_cumuls,
1034 std::vector<int64_t>* optimal_breaks);
1035
1037 return optimizer_core_.dimension();
1038 }
1039
1040 private:
1041 std::vector<std::unique_ptr<RoutingLinearSolverWrapper>> solver_;
1042 DimensionCumulOptimizerCore optimizer_core_;
1043};
1044
1046 public:
1050 RoutingSearchStats* search_stats);
1051 // If feasible, computes the optimal cost of the entire model with regards to
1052 // the optimizer_core_'s dimension costs, minimizing cumul soft lower/upper
1053 // bound costs and vehicle/global span costs, and stores it in "optimal_cost"
1054 // (if not null).
1055 // Returns true iff all the constraints can be respected.
1057 const std::function<int64_t(int64_t)>& next_accessor,
1058 int64_t* optimal_cost_without_transits);
1059 // If feasible, computes the optimal values for cumul, break and resource
1060 // variables, minimizing cumul soft lower/upper bound costs and vehicle/global
1061 // span costs, stores them in "optimal_cumuls" (if not null), "optimal_breaks"
1062 // and "optimal_resource_indices_per_group", and returns true.
1063 // Returns false if the routes are not feasible.
1065 const std::function<int64_t(int64_t)>& next_accessor,
1066 const std::vector<RoutingModel::RouteDimensionTravelInfo>&
1067 dimension_travel_info_per_route,
1068 std::vector<int64_t>* optimal_cumuls,
1069 std::vector<int64_t>* optimal_breaks,
1070 std::vector<std::vector<int>>* optimal_resource_indices_per_group);
1071
1072 // Similar to ComputeCumuls, but also tries to pack the cumul values on all
1073 // routes, such that the cost remains the same, the cumuls of route ends are
1074 // minimized, and then the cumuls of the starts of the routes are maximized.
1075 // NOTE: It's assumed that all resource variables (if any) are Bound() when
1076 // calling this method, so each vehicle's resource attributes are set as
1077 // constraint on its route and no resource assignment is required.
1079 const std::function<int64_t(int64_t)>& next_accessor,
1080 const std::vector<RoutingModel::RouteDimensionTravelInfo>&
1081 dimension_travel_info_per_route,
1082 std::vector<int64_t>* packed_cumuls, std::vector<int64_t>* packed_breaks);
1083
1085 return optimizer_core_.dimension();
1086 }
1087
1088 private:
1089 std::unique_ptr<RoutingLinearSolverWrapper> solver_;
1090 DimensionCumulOptimizerCore optimizer_core_;
1091};
1092
1093// Finds the approximate (*) min-cost (i.e. best) assignment of all vehicles
1094// v ∈ 'vehicles' to resources, i.e. indices in [0..num_resources), where the
1095// costs of assigning a vehicle v to a resource r of class r_c is given by
1096// 'vehicle_to_resource_class_assignment_costs(v)[r_c]', unless the latter is
1097// empty in which case vehicle v does not need a resource.
1098//
1099// Returns the cost of that optimal assignment, or -1 if it's infeasible.
1100// Moreover, if 'resource_indices' != nullptr, it assumes that its size is the
1101// global number of vehicles, and assigns its element #v with the resource r
1102// assigned to v, or -1 if none.
1103//
1104// (*) COST SCALING: When the costs are so large that they could possibly yield
1105// int64_t overflow, this method returns a *lower* bound of the actual optimal
1106// cost, and the assignment output in 'resource_indices' may be suboptimal if
1107// that lower bound isn't tight (but it should be very close).
1108//
1109// COMPLEXITY: in practice, should be roughly
1110// O(num_resource_classes * vehicles.size() + resource_indices->size()).
1112 absl::Span<const int> vehicles,
1114 std::vector<int>>&
1115 resource_indices_per_class,
1117 absl::flat_hash_set<int>>&
1118 ignored_resources_per_class,
1119 std::function<const std::vector<int64_t>*(int)>
1120 vehicle_to_resource_class_assignment_costs,
1121 std::vector<int>* resource_indices);
1122
1123// Computes the vehicle-to-resource-class assignment costs for the given vehicle
1124// to all resource classes in the group, and sets these costs in
1125// 'assignment_costs' (if non-null). The latter is cleared and kept empty if the
1126// vehicle 'v' should not have a resource assigned to it.
1127// optimize_vehicle_costs indicates if the costs should be optimized or if
1128// we merely care about feasibility (cost of 0) and infeasibility (cost of -1)
1129// of the assignments.
1130// The cumul and break values corresponding to the assignment of each resource
1131// are also set in cumul_values and break_values, if non-null.
1133 int v, double solve_duration_ratio,
1134 const RoutingModel::ResourceGroup& resource_group,
1136 absl::flat_hash_set<int>>&
1137 ignored_resources_per_class,
1138 const std::function<int64_t(int64_t)>& next_accessor,
1139 const std::function<int64_t(int64_t, int64_t)>& transit_accessor,
1140 bool optimize_vehicle_costs, LocalDimensionCumulOptimizer* lp_optimizer,
1141 LocalDimensionCumulOptimizer* mp_optimizer,
1142 std::vector<int64_t>* assignment_costs,
1143 std::vector<std::vector<int64_t>>* cumul_values,
1144 std::vector<std::vector<int64_t>>* break_values);
1145
1146// Structure to store the slope and y_intercept of a segment of a piecewise
1147// linear function.
1149 double slope;
1151
1152 friend ::std::ostream& operator<<(::std::ostream& os,
1153 const SlopeAndYIntercept& it) {
1154 return os << "{" << it.slope << ", " << it.y_intercept << "}";
1155 }
1156};
1157
1158// Given a FloatSlopePiecewiseLinearFunction, returns a vector of slope and
1159// y-intercept corresponding to each segment. Only the segments in
1160// [index_start, index_end[ will be considered.
1161// TODO(user): Consider making the following two functions methods of
1162// FloatSlopePiecewiseLinearFunction. They're only called in lp_scheduling.cc
1163// and ../tour_optimization/model_test.cc, but they might come in handy.
1164std::vector<SlopeAndYIntercept> PiecewiseLinearFunctionToSlopeAndYIntercept(
1165 const FloatSlopePiecewiseLinearFunction& pwl_function, int index_start = 0,
1166 int index_end = -1);
1167
1168// Converts a vector of SlopeAndYIntercept to a vector of convexity regions.
1169// Convexity regions are defined such that, all segment in a convexity region
1170// form a convex function. The boolean in the vector is set to true if the
1171// segment associated to it starts a new convexity region. Therefore, a convex
1172// function would yield {true, false, false, ...} and a concave function would
1173// yield {true, true, true, ...}.
1174std::vector<bool> SlopeAndYInterceptToConvexityRegions(
1175 absl::Span<const SlopeAndYIntercept> slope_and_y_intercept);
1176
1177} // namespace operations_research
1178
1179#endif // ORTOOLS_CONSTRAINT_SOLVER_ROUTING_LP_SCHEDULING_H_
CumulBoundsPropagator(const RoutingDimension *dimension)
bool PropagateCumulBounds(const std::function< int64_t(int64_t)> &next_accessor, int64_t cumul_offset, const std::vector< RoutingModel::RouteDimensionTravelInfo > *dimension_travel_info_per_route=nullptr)
const RoutingDimension & dimension() const
DimensionSchedulingStatus Optimize(const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RouteDimensionTravelInfo > &dimension_travel_info_per_route, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values, std::vector< std::vector< int > > *resource_indices_per_group, int64_t *cost_without_transits, int64_t *transit_cost, bool clear_lp=true, bool optimize_resource_assignment=true)
DimensionSchedulingStatus ComputeSingleRouteSolutionCostWithoutFixedTransits(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, const RouteDimensionTravelInfo *dimension_travel_info, RoutingLinearSolverWrapper *solver, absl::Span< const int64_t > solution_cumul_values, absl::Span< const int64_t > solution_break_values, int64_t *cost_without_transits, int64_t *cost_offset=nullptr, bool reuse_previous_model_if_possible=true, bool clear_lp=false, bool clear_solution_constraints=true, absl::Duration *solve_duration=nullptr)
DimensionSchedulingStatus OptimizeAndPack(const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RouteDimensionTravelInfo > &dimension_travel_info_per_route, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values)
DimensionSchedulingStatus OptimizeSingleRouteWithTransitTargets(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, absl::Span< const int64_t > transit_targets, TransitTargetCost transit_target_cost, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *optimal_transits, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks)
DimensionCumulOptimizerCore(const RoutingDimension *dimension, bool use_precedence_propagator)
DimensionSchedulingStatus OptimizeSingleRouteWithResource(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, const RouteDimensionTravelInfo *dimension_travel_info, const Resource *resource, bool optimize_vehicle_costs, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values, int64_t *cost_without_transit, int64_t *transit_cost, bool clear_lp=true)
DimensionSchedulingStatus OptimizeAndPackSingleRoute(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, const RouteDimensionTravelInfo *dimension_travel_info, const Resource *resource, RoutingLinearSolverWrapper *solver, std::vector< int64_t > *cumul_values, std::vector< int64_t > *break_values)
std::vector< DimensionSchedulingStatus > OptimizeSingleRouteWithResources(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, const std::function< int64_t(int64_t, int64_t)> &transit_accessor, const RouteDimensionTravelInfo *dimension_travel_info, absl::Span< const Resource > resources, absl::Span< const int > resource_indices, bool optimize_vehicle_costs, RoutingLinearSolverWrapper *solver, std::vector< std::vector< int64_t > > *cumul_values, std::vector< std::vector< int64_t > > *break_values, std::vector< int64_t > *costs_without_transits, int64_t *transit_cost, bool clear_lp=true)
DimensionSchedulingStatus ComputeCumulCostWithoutFixedTransits(const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost_without_transits)
GlobalDimensionCumulOptimizer(const RoutingDimension *dimension, RoutingSearchParameters::SchedulingSolver solver_type, RoutingSearchStats *search_stats)
DimensionSchedulingStatus ComputeCumuls(const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RoutingModel::RouteDimensionTravelInfo > &dimension_travel_info_per_route, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks, std::vector< std::vector< int > > *optimal_resource_indices_per_group)
DimensionSchedulingStatus ComputePackedCumuls(const std::function< int64_t(int64_t)> &next_accessor, const std::vector< RoutingModel::RouteDimensionTravelInfo > &dimension_travel_info_per_route, std::vector< int64_t > *packed_cumuls, std::vector< int64_t > *packed_breaks)
DimensionSchedulingStatus ComputeRouteSolutionCostWithoutFixedTransits(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::RouteDimensionTravelInfo *dimension_travel_info, absl::Span< const int64_t > solution_cumul_values, absl::Span< const int64_t > solution_break_values, int64_t *solution_cost, int64_t *cost_offset=nullptr, bool reuse_previous_model_if_possible=false, bool clear_lp=true, absl::Duration *solve_duration=nullptr)
DimensionSchedulingStatus ComputePackedRouteCumuls(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::RouteDimensionTravelInfo *dimension_travel_info, const RoutingModel::ResourceGroup::Resource *resource, std::vector< int64_t > *packed_cumuls, std::vector< int64_t > *packed_breaks)
LocalDimensionCumulOptimizer(const RoutingDimension *dimension, RoutingSearchParameters::SchedulingSolver solver_type, RoutingSearchStats *search_stats)
DimensionSchedulingStatus ComputeRouteCumuls(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::RouteDimensionTravelInfo *dimension_travel_info, const RoutingModel::ResourceGroup::Resource *resource, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks)
DimensionSchedulingStatus ComputeRouteCumulCostWithoutFixedTransits(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::ResourceGroup::Resource *resource, int64_t *optimal_cost_without_transits)
DimensionSchedulingStatus ComputeRouteCumulsAndCostWithoutFixedTransits(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, const RoutingModel::RouteDimensionTravelInfo *dimension_travel_info, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks, int64_t *optimal_cost_without_transits)
Merge with the packing method DimensionSchedulingStatus ComputeRouteCumulsWithTransitTargets(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, absl::Span< const int64_t > transit_targets, DimensionCumulOptimizerCore::TransitTargetCost transit_target_cost, std::vector< int64_t > *optimal_transits, std::vector< int64_t > *optimal_cumuls, std::vector< int64_t > *optimal_breaks)
std::vector< DimensionSchedulingStatus > ComputeRouteCumulCostsForResourcesWithoutFixedTransits(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, const std::function< int64_t(int64_t, int64_t)> &transit_accessor, absl::Span< const RoutingModel::ResourceGroup::Resource > resources, absl::Span< const int > resource_indices, bool optimize_vehicle_costs, std::vector< int64_t > *optimal_costs_without_transits, std::vector< std::vector< int64_t > > *optimal_cumuls, std::vector< std::vector< int64_t > > *optimal_breaks)
DimensionSchedulingStatus ComputeRouteCumulCost(int vehicle, double solve_duration_ratio, const std::function< int64_t(int64_t)> &next_accessor, int64_t *optimal_cost)
static IntOut Round(FloatIn x)
Definition mathutil.h:124
static T Abs(const T x)
Definition mathutil.h:95
void AddMaximumConstraint(int max_var, std::vector< int > vars) override
void SetParameters(const std::string &) override
void SetVariableDisjointBounds(int index, const std::vector< int64_t > &starts, const std::vector< int64_t > &ends) override
int CreateNewConstraint(int64_t lower_bound, int64_t upper_bound) override
DimensionSchedulingStatus Solve(absl::Duration duration_limit) override
void SetCoefficient(int ct_index, int index, double coefficient) override
void SetVariableName(int index, absl::string_view name) override
bool SetVariableBounds(int index, int64_t lower_bound, int64_t upper_bound) override
void SetObjectiveCoefficient(int index, double coefficient) override
double GetObjectiveCoefficient(int index) const override
int64_t GetVariableUpperBound(int index) const override
void SetEnforcementLiteral(int ct, int condition) override
void AddRoute(absl::Span< const int64_t > nodes, absl::Span< const int > schedule_variables) override
RoutingCPSatWrapper(RoutingSearchStats *const search_stats)
int64_t GetVariableLowerBound(int index) const override
int64_t GetVariableValue(int index) const override
void AddProductConstraint(int product_var, std::vector< int > vars) override
int64_t GetVariableLowerBound(int index) const override
bool SetVariableBounds(int index, int64_t lower_bound, int64_t upper_bound) override
int64_t GetVariableValue(int index) const override
double GetObjectiveCoefficient(int index) const override
RoutingGlopWrapper(bool is_relaxation, const glop::GlopParameters &parameters, RoutingSearchStats *search_stats)
void AddRoute(absl::Span< const int64_t >, absl::Span< const int >) override
void SetObjectiveCoefficient(int index, double coefficient) override
void AddMaximumConstraint(int, std::vector< int >) override
DimensionSchedulingStatus Solve(absl::Duration duration_limit) override
int64_t GetVariableUpperBound(int index) const override
void SetVariableDisjointBounds(int index, const std::vector< int64_t > &starts, const std::vector< int64_t > &ends) override
void SetCoefficient(int ct, int index, double coefficient) override
void SetVariableName(int index, absl::string_view name) override
int CreateNewConstraint(int64_t lower_bound, int64_t upper_bound) override
void AddProductConstraint(int, std::vector< int >) override
void SetParameters(const std::string &parameters) override
virtual int64_t GetObjectiveValue() const =0
virtual DimensionSchedulingStatus Solve(absl::Duration duration_limit)=0
virtual void SetCoefficient(int ct, int index, double coefficient)=0
virtual int64_t GetVariableUpperBound(int index) const =0
int AddLinearConstraint(int64_t lower_bound, int64_t upper_bound, absl::Span< const std::pair< int, double > > variable_coeffs)
virtual int CreateNewConstraint(int64_t lower_bound, int64_t upper_bound)=0
virtual void SetEnforcementLiteral(int ct, int condition)=0
virtual void AddProductConstraint(int product_var, std::vector< int > vars)=0
virtual std::string PrintModel() const =0
virtual int64_t GetVariableValue(int index) const =0
virtual void SetVariableDisjointBounds(int index, const std::vector< int64_t > &starts, const std::vector< int64_t > &ends)=0
int AddReifiedLinearConstraint(int64_t lower_bound, int64_t upper_bound, absl::Span< const std::pair< int, double > > weighted_variables)
virtual void AddRoute(absl::Span< const int64_t > nodes, absl::Span< const int > schedule_variables)=0
virtual void SetObjectiveCoefficient(int index, double coefficient)=0
virtual bool SetVariableBounds(int index, int64_t lower_bound, int64_t upper_bound)=0
virtual void SetParameters(const std::string &parameters)=0
virtual void AddMaximumConstraint(int max_var, std::vector< int > vars)=0
virtual void SetVariableName(int index, absl::string_view name)=0
virtual int64_t GetVariableLowerBound(int index) const =0
RoutingLinearSolverWrapper(RoutingSearchStats *search_stats)
virtual double GetObjectiveCoefficient(int index) const =0
int AddVariable(int64_t lower_bound, int64_t upper_bound)
A Resource sets attributes (costs/constraints) for a set of dimensions.
Definition routing.h:441
RoutingResourceClassIndex ResourceClassIndex
Definition routing.h:273
RoutingSearchParameters_SchedulingSolver SchedulingSolver
const DenseRow & variable_values() const
Definition lp_solver.h:109
void set_domain(int index, ::int64_t value)
::operations_research::sat::LinearExpressionProto *PROTOBUF_NONNULL mutable_target()
::operations_research::sat::LinearExpressionProto *PROTOBUF_NONNULL add_exprs()
T Add(std::function< T(Model *)> f)
Definition model.h:104
static constexpr SearchBranching PORTFOLIO_SEARCH
constexpr Fractional kInfinity
Definition lp_types.h:87
std::function< SatParameters(Model *)> NewSatParameters(absl::string_view params)
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
OR-Tools root namespace.
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)
std::vector< bool > SlopeAndYInterceptToConvexityRegions(absl::Span< const SlopeAndYIntercept > slope_and_y_intercept)
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)
std::string ProtobufDebugString(const P &message)
Definition proto_utils.h:31
std::vector< SlopeAndYIntercept > PiecewiseLinearFunctionToSlopeAndYIntercept(const FloatSlopePiecewiseLinearFunction &pwl_function, int index_start, int index_end)
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...
friend::std::ostream & operator<<(::std::ostream &os, const SlopeAndYIntercept &it)