Skip to content

Commit 8dd1169

Browse files
committed
[CP-SAT] speed up violation ls; improve presolve and symmetries, add transition time/cost sample
1 parent 1270579 commit 8dd1169

11 files changed

+577
-261
lines changed

ortools/sat/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ cc_library(
224224
"//ortools/algorithms:binary_search",
225225
"//ortools/util:sorted_interval_list",
226226
"//ortools/util:strong_integers",
227+
"@com_google_absl//absl/container:flat_hash_map",
227228
"@com_google_absl//absl/functional:any_invocable",
228229
"@com_google_absl//absl/functional:bind_front",
229230
"@com_google_absl//absl/functional:function_ref",
@@ -233,6 +234,7 @@ cc_library(
233234
"@com_google_absl//absl/random:bit_gen_ref",
234235
"@com_google_absl//absl/random:distributions",
235236
"@com_google_absl//absl/strings",
237+
"@com_google_absl//absl/synchronization",
236238
"@com_google_absl//absl/types:span",
237239
],
238240
)

ortools/sat/constraint_violation.cc

Lines changed: 69 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -218,26 +218,32 @@ void LinearIncrementalEvaluator::ComputeInitialActivities(
218218
num_false_enforcement_.assign(num_constraints_, 0);
219219

220220
// Update these numbers for all columns.
221-
for (int var = 0; var < columns_.size(); ++var) {
221+
const int num_vars = columns_.size();
222+
for (int var = 0; var < num_vars; ++var) {
222223
const SpanData& data = columns_[var];
223224
const int64_t value = solution[var];
224225

225-
int i = data.start;
226-
for (int k = 0; k < data.num_pos_literal; ++k, ++i) {
227-
const int c = ct_buffer_[i];
228-
if (value == 0) num_false_enforcement_[c]++;
226+
if (value == 0 && data.num_pos_literal > 0) {
227+
const int* ct_indices = &ct_buffer_[data.start];
228+
for (int k = 0; k < data.num_pos_literal; ++k) {
229+
num_false_enforcement_[ct_indices[k]]++;
230+
}
229231
}
230-
for (int k = 0; k < data.num_neg_literal; ++k, ++i) {
231-
const int c = ct_buffer_[i];
232-
if (value == 1) num_false_enforcement_[c]++;
232+
233+
if (value == 1 && data.num_neg_literal > 0) {
234+
const int* ct_indices = &ct_buffer_[data.start + data.num_pos_literal];
235+
for (int k = 0; k < data.num_neg_literal; ++k) {
236+
num_false_enforcement_[ct_indices[k]]++;
237+
}
233238
}
234239

235-
if (value == 0) continue;
236-
int j = data.linear_start;
237-
for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
238-
const int c = ct_buffer_[i];
239-
const int64_t coeff = coeff_buffer_[j];
240-
activities_[c] += coeff * value;
240+
if (value != 0 && data.num_linear_entries > 0) {
241+
const int* ct_indices =
242+
&ct_buffer_[data.start + data.num_pos_literal + data.num_neg_literal];
243+
const int64_t* coeffs = &coeff_buffer_[data.linear_start];
244+
for (int k = 0; k < data.num_linear_entries; ++k) {
245+
activities_[ct_indices[k]] += coeffs[k] * value;
246+
}
241247
}
242248
}
243249

@@ -249,9 +255,15 @@ void LinearIncrementalEvaluator::ComputeInitialActivities(
249255
}
250256

251257
void LinearIncrementalEvaluator::ClearAffectedVariables() {
252-
in_last_affected_variables_.resize(columns_.size(), false);
253-
for (const int var : last_affected_variables_) {
254-
in_last_affected_variables_[var] = false;
258+
if (10 * last_affected_variables_.size() < columns_.size()) {
259+
// Sparse.
260+
in_last_affected_variables_.resize(columns_.size(), false);
261+
for (const int var : last_affected_variables_) {
262+
in_last_affected_variables_[var] = false;
263+
}
264+
} else {
265+
// Dense.
266+
in_last_affected_variables_.assign(columns_.size(), false);
255267
}
256268
last_affected_variables_.clear();
257269
DCHECK(std::all_of(in_last_affected_variables_.begin(),
@@ -315,19 +327,18 @@ void LinearIncrementalEvaluator::UpdateScoreOnWeightUpdate(
315327
};
316328

317329
const int64_t old_distance = distances_[c];
330+
const int64_t activity = activities_[c];
318331
for (int k = 0; k < data.num_linear_entries; ++k) {
319332
const int var = row_vars[k];
320333
const int64_t coeff = row_coeffs[k];
321-
const int64_t new_distance =
322-
violation(activities_[c] + coeff * jump_deltas[var]);
334+
const int64_t diff =
335+
violation(activity + coeff * jump_deltas[var]) - old_distance;
323336
if (!in_last_affected_variables_[var]) {
324-
var_to_score_change[var] =
325-
static_cast<double>(new_distance - old_distance);
337+
var_to_score_change[var] = static_cast<double>(diff);
326338
in_last_affected_variables_[var] = true;
327339
last_affected_variables_.push_back(var);
328340
} else {
329-
var_to_score_change[var] +=
330-
static_cast<double>(new_distance - old_distance);
341+
var_to_score_change[var] += static_cast<double>(diff);
331342
}
332343
}
333344
}
@@ -1842,65 +1853,55 @@ bool LsEvaluator::ReduceObjectiveBounds(int64_t lb, int64_t ub) {
18421853
return false;
18431854
}
18441855

1845-
void LsEvaluator::OverwriteCurrentSolution(absl::Span<const int64_t> solution) {
1846-
current_solution_.assign(solution.begin(), solution.end());
1847-
}
1848-
1849-
void LsEvaluator::ComputeAllViolations() {
1856+
void LsEvaluator::ComputeAllViolations(absl::Span<const int64_t> solution) {
18501857
// Linear constraints.
1851-
linear_evaluator_.ComputeInitialActivities(current_solution_);
1858+
linear_evaluator_.ComputeInitialActivities(solution);
18521859

18531860
// Generic constraints.
18541861
for (const auto& ct : constraints_) {
1855-
ct->InitializeViolation(current_solution_);
1862+
ct->InitializeViolation(solution);
18561863
}
18571864

18581865
RecomputeViolatedList(/*linear_only=*/false);
18591866
}
18601867

1861-
void LsEvaluator::UpdateAllNonLinearViolations() {
1868+
void LsEvaluator::ComputeAllNonLinearViolations(
1869+
absl::Span<const int64_t> solution) {
18621870
// Generic constraints.
18631871
for (const auto& ct : constraints_) {
1864-
ct->InitializeViolation(current_solution_);
1872+
ct->InitializeViolation(solution);
18651873
}
18661874
}
18671875

1868-
void LsEvaluator::UpdateNonLinearViolations(int var, int64_t new_value) {
1869-
const int64_t old_value = current_solution_[var];
1870-
if (old_value == new_value) return;
1871-
1872-
current_solution_[var] = new_value;
1876+
void LsEvaluator::UpdateNonLinearViolations(
1877+
int var, int64_t old_value, absl::Span<const int64_t> new_solution) {
18731878
for (const int general_ct_index : var_to_constraints_[var]) {
18741879
const int c = general_ct_index + linear_evaluator_.num_constraints();
18751880
const int64_t v0 = constraints_[general_ct_index]->violation();
1876-
constraints_[general_ct_index]->PerformMove(var, old_value,
1877-
current_solution_);
1881+
constraints_[general_ct_index]->PerformMove(var, old_value, new_solution);
18781882
const int64_t violation_delta =
18791883
constraints_[general_ct_index]->violation() - v0;
18801884
if (violation_delta != 0) {
18811885
last_update_violation_changes_.push_back(c);
18821886
}
18831887
}
1884-
current_solution_[var] = old_value;
18851888
}
18861889

1887-
void LsEvaluator::UpdateLinearScores(int var, int64_t value,
1890+
void LsEvaluator::UpdateLinearScores(int var, int64_t old_value,
1891+
int64_t new_value,
18881892
absl::Span<const double> weights,
18891893
absl::Span<const int64_t> jump_deltas,
18901894
absl::Span<double> jump_scores) {
18911895
DCHECK(RefIsPositive(var));
1892-
const int64_t old_value = current_solution_[var];
1893-
if (old_value == value) return;
1896+
if (old_value == new_value) return;
18941897
last_update_violation_changes_.clear();
18951898
linear_evaluator_.ClearAffectedVariables();
1896-
linear_evaluator_.UpdateVariableAndScores(var, value - old_value, weights,
1899+
linear_evaluator_.UpdateVariableAndScores(var, new_value - old_value, weights,
18971900
jump_deltas, jump_scores,
18981901
&last_update_violation_changes_);
18991902
}
19001903

1901-
void LsEvaluator::UpdateVariableValue(int var, int64_t new_value) {
1902-
current_solution_[var] = new_value;
1903-
1904+
void LsEvaluator::UpdateViolatedList() {
19041905
// Maintain the list of violated constraints.
19051906
dtime_ += 1e-8 * last_update_violation_changes_.size();
19061907
for (const int c : last_update_violation_changes_) {
@@ -1977,44 +1978,42 @@ bool LsEvaluator::IsViolated(int c) const {
19771978

19781979
double LsEvaluator::WeightedViolation(absl::Span<const double> weights) const {
19791980
DCHECK_EQ(weights.size(), NumEvaluatorConstraints());
1980-
double violations = linear_evaluator_.WeightedViolation(weights);
1981+
double result = linear_evaluator_.WeightedViolation(weights);
19811982

19821983
const int num_linear_constraints = linear_evaluator_.num_constraints();
19831984
for (int c = 0; c < constraints_.size(); ++c) {
1984-
violations += static_cast<double>(constraints_[c]->violation()) *
1985-
weights[num_linear_constraints + c];
1985+
result += static_cast<double>(constraints_[c]->violation()) *
1986+
weights[num_linear_constraints + c];
19861987
}
1987-
return violations;
1988+
return result;
19881989
}
19891990

1990-
double LsEvaluator::WeightedNonLinearViolationDelta(
1991-
absl::Span<const double> weights, int var, int64_t delta) const {
1992-
const int64_t old_value = current_solution_[var];
1993-
double violation_delta = 0;
1994-
// We change the mutable solution here, are restore it after the evaluation.
1995-
current_solution_[var] += delta;
1991+
double LsEvaluator::WeightedViolationDelta(
1992+
bool linear_only, absl::Span<const double> weights, int var, int64_t delta,
1993+
absl::Span<int64_t> mutable_solution) const {
1994+
double result = linear_evaluator_.WeightedViolationDelta(weights, var, delta);
1995+
if (linear_only) return result;
1996+
1997+
// We change the mutable solution here, and restore it after the evaluation.
1998+
const int64_t old_value = mutable_solution[var];
1999+
mutable_solution[var] += delta;
2000+
19962001
const int num_linear_constraints = linear_evaluator_.num_constraints();
19972002
for (const int ct_index : var_to_constraints_[var]) {
19982003
// We assume linear time delta computation in number of variables.
19992004
// TODO(user): refine on a per constraint basis.
20002005
dtime_ += 1e-8 * static_cast<double>(constraint_to_vars_[ct_index].size());
20012006

20022007
DCHECK_LT(ct_index, constraints_.size());
2003-
const int64_t delta = constraints_[ct_index]->ViolationDelta(
2004-
var, old_value, current_solution_);
2005-
violation_delta +=
2006-
static_cast<double>(delta) * weights[ct_index + num_linear_constraints];
2008+
const int64_t ct_delta = constraints_[ct_index]->ViolationDelta(
2009+
var, old_value, mutable_solution);
2010+
result += static_cast<double>(ct_delta) *
2011+
weights[ct_index + num_linear_constraints];
20072012
}
2008-
// Restore.
2009-
current_solution_[var] -= delta;
2010-
return violation_delta;
2011-
}
20122013

2013-
double LsEvaluator::WeightedViolationDelta(absl::Span<const double> weights,
2014-
int var, int64_t delta) const {
2015-
DCHECK_LT(var, current_solution_.size());
2016-
return linear_evaluator_.WeightedViolationDelta(weights, var, delta) +
2017-
WeightedNonLinearViolationDelta(weights, var, delta);
2014+
// Restore.
2015+
mutable_solution[var] = old_value;
2016+
return result;
20182017
}
20192018

20202019
bool LsEvaluator::VariableOnlyInLinearConstraintWithConvexViolationChange(

ortools/sat/constraint_violation.h

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,10 @@ class LinearIncrementalEvaluator {
7474
void UpdateScoreOnWeightUpdate(int c, absl::Span<const int64_t> jump_deltas,
7575
absl::Span<double> var_to_score_change);
7676

77-
// Variables whose score might have decreased since the last clear for at
78-
// least one jump value.
77+
// Variables whose score/jump might have changed since the last clear.
7978
//
8079
// Note that because we reason on a per-constraint basis, this is actually
81-
// independent of the set of positive constraint weight used. We just list
82-
// variable for which the contribution to the violation of one constraint
83-
// might have decreased for any jump value.
80+
// independent of the set of positive constraint weight used.
8481
void ClearAffectedVariables();
8582
absl::Span<const int> VariablesAffectedByLastUpdate() const {
8683
return last_affected_variables_;
@@ -318,27 +315,24 @@ class LsEvaluator {
318315
// It returns true if a reduction of the domain took place.
319316
bool ReduceObjectiveBounds(int64_t lb, int64_t ub);
320317

321-
// Overwrites the current solution.
322-
void OverwriteCurrentSolution(absl::Span<const int64_t> solution);
323-
324-
// Computes the violations of all constraints.
325-
void ComputeAllViolations();
326-
327-
// Recompute the violations of non linear constraints.
328-
void UpdateAllNonLinearViolations();
329-
330-
// Sets the value of the variable in the current solution.
331-
// It must be called after UpdateLinearScores().
332-
void UpdateVariableValue(int var, int64_t new_value);
318+
// Recomputes the violations of all constraints (resp only non-linear one).
319+
void ComputeAllViolations(absl::Span<const int64_t> solution);
320+
void ComputeAllNonLinearViolations(absl::Span<const int64_t> solution);
333321

334322
// Recomputes the violations of all impacted non linear constraints.
335-
void UpdateNonLinearViolations(int var, int64_t new_value);
323+
void UpdateNonLinearViolations(int var, int64_t old_value,
324+
absl::Span<const int64_t> new_solution);
336325

337326
// Function specific to the linear only feasibility jump.
338-
void UpdateLinearScores(int var, int64_t value,
327+
void UpdateLinearScores(int var, int64_t old_value, int64_t new_value,
339328
absl::Span<const double> weights,
340329
absl::Span<const int64_t> jump_deltas,
341330
absl::Span<double> jump_scores);
331+
332+
// Must be called after UpdateLinearScores() / UpdateNonLinearViolations()
333+
// in order to update the ViolatedConstraints().
334+
void UpdateViolatedList();
335+
342336
absl::Span<const int> VariablesAffectedByLastLinearUpdate() const {
343337
return linear_evaluator_.VariablesAffectedByLastUpdate();
344338
}
@@ -371,15 +365,20 @@ class LsEvaluator {
371365
int64_t Violation(int c) const;
372366
bool IsViolated(int c) const;
373367
double WeightedViolation(absl::Span<const double> weights) const;
374-
double WeightedViolationDelta(absl::Span<const double> weights, int var,
375-
int64_t delta) const;
376-
// Ignores the violations of the linear constraints.
377-
double WeightedNonLinearViolationDelta(absl::Span<const double> weights,
378-
int var, int64_t delta) const;
368+
369+
// Computes the delta in weighted violation if solution[var] += delta.
370+
// We need a temporary mutable solution to evaluate the violation of generic
371+
// constraints. If linear_only is true, only the linear violation will be
372+
// used.
373+
double WeightedViolationDelta(bool linear_only,
374+
absl::Span<const double> weights, int var,
375+
int64_t delta,
376+
absl::Span<int64_t> mutable_solution) const;
379377

380378
const LinearIncrementalEvaluator& LinearEvaluator() {
381379
return linear_evaluator_;
382380
}
381+
383382
LinearIncrementalEvaluator* MutableLinearEvaluator() {
384383
return &linear_evaluator_;
385384
}
@@ -402,15 +401,6 @@ class LsEvaluator {
402401
// Indicates if the computed jump value is always the best choice.
403402
bool VariableOnlyInLinearConstraintWithConvexViolationChange(int var) const;
404403

405-
// Access the solution stored.
406-
const std::vector<int64_t>& current_solution() const {
407-
return current_solution_;
408-
}
409-
410-
std::vector<int64_t>* mutable_current_solution() {
411-
return &current_solution_;
412-
}
413-
414404
const std::vector<int>& last_update_violation_changes() const {
415405
return last_update_violation_changes_;
416406
}
@@ -454,9 +444,6 @@ class LsEvaluator {
454444
std::vector<std::vector<int>> constraint_to_vars_;
455445
std::vector<bool> jump_value_optimal_;
456446

457-
// We need the mutable to evaluate a move by temporarily modifying solution.
458-
mutable std::vector<int64_t> current_solution_;
459-
460447
UnsafeDenseSet<int> violated_constraints_;
461448
std::vector<int> num_violated_constraint_per_var_ignoring_objective_;
462449

0 commit comments

Comments
 (0)