Skip to content

Commit

Permalink
routing: Export from google3
Browse files Browse the repository at this point in the history
  • Loading branch information
Mizux committed Dec 9, 2024
1 parent a2afbd9 commit ee22150
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 42 deletions.
74 changes: 37 additions & 37 deletions ortools/routing/lp_scheduling.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1303,7 +1303,7 @@ std::vector<bool> SlopeAndYInterceptToConvexityRegions(
namespace {
// Find a "good" scaling factor for constraints with non-integers coefficients.
// See sat::FindBestScalingAndComputeErrors() for more infos.
double FindBestScaling(const std::vector<double>& coefficients,
double FindBestScaling(absl::Span<const double> coefficients,
absl::Span<const double> lower_bounds,
absl::Span<const double> upper_bounds,
int64_t max_absolute_activity,
Expand Down Expand Up @@ -1722,6 +1722,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
solver->SetVariableDisjointBounds(lp_cumul, starts, ends);
}
}
solver->AddRoute(path, lp_cumuls);
// Create LP variables for slacks.
std::vector<int> lp_slacks(path_size - 1, -1);
for (int pos = 0; pos < path_size - 1; ++pos) {
Expand Down Expand Up @@ -1966,13 +1967,14 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
// breaks are scheduled, those variables are used both in the pure breaks
// part and in the break distance part of the model.
// Otherwise, it doesn't need the variables and they are not created.
std::vector<int> lp_break_start;
std::vector<int> lp_break_duration;
std::vector<int> lp_break_end;
struct LpBreak {
int start;
int end;
int duration;
};
std::vector<LpBreak> lp_breaks;
if (solver->IsCPSATSolver()) {
lp_break_start.resize(num_breaks, -1);
lp_break_duration.resize(num_breaks, -1);
lp_break_end.resize(num_breaks, -1);
lp_breaks.resize(num_breaks, {.start = -1, .end = -1, .duration = -1});
}

std::vector<int> slack_exact_lower_bound_ct(path_size - 1, -1);
Expand Down Expand Up @@ -2004,29 +2006,26 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
current_route_break_variables_.push_back(-1);
continue;
}
lp_break_start[br] =
solver->AddVariable(break_start_min, break_start_max);
SET_DEBUG_VARIABLE_NAME(solver, lp_break_start[br],
lp_breaks[br] = {
.start = solver->AddVariable(break_start_min, break_start_max),
.end = solver->AddVariable(break_end_min, break_end_max),
.duration =
solver->AddVariable(break_duration_min, break_duration_max)};
const auto [break_start, break_end, break_duration] = lp_breaks[br];
SET_DEBUG_VARIABLE_NAME(solver, break_start,
absl::StrFormat("lp_break_start(%ld)", br));
lp_break_end[br] = solver->AddVariable(break_end_min, break_end_max);
SET_DEBUG_VARIABLE_NAME(solver, lp_break_end[br],
SET_DEBUG_VARIABLE_NAME(solver, break_end,
absl::StrFormat("lp_break_end(%ld)", br));
lp_break_duration[br] =
solver->AddVariable(break_duration_min, break_duration_max);
SET_DEBUG_VARIABLE_NAME(solver, lp_break_duration[br],
SET_DEBUG_VARIABLE_NAME(solver, break_duration,
absl::StrFormat("lp_break_duration(%ld)", br));
// start + duration = end.
solver->AddLinearConstraint(0, 0,
{{lp_break_end[br], 1},
{lp_break_start[br], -1},
{lp_break_duration[br], -1}});
solver->AddLinearConstraint(
0, 0, {{break_end, 1}, {break_start, -1}, {break_duration, -1}});
// Record index of variables
all_break_variables_[all_break_variables_offset + 2 * br] =
lp_break_start[br];
all_break_variables_[all_break_variables_offset + 2 * br + 1] =
lp_break_end[br];
current_route_break_variables_.push_back(lp_break_start[br]);
current_route_break_variables_.push_back(lp_break_end[br]);
all_break_variables_[all_break_variables_offset + 2 * br] = break_start;
all_break_variables_[all_break_variables_offset + 2 * br + 1] = break_end;
current_route_break_variables_.push_back(break_start);
current_route_break_variables_.push_back(break_end);
} else {
if (break_end_min <= vehicle_start_max ||
vehicle_end_min <= break_start_max) {
Expand All @@ -2048,7 +2047,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
if (break_end_min <= vehicle_start_max) {
const int ct = solver->AddLinearConstraint(
0, std::numeric_limits<int64_t>::max(),
{{lp_cumuls.front(), 1}, {lp_break_end[br], -1}});
{{lp_cumuls.front(), 1}, {lp_breaks[br].end, -1}});
const int break_is_before_route = solver->AddVariable(0, 1);
SET_DEBUG_VARIABLE_NAME(
solver, break_is_before_route,
Expand All @@ -2060,7 +2059,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
if (vehicle_end_min <= break_start_max) {
const int ct = solver->AddLinearConstraint(
0, std::numeric_limits<int64_t>::max(),
{{lp_break_start[br], 1}, {lp_cumuls.back(), -1}});
{{lp_breaks[br].start, 1}, {lp_cumuls.back(), -1}});
const int break_is_after_route = solver->AddVariable(0, 1);
SET_DEBUG_VARIABLE_NAME(
solver, break_is_after_route,
Expand Down Expand Up @@ -2124,7 +2123,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
solver, break_duration_in_slack,
absl::StrFormat("break_duration_in_slack(%ld, %ld)", br, pos));
solver->AddProductConstraint(break_duration_in_slack,
{break_in_slack, lp_break_duration[br]});
{break_in_slack, lp_breaks[br].duration});
if (slack_exact_lower_bound_ct[pos] == -1) {
slack_exact_lower_bound_ct[pos] = solver->AddLinearConstraint(
std::numeric_limits<int64_t>::min(), 0, {{lp_slacks[pos], -1}});
Expand All @@ -2135,13 +2134,13 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
// 1) break_start >= cumul[pos] + pre_travel[pos]
const int break_start_after_current_ct = solver->AddLinearConstraint(
pre_travel[pos], std::numeric_limits<int64_t>::max(),
{{lp_break_start[br], 1}, {lp_cumuls[pos], -1}});
{{lp_breaks[br].start, 1}, {lp_cumuls[pos], -1}});
solver->SetEnforcementLiteral(break_start_after_current_ct,
break_in_slack);
// 2) break_end <= cumul[pos+1] - post_travel[pos]
const int break_ends_before_next_ct = solver->AddLinearConstraint(
post_travel[pos], std::numeric_limits<int64_t>::max(),
{{lp_cumuls[pos + 1], 1}, {lp_break_end[br], -1}});
{{lp_cumuls[pos + 1], 1}, {lp_breaks[br].end, -1}});
solver->SetEnforcementLiteral(break_ends_before_next_ct,
break_in_slack);
}
Expand All @@ -2158,10 +2157,10 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
}
// When this feature is used, breaks are in sorted order.
for (int br = 1; br < num_breaks; ++br) {
if (lp_break_start[br] == -1 || lp_break_start[br - 1] == -1) continue;
if (lp_breaks[br].start == -1 || lp_breaks[br - 1].start == -1) continue;
solver->AddLinearConstraint(
0, std::numeric_limits<int64_t>::max(),
{{lp_break_end[br - 1], -1}, {lp_break_start[br], 1}});
{{lp_breaks[br - 1].end, -1}, {lp_breaks[br].start, 1}});
}
}
for (const auto& distance_duration :
Expand Down Expand Up @@ -2201,7 +2200,8 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
solver->AddLinearConstraint(limit, limit,
{{previous_cover, 1}, {lp_cumuls.front(), -1}});
for (int br = 0; br < num_breaks; ++br) {
if (lp_break_start[br] == -1) continue;
const LpBreak& lp_break = lp_breaks[br];
if (lp_break.start == -1) continue;
const int64_t break_end_min = CapSub(breaks[br]->EndMin(), cumul_offset);
const int64_t break_end_max = CapSub(breaks[br]->EndMax(), cumul_offset);
// break_is_eligible <=>
Expand All @@ -2218,11 +2218,11 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
1, 1, {{break_is_eligible, 1}, {break_is_not_eligible, 1}});
const int positive_ct = solver->AddLinearConstraint(
min_break_duration, std::numeric_limits<int64_t>::max(),
{{lp_break_end[br], 1}, {lp_break_start[br], -1}});
{{lp_break.end, 1}, {lp_break.start, -1}});
solver->SetEnforcementLiteral(positive_ct, break_is_eligible);
const int negative_ct = solver->AddLinearConstraint(
std::numeric_limits<int64_t>::min(), min_break_duration - 1,
{{lp_break_end[br], 1}, {lp_break_start[br], -1}});
{{lp_break.end, 1}, {lp_break.start, -1}});
solver->SetEnforcementLiteral(negative_ct, break_is_not_eligible);
}
// break_is_eligible => break_cover == break_end + limit.
Expand All @@ -2236,7 +2236,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
SET_DEBUG_VARIABLE_NAME(solver, break_cover,
absl::StrFormat("break_cover(%ld)", br));
const int limit_cover_ct = solver->AddLinearConstraint(
limit, limit, {{break_cover, 1}, {lp_break_end[br], -1}});
limit, limit, {{break_cover, 1}, {lp_break.end, -1}});
solver->SetEnforcementLiteral(limit_cover_ct, break_is_eligible);
const int empty_cover_ct = solver->AddLinearConstraint(
CapAdd(vehicle_start_min, limit), CapAdd(vehicle_start_min, limit),
Expand All @@ -2255,7 +2255,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints(
{{lp_cumuls.back(), 1}, {previous_cover, -1}});
const int break_start_cover_ct = solver->AddLinearConstraint(
0, std::numeric_limits<int64_t>::max(),
{{previous_cover, 1}, {lp_break_start[br], -1}});
{{previous_cover, 1}, {lp_break.start, -1}});
solver->SetEnforcementLiteral(break_start_cover_ct,
route_end_is_not_covered);

