@@ -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+
120129pub 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