improve time placement when nodes have disjoint time windows
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 :
|
||||
|
||||
@@ -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()});
|
||||
|
||||
Reference in New Issue
Block a user