Skip to content

Commit 0782d13

Browse files
lperronMizux
authored andcommitted
[CP-SAT] work on lrat, clause congruence, inprocess ; bug fixes
1 parent 9190a5d commit 0782d13

File tree

14 files changed

+883
-231
lines changed

14 files changed

+883
-231
lines changed

ortools/sat/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,6 +1638,7 @@ cc_library(
16381638
"//ortools/util:sorted_interval_list",
16391639
"//ortools/util:strong_integers",
16401640
"//ortools/util:time_limit",
1641+
"@abseil-cpp//absl/algorithm:container",
16411642
"@abseil-cpp//absl/cleanup",
16421643
"@abseil-cpp//absl/container:btree",
16431644
"@abseil-cpp//absl/container:flat_hash_map",

ortools/sat/clause.cc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ ClauseManager::~ClauseManager() {
125125
}
126126

127127
void ClauseManager::Resize(int num_variables) {
128-
DCHECK(is_clean_);
129128
watchers_on_false_.resize(num_variables << 1);
130129
reasons_.resize(num_variables);
131130
needs_cleaning_.Resize(LiteralIndex(num_variables << 1));
@@ -980,8 +979,29 @@ bool BinaryImplicationGraph::AddBinaryClauseInternal(
980979
trail_->ChangeReason(trail_index, propagator_id_);
981980
}
982981

982+
// Deal with literal fixing and do not even add a binary clause in that case.
983983
if (rep_a == rep_b) {
984984
return FixLiteral(rep_a, {rep_id});
985+
} else if (trail_->CurrentDecisionLevel() == 0) {
986+
const auto& assignment = trail_->Assignment();
987+
988+
// TODO(user): just make GetUnitClauseId() work all the time? for that
989+
// we need to make sure all level zero are pushed with kUnitReason.
990+
if (lrat_proof_handler_ != nullptr) {
991+
if (assignment.LiteralIsFalse(rep_a)) {
992+
return FixLiteral(rep_b,
993+
{rep_id, trail_->GetUnitClauseId(rep_a.Variable())});
994+
} else if (assignment.LiteralIsFalse(rep_b)) {
995+
return FixLiteral(rep_a,
996+
{rep_id, trail_->GetUnitClauseId(rep_b.Variable())});
997+
}
998+
} else {
999+
if (assignment.LiteralIsFalse(rep_a)) {
1000+
return FixLiteral(rep_b, {});
1001+
} else if (assignment.LiteralIsFalse(rep_b)) {
1002+
return FixLiteral(rep_a, {});
1003+
}
1004+
}
9851005
}
9861006

9871007
a = rep_a;

ortools/sat/cp_model_checker.cc

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -284,15 +284,6 @@ std::string ValidateLinearExpression(const CpModelProto& model,
284284
return "";
285285
}
286286

287-
std::string ValidateConstantExpression(const CpModelProto& model,
288-
const LinearExpressionProto& expr) {
289-
if (!expr.vars().empty()) {
290-
return absl::StrCat("expression must be constant: ",
291-
ProtobufShortDebugString(expr));
292-
}
293-
return ValidateLinearExpression(model, expr);
294-
}
295-
296287
std::string ValidateLinearConstraint(const CpModelProto& model,
297288
const ConstraintProto& ct) {
298289
if (!DomainInProtoIsValid(ct.linear())) {
@@ -1376,8 +1367,8 @@ class ConstraintChecker {
13761367
bool LinearConstraintIsFeasible(const ConstraintProto& ct) {
13771368
int64_t sum = 0;
13781369
const int num_variables = ct.linear().coeffs_size();
1379-
const int* const vars = ct.linear().vars().data();
1380-
const int64_t* const coeffs = ct.linear().coeffs().data();
1370+
absl::Span<const int> vars = absl::MakeSpan(ct.linear().vars());
1371+
absl::Span<const int64_t> coeffs = absl::MakeSpan(ct.linear().coeffs());
13811372
for (int i = 0; i < num_variables; ++i) {
13821373
// We know we only have positive reference now.
13831374
DCHECK(RefIsPositive(vars[i]));

ortools/sat/cp_model_solver.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2529,6 +2529,12 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
25292529
}
25302530
#endif // ORTOOLS_TARGET_OS_SUPPORTS_THREADS
25312531

2532+
if (DEBUG_MODE) {
2533+
LOG(WARNING)
2534+
<< "WARNING: CP-SAT is running in debug mode. The solver will "
2535+
"be slow because we will do a lot of extra checks. Compile in "
2536+
"optimization mode to gain an order of magnitude speedup.";
2537+
}
25322538
SOLVER_LOG(logger, "");
25332539
SOLVER_LOG(logger, "Starting ", CpSatSolverVersion());
25342540
SOLVER_LOG(logger, "Parameters: ", ProtobufShortDebugString(params));

ortools/sat/csharp/CpSolver.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ public bool BooleanValue(ILiteral literal)
210210
/// <summary>
211211
/// Releases unmanaged resources and optionally releases managed resources.
212212
/// </summary>
213-
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
213+
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged
214+
/// resources.</param>
214215
protected virtual void Dispose(bool disposing)
215216
{
216217
if (_disposed)
@@ -270,4 +271,4 @@ class BestBoundCallbackDelegate : BestBoundCallback
270271
public override void NewBestBound(double bound) => _delegate(bound);
271272
}
272273

273-
} // namespace Google.OrTools.Sat
274+
} // namespace Google.OrTools.Sat

ortools/sat/gate_utils.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,28 @@ inline int AddHoleAtPosition(int i, int bitset) {
9898
return (bitset & ((1 << i) - 1)) + ((bitset >> i) << (i + 1));
9999
}
100100

101+
inline int RemoveFixedInput(int i, bool at_true,
102+
absl::Span<LiteralIndex> inputs,
103+
int& int_function_values) {
104+
DCHECK_LT(i, inputs.size());
105+
const int value = at_true ? 1 : 0;
106+
107+
// Re-compute the bitset.
108+
SmallBitset values = int_function_values;
109+
SmallBitset new_truth_table = 0;
110+
const int new_size = inputs.size() - 1;
111+
for (int p = 0; p < (1 << new_size); ++p) {
112+
const int extended_p = AddHoleAtPosition(i, p) | (value << i);
113+
new_truth_table |= ((values >> extended_p) & 1) << p;
114+
}
115+
int_function_values = new_truth_table;
116+
117+
for (int j = i + 1; j < inputs.size(); ++j) {
118+
inputs[j - 1] = inputs[j];
119+
}
120+
return new_size;
121+
}
122+
101123
// The function is target = function_values[inputs as bit position].
102124
//
103125
// TODO(user): This can be optimized with more bit twiddling if needed.

ortools/sat/gate_utils_test.cc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,47 @@ TEST(AddHoleAtPositionTest, BasicTest) {
7575
EXPECT_EQ(AddHoleAtPosition(8, 0xFF), 0b011111111);
7676
}
7777

78+
TEST(CanonicalizeFunctionTruthTableTest, AndGateWithXAndNotX) {
79+
LiteralIndex output = Literal(+1).Index();
80+
std::vector<LiteralIndex> inputs{Literal(+2).Index(), Literal(-2).Index()};
81+
int table = 0b1000;
82+
const int new_size =
83+
CanonicalizeFunctionTruthTable(output, absl::MakeSpan(inputs), table);
84+
CHECK_EQ(new_size, 0); // Fixed to zero.
85+
}
86+
87+
TEST(RemoveFixedInputTest, BasicTest1) {
88+
std::vector<LiteralIndex> inputs{Literal(+1).Index(), Literal(+2).Index(),
89+
Literal(+3).Index()};
90+
int table = 0b01011010;
91+
const int new_size = RemoveFixedInput(1, true, absl::MakeSpan(inputs), table);
92+
EXPECT_EQ(new_size, 2);
93+
EXPECT_EQ(inputs[0], Literal(+1).Index());
94+
EXPECT_EQ(inputs[1], Literal(+3).Index());
95+
EXPECT_EQ(table, 0b0110) << std::bitset<4>(table);
96+
}
97+
98+
TEST(RemoveFixedInputTest, BasicTest2) {
99+
std::vector<LiteralIndex> inputs{Literal(+1).Index(), Literal(+2).Index(),
100+
Literal(+3).Index()};
101+
int table = 0b01011010;
102+
const int new_size =
103+
RemoveFixedInput(1, false, absl::MakeSpan(inputs), table);
104+
EXPECT_EQ(new_size, 2);
105+
EXPECT_EQ(inputs[0], Literal(+1).Index());
106+
EXPECT_EQ(inputs[1], Literal(+3).Index());
107+
EXPECT_EQ(table, 0b0110) << std::bitset<4>(table);
108+
}
109+
110+
TEST(CanonicalizeFunctionTruthTableTest, AndGateWithXAndNotX2) {
111+
LiteralIndex output = Literal(+1).Index();
112+
std::vector<LiteralIndex> inputs{Literal(-2).Index(), Literal(+2).Index()};
113+
int table = 0b1000;
114+
const int new_size =
115+
CanonicalizeFunctionTruthTable(output, absl::MakeSpan(inputs), table);
116+
CHECK_EQ(new_size, 0); // Fixed to zero.
117+
}
118+
78119
TEST(CanonicalizeFunctionTruthTableTest, RandomTest) {
79120
absl::BitGen random;
80121
const int num_vars = 8;

ortools/sat/lrat_proof_handler.cc

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,6 @@ ClauseId LratProofHandler::AddAndProveInferredClauseByEnumeration(
802802
// That give us 2^(n + 1) intermediate clauses.
803803
// Their ids will be stored in (1 << k) + binary_encoding_of_the_li.
804804
const int n = to_dense_index.size() - new_clause.size();
805-
CHECK_GT(n, 0); // We dealt with this above.
806805
CHECK_EQ(n, relevant_literals.size());
807806
const int num_intermediates = 1 << (n + 1);
808807
std::vector<ClauseId> ids(num_intermediates, kNoClauseId);
@@ -838,11 +837,16 @@ ClauseId LratProofHandler::AddAndProveInferredClauseByEnumeration(
838837
// The clause is the same as the one we try to prove! or smaller.
839838
if (clauses_for_proof[i].size() == new_clause.size()) {
840839
return ids_for_proof[i];
840+
} else {
841+
// TODO(user): Likely we could have simplified what we are trying to
842+
// prove. Like I saw this happen when we prove an equivalence but we
843+
// can actually prove that the variables are fixed.
844+
const ClauseId new_id = id_generator_->GetNextId();
845+
if (!AddInferredClause(new_id, new_clause, {ids_for_proof[i]})) {
846+
return error("failed trivial inclusion proof");
847+
}
848+
return new_id;
841849
}
842-
843-
// TODO(user): if this ever happen we can create a new id and prove it
844-
// with clauses_for_proof[i], but for now I never saw that.
845-
return error("Case not yet supported");
846850
}
847851

848852
mask >>= new_clause.size();

0 commit comments

Comments
 (0)