fix checker bug in routing

This commit is contained in:
Laurent Perron
2019-03-25 11:21:06 +01:00
parent 1a53d1d749
commit 325ca6d78d
8 changed files with 180 additions and 17 deletions

View File

@@ -14,6 +14,7 @@
syntax = "proto3";
package operations_research;
message DemonRuns {

View File

@@ -17,6 +17,7 @@ from __future__ import print_function
import argparse
from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2
# [END import]

View File

@@ -1919,9 +1919,15 @@ namespace operations_research {
%template (NEW_NAME) TYPE;
%enddef
RENAME_ASSIGNMENT_CONTAINER(PARENTHIZE(AssignmentContainer<IntVar, IntVarElement>), IntVarContainer)
RENAME_ASSIGNMENT_CONTAINER(PARENTHIZE(AssignmentContainer<IntervalVar, IntervalVarElement>), IntervalVarContainer)
RENAME_ASSIGNMENT_CONTAINER(PARENTHIZE(AssignmentContainer<SequenceVar, SequenceVarElement>), SequenceVarContainer)
RENAME_ASSIGNMENT_CONTAINER(
PARENTHIZE(AssignmentContainer<IntVar, IntVarElement>),
IntVarContainer)
RENAME_ASSIGNMENT_CONTAINER(
PARENTHIZE(AssignmentContainer<IntervalVar, IntervalVarElement>),
IntervalVarContainer)
RENAME_ASSIGNMENT_CONTAINER(
PARENTHIZE(AssignmentContainer<SequenceVar, SequenceVarElement>),
SequenceVarContainer)
#undef RENAME_ASSIGNMENT_CONTAINER
#undef PARENTHIZE

View File

@@ -2879,6 +2879,9 @@ class BasePathFilter : public IntVarLocalSearchFilter {
int GetPath(int64 node) const { return paths_[node]; }
int Rank(int64 node) const { return ranks_[node]; }
bool IsDisabled() const { return status_ == DISABLED; }
const std::vector<int64>& GetNewSynchronizedUnperformedNodes() const {
return new_synchronized_unperformed_nodes_.PositionsSetAtLeastOnce();
}
private:
enum Status { UNKNOWN, ENABLED, DISABLED };
@@ -2902,6 +2905,7 @@ class BasePathFilter : public IntVarLocalSearchFilter {
std::vector<int64> node_path_starts_;
std::vector<int64> starts_;
std::vector<int> paths_;
SparseBitset<int64> new_synchronized_unperformed_nodes_;
std::vector<int64> new_nexts_;
std::vector<int> delta_touched_;
SparseBitset<> touched_paths_;

View File

@@ -14,6 +14,7 @@
// Enums used to define routing parameters.
syntax = "proto3";
option java_package = "com.google.ortools.constraintsolver";
option java_multiple_files = true;
option csharp_namespace = "Google.OrTools.ConstraintSolver";

View File

@@ -24,6 +24,7 @@
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "ortools/base/map_util.h"
#include "ortools/base/small_map.h"
#include "ortools/base/small_ordered_set.h"
#include "ortools/base/stl_util.h"
@@ -296,6 +297,7 @@ BasePathFilter::BasePathFilter(const std::vector<IntVar*>& nexts,
: IntVarLocalSearchFilter(nexts, std::move(objective_callback)),
node_path_starts_(next_domain_size, kUnassigned),
paths_(nexts.size(), -1),
new_synchronized_unperformed_nodes_(nexts.size()),
new_nexts_(nexts.size(), kUnassigned),
touched_paths_(nexts.size()),
touched_path_nodes_(next_domain_size),
@@ -424,6 +426,13 @@ void BasePathFilter::SynchronizeFullAssignment() {
// needed).
PropagateObjectiveValue(injected_objective_value_);
ComputePathStarts(&starts_, &paths_);
for (int64 index = 0; index < Size(); index++) {
if (IsVarSynced(index) && Value(index) == index &&
node_path_starts_[index] != kUnassigned) {
// index was performed before and is now unperformed.
new_synchronized_unperformed_nodes_.Set(index);
}
}
// Marking unactive nodes (which are not on a path).
node_path_starts_.assign(node_path_starts_.size(), kUnassigned);
// Marking nodes on a path and storing next values.
@@ -452,6 +461,7 @@ void BasePathFilter::OnSynchronize(const Assignment* delta) {
DisableFiltering() ? BasePathFilter::DISABLED : BasePathFilter::ENABLED;
}
if (IsDisabled()) return;
new_synchronized_unperformed_nodes_.ClearAll();
if (delta == nullptr || delta->Empty() || starts_.empty()) {
SynchronizeFullAssignment();
return;
@@ -471,6 +481,12 @@ void BasePathFilter::OnSynchronize(const Assignment* delta) {
const int64 start = node_path_starts_[index];
if (start != kUnassigned) {
touched_paths_.Set(start);
if (Value(index) == index) {
// New unperformed node (its previous start isn't unassigned).
DCHECK_LT(index, new_nexts_.size());
new_synchronized_unperformed_nodes_.Set(index);
node_path_starts_[index] = kUnassigned;
}
}
}
}
@@ -1033,6 +1049,13 @@ class PathCumulFilter : public BasePathFilter {
void InitializeAcceptPath() override {
cumul_cost_delta_ = total_current_cumul_cost_value_;
node_with_precedence_to_delta_min_max_cumuls_.clear();
// Cleaning up for the new delta.
delta_max_end_cumul_ = kint64min;
delta_paths_.clear();
delta_path_transits_.Clear();
lns_detected_ = false;
delta_nodes_with_precedences_and_changed_cumul_.ClearAll();
}
bool AcceptPath(int64 path_start, int64 chain_start,
int64 chain_end) override;
@@ -1079,6 +1102,8 @@ class PathCumulFilter : public BasePathFilter {
return !cumul_soft_lower_bounds_.empty();
}
bool FilterPrecedences() const { return !node_index_to_precedences_.empty(); }
int64 GetCumulSoftLowerBoundCost(int64 node, int64 cumul_value) const;
int64 GetPathCumulSoftLowerBoundCost(const PathTransits& path_transits,
@@ -1099,6 +1124,18 @@ class PathCumulFilter : public BasePathFilter {
const PathTransits& path_transits, int path,
const std::vector<int64>& min_path_cumuls) const;
// Computes the maximum cumul value of nodes along the path using
// [current|delta]_path_transits_, and stores the min/max cumul
// related to each node in the corresponding vector
// [current|delta]_[min|max]_node_cumuls_.
// The boolean is_delta indicates if the computations should take place on the
// "delta" or "current" members. When true, the nodes for which the min/max
// cumul has changed from the current value are marked in
// delta_nodes_with_precedences_and_changed_cumul_.
void StoreMinMaxCumulOfNodesOnPath(int path,
const std::vector<int64>& min_path_cumuls,
bool is_delta);
// Compute the max start cumul value for a given path given an end cumul
// value. Does not take time windows into account.
int64 ComputePathMaxStartFromEndCumul(const PathTransits& path_transits,
@@ -1128,15 +1165,25 @@ class PathCumulFilter : public BasePathFilter {
bool has_nonzero_vehicle_span_cost_coefficients_;
IntVar* const cost_var_;
const std::vector<int64> vehicle_capacities_;
// node_index_to_precedences_[node_index] contains all NodePrecedence elements
// with node_index as either "first_node" or "second_node".
// This vector is empty if there are no precedences on the dimension_.
std::vector<std::vector<RoutingDimension::NodePrecedence>>
node_index_to_precedences_;
// Data reflecting information on paths and cumul variables for the solution
// to which the filter was synchronized.
SupportedPathCumul current_min_start_;
SupportedPathCumul current_max_end_;
PathTransits current_path_transits_;
// Current min/max cumul values, indexed by node.
std::vector<std::pair<int64, int64>> current_min_max_node_cumuls_;
// Data reflecting information on paths and cumul variables for the "delta"
// solution (aka neighbor solution) being examined.
PathTransits delta_path_transits_;
int64 delta_max_end_cumul_;
SparseBitset<int64> delta_nodes_with_precedences_and_changed_cumul_;
absl::flat_hash_map<int64, std::pair<int64, int64>>
node_with_precedence_to_delta_min_max_cumuls_;
// Note: small_ordered_set only support non-hash sets.
gtl::small_ordered_set<std::set<int>> delta_paths_;
const std::string name_;
@@ -1173,6 +1220,7 @@ PathCumulFilter::PathCumulFilter(const RoutingModel& routing_model,
cost_var_(routing_model.CostVar()),
vehicle_capacities_(dimension.vehicle_capacities()),
delta_max_end_cumul_(kint64min),
delta_nodes_with_precedences_and_changed_cumul_(routing_model.Size()),
name_(dimension.name()),
optimizer_(&dimension),
propagate_own_objective_value_(propagate_own_objective_value),
@@ -1248,6 +1296,19 @@ PathCumulFilter::PathCumulFilter(const RoutingModel& routing_model,
start_to_vehicle_[routing_model.Start(i)] = i;
evaluators_[i] = &dimension.transit_evaluator(i);
}
const std::vector<RoutingDimension::NodePrecedence>& node_precedences =
dimension.GetNodePrecedences();
if (!node_precedences.empty()) {
current_min_max_node_cumuls_.resize(cumuls_.size(), {-1, -1});
node_index_to_precedences_.resize(cumuls_.size());
for (const auto& node_precedence : node_precedences) {
node_index_to_precedences_[node_precedence.first_node].push_back(
node_precedence);
node_index_to_precedences_[node_precedence.second_node].push_back(
node_precedence);
}
}
}
int64 PathCumulFilter::GetCumulSoftCost(int64 node, int64 cumul_value) const {
@@ -1304,7 +1365,8 @@ void PathCumulFilter::OnBeforeSynchronizePaths() {
cumul_cost_delta_ = 0;
current_cumul_cost_values_.clear();
if (FilterSpanCost() || FilterCumulSoftBounds() || FilterSlackCost() ||
FilterCumulSoftLowerBounds() || FilterCumulPiecewiseLinearCosts()) {
FilterCumulSoftLowerBounds() || FilterCumulPiecewiseLinearCosts() ||
FilterPrecedences()) {
InitializeSupportedPathCumul(&current_min_start_, kint64max);
InitializeSupportedPathCumul(&current_max_end_, kint64min);
current_path_transits_.Clear();
@@ -1327,6 +1389,9 @@ void PathCumulFilter::OnBeforeSynchronizePaths() {
// Second pass: update cumul, transit and cost values.
node = Start(r);
int64 cumul = cumuls_[node]->Min();
std::vector<int64> min_path_cumuls;
min_path_cumuls.reserve(number_of_route_arcs + 1);
min_path_cumuls.push_back(cumul);
int64 current_cumul_cost_value = 0;
if (filter_with_optimizer) {
const bool cumuls_optimized =
@@ -1350,6 +1415,7 @@ void PathCumulFilter::OnBeforeSynchronizePaths() {
cumul = GetNextValueFromForbiddenIntervals(cumul,
forbidden_intervals_[next]);
cumul = std::max(cumuls_[next]->Min(), cumul);
min_path_cumuls.push_back(cumul);
node = next;
if (!filter_with_optimizer) {
current_cumul_cost_value =
@@ -1359,6 +1425,10 @@ void PathCumulFilter::OnBeforeSynchronizePaths() {
GetCumulPiecewiseLinearCost(node, cumul));
}
}
if (FilterPrecedences()) {
StoreMinMaxCumulOfNodesOnPath(/*path=*/r, min_path_cumuls,
/*is_delta=*/false);
}
if (FilterSlackCost() && !filter_with_optimizer) {
const int64 start =
ComputePathMaxStartFromEndCumul(current_path_transits_, r, cumul);
@@ -1381,6 +1451,12 @@ void PathCumulFilter::OnBeforeSynchronizePaths() {
total_current_cumul_cost_value_ =
CapAdd(total_current_cumul_cost_value_, current_cumul_cost_value);
}
if (FilterPrecedences()) {
// Update the min/max node cumuls of new unperformed nodes.
for (int64 node : GetNewSynchronizedUnperformedNodes()) {
current_min_max_node_cumuls_[node] = {-1, -1};
}
}
// Use the max of the path end cumul mins to compute the corresponding
// maximum start cumul of each path; store the minimum of these.
for (int r = 0; r < NumPaths(); ++r) {
@@ -1519,6 +1595,9 @@ bool PathCumulFilter::AcceptPath(int64 path_start, int64 chain_start,
&cumul_cost_delta)) {
return false;
}
if (FilterPrecedences()) {
StoreMinMaxCumulOfNodesOnPath(path, min_path_cumuls, /*is_delta=*/true);
}
if (FilterSpanCost() || FilterCumulSoftBounds() || FilterSlackCost() ||
FilterCumulSoftLowerBounds() || FilterCumulPiecewiseLinearCosts()) {
delta_paths_.insert(GetPath(path_start));
@@ -1532,16 +1611,53 @@ bool PathCumulFilter::AcceptPath(int64 path_start, int64 chain_start,
bool PathCumulFilter::FinalizeAcceptPath(Assignment* delta) {
if ((!FilterSpanCost() && !FilterCumulSoftBounds() && !FilterSlackCost() &&
!FilterCumulSoftLowerBounds() && !FilterCumulPiecewiseLinearCosts()) ||
!FilterCumulSoftLowerBounds() && !FilterCumulPiecewiseLinearCosts() &&
!FilterPrecedences()) ||
lns_detected_) {
// Cleaning up for the next delta.
delta_max_end_cumul_ = kint64min;
delta_paths_.clear();
delta_path_transits_.Clear();
lns_detected_ = false;
PropagateObjectiveValue(injected_objective_value_);
return true;
}
if (FilterPrecedences()) {
for (int64 node : delta_nodes_with_precedences_and_changed_cumul_
.PositionsSetAtLeastOnce()) {
const std::pair<int64, int64> node_min_max_cumul_in_delta =
gtl::FindWithDefault(node_with_precedence_to_delta_min_max_cumuls_,
node, {-1, -1});
// NOTE: This node was seen in delta, so its delta min/max cumul should be
// stored in the map.
DCHECK(node_min_max_cumul_in_delta.first >= 0 &&
node_min_max_cumul_in_delta.second >= 0);
for (const RoutingDimension::NodePrecedence& precedence :
node_index_to_precedences_[node]) {
const bool node_is_first = (precedence.first_node == node);
const int64 other_node =
node_is_first ? precedence.second_node : precedence.first_node;
if (GetNext(other_node) == kUnassigned ||
GetNext(other_node) == other_node) {
// The other node is unperformed, so the precedence constraint is
// inactive.
continue;
}
// max_cumul[second_node] should be greater or equal than
// min_cumul[first_node] + offset.
const std::pair<int64, int64>& other_min_max_cumul_in_delta =
gtl::FindWithDefault(node_with_precedence_to_delta_min_max_cumuls_,
other_node,
current_min_max_node_cumuls_[other_node]);
const int64 first_min_cumul = node_is_first
? node_min_max_cumul_in_delta.first
: other_min_max_cumul_in_delta.first;
const int64 second_max_cumul = node_is_first
? other_min_max_cumul_in_delta.second
: node_min_max_cumul_in_delta.second;
if (second_max_cumul < first_min_cumul + precedence.offset) {
return false;
}
}
}
}
int64 new_max_end = delta_max_end_cumul_;
int64 new_min_start = kint64max;
if (FilterSpanCost()) {
@@ -1595,11 +1711,6 @@ bool PathCumulFilter::FinalizeAcceptPath(Assignment* delta) {
}
}
}
// Cleaning up for the next delta.
delta_max_end_cumul_ = kint64min;
delta_paths_.clear();
delta_path_transits_.Clear();
lns_detected_ = false;
// Filtering on objective value, including the injected part of it.
accepted_objective_value_ =
CapAdd(cumul_cost_delta_, CapProd(global_span_cost_coefficient_,
@@ -1692,6 +1803,43 @@ bool PathCumulFilter::PickupToDeliveryLimitsRespected(
return true;
}
void PathCumulFilter::StoreMinMaxCumulOfNodesOnPath(
int path, const std::vector<int64>& min_path_cumuls, bool is_delta) {
const PathTransits& path_transits =
is_delta ? delta_path_transits_ : current_path_transits_;
const int path_size = path_transits.PathSize(path);
DCHECK_EQ(min_path_cumuls.size(), path_size);
int64 max_cumul = cumuls_[path_transits.Node(path, path_size - 1)]->Max();
for (int i = path_size - 1; i >= 0; i--) {
const int node_index = path_transits.Node(path, i);
if (i < path_size - 1) {
max_cumul = CapSub(max_cumul, path_transits.Transit(path, i));
max_cumul = std::min(cumuls_[node_index]->Max(), max_cumul);
}
if (is_delta && node_index_to_precedences_[node_index].empty()) {
// No need to update the delta cumul map for nodes without precedences.
continue;
}
std::pair<int64, int64>& min_max_cumuls =
is_delta ? node_with_precedence_to_delta_min_max_cumuls_[node_index]
: current_min_max_node_cumuls_[node_index];
min_max_cumuls.first = min_path_cumuls[i];
min_max_cumuls.second = max_cumul;
if (is_delta && !routing_model_.IsEnd(node_index) &&
(min_max_cumuls.first !=
current_min_max_node_cumuls_[node_index].first ||
max_cumul != current_min_max_node_cumuls_[node_index].second)) {
delta_nodes_with_precedences_and_changed_cumul_.Set(node_index);
}
}
}
int64 PathCumulFilter::ComputePathMaxStartFromEndCumul(
const PathTransits& path_transits, int path, int64 end_cumul) const {
int64 cumul = end_cumul;

View File

@@ -15,6 +15,7 @@
// This file contains a protocol buffer definition for search limits.
syntax = "proto3";
option java_package = "com.google.ortools.constraintsolver";
option java_multiple_files = true;
// We set the outer classname to something other than 'SearchLimit', which is
@@ -34,4 +35,4 @@ message SearchLimitParameters {
int64 solutions = 4;
bool smart_time_check = 5;
bool cumulative = 6;
};
}

View File

@@ -14,6 +14,7 @@
// This file contains protocol buffers for all parameters of the CP solver.
syntax = "proto3";
option java_package = "com.google.ortools.constraintsolver";
option java_multiple_files = true;
option csharp_namespace = "Google.OrTools.ConstraintSolver";
@@ -136,4 +137,4 @@ message ConstraintSolverParameters {
// Control the behavior of local search.
//
int32 check_solution_period = 114;
};
}