Skip to content

Commit 9f86916

Browse files
committed
routing: export from google3
1 parent 2d30738 commit 9f86916

File tree

2 files changed

+83
-42
lines changed

2 files changed

+83
-42
lines changed

ortools/constraint_solver/routing_lp_scheduling.cc

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,7 +1303,7 @@ std::vector<bool> SlopeAndYInterceptToConvexityRegions(
13031303
namespace {
13041304
// Find a "good" scaling factor for constraints with non-integers coefficients.
13051305
// See sat::FindBestScalingAndComputeErrors() for more infos.
1306-
double FindBestScaling(const std::vector<double>& coefficients,
1306+
double FindBestScaling(absl::Span<const double> coefficients,
13071307
absl::Span<const double> lower_bounds,
13081308
absl::Span<const double> upper_bounds,
13091309
int64_t max_absolute_activity,
@@ -1722,6 +1722,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
17221722
solver->SetVariableDisjointBounds(lp_cumul, starts, ends);
17231723
}
17241724
}
1725+
solver->AddRoute(path, lp_cumuls);
17251726
// Create LP variables for slacks.
17261727
std::vector<int> lp_slacks(path_size - 1, -1);
17271728
for (int pos = 0; pos < path_size - 1; ++pos) {
@@ -1966,13 +1967,14 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
19661967
// breaks are scheduled, those variables are used both in the pure breaks
19671968
// part and in the break distance part of the model.
19681969
// Otherwise, it doesn't need the variables and they are not created.
1969-
std::vector<int> lp_break_start;
1970-
std::vector<int> lp_break_duration;
1971-
std::vector<int> lp_break_end;
1970+
struct LpBreak {
1971+
int start;
1972+
int end;
1973+
int duration;
1974+
};
1975+
std::vector<LpBreak> lp_breaks;
19721976
if (solver->IsCPSATSolver()) {
1973-
lp_break_start.resize(num_breaks, -1);
1974-
lp_break_duration.resize(num_breaks, -1);
1975-
lp_break_end.resize(num_breaks, -1);
1977+
lp_breaks.resize(num_breaks, {.start = -1, .end = -1, .duration = -1});
19761978
}
19771979

19781980
std::vector<int> slack_exact_lower_bound_ct(path_size - 1, -1);
@@ -2004,29 +2006,26 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
20042006
current_route_break_variables_.push_back(-1);
20052007
continue;
20062008
}
2007-
lp_break_start[br] =
2008-
solver->AddVariable(break_start_min, break_start_max);
2009-
SET_DEBUG_VARIABLE_NAME(solver, lp_break_start[br],
2009+
lp_breaks[br] = {
2010+
.start = solver->AddVariable(break_start_min, break_start_max),
2011+
.end = solver->AddVariable(break_end_min, break_end_max),
2012+
.duration =
2013+
solver->AddVariable(break_duration_min, break_duration_max)};
2014+
const auto [break_start, break_end, break_duration] = lp_breaks[br];
2015+
SET_DEBUG_VARIABLE_NAME(solver, break_start,
20102016
absl::StrFormat("lp_break_start(%ld)", br));
2011-
lp_break_end[br] = solver->AddVariable(break_end_min, break_end_max);
2012-
SET_DEBUG_VARIABLE_NAME(solver, lp_break_end[br],
2017+
SET_DEBUG_VARIABLE_NAME(solver, break_end,
20132018
absl::StrFormat("lp_break_end(%ld)", br));
2014-
lp_break_duration[br] =
2015-
solver->AddVariable(break_duration_min, break_duration_max);
2016-
SET_DEBUG_VARIABLE_NAME(solver, lp_break_duration[br],
2019+
SET_DEBUG_VARIABLE_NAME(solver, break_duration,
20172020
absl::StrFormat("lp_break_duration(%ld)", br));
20182021
// start + duration = end.
2019-
solver->AddLinearConstraint(0, 0,
2020-
{{lp_break_end[br], 1},
2021-
{lp_break_start[br], -1},
2022-
{lp_break_duration[br], -1}});
2022+
solver->AddLinearConstraint(
2023+
0, 0, {{break_end, 1}, {break_start, -1}, {break_duration, -1}});
20232024
// Record index of variables
2024-
all_break_variables_[all_break_variables_offset + 2 * br] =
2025-
lp_break_start[br];
2026-
all_break_variables_[all_break_variables_offset + 2 * br + 1] =
2027-
lp_break_end[br];
2028-
current_route_break_variables_.push_back(lp_break_start[br]);
2029-
current_route_break_variables_.push_back(lp_break_end[br]);
2025+
all_break_variables_[all_break_variables_offset + 2 * br] = break_start;
2026+
all_break_variables_[all_break_variables_offset + 2 * br + 1] = break_end;
2027+
current_route_break_variables_.push_back(break_start);
2028+
current_route_break_variables_.push_back(break_end);
20302029
} else {
20312030
if (break_end_min <= vehicle_start_max ||
20322031
vehicle_end_min <= break_start_max) {
@@ -2048,7 +2047,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
20482047
if (break_end_min <= vehicle_start_max) {
20492048
const int ct = solver->AddLinearConstraint(
20502049
0, std::numeric_limits<int64_t>::max(),
2051-
{{lp_cumuls.front(), 1}, {lp_break_end[br], -1}});
2050+
{{lp_cumuls.front(), 1}, {lp_breaks[br].end, -1}});
20522051
const int break_is_before_route = solver->AddVariable(0, 1);
20532052
SET_DEBUG_VARIABLE_NAME(
20542053
solver, break_is_before_route,
@@ -2060,7 +2059,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
20602059
if (vehicle_end_min <= break_start_max) {
20612060
const int ct = solver->AddLinearConstraint(
20622061
0, std::numeric_limits<int64_t>::max(),
2063-
{{lp_break_start[br], 1}, {lp_cumuls.back(), -1}});
2062+
{{lp_breaks[br].start, 1}, {lp_cumuls.back(), -1}});
20642063
const int break_is_after_route = solver->AddVariable(0, 1);
20652064
SET_DEBUG_VARIABLE_NAME(
20662065
solver, break_is_after_route,
@@ -2124,7 +2123,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
21242123
solver, break_duration_in_slack,
21252124
absl::StrFormat("break_duration_in_slack(%ld, %ld)", br, pos));
21262125
solver->AddProductConstraint(break_duration_in_slack,
2127-
{break_in_slack, lp_break_duration[br]});
2126+
{break_in_slack, lp_breaks[br].duration});
21282127
if (slack_exact_lower_bound_ct[pos] == -1) {
21292128
slack_exact_lower_bound_ct[pos] = solver->AddLinearConstraint(
21302129
std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
@@ -2135,13 +2134,13 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
21352134
// 1) break_start >= cumul[pos] + pre_travel[pos]
21362135
const int break_start_after_current_ct = solver->AddLinearConstraint(
21372136
pre_travel[pos], std::numeric_limits<int64_t>::max(),
2138-
{{lp_break_start[br], 1}, {lp_cumuls[pos], -1}});
2137+
{{lp_breaks[br].start, 1}, {lp_cumuls[pos], -1}});
21392138
solver->SetEnforcementLiteral(break_start_after_current_ct,
21402139
break_in_slack);
21412140
// 2) break_end <= cumul[pos+1] - post_travel[pos]
21422141
const int break_ends_before_next_ct = solver->AddLinearConstraint(
21432142
post_travel[pos], std::numeric_limits<int64_t>::max(),
2144-
{{lp_cumuls[pos + 1], 1}, {lp_break_end[br], -1}});
2143+
{{lp_cumuls[pos + 1], 1}, {lp_breaks[br].end, -1}});
21452144
solver->SetEnforcementLiteral(break_ends_before_next_ct,
21462145
break_in_slack);
21472146
}
@@ -2158,10 +2157,10 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
21582157
}
21592158
// When this feature is used, breaks are in sorted order.
21602159
for (int br = 1; br < num_breaks; ++br) {
2161-
if (lp_break_start[br] == -1 || lp_break_start[br - 1] == -1) continue;
2160+
if (lp_breaks[br].start == -1 || lp_breaks[br - 1].start == -1) continue;
21622161
solver->AddLinearConstraint(
21632162
0, std::numeric_limits<int64_t>::max(),
2164-
{{lp_break_end[br - 1], -1}, {lp_break_start[br], 1}});
2163+
{{lp_breaks[br - 1].end, -1}, {lp_breaks[br].start, 1}});
21652164
}
21662165
}
21672166
for (const auto& distance_duration :
@@ -2201,7 +2200,8 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
22012200
solver->AddLinearConstraint(limit, limit,
22022201
{{previous_cover, 1}, {lp_cumuls.front(), -1}});
22032202
for (int br = 0; br < num_breaks; ++br) {
2204-
if (lp_break_start[br] == -1) continue;
2203+
const LpBreak& lp_break = lp_breaks[br];
2204+
if (lp_break.start == -1) continue;
22052205
const int64_t break_end_min = CapSub(breaks[br]->EndMin(), cumul_offset);
22062206
const int64_t break_end_max = CapSub(breaks[br]->EndMax(), cumul_offset);
22072207
// break_is_eligible <=>
@@ -2218,11 +2218,11 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
22182218
1, 1, {{break_is_eligible, 1}, {break_is_not_eligible, 1}});
22192219
const int positive_ct = solver->AddLinearConstraint(
22202220
min_break_duration, std::numeric_limits<int64_t>::max(),
2221-
{{lp_break_end[br], 1}, {lp_break_start[br], -1}});
2221+
{{lp_break.end, 1}, {lp_break.start, -1}});
22222222
solver->SetEnforcementLiteral(positive_ct, break_is_eligible);
22232223
const int negative_ct = solver->AddLinearConstraint(
22242224
std::numeric_limits<int64_t>::min(), min_break_duration - 1,
2225-
{{lp_break_end[br], 1}, {lp_break_start[br], -1}});
2225+
{{lp_break.end, 1}, {lp_break.start, -1}});
22262226
solver->SetEnforcementLiteral(negative_ct, break_is_not_eligible);
22272227
}
22282228
// break_is_eligible => break_cover == break_end + limit.
@@ -2236,7 +2236,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
22362236
SET_DEBUG_VARIABLE_NAME(solver, break_cover,
22372237
absl::StrFormat("break_cover(%ld)", br));
22382238
const int limit_cover_ct = solver->AddLinearConstraint(
2239-
limit, limit, {{break_cover, 1}, {lp_break_end[br], -1}});
2239+
limit, limit, {{break_cover, 1}, {lp_break.end, -1}});
22402240
solver->SetEnforcementLiteral(limit_cover_ct, break_is_eligible);
22412241
const int empty_cover_ct = solver->AddLinearConstraint(
22422242
CapAdd(vehicle_start_min, limit), CapAdd(vehicle_start_min, limit),
@@ -2255,7 +2255,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
22552255
{{lp_cumuls.back(), 1}, {previous_cover, -1}});
22562256
const int break_start_cover_ct = solver->AddLinearConstraint(
22572257
0, std::numeric_limits<int64_t>::max(),
2258-
{{previous_cover, 1}, {lp_break_start[br], -1}});
2258+
{{previous_cover, 1}, {lp_break.start, -1}});
22592259
solver->SetEnforcementLiteral(break_start_cover_ct,
22602260
route_end_is_not_covered);
22612261

