From 52e4c24a9e494751216dd50f3ab2ef7a018a8df5 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Tue, 10 Jun 2025 16:29:34 +0200 Subject: [PATCH] fix #4677 --- ortools/sat/scheduling_cuts.cc | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/ortools/sat/scheduling_cuts.cc b/ortools/sat/scheduling_cuts.cc index e51929b16d..2af85e168a 100644 --- a/ortools/sat/scheduling_cuts.cc +++ b/ortools/sat/scheduling_cuts.cc @@ -1102,26 +1102,25 @@ std::string CompletionTimeEvent::DebugString() const { void CtExhaustiveHelper::Init( const absl::Span events, Model* model) { + max_task_index_ = 0; + if (events.empty() || events.size() > 100) return; + BinaryRelationsMaps* binary_relations = model->GetOrCreate(); - max_task_index_ = 0; - for (const auto& event : events) { - max_task_index_ = std::max(max_task_index_, event.task_index); - } + + std::vector sorted_events(events.begin(), events.end()); + std::sort(sorted_events.begin(), sorted_events.end(), + [](const CompletionTimeEvent& a, const CompletionTimeEvent& b) { + return a.task_index < b.task_index; + }); + max_task_index_ = sorted_events.back().task_index; predecessors_.reserve(max_task_index_ + 1); for (const auto& e1 : events) { - CHECK_LE(predecessors_.size(), e1.task_index); - while (predecessors_.size() <= e1.task_index) { - predecessors_.Add({}); - } - - // Cap the number of precedences to avoid O(n^2) time complexity. - if (predecessors_.num_entries() > 20000) break; - for (const auto& e2 : events) { if (e2.task_index == e1.task_index) continue; if (binary_relations->GetLevelZeroPrecedenceStatus(e2.end, e1.start) == RelationStatus::IS_TRUE) { + while (predecessors_.size() <= e1.task_index) predecessors_.Add({}); predecessors_.AppendToLastVector(e2.task_index); } } @@ -1138,6 +1137,7 @@ bool CtExhaustiveHelper::PermutationIsCompatibleWithPrecedences( visited_.assign(max_task_index_ + 1, false); for (int i = permutation.size() - 1; i >= 0; --i) { const CompletionTimeEvent& event = events[permutation[i]]; + if (event.task_index >= predecessors_.size()) continue; for (const int predecessor : predecessors_[event.task_index]) { if (visited_[predecessor]) return false; } @@ -1328,9 +1328,11 @@ CompletionTimeExplorationStatus ComputeMinSumOfWeightedEndMins( helper.task_to_index_[events[i].task_index] = i; } helper.valid_permutation_iterator_.Reset(events.size()); + const auto& predecessors = helper.predecessors(); for (int i = 0; i < events.size(); ++i) { const int task_i = events[i].task_index; - for (const int task_j : helper.predecessors()[task_i]) { + if (task_i >= predecessors.size()) continue; + for (const int task_j : predecessors[task_i]) { const int j = helper.task_to_index_[task_j]; if (j != -1) { helper.valid_permutation_iterator_.AddArc(j, i); @@ -1456,6 +1458,7 @@ ABSL_MUST_USE_RESULT bool GenerateShortCompletionTimeCutsWithExactBound( helper, min_sum_of_ends, min_sum_of_weighted_ends, cut_use_precedences, exploration_limit); if (status == CompletionTimeExplorationStatus::NO_VALID_PERMUTATION) { + // TODO(user): We should return false here but there is a bug. break; } else if (status == CompletionTimeExplorationStatus::ABORTED) { break; @@ -1846,6 +1849,7 @@ CutGenerator CreateCumulativeCompletionTimeCutGenerator( auto generate_cuts = [integer_trail, sat_solver, model, manager, helper, demands_helper, capacity](bool time_is_forward) -> bool { + DCHECK_EQ(sat_solver->CurrentDecisionLevel(), 0); if (!helper->SynchronizeAndSetTimeDirection(time_is_forward)) { return false; }