Skip to content

Commit 185b77c

Browse files
committed
[CP-SAT] limit effort in feasibility_jump; new experimental scheduling search on cumulative
1 parent 79bd144 commit 185b77c

12 files changed

+129
-63
lines changed

ortools/sat/feasibility_jump.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,8 @@ std::function<void()> FeasibilityJumpSolver::GenerateTask(int64_t /*task_id*/) {
508508

509509
double FeasibilityJumpSolver::ComputeScore(absl::Span<const double> weights,
510510
int var, int64_t delta,
511-
bool linear_only) const {
511+
bool linear_only) {
512+
++num_scores_computed_;
512513
constexpr double kEpsilon = 1.0 / std::numeric_limits<int64_t>::max();
513514
double score =
514515
evaluator_->LinearEvaluator().WeightedViolationDelta(weights, var, delta);
@@ -818,10 +819,9 @@ bool FeasibilityJumpSolver::DoSomeGeneralIterations() {
818819
RecomputeVarsToScan(general_jumps_);
819820
InitializeCompoundWeights();
820821
auto effort = [&]() {
821-
return num_general_evals_ + num_linear_evals_ + num_weight_updates_ +
822-
num_general_moves_;
822+
return num_scores_computed_ + num_weight_updates_ + num_general_moves_;
823823
};
824-
const int64_t effort_limit = effort() + 20000;
824+
const int64_t effort_limit = effort() + 100000;
825825
while (effort() < effort_limit) {
826826
int var;
827827
int64_t value;

ortools/sat/feasibility_jump.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ class FeasibilityJumpSolver : public SubSolver {
165165

166166
// Returns the weighted violation delta plus epsilon * the objective delta.
167167
double ComputeScore(absl::Span<const double> weights, int var, int64_t delta,
168-
bool linear_only) const;
168+
bool linear_only);
169169

170170
// Computes the optimal value for variable v, considering only the violation
171171
// of linear constraints.
@@ -303,6 +303,7 @@ class FeasibilityJumpSolver : public SubSolver {
303303
int64_t num_restarts_ = 0;
304304
int64_t num_solutions_imported_ = 0;
305305
int64_t num_weight_updates_ = 0;
306+
int64_t num_scores_computed_ = 0;
306307

307308
std::unique_ptr<CompoundMoveBuilder> move_;
308309

ortools/sat/integer_search.cc

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,8 @@ std::function<BooleanOrIntegerLiteral()> CumulativePrecedenceSearchHeuristic(
679679
auto* repo = model->GetOrCreate<IntervalsRepository>();
680680
auto* integer_trail = model->GetOrCreate<IntegerTrail>();
681681
auto* trail = model->GetOrCreate<Trail>();
682-
return [repo, integer_trail, trail]() {
682+
auto* search_helper = model->GetOrCreate<IntegerSearchHelper>();
683+
return [repo, integer_trail, trail, search_helper]() {
683684
SchedulingConstraintHelper* best_helper = nullptr;
684685
int best_before = 0;
685686
int best_after = 0;
@@ -715,6 +716,7 @@ std::function<BooleanOrIntegerLiteral()> CumulativePrecedenceSearchHeuristic(
715716
// Set their demand to zero.
716717
while (next_end < num_tasks && by_emin[next_end].time == time) {
717718
const int t = by_emin[next_end].task_index;
719+
if (!helper->IsPresent(t)) continue;
718720
if (added_demand[t] > 0) {
719721
current_height -= added_demand[t];
720722
added_demand[t] = 0;
@@ -730,6 +732,7 @@ std::function<BooleanOrIntegerLiteral()> CumulativePrecedenceSearchHeuristic(
730732
// TODO(user): tie-break tasks not fitting in the profile smartly.
731733
while (next_start < num_tasks && by_smin[next_start].time == time) {
732734
const int t = by_smin[next_start].task_index;
735+
if (!helper->IsPresent(t)) continue;
733736
if (added_demand[t] == -1) continue; // Corner case.
734737
const IntegerValue demand_min = h.demand_helper->DemandMin(t);
735738
if (current_height + demand_min <= capacity_max) {
@@ -763,6 +766,9 @@ std::function<BooleanOrIntegerLiteral()> CumulativePrecedenceSearchHeuristic(
763766
}
764767
open_tasks.push_back(first_skipped_task);
765768

769+
// TODO(user): If the two box cannot overlap because of high demand, use
770+
// repo.CreateDisjunctivePrecedenceLiteral() instead.
771+
//
766772
// TODO(user): Add heuristic ordering for creating interesting precedence
767773
// first.
768774
bool found_precedence_to_add = false;
@@ -817,10 +823,29 @@ std::function<BooleanOrIntegerLiteral()> CumulativePrecedenceSearchHeuristic(
817823
//
818824
// TODO(user): We need to add the reason for demand_min and capacity_max.
819825
// TODO(user): unfortunately we can't report it from here.
826+
std::vector<IntegerLiteral> integer_reason;
827+
if (!h.capacity.IsConstant()) {
828+
integer_reason.push_back(
829+
integer_trail->UpperBoundAsLiteral(h.capacity));
830+
}
831+
const auto& demands = h.demand_helper->Demands();
832+
for (const int t : open_tasks) {
833+
if (helper->IsOptional(t)) {
834+
CHECK(trail->Assignment().LiteralIsTrue(helper->PresenceLiteral(t)));
835+
conflict.push_back(helper->PresenceLiteral(t).Negated());
836+
}
837+
const AffineExpression d = demands[t];
838+
if (!d.IsConstant()) {
839+
integer_reason.push_back(integer_trail->LowerBoundAsLiteral(d));
840+
}
841+
}
842+
integer_trail->ReportConflict(conflict, integer_reason);
843+
search_helper->NotifyThatConflictWasFoundDuringGetDecision();
820844
if (VLOG_IS_ON(2)) {
821845
LOG(INFO) << "Conflict between precedences !";
822846
for (const int t : open_tasks) LOG(INFO) << helper->TaskDebugString(t);
823847
}
848+
return BooleanOrIntegerLiteral();
824849
}
825850

826851
// TODO(user): add heuristic criteria, right now we stop at first
@@ -1252,9 +1277,9 @@ bool IntegerSearchHelper::BeforeTakingDecision() {
12521277
return true;
12531278
}
12541279

1255-
LiteralIndex IntegerSearchHelper::GetDecision(
1256-
const std::function<BooleanOrIntegerLiteral()>& f) {
1257-
LiteralIndex decision = kNoLiteralIndex;
1280+
bool IntegerSearchHelper::GetDecision(
1281+
const std::function<BooleanOrIntegerLiteral()>& f, LiteralIndex* decision) {
1282+
*decision = kNoLiteralIndex;
12581283
while (!time_limit_->LimitReached()) {
12591284
BooleanOrIntegerLiteral new_decision;
12601285
if (integer_trail_->InPropagationLoop()) {
@@ -1267,6 +1292,12 @@ LiteralIndex IntegerSearchHelper::GetDecision(
12671292
}
12681293
if (!new_decision.HasValue()) {
12691294
new_decision = f();
1295+
if (must_process_conflict_) {
1296+
must_process_conflict_ = false;
1297+
sat_solver_->ProcessCurrentConflict();
1298+
(void)sat_solver_->FinishPropagation();
1299+
return false;
1300+
}
12701301
}
12711302
if (!new_decision.HasValue() &&
12721303
integer_trail_->CurrentBranchHadAnIncompletePropagation()) {
@@ -1283,13 +1314,13 @@ LiteralIndex IntegerSearchHelper::GetDecision(
12831314
// until we have a conflict with these decisions, but it is currently
12841315
// hard to do so.
12851316
if (new_decision.boolean_literal_index != kNoLiteralIndex) {
1286-
decision = new_decision.boolean_literal_index;
1317+
*decision = new_decision.boolean_literal_index;
12871318
} else {
1288-
decision =
1319+
*decision =
12891320
encoder_->GetOrCreateAssociatedLiteral(new_decision.integer_literal)
12901321
.Index();
12911322
}
1292-
if (sat_solver_->Assignment().LiteralIsAssigned(Literal(decision))) {
1323+
if (sat_solver_->Assignment().LiteralIsAssigned(Literal(*decision))) {
12931324
// TODO(user): It would be nicer if this can never happen. For now, it
12941325
// does because of the Propagate() not reaching the fixed point as
12951326
// mentioned in a TODO above. As a work-around, we display a message
@@ -1300,7 +1331,7 @@ LiteralIndex IntegerSearchHelper::GetDecision(
13001331
}
13011332
break;
13021333
}
1303-
return decision;
1334+
return true;
13041335
}
13051336

13061337
bool IntegerSearchHelper::TakeDecision(Literal decision) {
@@ -1382,17 +1413,22 @@ SatSolver::Status IntegerSearchHelper::SolveIntegerProblem() {
13821413

13831414
LiteralIndex decision = kNoLiteralIndex;
13841415
while (true) {
1416+
if (sat_solver_->ModelIsUnsat()) return sat_solver_->UnsatStatus();
13851417
if (heuristics.next_decision_override != nullptr) {
13861418
// Note that to properly count the num_times, we do not want to move
13871419
// this function, but actually call that copy.
1388-
decision = GetDecision(heuristics.next_decision_override);
1420+
if (!GetDecision(heuristics.next_decision_override, &decision)) {
1421+
continue;
1422+
}
13891423
if (decision == kNoLiteralIndex) {
13901424
heuristics.next_decision_override = nullptr;
13911425
}
13921426
}
13931427
if (decision == kNoLiteralIndex) {
1394-
decision =
1395-
GetDecision(heuristics.decision_policies[heuristics.policy_index]);
1428+
if (!GetDecision(heuristics.decision_policies[heuristics.policy_index],
1429+
&decision)) {
1430+
continue;
1431+
}
13961432
}
13971433

13981434
// Probing?

ortools/sat/integer_search.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,16 @@ class IntegerSearchHelper {
271271

272272
// Calls the decision heuristics and extract a non-fixed literal.
273273
// Note that we do not want to copy the function here.
274-
LiteralIndex GetDecision(const std::function<BooleanOrIntegerLiteral()>& f);
274+
//
275+
// Returns false if a conflict was found while trying to take a decision.
276+
bool GetDecision(const std::function<BooleanOrIntegerLiteral()>& f,
277+
LiteralIndex* decision);
278+
279+
// Functions passed to GetDecision() might call this to notify a conflict
280+
// was detected.
281+
void NotifyThatConflictWasFoundDuringGetDecision() {
282+
must_process_conflict_ = true;
283+
}
275284

276285
// Tries to take the current decision, this might backjump.
277286
// Returns false if the model is UNSAT.
@@ -301,6 +310,8 @@ class IntegerSearchHelper {
301310
TimeLimit* time_limit_;
302311
PseudoCosts* pseudo_costs_;
303312
IntegerVariable objective_var_ = kNoIntegerVariable;
313+
314+
bool must_process_conflict_ = false;
304315
};
305316

306317
// This class will loop continuously on model variables and try to probe/shave

ortools/sat/intervals.cc

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,9 @@ void IntervalsRepository::CreateDisjunctivePrecedenceLiteral(
127127
disjunctive_precedences_.insert({{b, a}, a_before_b.Negated()});
128128

129129
// Also insert it in precedences.
130-
precedences_.insert({{a, b}, a_before_b});
131-
precedences_.insert({{b, a}, a_before_b.Negated()});
130+
// TODO(user): also add the reverse like start_b + 1 <= end_a if negated?
131+
precedences_.insert({{end_a, start_b}, a_before_b});
132+
precedences_.insert({{end_b, start_a}, a_before_b.Negated()});
132133

133134
enforcement_literals.push_back(a_before_b);
134135
AddConditionalAffinePrecedence(enforcement_literals, end_a, start_b, model_);
@@ -147,12 +148,12 @@ void IntervalsRepository::CreateDisjunctivePrecedenceLiteral(
147148

148149
bool IntervalsRepository::CreatePrecedenceLiteral(IntervalVariable a,
149150
IntervalVariable b) {
150-
if (precedences_.find({a, b}) != disjunctive_precedences_.end()) return false;
151+
const AffineExpression x = End(a);
152+
const AffineExpression y = Start(b);
153+
if (precedences_.find({x, y}) != precedences_.end()) return false;
151154

152155
// We want l => x <= y and not(l) => x > y <=> y + 1 <= x
153156
// Do not create l if the relation is always true or false.
154-
const AffineExpression x = End(a);
155-
const AffineExpression y = Start(b);
156157
if (integer_trail_->UpperBound(x) <= integer_trail_->LowerBound(y)) {
157158
return false;
158159
}
@@ -163,7 +164,9 @@ bool IntervalsRepository::CreatePrecedenceLiteral(IntervalVariable a,
163164
// Create a new literal.
164165
const BooleanVariable boolean_var = sat_solver_->NewBooleanVariable();
165166
const Literal x_before_y = Literal(boolean_var, true);
166-
precedences_.insert({{a, b}, x_before_y});
167+
168+
// TODO(user): Also add {{y_plus_one, x}, x_before_y.Negated()} ?
169+
precedences_.insert({{x, y}, x_before_y});
167170

168171
AffineExpression y_plus_one = y;
169172
y_plus_one.constant += 1;
@@ -174,7 +177,9 @@ bool IntervalsRepository::CreatePrecedenceLiteral(IntervalVariable a,
174177

175178
LiteralIndex IntervalsRepository::GetPrecedenceLiteral(
176179
IntervalVariable a, IntervalVariable b) const {
177-
const auto it = precedences_.find({a, b});
180+
const AffineExpression x = End(a);
181+
const AffineExpression y = Start(b);
182+
const auto it = precedences_.find({x, y});
178183
if (it != precedences_.end()) return it->second.Index();
179184
return kNoLiteralIndex;
180185
}

ortools/sat/intervals.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,13 @@ class IntervalsRepository {
241241
demand_helper_repository_;
242242

243243
// Disjunctive and normal precedences.
244+
//
245+
// Note that for normal precedences, we use directly the affine expression so
246+
// that if many intervals share the same start, we don't re-create Booleans
247+
// for no reason.
244248
absl::flat_hash_map<std::pair<IntervalVariable, IntervalVariable>, Literal>
245249
disjunctive_precedences_;
246-
absl::flat_hash_map<std::pair<IntervalVariable, IntervalVariable>, Literal>
250+
absl::flat_hash_map<std::pair<AffineExpression, AffineExpression>, Literal>
247251
precedences_;
248252

249253
// Disjunctive/Cumulative helpers_.

ortools/sat/lb_tree_search.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -538,8 +538,11 @@ SatSolver::Status LbTreeSearch::Search(
538538
while (true) {
539539
// TODO(user): We sometimes branch on the objective variable, this should
540540
// probably be avoided.
541-
const LiteralIndex decision =
542-
search_helper_->GetDecision(search_heuristic_);
541+
if (sat_solver_->ModelIsUnsat()) return sat_solver_->UnsatStatus();
542+
LiteralIndex decision;
543+
if (!search_helper_->GetDecision(search_heuristic_, &decision)) {
544+
continue;
545+
}
543546

544547
// No new decision: search done.
545548
if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;

ortools/sat/sat_parameters.proto

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -745,9 +745,8 @@ message SatParameters {
745745

746746
// Whether we try to branch on decision "interval A before interval B" rather
747747
// than on intervals bounds. This usually works better, but slow down a bit
748-
// the time to find first solutions.
748+
// the time to find the first solution.
749749
//
750-
// Note that for cumulative, this currently seems worse.
751750
// These parameters are still EXPERIMENTAL, the result should be correct, but
752751
// it some corner cases, they can cause some failing CHECK in the solver.
753752
optional bool use_dynamic_precedence_in_disjunctive = 263 [default = false];

ortools/sat/sat_solver.cc

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,8 @@ bool SatSolver::FinishPropagation() {
584584
int num_loop = 0;
585585
while (true) {
586586
const int old_decision_level = current_decision_level_;
587-
if (!PropagateAndStopAfterOneConflictResolution()) {
587+
if (!Propagate()) {
588+
ProcessCurrentConflict();
588589
if (model_is_unsat_) return false;
589590
if (current_decision_level_ == old_decision_level) {
590591
CHECK(!assumptions_.empty());
@@ -682,10 +683,9 @@ bool SatSolver::ReapplyAssumptionsIfNeeded() {
682683
return (status == SatSolver::FEASIBLE);
683684
}
684685

685-
bool SatSolver::PropagateAndStopAfterOneConflictResolution() {
686+
void SatSolver::ProcessCurrentConflict() {
686687
SCOPED_TIME_STAT(&stats_);
687-
if (Propagate()) return true;
688-
if (model_is_unsat_) return false;
688+
if (model_is_unsat_) return;
689689

690690
++counters_.num_failures;
691691
const int conflict_trail_index = trail_->Index();
@@ -703,15 +703,15 @@ bool SatSolver::PropagateAndStopAfterOneConflictResolution() {
703703
// it reduces to only one literal in which case we can just fix it.
704704
const int highest_level =
705705
DecisionLevel((*trail_)[max_trail_index].Variable());
706-
if (highest_level == 1) return false;
706+
if (highest_level == 1) return;
707707
}
708708

709709
ComputeFirstUIPConflict(max_trail_index, &learned_conflict_,
710710
&reason_used_to_infer_the_conflict_,
711711
&subsumed_clauses_);
712712

713713
// An empty conflict means that the problem is UNSAT.
714-
if (learned_conflict_.empty()) return SetModelUnsat();
714+
if (learned_conflict_.empty()) return (void)SetModelUnsat();
715715
DCHECK(IsConflictValid(learned_conflict_));
716716
DCHECK(ClauseIsValidUnderDebugAssignment(learned_conflict_));
717717

@@ -789,7 +789,7 @@ bool SatSolver::PropagateAndStopAfterOneConflictResolution() {
789789
int pb_backjump_level;
790790
ComputePBConflict(max_trail_index, initial_slack, &pb_conflict_,
791791
&pb_backjump_level);
792-
if (pb_backjump_level == -1) return SetModelUnsat();
792+
if (pb_backjump_level == -1) return (void)SetModelUnsat();
793793

794794
// Convert the conflict into the vector<LiteralWithCoeff> form.
795795
std::vector<LiteralWithCoeff> cst;
@@ -817,7 +817,7 @@ bool SatSolver::PropagateAndStopAfterOneConflictResolution() {
817817
trail_));
818818
CHECK_GT(trail_->Index(), last_decision_or_backtrack_trail_index_);
819819
counters_.num_learned_pb_literals += cst.size();
820-
return false;
820+
return;
821821
}
822822

823823
// Continue with the normal clause flow, but use the PB conflict clause
@@ -941,7 +941,6 @@ bool SatSolver::PropagateAndStopAfterOneConflictResolution() {
941941
learned_conflict_, is_redundant);
942942
restart_->OnConflict(conflict_trail_index, conflict_decision_level,
943943
conflict_lbd);
944-
return false;
945944
}
946945

947946
SatSolver::Status SatSolver::ReapplyDecisionsUpTo(
@@ -1339,8 +1338,9 @@ SatSolver::Status SatSolver::SolveInternal(TimeLimit* time_limit,
13391338
}
13401339

13411340
const int old_level = current_decision_level_;
1342-
if (!PropagateAndStopAfterOneConflictResolution()) {
1341+
if (!Propagate()) {
13431342
// A conflict occurred, continue the loop.
1343+
ProcessCurrentConflict();
13441344
if (model_is_unsat_) return StatusWithLog(INFEASIBLE);
13451345
if (old_level == current_decision_level_) {
13461346
CHECK(!assumptions_.empty());

0 commit comments

Comments
 (0)