Expand Down
51 changes: 46 additions & 5 deletions ortools/routing/lp_scheduling.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@ class RoutingLinearSolverWrapper {

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

// Returns if the model is empty or not.
virtual bool ModelIsEmpty() const { return true; }
Expand Down Expand Up @@ -384,6 +391,7 @@ class RoutingGlopWrapper : public RoutingLinearSolverWrapper {
void AddProductConstraint(int /*product_var*/,
std::vector<int> /*vars*/) override {}
void SetEnforcementLiteral(int /*ct*/, int /*condition*/) override {};
void AddRoute(absl::Span<const int64_t>, absl::Span<const int>) override{};
DimensionSchedulingStatus Solve(absl::Duration duration_limit) override {
lp_solver_.GetMutableParameters()->set_max_time_in_seconds(
absl::ToDoubleSeconds(duration_limit));
Expand Down Expand Up @@ -471,6 +479,7 @@ class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
model_.Clear();
response_.Clear();
objective_coefficients_.clear();
schedule_variables_.clear();
}
int CreateNewPositiveVariable() override {
const int index = model_.variables_size();
Expand Down Expand Up @@ -590,11 +599,39 @@ class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
DCHECK_LT(ct, model_.constraints_size());
model_.mutable_constraints(ct)->add_enforcement_literal(condition);
}
void AddRoute(absl::Span<const int64_t> nodes,
absl::Span<const int> schedule_variables) override {
DCHECK_EQ(nodes.size(), schedule_variables.size());
for (const int64_t node : nodes) {
if (node >= schedule_hint_.size()) schedule_hint_.resize(node + 1, 0);
}
for (int n = 0; n < nodes.size(); ++n) {
schedule_variables_.push_back(
{.node = nodes[n], .cumul = schedule_variables[n]});
}
}
DimensionSchedulingStatus Solve(absl::Duration duration_limit) override {
const double max_time = absl::ToDoubleSeconds(duration_limit);
if (max_time <= 0.0) return DimensionSchedulingStatus::INFEASIBLE;
parameters_.set_max_time_in_seconds(max_time);
VLOG(2) << ProtobufDebugString(model_);
auto record_hint = [this]() {
hint_.Clear();
for (int i = 0; i < response_.solution_size(); ++i) {
hint_.add_vars(i);
hint_.add_values(response_.solution(i));
}
for (const auto& [node, cumul] : schedule_variables_) {
schedule_hint_[node] = response_.solution(cumul);
}
};
model_.clear_solution_hint();
auto* hint = model_.mutable_solution_hint();
for (const auto& [node, cumul] : schedule_variables_) {
if (schedule_hint_[node] == 0) continue;
hint->add_vars(cumul);
hint->add_values(schedule_hint_[node]);
}
if (hint_.vars_size() == model_.variables_size()) {
*model_.mutable_solution_hint() = hint_;
}
Expand All @@ -606,11 +643,7 @@ class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
if (response_.status() == sat::CpSolverStatus::OPTIMAL ||
(response_.status() == sat::CpSolverStatus::FEASIBLE &&
!model_.has_floating_point_objective())) {
hint_.Clear();
for (int i = 0; i < response_.solution_size(); ++i) {
hint_.add_vars(i);
hint_.add_values(response_.solution(i));
}
record_hint();
return DimensionSchedulingStatus::OPTIMAL;
}
return DimensionSchedulingStatus::INFEASIBLE;
Expand Down Expand Up @@ -639,6 +672,14 @@ class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
sat::SatParameters parameters_;
std::vector<double> objective_coefficients_;
sat::PartialVariableAssignment hint_;
struct NodeAndCumul {
int64_t node;
int cumul;
};
// Stores node/cumul pairs of the routes in the current model.
std::vector<NodeAndCumul> schedule_variables_;
// Maps node to its last known value in any optimal solution.
std::vector<int64_t> schedule_hint_;
};

// Utility class used in Local/GlobalDimensionCumulOptimizer to set the linear
Expand Down

0 comments on commit ee22150

Please sign in to comment.