diff --git a/ortools/sat/routing_cuts.cc b/ortools/sat/routing_cuts.cc index a453e351c7..7c06a228c8 100644 --- a/ortools/sat/routing_cuts.cc +++ b/ortools/sat/routing_cuts.cc @@ -283,6 +283,13 @@ void SeparateSubtourInequalities( /*stop_at_num_components=*/2, &subset_data, &subsets); + const int depot = 0; + if (!demands.empty()) { + // Add the depot so that we have a trivial bound on the number of + // vehicle. + subsets.push_back(absl::MakeSpan(&depot, 1)); + } + // Compute the total demands in order to know the minimum incoming/outgoing // flow. int64_t total_demands = 0; @@ -293,7 +300,7 @@ void SeparateSubtourInequalities( // Process each subsets and add any violated cut. std::vector in_subset(num_nodes, false); for (const absl::Span subset : subsets) { - DCHECK_GT(subset.size(), 1); + DCHECK_GE(subset.size(), 1); DCHECK_LT(subset.size(), num_nodes); // These fields will be left untouched if demands.empty(). @@ -311,9 +318,9 @@ void SeparateSubtourInequalities( // Compute a lower bound on the outgoing flow. // - // TODO(user): This lower bound assume all nodes in subset must be served, - // which is not the case. For TSP we do the correct thing in - // AddOutgoingCut() but not for CVRP... Fix!! + // TODO(user): This lower bound assume all nodes in subset must be served. + // If this is not the case, we are really defensive in AddOutgoingCut(). + // Improve depending on where the self-loop are. // // TODO(user): It could be very interesting to see if this "min outgoing // flow" cannot be automatically infered from the constraint in the @@ -325,9 +332,8 @@ void SeparateSubtourInequalities( int64_t min_outgoing_flow = 1; if (!demands.empty()) { min_outgoing_flow = - contain_depot - ? (total_demands - subset_demand + capacity - 1) / capacity - : (subset_demand + capacity - 1) / capacity; + contain_depot ? CeilOfRatio(total_demands - subset_demand, capacity) + : CeilOfRatio(subset_demand, capacity); } // We still need to serve nodes with a demand of zero, and in the corner @@ -384,20 +390,45 @@ std::vector GetAssociatedVariables( return result; } +// This is especially useful to remove fixed self loop. +void FilterFalseArcsAtLevelZero(std::vector& tails, + std::vector& heads, + std::vector& literals, Model* model) { + const Trail& trail = *model->GetOrCreate(); + if (trail.CurrentDecisionLevel() != 0) return; + + int new_size = 0; + const int size = static_cast(tails.size()); + const VariablesAssignment& assignment = trail.Assignment(); + for (int i = 0; i < size; ++i) { + if (assignment.LiteralIsFalse(literals[i])) continue; + tails[new_size] = tails[i]; + heads[new_size] = heads[i]; + literals[new_size] = literals[i]; + ++new_size; + } + if (new_size < size) { + tails.resize(new_size); + heads.resize(new_size); + literals.resize(new_size); + } +} + } // namespace // We use a basic algorithm to detect components that are not connected to the // rest of the graph in the LP solution, and add cuts to force some arcs to // enter and leave this component from outside. CutGenerator CreateStronglyConnectedGraphCutGenerator( - int num_nodes, const std::vector& tails, const std::vector& heads, - const std::vector& literals, Model* model) { + int num_nodes, std::vector tails, std::vector heads, + std::vector literals, Model* model) { CutGenerator result; result.vars = GetAssociatedVariables(literals, model); result.generate_cuts = [num_nodes, tails, heads, literals, model]( const absl::StrongVector& lp_values, - LinearConstraintManager* manager) { + LinearConstraintManager* manager) mutable { + FilterFalseArcsAtLevelZero(tails, heads, literals, model); SeparateSubtourInequalities( num_nodes, tails, heads, literals, lp_values, /*demands=*/{}, /*capacity=*/0, manager, model); @@ -406,18 +437,18 @@ CutGenerator CreateStronglyConnectedGraphCutGenerator( return result; } -CutGenerator CreateCVRPCutGenerator(int num_nodes, - const std::vector& tails, - const std::vector& heads, - const std::vector& literals, - const std::vector& demands, +CutGenerator CreateCVRPCutGenerator(int num_nodes, std::vector tails, + std::vector heads, + std::vector literals, + std::vector demands, int64_t capacity, Model* model) { CutGenerator result; result.vars = GetAssociatedVariables(literals, model); result.generate_cuts = [num_nodes, tails, heads, demands, capacity, literals, model]( const absl::StrongVector& lp_values, - LinearConstraintManager* manager) { + LinearConstraintManager* manager) mutable { + FilterFalseArcsAtLevelZero(tails, heads, literals, model); SeparateSubtourInequalities(num_nodes, tails, heads, literals, lp_values, demands, capacity, manager, model); diff --git a/ortools/sat/routing_cuts.h b/ortools/sat/routing_cuts.h index 54a616da21..d5bc8fff4b 100644 --- a/ortools/sat/routing_cuts.h +++ b/ortools/sat/routing_cuts.h @@ -61,18 +61,17 @@ void GenerateInterestingSubsets(int num_nodes, // connected. Note that we already assume basic constraint to be in the lp, so // we do not add any cuts for components of size 1. CutGenerator CreateStronglyConnectedGraphCutGenerator( - int num_nodes, const std::vector& tails, const std::vector& heads, - const std::vector& literals, Model* model); + int num_nodes, std::vector tails, std::vector heads, + std::vector literals, Model* model); // Almost the same as CreateStronglyConnectedGraphCutGenerator() but for each // components, computes the demand needed to serves it, and depending on whether // it contains the depot (node zero) or not, compute the minimum number of // vehicle that needs to cross the component border. -CutGenerator CreateCVRPCutGenerator(int num_nodes, - const std::vector& tails, - const std::vector& heads, - const std::vector& literals, - const std::vector& demands, +CutGenerator CreateCVRPCutGenerator(int num_nodes, std::vector tails, + std::vector heads, + std::vector literals, + std::vector demands, int64_t capacity, Model* model); // Try to find a subset where the current LP capacity of the outgoing or