Skip to content

Commit

Permalink
[CP-SAT] Add new sample; change violation expression for no_overlap a…
Browse files Browse the repository at this point in the history
…nd no_overlap_2d
  • Loading branch information
lperron committed Jun 3, 2024
1 parent e427afb commit bc108f5
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 80 deletions.
1 change: 1 addition & 0 deletions ortools/sat/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ cc_library(
":util",
"//ortools/base:stl_util",
"//ortools/graph:strongly_connected_components",
"//ortools/util:dense_set",
"//ortools/util:saturated_arithmetic",
"//ortools/util:sorted_interval_list",
"@com_google_absl//absl/algorithm:container",
Expand Down
130 changes: 70 additions & 60 deletions ortools/sat/constraint_violation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1121,9 +1121,9 @@ int64_t ComputeOverloadArea(
return overload;
}

int64_t ComputeOverlap(const ConstraintProto& interval1,
const ConstraintProto& interval2,
absl::Span<const int64_t> solution) {
int64_t OverlapOfTwoIntervals(const ConstraintProto& interval1,
const ConstraintProto& interval2,
absl::Span<const int64_t> solution) {
for (const int lit : interval1.enforcement_literal()) {
if (!LiteralValue(lit, solution)) return 0;
}
Expand All @@ -1132,14 +1132,10 @@ int64_t ComputeOverlap(const ConstraintProto& interval1,
}

const int64_t start1 = ExprValue(interval1.interval().start(), solution);
const int64_t size1 = ExprValue(interval1.interval().size(), solution);
const int64_t end1 =
std::min(start1 + size1, ExprValue(interval1.interval().end(), solution));
const int64_t end1 = ExprValue(interval1.interval().end(), solution);

const int64_t start2 = ExprValue(interval2.interval().start(), solution);
const int64_t size2 = ExprValue(interval2.interval().size(), solution);
const int64_t end2 =
std::min(start2 + size2, ExprValue(interval2.interval().end(), solution));
const int64_t end2 = ExprValue(interval2.interval().end(), solution);

if (start1 >= end2 || start2 >= end1) return 0; // Disjoint.

Expand All @@ -1150,6 +1146,25 @@ int64_t ComputeOverlap(const ConstraintProto& interval1,
int64_t{1});
}

int64_t NoOverlapMinRepairDistance(const ConstraintProto& interval1,
const ConstraintProto& interval2,
absl::Span<const int64_t> solution) {
for (const int lit : interval1.enforcement_literal()) {
if (!LiteralValue(lit, solution)) return 0;
}
for (const int lit : interval2.enforcement_literal()) {
if (!LiteralValue(lit, solution)) return 0;
}

const int64_t start1 = ExprValue(interval1.interval().start(), solution);
const int64_t end1 = ExprValue(interval1.interval().end(), solution);

const int64_t start2 = ExprValue(interval2.interval().start(), solution);
const int64_t end2 = ExprValue(interval2.interval().end(), solution);

return std::max(std::min(end2 - start1, end1 - start2), int64_t{0});
}

} // namespace

CompiledNoOverlapConstraint::CompiledNoOverlapConstraint(
Expand All @@ -1160,7 +1175,7 @@ int64_t CompiledNoOverlapConstraint::ComputeViolation(
absl::Span<const int64_t> solution) {
DCHECK_GE(ct_proto().no_overlap().intervals_size(), 2);
if (ct_proto().no_overlap().intervals_size() == 2) {
return ComputeOverlap(
return NoOverlapMinRepairDistance(
cp_model_.constraints(ct_proto().no_overlap().intervals(0)),
cp_model_.constraints(ct_proto().no_overlap().intervals(1)), solution);
}
Expand All @@ -1182,37 +1197,41 @@ int64_t CompiledCumulativeConstraint::ComputeViolation(
ExprValue(ct_proto().cumulative().capacity(), solution), events_);
}

// ----- CompiledCumulativeConstraint -----
// ----- CompiledNoOverlap2dConstraint -----

CompiledNoOverlap2dConstraint::CompiledNoOverlap2dConstraint(
const ConstraintProto& ct_proto, const CpModelProto& cp_model)
: CompiledConstraint(ct_proto), cp_model_(cp_model) {}

int64_t CompiledNoOverlap2dConstraint::ComputeOverlapArea(
absl::Span<const int64_t> solution, int i, int j) const {
const int64_t x_overlap = ComputeOverlap(
cp_model_.constraints(ct_proto().no_overlap_2d().x_intervals(i)),
cp_model_.constraints(ct_proto().no_overlap_2d().x_intervals(j)),
solution);
if (x_overlap > 0) {
return x_overlap *
ComputeOverlap(
cp_model_.constraints(ct_proto().no_overlap_2d().y_intervals(i)),
cp_model_.constraints(ct_proto().no_overlap_2d().y_intervals(j)),
solution);
} else {
return 0;
}
}

int64_t CompiledNoOverlap2dConstraint::ComputeViolation(
absl::Span<const int64_t> solution) {
DCHECK_GE(ct_proto().no_overlap_2d().x_intervals_size(), 2);
const int size = ct_proto().no_overlap_2d().x_intervals_size();

int64_t violation = 0;
for (int i = 0; i + 1 < size; ++i) {
const ConstraintProto& x_i =
cp_model_.constraints(ct_proto().no_overlap_2d().x_intervals(i));
const ConstraintProto& y_i =
cp_model_.constraints(ct_proto().no_overlap_2d().y_intervals(i));
for (int j = i + 1; j < size; ++j) {
violation += ComputeOverlapArea(solution, i, j);
const ConstraintProto& x_j =
cp_model_.constraints(ct_proto().no_overlap_2d().x_intervals(j));
const ConstraintProto& y_j =
cp_model_.constraints(ct_proto().no_overlap_2d().y_intervals(j));

// TODO(user): Experiment with
// violation +=
// std::max(std::min(NoOverlapMinRepairDistance(x_i, x_j, solution),
// NoOverlapMinRepairDistance(y_i, y_j, solution)),
// int64_t{0});
// Currently, the effect is unclear on 2d packing problems.
violation +=
std::max(std::min(NoOverlapMinRepairDistance(x_i, x_j, solution) *
OverlapOfTwoIntervals(y_i, y_j, solution),
NoOverlapMinRepairDistance(y_i, y_j, solution) *
OverlapOfTwoIntervals(x_i, x_j, solution)),
int64_t{0});
}
}
return violation;
Expand Down Expand Up @@ -1392,7 +1411,7 @@ LsEvaluator::LsEvaluator(const CpModelProto& cp_model,
std::vector<ConstraintProto> additional_constraints;
CompileConstraintsAndObjective(ignored_constraints, additional_constraints);
BuildVarConstraintGraph();
pos_in_violated_constraints_.assign(NumEvaluatorConstraints(), -1);
violated_constraints_.reserve(NumEvaluatorConstraints());
}

LsEvaluator::LsEvaluator(
Expand All @@ -1405,7 +1424,7 @@ LsEvaluator::LsEvaluator(
num_violated_constraint_per_var_.assign(cp_model_.variables_size(), 0);
CompileConstraintsAndObjective(ignored_constraints, additional_constraints);
BuildVarConstraintGraph();
pos_in_violated_constraints_.assign(NumEvaluatorConstraints(), -1);
violated_constraints_.reserve(NumEvaluatorConstraints());
}

void LsEvaluator::BuildVarConstraintGraph() {
Expand Down Expand Up @@ -1550,21 +1569,22 @@ void LsEvaluator::CompileOneConstraint(const ConstraintProto& ct) {
break;
}
case ConstraintProto::ConstraintCase::kNoOverlap: {
if (ct.no_overlap().intervals_size() <= 1) break;
if (ct.no_overlap().intervals_size() >
params_.feasibility_jump_max_expanded_constraint_size()) {
const int size = ct.no_overlap().intervals_size();
if (size <= 1) break;
if (size == 2 ||
size > params_.feasibility_jump_max_expanded_constraint_size()) {
CompiledNoOverlapConstraint* no_overlap =
new CompiledNoOverlapConstraint(ct, cp_model_);
constraints_.emplace_back(no_overlap);
} else {
// We expand the no_overlap constraints into a quadratic number of
// disjunctions.
for (int i = 0; i + 1 < ct.no_overlap().intervals_size(); ++i) {
for (int i = 0; i + 1 < size; ++i) {
const IntervalConstraintProto& interval_i =
cp_model_.constraints(ct.no_overlap().intervals(i)).interval();
const int64_t min_start_i = ExprMin(interval_i.start(), cp_model_);
const int64_t max_end_i = ExprMax(interval_i.end(), cp_model_);
for (int j = i + 1; j < ct.no_overlap().intervals_size(); ++j) {
for (int j = i + 1; j < size; ++j) {
const IntervalConstraintProto& interval_j =
cp_model_.constraints(ct.no_overlap().intervals(j)).interval();
const int64_t min_start_j = ExprMin(interval_j.start(), cp_model_);
Expand All @@ -1575,9 +1595,9 @@ void LsEvaluator::CompileOneConstraint(const ConstraintProto& ct) {
ct.no_overlap().intervals(i));
disj->mutable_no_overlap()->add_intervals(
ct.no_overlap().intervals(j));
CompiledNoOverlapConstraint* no_overlap =
CompiledNoOverlapConstraint* disjunction =
new CompiledNoOverlapConstraint(*disj, cp_model_);
constraints_.emplace_back(no_overlap);
constraints_.emplace_back(disjunction);
}
}
}
Expand All @@ -1591,16 +1611,17 @@ void LsEvaluator::CompileOneConstraint(const ConstraintProto& ct) {
case ConstraintProto::ConstraintCase::kNoOverlap2D: {
const auto& x_intervals = ct.no_overlap_2d().x_intervals();
const auto& y_intervals = ct.no_overlap_2d().y_intervals();
if (x_intervals.size() <= 1) break;
if (x_intervals.size() >
params_.feasibility_jump_max_expanded_constraint_size()) {
const int size = x_intervals.size();
if (size <= 1) break;
if (size == 2 ||
size > params_.feasibility_jump_max_expanded_constraint_size()) {
CompiledNoOverlap2dConstraint* no_overlap_2d =
new CompiledNoOverlap2dConstraint(ct, cp_model_);
constraints_.emplace_back(no_overlap_2d);
break;
}

for (int i = 0; i + 1 < x_intervals.size(); ++i) {
for (int i = 0; i + 1 < size; ++i) {
const IntervalConstraintProto& x_interval_i =
cp_model_.constraints(x_intervals[i]).interval();
const int64_t x_min_start_i = ExprMin(x_interval_i.start(), cp_model_);
Expand All @@ -1609,7 +1630,7 @@ void LsEvaluator::CompileOneConstraint(const ConstraintProto& ct) {
cp_model_.constraints(y_intervals[i]).interval();
const int64_t y_min_start_i = ExprMin(y_interval_i.start(), cp_model_);
const int64_t y_max_end_i = ExprMax(y_interval_i.end(), cp_model_);
for (int j = i + 1; j < x_intervals.size(); ++j) {
for (int j = i + 1; j < size; ++j) {
const IntervalConstraintProto& x_interval_j =
cp_model_.constraints(x_intervals[j]).interval();
const int64_t x_min_start_j =
Expand Down Expand Up @@ -1869,8 +1890,6 @@ bool LsEvaluator::VariableOnlyInLinearConstraintWithConvexViolationChange(
void LsEvaluator::RecomputeViolatedList(bool linear_only) {
num_violated_constraint_per_var_.assign(cp_model_.variables_size(), 0);
violated_constraints_.clear();
pos_in_violated_constraints_.assign(NumEvaluatorConstraints(), -1);

const int num_constraints =
linear_only ? NumLinearConstraints() : NumEvaluatorConstraints();
for (int c = 0; c < num_constraints; ++c) {
Expand All @@ -1879,28 +1898,19 @@ void LsEvaluator::RecomputeViolatedList(bool linear_only) {
}

void LsEvaluator::UpdateViolatedList(const int c) {
const int pos = pos_in_violated_constraints_[c];

if (Violation(c) > 0) {
auto [it, inserted] = violated_constraints_.insert(c);
// The constraint is violated. Add if needed.
if (pos != -1) return;
pos_in_violated_constraints_[c] = violated_constraints_.size();
violated_constraints_.push_back(c);
if (!inserted) return;
for (const int v : ConstraintToVars(c)) {
num_violated_constraint_per_var_[v] += 1;
}
return;
}

// The constraint is not violated. Remove if needed.
if (pos == -1) return;
const int last = violated_constraints_.back();
pos_in_violated_constraints_[last] = pos;
violated_constraints_[pos] = last;
pos_in_violated_constraints_[c] = -1;
violated_constraints_.pop_back();
for (const int v : ConstraintToVars(c)) {
num_violated_constraint_per_var_[v] -= 1;
if (violated_constraints_.erase(c) == 1) {
for (const int v : ConstraintToVars(c)) {
num_violated_constraint_per_var_[v] -= 1;
}
}
}

Expand Down
14 changes: 4 additions & 10 deletions ortools/sat/constraint_violation.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "absl/types/span.h"
#include "ortools/sat/cp_model.pb.h"
#include "ortools/sat/sat_parameters.pb.h"
#include "ortools/util/dense_set.h"
#include "ortools/util/sorted_interval_list.h"

namespace operations_research {
Expand Down Expand Up @@ -364,8 +365,8 @@ class LsEvaluator {
//
// The order depends on the algorithm used and shouldn't be relied on.
void RecomputeViolatedList(bool linear_only);
const std::vector<int>& ViolatedConstraints() const {
return violated_constraints_;
absl::Span<const int> ViolatedConstraints() const {
return violated_constraints_.values();
}
// Returns the number of constraints in ViolatedConstraints containing `var`.
int NumViolatedConstraintsForVar(int var) const {
Expand Down Expand Up @@ -430,12 +431,7 @@ class LsEvaluator {
// We need the mutable to evaluate a move by temporarily modifying solution.
mutable std::vector<int64_t> current_solution_;

// List of violated constraints.
// Invariants:
// - pos_in_violated_constraints_[c] == -1 iff c not int violated_constraints_
// - violated_constraints_[pos_in_violated_constraints_[c]] = c
std::vector<int> pos_in_violated_constraints_;
std::vector<int> violated_constraints_;
UnsafeDenseSet<int> violated_constraints_;
std::vector<int> num_violated_constraint_per_var_;

// Constraint index and violation delta for the last update.
Expand Down Expand Up @@ -561,8 +557,6 @@ class CompiledNoOverlap2dConstraint : public CompiledConstraint {
int64_t ComputeViolation(absl::Span<const int64_t> solution) override;

private:
int64_t ComputeOverlapArea(absl::Span<const int64_t> solution, int i,
int j) const;
const CpModelProto& cp_model_;
};

Expand Down
25 changes: 20 additions & 5 deletions ortools/sat/cp_model_solver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2308,14 +2308,29 @@ void AdaptGlobalParameters(const CpModelProto& model_proto, Model* model) {
params->set_num_workers(params->num_search_workers());
}

// Initialize the number of workers if set to 0.
if (params->enumerate_all_solutions()) {
if (params->num_workers() >= 1) {
SOLVER_LOG(logger,
"Forcing sequential search as enumerating all solutions is "
"not supported in multi-thread.");
}
params->set_num_workers(1);
}

if (!model_proto.assumptions().empty()) {
if (params->num_workers() >= 1) {
SOLVER_LOG(logger,
"Forcing sequential search as assumptions are not supported "
"in multi-thread.");
}
params->set_num_workers(1);
}

if (params->num_workers() == 0) {
// Initialize the number of workers if set to 0.
#if !defined(__PORTABLE_PLATFORM__)
// Sometimes, hardware_concurrency will return 0. So always default to 1.
const int num_cores =
params->enumerate_all_solutions() || !model_proto.assumptions().empty()
? 1
: std::max<int>(std::thread::hardware_concurrency(), 1);
const int num_cores = std::max<int>(std::thread::hardware_concurrency(), 1);
#else
const int num_cores = 1;
#endif
Expand Down
Loading

0 comments on commit bc108f5

Please sign in to comment.