ortools/constraint_solver/routing_lp_scheduling.h

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,13 @@ class RoutingLinearSolverWrapper {
198198

199199
// This function is meant to override the parameters of the solver.
200200
virtual void SetParameters(const std::string& parameters) = 0;
201+
// When solving a scheduling problem, this can be called to add hints that
202+
// help the underlying solver:
203+
// - nodes is the sequence of nodes in a route represented in the model.
204+
// - schedule_variables is the sequence of variables used to represent the
205+
// time at which each node is scheduled.
206+
virtual void AddRoute(absl::Span<const int64_t> nodes,
207+
absl::Span<const int> schedule_variables) = 0;
201208

202209
// Returns if the model is empty or not.
203210
virtual bool ModelIsEmpty() const { return true; }
@@ -384,6 +391,7 @@ class RoutingGlopWrapper : public RoutingLinearSolverWrapper {
384391
void AddProductConstraint(int /*product_var*/,
385392
std::vector<int> /*vars*/) override {}
386393
void SetEnforcementLiteral(int /*ct*/, int /*condition*/) override {};
394+
void AddRoute(absl::Span<const int64_t>, absl::Span<const int>) override{};
387395
DimensionSchedulingStatus Solve(absl::Duration duration_limit) override {
388396
lp_solver_.GetMutableParameters()->set_max_time_in_seconds(
389397
absl::ToDoubleSeconds(duration_limit));
@@ -471,6 +479,7 @@ class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
471479
model_.Clear();
472480
response_.Clear();
473481
objective_coefficients_.clear();
482+
schedule_variables_.clear();
474483
}
475484
int CreateNewPositiveVariable() override {
476485
const int index = model_.variables_size();
@@ -590,11 +599,39 @@ class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
590599
DCHECK_LT(ct, model_.constraints_size());
591600
model_.mutable_constraints(ct)->add_enforcement_literal(condition);
592601
}
602+
void AddRoute(absl::Span<const int64_t> nodes,
603+
absl::Span<const int> schedule_variables) override {
604+
DCHECK_EQ(nodes.size(), schedule_variables.size());
605+
for (const int64_t node : nodes) {
606+
if (node >= schedule_hint_.size()) schedule_hint_.resize(node + 1, 0);
607+
}
608+
for (int n = 0; n < nodes.size(); ++n) {
609+
schedule_variables_.push_back(
610+
{.node = nodes[n], .cumul = schedule_variables[n]});
611+
}
612+
}
593613
DimensionSchedulingStatus Solve(absl::Duration duration_limit) override {
594614
const double max_time = absl::ToDoubleSeconds(duration_limit);
595615
if (max_time <= 0.0) return DimensionSchedulingStatus::INFEASIBLE;
596616
parameters_.set_max_time_in_seconds(max_time);
597617
VLOG(2) << ProtobufDebugString(model_);
618+
auto record_hint = [this]() {
619+
hint_.Clear();
620+
for (int i = 0; i < response_.solution_size(); ++i) {
621+
hint_.add_vars(i);
622+
hint_.add_values(response_.solution(i));
623+
}
624+
for (const auto& [node, cumul] : schedule_variables_) {
625+
schedule_hint_[node] = response_.solution(cumul);
626+
}
627+
};
628+
model_.clear_solution_hint();
629+
auto* hint = model_.mutable_solution_hint();
630+
for (const auto& [node, cumul] : schedule_variables_) {
631+
if (schedule_hint_[node] == 0) continue;
632+
hint->add_vars(cumul);
633+
hint->add_values(schedule_hint_[node]);
634+
}
598635
if (hint_.vars_size() == model_.variables_size()) {
599636
*model_.mutable_solution_hint() = hint_;
600637
}
@@ -606,11 +643,7 @@ class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
606643
if (response_.status() == sat::CpSolverStatus::OPTIMAL ||
607644
(response_.status() == sat::CpSolverStatus::FEASIBLE &&
608645
!model_.has_floating_point_objective())) {
609-
hint_.Clear();
610-
for (int i = 0; i < response_.solution_size(); ++i) {
611-
hint_.add_vars(i);
612-
hint_.add_values(response_.solution(i));
613-
}
646+
record_hint();
614647
return DimensionSchedulingStatus::OPTIMAL;
615648
}
616649
return DimensionSchedulingStatus::INFEASIBLE;
@@ -639,6 +672,14 @@ class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
639672
sat::SatParameters parameters_;
640673
std::vector<double> objective_coefficients_;
641674
sat::PartialVariableAssignment hint_;
675+
struct NodeAndCumul {
676+
int64_t node;
677+
int cumul;
678+
};
679+
// Stores node/cumul pairs of the routes in the current model.
680+
std::vector<NodeAndCumul> schedule_variables_;
681+
// Maps node to its last known value in any optimal solution.
682+
std::vector<int64_t> schedule_hint_;
642683
};
643684

644685
// Utility class used in Local/GlobalDimensionCumulOptimizer to set the linear

0 commit comments

Comments
 (0)