Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
two_sat_test.cc
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
6 version 2.0.0b
7 https://github.com/lrleon/Aleph-w
8
9 This file is part of Aleph-w library
10
11 Copyright (c) 2002-2026 Leandro Rabindranath Leon
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30*/
31
32# include <gtest/gtest.h>
33# include <random>
34# include <limits>
35
36# include <Two_Sat.H>
37# include <tpl_sgraph.H>
38# include <tpl_agraph.H>
39
40using namespace Aleph;
41
42// ---------------------------------------------------------------------------
43// Helper: verify that an assignment satisfies a list of clauses.
44// Each clause is a pair of signed 1-based variables.
45// ---------------------------------------------------------------------------
47 const Array<std::pair<long, long>> & clauses)
48{
49 auto lit_val = [&](long v) -> bool {
50 return v > 0 ? assignment(static_cast<size_t>(v - 1))
51 : not assignment(static_cast<size_t>(-v - 1));
52 };
53
54 for (size_t i = 0; i < clauses.size(); ++i)
55 {
56 auto [a, b] = clauses(i);
57 if (not lit_val(a) and not lit_val(b))
58 return false;
59 }
60 return true;
61}
62
63// ---------------------------------------------------------------------------
64// Basic tests
65// ---------------------------------------------------------------------------
66
68{
69 Two_Sat<> sat(3);
70
71 auto [ok, assignment] = sat.solve();
73 EXPECT_EQ(assignment.size(), 3U);
74}
75
77{
78 // (x0 OR x1)
79 Two_Sat<> sat(2);
80 sat.add_clause(sat.pos_lit(0), sat.pos_lit(1));
81
82 auto [ok, assignment] = sat.solve();
84
85 // At least one must be true
87}
88
90{
91 // Force x0 = true
92 Two_Sat<> sat(1);
93 sat.set_true(sat.pos_lit(0));
94
95 auto [ok, assignment] = sat.solve();
98}
99
101{
102 // Force x0 = false
103 Two_Sat<> sat(1);
104 sat.set_false(sat.pos_lit(0));
105
106 auto [ok, assignment] = sat.solve();
109}
110
112{
113 // x0 AND ~x0 is UNSAT
114 Two_Sat<> sat(1);
115 sat.set_true(sat.pos_lit(0));
116 sat.set_true(sat.neg_lit(0));
117
118 auto [ok, assignment] = sat.solve();
120}
121
123{
124 // x0 -> x1, x0 forced true => x1 must be true
125 Two_Sat<> sat(2);
126 sat.set_true(sat.pos_lit(0));
127 sat.add_implication(sat.pos_lit(0), sat.pos_lit(1));
128
129 auto [ok, assignment] = sat.solve();
133}
134
136{
137 // x0 -> x1 -> x2 -> x3, x0 = true => all true
138 Two_Sat<> sat(4);
139 sat.set_true(sat.pos_lit(0));
140 sat.add_implication(sat.pos_lit(0), sat.pos_lit(1));
141 sat.add_implication(sat.pos_lit(1), sat.pos_lit(2));
142 sat.add_implication(sat.pos_lit(2), sat.pos_lit(3));
143
144 auto [ok, assignment] = sat.solve();
146 for (size_t i = 0; i < 4; ++i)
147 EXPECT_TRUE(assignment(i)) << "variable " << i;
148}
149
151{
152 // x0 -> x1 -> x2, x0 = true, x2 = false => UNSAT
153 Two_Sat<> sat(3);
154 sat.set_true(sat.pos_lit(0));
155 sat.add_implication(sat.pos_lit(0), sat.pos_lit(1));
156 sat.add_implication(sat.pos_lit(1), sat.pos_lit(2));
157 sat.set_false(sat.pos_lit(2));
158
159 EXPECT_FALSE(sat.is_satisfiable());
160}
161
163{
164 // x0 <-> x1, x0 = true => x1 = true
165 Two_Sat<> sat(2);
166 sat.set_true(sat.pos_lit(0));
167 sat.add_equiv(sat.pos_lit(0), sat.pos_lit(1));
168
169 auto [ok, assignment] = sat.solve();
173}
174
176{
177 // x0 XOR x1: exactly one is true
178 Two_Sat<> sat(2);
179 sat.add_xor(sat.pos_lit(0), sat.pos_lit(1));
180
181 auto [ok, assignment] = sat.solve();
184}
185
187{
188 // x0 XOR x1, but also x0 = true, x1 = true => UNSAT
189 Two_Sat<> sat(2);
190 sat.add_xor(sat.pos_lit(0), sat.pos_lit(1));
191 sat.set_true(sat.pos_lit(0));
192 sat.set_true(sat.pos_lit(1));
193
194 EXPECT_FALSE(sat.is_satisfiable());
195}
196
197// ---------------------------------------------------------------------------
198// Signed variable API
199// ---------------------------------------------------------------------------
200
202{
203 // (x0 OR x1) AND (~x0 OR x2) AND (~x1 OR ~x2)
204 Two_Sat<> sat(3);
205 sat.add_clause_signed(1, 2);
206 sat.add_clause_signed(-1, 3);
207 sat.add_clause_signed(-2, -3);
208
209 Array<std::pair<long, long>> clauses = {{1, 2}, {-1, 3}, {-2, -3}};
210
211 auto [ok, assignment] = sat.solve();
214}
215
217{
218 // (x0) AND (~x0)
219 Two_Sat<> sat(1);
220 sat.add_clause_signed(1, 1);
221 sat.add_clause_signed(-1, -1);
222
223 EXPECT_FALSE(sat.is_satisfiable());
224}
225
226// ---------------------------------------------------------------------------
227// At most one
228// ---------------------------------------------------------------------------
229
231{
232 // At most one of x0, x1, x2 is true.
233 Two_Sat<> sat(3);
234 Array<size_t> lits = {sat.pos_lit(0), sat.pos_lit(1), sat.pos_lit(2)};
235 sat.add_at_most_one(lits);
236
237 auto [ok, assignment] = sat.solve();
239
240 int count = 0;
241 for (size_t i = 0; i < 3; ++i)
242 if (assignment(i))
243 ++count;
244 EXPECT_LE(count, 1);
245}
246
248{
249 // At most one of x0, x1, x2 is true; x0 forced true.
250 // => x1 = false, x2 = false
251 Two_Sat<> sat(3);
252 Array<size_t> lits = {sat.pos_lit(0), sat.pos_lit(1), sat.pos_lit(2)};
253 sat.add_at_most_one(lits);
254 sat.set_true(sat.pos_lit(0));
255
256 auto [ok, assignment] = sat.solve();
261}
262
264{
265 // At most one of x0, x1, x2; but x0 and x1 forced true => UNSAT
266 Two_Sat<> sat(3);
267 Array<size_t> lits = {sat.pos_lit(0), sat.pos_lit(1), sat.pos_lit(2)};
268 sat.add_at_most_one(lits);
269 sat.set_true(sat.pos_lit(0));
270 sat.set_true(sat.pos_lit(1));
271
272 EXPECT_FALSE(sat.is_satisfiable());
273}
274
276{
277 Two_Sat<> sat(3);
278
279 // Singleton: at most one of {x0} is true. Always SAT.
280 Array<size_t> singleton = {sat.pos_lit(0)};
281 sat.add_at_most_one(singleton);
282 EXPECT_TRUE(sat.is_satisfiable());
283
284 // Duplicate: at most one of {x1, x1} is true. Equivalent to at most one {x1}.
285 // Should NOT add (~x1 or ~x1) which would force x1 to be false.
286 Two_Sat<> sat2(3);
287 Array<size_t> duplicates = {sat2.pos_lit(1), sat2.pos_lit(1)};
288 sat2.add_at_most_one(duplicates);
289 sat2.set_true(sat2.pos_lit(1));
290 EXPECT_TRUE(sat2.is_satisfiable());
291
292 // Out of range validation in add_at_most_one.
293 EXPECT_THROW(sat.add_at_most_one({sat.pos_lit(5)}), std::domain_error);
294}
295
296// ---------------------------------------------------------------------------
297// Classical examples
298// ---------------------------------------------------------------------------
299
301{
302 // 2-color a triangle (3 nodes, 3 edges) => UNSAT
303 // Variables: x0, x1, x2 (true = color A, false = color B)
304 // Edge (i,j): xi XOR xj (different colors)
305 Two_Sat<> sat(3);
306 sat.add_xor(sat.pos_lit(0), sat.pos_lit(1)); // edge 0-1
307 sat.add_xor(sat.pos_lit(1), sat.pos_lit(2)); // edge 1-2
308 sat.add_xor(sat.pos_lit(0), sat.pos_lit(2)); // edge 0-2
309
310 EXPECT_FALSE(sat.is_satisfiable());
311}
312
314{
315 // 2-color a path 0-1-2-3 => SAT (bipartite)
316 Two_Sat<> sat(4);
317 sat.add_xor(sat.pos_lit(0), sat.pos_lit(1));
318 sat.add_xor(sat.pos_lit(1), sat.pos_lit(2));
319 sat.add_xor(sat.pos_lit(2), sat.pos_lit(3));
320
321 auto [ok, assignment] = sat.solve();
323
324 // Adjacent nodes must differ
328}
329
331{
332 // All clauses are (x0 OR x0) => x0 = true
333 Two_Sat<> sat(2);
334 sat.add_clause(sat.pos_lit(0), sat.pos_lit(0));
335
336 auto [ok, assignment] = sat.solve();
339}
340
341// ---------------------------------------------------------------------------
342// Bounds checking
343// ---------------------------------------------------------------------------
344
346{
347 Two_Sat<> sat(3);
348
349 EXPECT_THROW(sat.add_clause(sat.pos_lit(3), sat.pos_lit(0)),
350 std::domain_error);
351 EXPECT_THROW(sat.add_clause(sat.pos_lit(0), sat.neg_lit(5)),
352 std::domain_error);
353 EXPECT_THROW(sat.add_clause_signed(0, 1), std::domain_error);
354 EXPECT_THROW(sat.add_clause_signed(1, 0), std::domain_error);
355 EXPECT_THROW(sat.add_clause_signed(std::numeric_limits<long>::min(), 1),
356 std::domain_error);
357 EXPECT_THROW(sat.add_clause_signed(1, std::numeric_limits<long>::min()),
358 std::domain_error);
359}
360
361// ---------------------------------------------------------------------------
362// Stress test with brute force verification
363// ---------------------------------------------------------------------------
364
366{
367 // Test with small enough n to brute-force all 2^n assignments
368 constexpr size_t N = 8;
369 constexpr size_t NUM_CLAUSES = 20;
370 constexpr size_t NUM_TRIALS = 50;
371
372 std::mt19937 rng(2024);
373 std::uniform_int_distribution<size_t> var_dist(0, N - 1);
374 std::uniform_int_distribution<int> sign_dist(0, 1);
375
376 for (size_t trial = 0; trial < NUM_TRIALS; ++trial)
377 {
378 Two_Sat<> sat(N);
380
381 for (size_t c = 0; c < NUM_CLAUSES; ++c)
382 {
383 long a = static_cast<long>(var_dist(rng)) + 1;
384 long b = static_cast<long>(var_dist(rng)) + 1;
385 if (sign_dist(rng)) a = -a;
386 if (sign_dist(rng)) b = -b;
387 sat.add_clause_signed(a, b);
388 clauses.append(std::pair<long, long>(a, b));
389 }
390
391 auto [ok, assignment] = sat.solve();
392
393 // Brute-force check
394 bool brute_sat = false;
395 for (unsigned mask = 0; mask < (1U << N); ++mask)
396 {
398 for (size_t i = 0; i < N; ++i)
399 bf(i) = ((mask >> i) & 1) != 0;
400
402 {
403 brute_sat = true;
404 break;
405 }
406 }
407
409 << "Trial " << trial << ": solver says "
410 << (ok ? "SAT" : "UNSAT") << " but brute-force says "
411 << (brute_sat ? "SAT" : "UNSAT");
412
413 if (ok)
415 << "Trial " << trial << ": assignment does not satisfy formula";
416 }
417}
418
419// ---------------------------------------------------------------------------
420// Test with different graph types
421// ---------------------------------------------------------------------------
422
424{
426 Two_Sat<DG> sat(3);
427
428 // (x0 OR x1) AND (~x0 OR x2) AND (~x1 OR ~x2)
429 sat.add_clause(sat.pos_lit(0), sat.pos_lit(1));
430 sat.add_clause(sat.neg_lit(0), sat.pos_lit(2));
431 sat.add_clause(sat.neg_lit(1), sat.neg_lit(2));
432
433 Array<std::pair<long, long>> clauses = {{1, 2}, {-1, 3}, {-2, -3}};
434
435 auto [ok, assignment] = sat.solve();
438}
439
441{
443 Two_Sat<DG> sat(3);
444
445 sat.add_clause(sat.pos_lit(0), sat.pos_lit(1));
446 sat.add_clause(sat.neg_lit(0), sat.pos_lit(2));
447 sat.add_clause(sat.neg_lit(1), sat.neg_lit(2));
448
449 Array<std::pair<long, long>> clauses = {{1, 2}, {-1, 3}, {-2, -3}};
450
451 auto [ok, assignment] = sat.solve();
454}
455
456// ---------------------------------------------------------------------------
457// Larger test
458// ---------------------------------------------------------------------------
459
461{
462 // Build a satisfiable instance: pick a random assignment and generate
463 // clauses that are consistent with it.
464 constexpr size_t N = 500;
465 constexpr size_t M = 2000;
466
467 std::mt19937 rng(42);
468 std::uniform_int_distribution<size_t> var_dist(0, N - 1);
469 std::uniform_int_distribution<int> sign_dist(0, 1);
470
471 // Generate a "target" assignment
473 for (size_t i = 0; i < N; ++i)
474 target(i) = sign_dist(rng) != 0;
475
476 Two_Sat<> sat(N);
478
479 for (size_t c = 0; c < M; ++c)
480 {
481 size_t v1 = var_dist(rng);
482 size_t v2 = var_dist(rng);
483
484 // Create a clause satisfied by `target`
485 size_t l1 = target(v1) ? sat.pos_lit(v1) : sat.neg_lit(v1);
486 size_t l2 = target(v2) ? sat.pos_lit(v2) : sat.neg_lit(v2);
487 sat.add_clause(l1, l2);
488 clauses.append(std::pair<size_t, size_t>(l1, l2));
489 }
490
491 auto [ok, assignment] = sat.solve();
493
494 for (size_t c = 0; c < clauses.size(); ++c)
495 {
496 auto [l1, l2] = clauses[c];
497 bool val1 = sat.is_pos_lit(l1) ? assignment[sat.lit_var(l1)]
498 : not assignment[sat.lit_var(l1)];
499 bool val2 = sat.is_pos_lit(l2) ? assignment[sat.lit_var(l2)]
500 : not assignment[sat.lit_var(l2)];
501 EXPECT_TRUE(val1 or val2);
502 }
503}
504
506{
507 // x0 -> x1 -> x2 -> ... -> x_{n-1} -> ~x0
508 // AND x0 = true
509 // This forces all variables true, but then ~x0 must be true => contradiction
510 constexpr size_t N = 200;
511 Two_Sat<> sat(N);
512 sat.set_true(sat.pos_lit(0));
513
514 for (size_t i = 0; i + 1 < N; ++i)
515 sat.add_implication(sat.pos_lit(i), sat.pos_lit(i + 1));
516
517 // Last variable implies ~x0
518 sat.add_implication(sat.pos_lit(N - 1), sat.neg_lit(0));
519
520 EXPECT_FALSE(sat.is_satisfiable());
521}
522
523// ---------------------------------------------------------------------------
524// Literal helpers
525// ---------------------------------------------------------------------------
526
528{
533
538
543
548
549 EXPECT_EQ(Two_Sat<>::signed_to_lit(1), 0U); // +1 -> pos_lit(0)
550 EXPECT_EQ(Two_Sat<>::signed_to_lit(-1), 1U); // -1 -> neg_lit(0)
551 EXPECT_EQ(Two_Sat<>::signed_to_lit(3), 4U); // +3 -> pos_lit(2)
552 EXPECT_EQ(Two_Sat<>::signed_to_lit(-3), 5U); // -3 -> neg_lit(2)
553 EXPECT_THROW(Two_Sat<>::signed_to_lit(0), std::domain_error);
554 EXPECT_THROW(Two_Sat<>::signed_to_lit(std::numeric_limits<long>::min()),
555 std::domain_error);
556}
2-SAT solver based on Tarjan's SCC algorithm.
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
static Array create(size_t n)
Create an array with n logical elements.
Definition tpl_array.H:194
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
Generic directed graph (digraph) wrapper template.
Definition graph-dry.H:3854
Arc for graphs implemented with simple adjacency lists.
Definition tpl_sgraph.H:197
2-SAT solver using Tarjan's SCC algorithm.
Definition Two_Sat.H:84
#define TEST(name)
static mt19937 rng
#define N
Definition fib.C:294
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
DynList< int > l1
DynList< int > l2
Array-based graph implementation.
Simple graph implementation with adjacency lists.
static bool verify_assignment(const Array< bool > &assignment, const Array< std::pair< long, long > > &clauses)