improve time placement when nodes have disjoint time windows

This commit is contained in:
Laurent Perron
2019-12-16 12:34:17 +01:00
parent d2b505ab99
commit bd5c282a51
5 changed files with 97 additions and 41 deletions

View File

@@ -177,8 +177,11 @@ class SetCumulsFromLocalDimensionCosts : public DecisionBuilder {
SetCumulsFromLocalDimensionCosts(
const std::vector<std::unique_ptr<LocalDimensionCumulOptimizer>>*
local_optimizers,
const std::vector<std::unique_ptr<LocalDimensionCumulOptimizer>>*
local_mp_optimizers,
SearchMonitor* monitor, bool optimize_and_pack = false)
: local_optimizers_(*local_optimizers),
local_mp_optimizers_(*local_mp_optimizers),
monitor_(monitor),
optimize_and_pack_(optimize_and_pack) {}
Decision* Next(Solver* const solver) override {
@@ -186,7 +189,10 @@ class SetCumulsFromLocalDimensionCosts : public DecisionBuilder {
// order to postpone the Fail() call until after the internal for loop, so
// there are no memory leaks related to the cumul_values vector.
bool should_fail = false;
for (const auto& optimizer : local_optimizers_) {
for (int i = 0; i < local_optimizers_.size(); ++i) {
const auto& optimizer = local_mp_optimizers_[i] != nullptr
? local_mp_optimizers_[i]
: local_optimizers_[i];
const RoutingDimension* const dimension = optimizer->dimension();
RoutingModel* const model = dimension->model();
const auto next = [model](int64 i) { return model->NextVar(i)->Value(); };
@@ -242,6 +248,8 @@ class SetCumulsFromLocalDimensionCosts : public DecisionBuilder {
private:
const std::vector<std::unique_ptr<LocalDimensionCumulOptimizer>>&
local_optimizers_;
const std::vector<std::unique_ptr<LocalDimensionCumulOptimizer>>&
local_mp_optimizers_;
SearchMonitor* const monitor_;
const bool optimize_and_pack_;
};
@@ -309,6 +317,7 @@ const Assignment* RoutingModel::PackCumulsOfOptimizerDimensionsFromAssignment(
if (time_limit_ms <= 0 || original_assignment == nullptr ||
(global_dimension_optimizers_.empty() &&
local_dimension_optimizers_.empty())) {
DCHECK(local_dimension_mp_optimizers_.empty());
return original_assignment;
}
@@ -327,7 +336,7 @@ const Assignment* RoutingModel::PackCumulsOfOptimizerDimensionsFromAssignment(
solver_->MakeRestoreAssignment(packed_assignment));
decision_builders.push_back(
solver_->RevAlloc(new SetCumulsFromLocalDimensionCosts(
&local_dimension_optimizers_,
&local_dimension_optimizers_, &local_dimension_mp_optimizers_,
GetOrCreateLargeNeighborhoodSearchLimit(),
/*optimize_and_pack=*/true)));
decision_builders.push_back(
@@ -1164,6 +1173,18 @@ LocalDimensionCumulOptimizer* RoutingModel::GetMutableLocalCumulOptimizer(
return local_dimension_optimizers_[optimizer_index].get();
}
LocalDimensionCumulOptimizer* RoutingModel::GetMutableLocalCumulMPOptimizer(
const RoutingDimension& dimension) const {
const DimensionIndex dim_index = GetDimensionIndex(dimension.name());
if (dim_index < 0 || dim_index >= local_optimizer_index_.size() ||
local_optimizer_index_[dim_index] < 0) {
return nullptr;
}
const int optimizer_index = local_optimizer_index_[dim_index];
DCHECK_LT(optimizer_index, local_dimension_mp_optimizers_.size());
return local_dimension_mp_optimizers_[optimizer_index].get();
}
bool RoutingModel::HasDimension(const std::string& dimension_name) const {
return gtl::ContainsKey(dimension_name_to_index_, dimension_name);
}
@@ -4511,9 +4532,28 @@ void RoutingModel::StoreDimensionCumulOptimizers(
local_dimension_optimizers_.push_back(
absl::make_unique<LocalDimensionCumulOptimizer>(
dimension, parameters.continuous_scheduling_solver()));
bool has_intervals = false;
for (const SortedDisjointIntervalList& intervals :
dimension->forbidden_intervals()) {
// TODO(user): Change the following test to check intervals within
// the domain of the corresponding variables.
if (intervals.NumIntervals() > 0) {
has_intervals = true;
break;
}
}
if (has_intervals) {
local_dimension_mp_optimizers_.push_back(
absl::make_unique<LocalDimensionCumulOptimizer>(
dimension, parameters.mixed_integer_scheduling_solver()));
} else {
local_dimension_mp_optimizers_.push_back(nullptr);
}
packed_dimensions_collector_assignment->Add(dimension->cumuls());
}
}
DCHECK_EQ(local_dimension_mp_optimizers_.size(),
local_dimension_optimizers_.size());
}
// NOTE(b/129252839): We also add all other extra variables to the
@@ -4589,7 +4629,8 @@ DecisionBuilder* RoutingModel::CreateSolutionFinalizer(SearchLimit* lns_limit) {
if (!local_dimension_optimizers_.empty()) {
decision_builders.push_back(
solver_->RevAlloc(new SetCumulsFromLocalDimensionCosts(
&local_dimension_optimizers_, lns_limit)));
&local_dimension_optimizers_, &local_dimension_mp_optimizers_,
lns_limit)));
}
if (!global_dimension_optimizers_.empty()) {
decision_builders.push_back(

View File

@@ -531,6 +531,10 @@ class RoutingModel {
GetLocalDimensionCumulOptimizers() const {
return local_dimension_optimizers_;
}
const std::vector<std::unique_ptr<LocalDimensionCumulOptimizer> >&
GetLocalDimensionCumulMPOptimizers() const {
return local_dimension_mp_optimizers_;
}
// clang-format on
/// Returns the global/local dimension cumul optimizer for a given dimension,
@@ -539,6 +543,8 @@ class RoutingModel {
const RoutingDimension& dimension) const;
LocalDimensionCumulOptimizer* GetMutableLocalCumulOptimizer(
const RoutingDimension& dimension) const;
LocalDimensionCumulOptimizer* GetMutableLocalCumulMPOptimizer(
const RoutingDimension& dimension) const;
/// Returns true if a dimension exists for a given dimension name.
bool HasDimension(const std::string& dimension_name) const;
@@ -1550,6 +1556,8 @@ class RoutingModel {
gtl::ITIVector<DimensionIndex, int> global_optimizer_index_;
std::vector<std::unique_ptr<LocalDimensionCumulOptimizer> >
local_dimension_optimizers_;
std::vector<std::unique_ptr<LocalDimensionCumulOptimizer> >
local_dimension_mp_optimizers_;
// clang-format off
gtl::ITIVector<DimensionIndex, int> local_optimizer_index_;
std::string primary_constrained_dimension_;
@@ -2841,7 +2849,7 @@ class GlobalCheapestInsertionFilteredHeuristic
/// newly modified route arcs: after the node insertion position and after the
/// node position.
void InsertNodesOnRoutes(const std::vector<int>& nodes,
const std::vector<int>& vehicles);
const absl::flat_hash_set<int>& vehicles);
/// Inserts non-inserted individual nodes on routes by constructing routes
/// sequentially.
@@ -2854,8 +2862,8 @@ class GlobalCheapestInsertionFilteredHeuristic
/// (i.e. Value(start) != end) or not.
/// Updates the three passed vectors accordingly.
void DetectUsedVehicles(std::vector<bool>* is_vehicle_used,
std::vector<int>* used_vehicles,
std::vector<int>* unused_vehicles);
std::vector<int>* unused_vehicles,
absl::flat_hash_set<int>* used_vehicles);
/// Inserts the (farthest_seeds_ratio_ * model()->vehicles()) nodes farthest
/// from the start/ends of the available vehicle routes as seeds on their
@@ -2927,14 +2935,14 @@ class GlobalCheapestInsertionFilteredHeuristic
void InitializePositions(const std::vector<int>& nodes,
AdjustablePriorityQueue<NodeEntry>* priority_queue,
std::vector<NodeEntries>* position_to_node_entries,
const std::vector<int>& vehicles);
const absl::flat_hash_set<int>& vehicles);
/// Adds insertion entries performing 'node', and updates 'priority_queue' and
/// position_to_node_entries accordingly.
/// Based on gci_params_.use_neighbors_ratio_for_initialization, either all
/// contained nodes are considered as insertion positions, or only the
/// closest neighbors of 'node'.
void InitializeInsertionEntriesPerformingNode(
int64 node, int64 penalty, const std::vector<int>& vehicles,
int64 node, int64 penalty, const absl::flat_hash_set<int>& vehicles,
AdjustablePriorityQueue<NodeEntry>* priority_queue,
std::vector<NodeEntries>* position_to_node_entries);
/// Updates all node entries inserting a node after node "insert_after" and

View File

@@ -339,7 +339,14 @@ void AddSolutionAsHintToModel(const Assignment* solution,
const int head = solution->Value(model.NextVar(tail));
const int head_index = model.IsEnd(head) ? depot : head;
if (tail_index == depot && head_index == depot) continue;
hint->add_vars(gtl::FindOrDie(arc_vars, {tail_index, head_index}));
const int* const var_index =
gtl::FindOrNull(arc_vars, {tail_index, head_index});
// Arcs with a cost of kint64max are not added to the model (considered as
// infeasible). In some rare cases CP solutions might contain such arcs in
// which case they are skipped here and a partial solution is used as a
// hint.
if (var_index == nullptr) continue;
hint->add_vars(*var_index);
hint->add_values(1);
}
}

View File

@@ -3128,36 +3128,38 @@ bool GlobalCheapestInsertionFilteredHeuristic::CheckVehicleIndices() const {
bool GlobalCheapestInsertionFilteredHeuristic::BuildSolutionInternal() {
// Insert partially inserted pairs.
std::vector<int> pair_nodes;
absl::flat_hash_map<int, std::vector<int>> vehicle_to_pair_nodes;
for (const RoutingModel::IndexPair& index_pair :
model()->GetPickupAndDeliveryPairs()) {
bool has_inserted_pickup = false;
int pickup_vehicle = -1;
for (int64 pickup : index_pair.first) {
if (Contains(pickup)) {
has_inserted_pickup = true;
pickup_vehicle = node_index_to_vehicle_[pickup];
break;
}
}
bool has_inserted_delivery = false;
int delivery_vehicle = -1;
for (int64 delivery : index_pair.second) {
if (Contains(delivery)) {
has_inserted_delivery = true;
delivery_vehicle = node_index_to_vehicle_[delivery];
break;
}
}
if (has_inserted_pickup && !has_inserted_delivery) {
if (pickup_vehicle >= 0 && delivery_vehicle < 0) {
std::vector<int>& pair_nodes = vehicle_to_pair_nodes[pickup_vehicle];
for (int64 delivery : index_pair.second) {
pair_nodes.push_back(delivery);
}
}
if (!has_inserted_pickup && has_inserted_delivery) {
if (pickup_vehicle < 0 && delivery_vehicle >= 0) {
std::vector<int>& pair_nodes = vehicle_to_pair_nodes[delivery_vehicle];
for (int64 pickup : index_pair.first) {
pair_nodes.push_back(pickup);
}
}
}
if (!pair_nodes.empty()) {
InsertNodesOnRoutes(pair_nodes, {});
for (const auto& vehicle_and_nodes : vehicle_to_pair_nodes) {
InsertNodesOnRoutes(vehicle_and_nodes.second, {vehicle_and_nodes.first});
}
// TODO(user): Adapt the pair insertions to also support seed and
// sequential insertion.
@@ -3247,7 +3249,7 @@ void GlobalCheapestInsertionFilteredHeuristic::InsertPairs() {
}
void GlobalCheapestInsertionFilteredHeuristic::InsertNodesOnRoutes(
const std::vector<int>& nodes, const std::vector<int>& vehicles) {
const std::vector<int>& nodes, const absl::flat_hash_set<int>& vehicles) {
AdjustablePriorityQueue<NodeEntry> priority_queue;
std::vector<NodeEntries> position_to_node_entries;
InitializePositions(nodes, &priority_queue, &position_to_node_entries,
@@ -3316,10 +3318,10 @@ void GlobalCheapestInsertionFilteredHeuristic::InsertNodesOnRoutes(
void GlobalCheapestInsertionFilteredHeuristic::SequentialInsertNodes(
const std::vector<int>& nodes) {
std::vector<bool> is_vehicle_used;
std::vector<int> used_vehicles;
absl::flat_hash_set<int> used_vehicles;
std::vector<int> unused_vehicles;
DetectUsedVehicles(&is_vehicle_used, &used_vehicles, &unused_vehicles);
DetectUsedVehicles(&is_vehicle_used, &unused_vehicles, &used_vehicles);
if (!used_vehicles.empty()) {
InsertNodesOnRoutes(nodes, used_vehicles);
}
@@ -3341,8 +3343,8 @@ void GlobalCheapestInsertionFilteredHeuristic::SequentialInsertNodes(
}
void GlobalCheapestInsertionFilteredHeuristic::DetectUsedVehicles(
std::vector<bool>* is_vehicle_used, std::vector<int>* used_vehicles,
std::vector<int>* unused_vehicles) {
std::vector<bool>* is_vehicle_used, std::vector<int>* unused_vehicles,
absl::flat_hash_set<int>* used_vehicles) {
is_vehicle_used->clear();
is_vehicle_used->resize(model()->vehicles());
@@ -3355,7 +3357,7 @@ void GlobalCheapestInsertionFilteredHeuristic::DetectUsedVehicles(
for (int vehicle = 0; vehicle < model()->vehicles(); vehicle++) {
if (Value(model()->Start(vehicle)) != model()->End(vehicle)) {
(*is_vehicle_used)[vehicle] = true;
used_vehicles->push_back(vehicle);
used_vehicles->insert(vehicle);
} else {
(*is_vehicle_used)[vehicle] = false;
unused_vehicles->push_back(vehicle);
@@ -3370,9 +3372,9 @@ void GlobalCheapestInsertionFilteredHeuristic::InsertFarthestNodesAsSeeds() {
std::ceil(gci_params_.farthest_seeds_ratio * model()->vehicles()));
std::vector<bool> is_vehicle_used;
std::vector<int> used_vehicles;
absl::flat_hash_set<int> used_vehicles;
std::vector<int> unused_vehicles;
DetectUsedVehicles(&is_vehicle_used, &used_vehicles, &unused_vehicles);
DetectUsedVehicles(&is_vehicle_used, &unused_vehicles, &used_vehicles);
std::vector<std::vector<StartEndValue>> start_end_distances_per_node =
ComputeStartEndDistanceForVehicles(unused_vehicles);
@@ -3837,7 +3839,7 @@ void GlobalCheapestInsertionFilteredHeuristic::InitializePositions(
GlobalCheapestInsertionFilteredHeuristic::NodeEntry>* priority_queue,
std::vector<GlobalCheapestInsertionFilteredHeuristic::NodeEntries>*
position_to_node_entries,
const std::vector<int>& vehicles) {
const absl::flat_hash_set<int>& vehicles) {
priority_queue->Clear();
position_to_node_entries->clear();
position_to_node_entries->resize(model()->Size());
@@ -3875,7 +3877,7 @@ void GlobalCheapestInsertionFilteredHeuristic::InitializePositions(
void GlobalCheapestInsertionFilteredHeuristic::
InitializeInsertionEntriesPerformingNode(
int64 node, int64 penalty, const std::vector<int>& vehicles,
int64 node, int64 penalty, const absl::flat_hash_set<int>& vehicles,
AdjustablePriorityQueue<
GlobalCheapestInsertionFilteredHeuristic::NodeEntry>*
priority_queue,
@@ -3884,8 +3886,9 @@ void GlobalCheapestInsertionFilteredHeuristic::
const int num_vehicles =
vehicles.empty() ? model()->vehicles() : vehicles.size();
if (!gci_params_.use_neighbors_ratio_for_initialization) {
auto vehicles_it = vehicles.begin();
for (int v = 0; v < num_vehicles; v++) {
const int vehicle = vehicles.empty() ? v : vehicles[v];
const int vehicle = vehicles.empty() ? v : *vehicles_it++;
std::vector<ValuedPosition> valued_positions;
const int64 start = model()->Start(vehicle);
@@ -3906,15 +3909,11 @@ void GlobalCheapestInsertionFilteredHeuristic::
// the node.
absl::flat_hash_set<int> vehicles_to_consider;
const bool all_vehicles = (num_vehicles == model()->vehicles());
if (!all_vehicles) {
vehicles_to_consider =
absl::flat_hash_set<int>(vehicles.begin(), vehicles.end());
}
const auto insert_on_vehicle_for_cost_class =
[this, &vehicles_to_consider, all_vehicles](int v, int cost_class) {
return (model()->GetCostClassIndexOfVehicle(v).value() == cost_class) &&
(all_vehicles || vehicles_to_consider.contains(v));
};
const auto insert_on_vehicle_for_cost_class = [this, &vehicles, all_vehicles](
int v, int cost_class) {
return (model()->GetCostClassIndexOfVehicle(v).value() == cost_class) &&
(all_vehicles || vehicles.contains(v));
};
for (int cost_class = 0; cost_class < model()->GetCostClassesCount();
cost_class++) {
for (const std::vector<int64>* const neighbors :

View File

@@ -2514,11 +2514,12 @@ bool BestValueSolutionCollector::AtSolution() {
if (prototype_ != nullptr) {
const IntVar* objective = prototype_->Objective();
if (objective != nullptr) {
if (maximize_ && objective->Max() > best_) {
if (maximize_ && (solution_count() == 0 || objective->Max() > best_)) {
PopSolution();
PushSolution();
best_ = objective->Max();
} else if (!maximize_ && objective->Min() < best_) {
} else if (!maximize_ &&
(solution_count() == 0 || objective->Min() < best_)) {
PopSolution();
PushSolution();
best_ = objective->Min();
@@ -2606,7 +2607,7 @@ bool NBestValueSolutionCollector::AtSolution() {
const IntVar* objective = prototype_->Objective();
if (objective != nullptr) {
const int64 objective_value =
maximize_ ? -objective->Max() : objective->Min();
maximize_ ? CapSub(0, objective->Max()) : objective->Min();
if (solutions_pq_.size() < solution_count_) {
solutions_pq_.push(
{objective_value, BuildSolutionDataForCurrentState()});