Skip to content

Commit 5a97da2

Browse files
Add Clause struct and update Engine to use it for clause management
1 parent 7db63bf commit 5a97da2

1 file changed

Lines changed: 90 additions & 18 deletions

File tree

src/lib.rs

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ impl PartialOrd for Lit {
117117
}
118118
}
119119

120+
pub struct Clause(pub Vec<Lit>);
121+
122+
impl Display for Clause {
123+
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
124+
let lits: Vec<String> = self.0.iter().map(|l| l.to_string()).collect();
125+
write!(f, "{}", lits.join(" ∨ "))
126+
}
127+
}
128+
120129
pub struct Engine {
121130
assigns: Vec<LBool>, // Current assignments of variables
122131
seen: Vec<bool>, // Used during conflict analysis to track seen variables
@@ -126,7 +135,7 @@ pub struct Engine {
126135
decision_var: usize, // Current decision variable index
127136
pos_watches: Vec<Vec<usize>>, // Clauses watching the positive literal of each variable
128137
neg_watches: Vec<Vec<usize>>, // Clauses watching the negative literal of each variable
129-
clauses: Vec<Vec<Lit>>, // List of clauses in the engine
138+
clauses: Vec<Clause>, // List of clauses in the engine
130139
prop_q: VecDeque<usize>, // Queue of variables to propagate
131140
learnt: Vec<Lit>, // Temporary storage for learnt clauses during conflict analysis
132141
listeners: HashMap<usize, Vec<Callback>>, // Listeners for variable assignments
@@ -205,7 +214,7 @@ impl Engine {
205214
self.neg_watches[lit.var()].push(clause_id);
206215
}
207216
}
208-
self.clauses.push(lits.to_vec());
217+
self.clauses.push(Clause(lits));
209218
true
210219
}
211220

@@ -215,9 +224,18 @@ impl Engine {
215224
self.propagated_vars.clear();
216225
self.enqueue(lit, None);
217226
while let Some(var) = self.prop_q.pop_front() {
218-
for clause in if self.value(var) == &LBool::True { mem::take(&mut self.neg_watches[var]) } else { mem::take(&mut self.pos_watches[var]) } {
219-
if !self.propagate(clause, Lit::new(var, self.value(var) == &LBool::True)) {
220-
self.analyze_conflict(clause);
227+
let watches = if self.value(var) == &LBool::True { mem::take(&mut self.neg_watches[var]) } else { mem::take(&mut self.pos_watches[var]) };
228+
for i in 0..watches.len() {
229+
if !self.propagate(watches[i], Lit::new(var, self.value(var) == &LBool::True)) {
230+
for j in i..watches.len() {
231+
if self.value(var) == &LBool::True {
232+
self.neg_watches[var].push(watches[j]);
233+
} else {
234+
self.pos_watches[var].push(watches[j]);
235+
}
236+
}
237+
self.prop_q.clear();
238+
self.analyze_conflict(watches[i]);
221239
return false;
222240
}
223241
}
@@ -260,7 +278,7 @@ impl Engine {
260278

261279
loop {
262280
// 1. Process the current clause (either the conflict or a reason)
263-
for lit in &self.clauses[clause] {
281+
for lit in &self.clauses[clause].0 {
264282
let v = lit.var();
265283

266284
// Skip the variable we are currently resolving away
@@ -319,11 +337,11 @@ impl Engine {
319337

320338
fn propagate(&mut self, clause_id: usize, lit: Lit) -> bool {
321339
// Ensure the first literal is not the one that was just assigned
322-
if self.clauses[clause_id][0].var() == lit.var() {
323-
self.clauses[clause_id].swap(0, 1);
340+
if self.clauses[clause_id].0[0].var() == lit.var() {
341+
self.clauses[clause_id].0.swap(0, 1);
324342
}
325343
// Check if clause is already satisfied
326-
if self.lit_value(&self.clauses[clause_id][0]) == LBool::True {
344+
if self.lit_value(&self.clauses[clause_id].0[0]) == LBool::True {
327345
// Re-add the clause to the watch list
328346
if lit.is_positive() {
329347
self.pos_watches[lit.var()].push(clause_id);
@@ -334,15 +352,15 @@ impl Engine {
334352
}
335353

336354
// Find the next unassigned literal
337-
for i in 2..self.clauses[clause_id].len() {
338-
if self.lit_value(&self.clauses[clause_id][i]) != LBool::False {
355+
for i in 2..self.clauses[clause_id].0.len() {
356+
if self.lit_value(&self.clauses[clause_id].0[i]) != LBool::False {
339357
// Move this literal to the second position
340-
self.clauses[clause_id].swap(1, i);
358+
self.clauses[clause_id].0.swap(1, i);
341359
// Update watch lists
342-
if self.clauses[clause_id][1].is_positive() {
343-
self.pos_watches[self.clauses[clause_id][1].var()].push(clause_id);
360+
if self.clauses[clause_id].0[1].is_positive() {
361+
self.pos_watches[self.clauses[clause_id].0[1].var()].push(clause_id);
344362
} else {
345-
self.neg_watches[self.clauses[clause_id][1].var()].push(clause_id);
363+
self.neg_watches[self.clauses[clause_id].0[1].var()].push(clause_id);
346364
}
347365
return true;
348366
}
@@ -354,7 +372,7 @@ impl Engine {
354372
} else {
355373
self.pos_watches[lit.var()].push(clause_id);
356374
}
357-
self.enqueue(self.clauses[clause_id][0], Some(clause_id))
375+
self.enqueue(self.clauses[clause_id].0[0], Some(clause_id))
358376
}
359377

360378
pub fn add_listener<F>(&mut self, var: usize, listener: F)
@@ -371,8 +389,7 @@ impl Display for Engine {
371389
writeln!(f, "b{}: {:?}", i, val)?;
372390
}
373391
for clause in &self.clauses {
374-
let lits: Vec<String> = clause.iter().map(|l| l.to_string()).collect();
375-
writeln!(f, "{}", lits.join(" ∨ "))?;
392+
writeln!(f, "{}", clause)?;
376393
}
377394
Ok(())
378395
}
@@ -486,6 +503,61 @@ mod tests {
486503
engine.assert(neg(a)); // Should panic
487504
}
488505

506+
#[test]
507+
fn test_diamond_propagation() {
508+
let mut engine = Engine::new();
509+
let x1 = engine.add_var();
510+
let x2 = engine.add_var();
511+
let x3 = engine.add_var();
512+
let x4 = engine.add_var();
513+
514+
// x1 -> x2 (¬x1 ∨ x2)
515+
engine.add_clause(vec![neg(x1), pos(x2)]);
516+
// x1 -> x3 (¬x1 ∨ x3)
517+
engine.add_clause(vec![neg(x1), pos(x3)]);
518+
// (x2 ∧ x3) -> x4 (¬x2 ∨ ¬x3 ∨ x4)
519+
engine.add_clause(vec![neg(x2), neg(x3), pos(x4)]);
520+
521+
engine.assert(pos(x1));
522+
523+
assert_eq!(*engine.value(x4), LBool::True, "x4 should be forced via x2 and x3");
524+
}
525+
526+
#[test]
527+
fn test_complex_conflict_1uip() {
528+
let mut engine = Engine::new();
529+
let vars: Vec<usize> = (0..10).map(|_| engine.add_var()).collect();
530+
531+
// Setup a chain: x1 -> x2 -> x3 -> x4
532+
engine.add_clause(vec![neg(vars[1]), pos(vars[2])]);
533+
engine.add_clause(vec![neg(vars[2]), pos(vars[3])]);
534+
engine.add_clause(vec![neg(vars[3]), pos(vars[4])]);
535+
536+
// Create a conflict path:
537+
// (x4 ∧ x5) -> Conflict
538+
// x5 is another decision or forced var
539+
engine.add_clause(vec![neg(vars[4]), neg(vars[5])]);
540+
541+
// Another path to the same conflict
542+
// (x3 ∧ x6) -> x5
543+
engine.add_clause(vec![neg(vars[3]), neg(vars[6]), pos(vars[5])]);
544+
545+
// Assert "side" variables that set the stage
546+
engine.assert(pos(vars[6]));
547+
548+
// Now trigger the chain
549+
// This should cause: x1 -> x2 -> x3 -> x4 -> conflict with x5
550+
let success = engine.assert(pos(vars[1]));
551+
552+
assert!(!success, "Should detect a conflict");
553+
554+
let explanation = engine.get_conflict_explanation().unwrap();
555+
// The explanation should ideally contain the 1-UIP literal
556+
// and the "reason" variables from lower levels.
557+
assert!(!explanation.is_empty());
558+
println!("Conflict explanation: {:?}", explanation);
559+
}
560+
489561
#[test]
490562
fn test_conflict_analysis() {
491563
let mut engine = Engine::new();

0 commit comments

Comments
 (0)