@@ -184,6 +184,10 @@ struct Cli {
184184 /// Enable verbose step-by-step output.
185185 #[ clap( short, long) ]
186186 verbose : bool ,
187+
188+ /// Use incremental row-wise solver instead of constraint-based solver.
189+ #[ clap( long) ]
190+ incremental : bool ,
187191}
188192
189193fn main ( ) -> color_eyre:: Result < ( ) > {
@@ -227,7 +231,11 @@ fn main() -> color_eyre::Result<()> {
227231 ) ;
228232 }
229233
230- let ( res, stats) = solve_queens ( & bdd, n, args. gc , args. verbose ) ;
234+ let ( res, stats) = if args. incremental {
235+ solve_queens_incremental ( & bdd, n, args. gc , args. verbose )
236+ } else {
237+ solve_queens ( & bdd, n, args. gc , args. verbose )
238+ } ;
231239
232240 // Print results
233241 println ! ( ) ;
@@ -458,3 +466,128 @@ fn encode_at_most_one(bdd: &Bdd, vars: &[u32]) -> Ref {
458466
459467 result
460468}
469+
470+ // ============================================================================
471+ // Alternative: Incremental row-wise construction
472+ // ============================================================================
473+
474+ /// Incremental row-wise construction of N-Queens BDD.
475+ /// Builds constraints row by row, only considering conflicts with upper rows.
476+ fn solve_queens_incremental ( bdd : & Bdd , n : usize , _gc : bool , verbose : bool ) -> ( Ref , Stats ) {
477+ let mut stats = Stats :: default ( ) ;
478+ let mut tracker = StepTracker :: new ( bdd, verbose) ;
479+ tracker. print_header ( ) ;
480+
481+ let mut solution_bdd = bdd. one ( ) ;
482+
483+ // Process each row incrementally
484+ for row in 0 ..n {
485+ println ! ( "[Row {}/{}]" , row + 1 , n) ;
486+ let t = Instant :: now ( ) ;
487+ tracker. reset_step ( ) ;
488+
489+ // Step 1: Exactly one queen in this row
490+ let row_exact = exactly_one_in_row ( bdd, n, row) ;
491+ solution_bdd = bdd. apply_and ( solution_bdd, row_exact) ;
492+ tracker. micro_step ( "exact-one" , bdd. size ( row_exact) , bdd. size ( solution_bdd) ) ;
493+
494+ // Step 2: Conflicts with previous rows (column + diagonal)
495+ if row > 0 {
496+ for prev_row in 0 ..row {
497+ // Column conflicts
498+ let col_conflicts = column_conflicts_with_previous ( bdd, n, row, prev_row) ;
499+ solution_bdd = bdd. apply_and ( solution_bdd, col_conflicts) ;
500+
501+ // Diagonal conflicts
502+ let diag_conflicts = diagonal_conflicts_with_previous ( bdd, n, row, prev_row) ;
503+ solution_bdd = bdd. apply_and ( solution_bdd, diag_conflicts) ;
504+ }
505+ tracker. micro_step ( "conflicts" , 0 , bdd. size ( solution_bdd) ) ;
506+ }
507+
508+ let elapsed = t. elapsed ( ) . as_secs_f64 ( ) ;
509+ stats. times . push ( ( "Row processing" , elapsed) ) ;
510+ stats. sizes . push ( ( "After row" , bdd. size ( solution_bdd) ) ) ;
511+ tracker. phase_done ( & format ! ( "[Row {}/{}]" , row + 1 , n) , bdd. size ( solution_bdd) , elapsed) ;
512+
513+ // Early exit if no solutions exist
514+ if solution_bdd == bdd. zero ( ) {
515+ println ! ( "No solutions possible, stopping early." ) ;
516+ break ;
517+ }
518+ }
519+
520+ ( solution_bdd, stats)
521+ }
522+
523+ /// Exactly one queen in a specific row (at least one AND at most one)
524+ fn exactly_one_in_row ( bdd : & Bdd , n : usize , row : usize ) -> Ref {
525+ let at_least = at_least_one_in_row ( bdd, n, row) ;
526+ let at_most = at_most_one_in_row ( bdd, n, row) ;
527+ bdd. apply_and ( at_least, at_most)
528+ }
529+
530+ /// At least one queen in a specific row
531+ fn at_least_one_in_row ( bdd : & Bdd , n : usize , row : usize ) -> Ref {
532+ let mut result = bdd. zero ( ) ;
533+ for col in 0 ..n {
534+ let v = bdd. mk_var ( var ( n, row, col) ) ;
535+ result = bdd. apply_or ( result, v) ;
536+ }
537+ result
538+ }
539+
540+ /// At most one queen in a specific row (pairwise encoding)
541+ fn at_most_one_in_row ( bdd : & Bdd , n : usize , row : usize ) -> Ref {
542+ let mut result = bdd. one ( ) ;
543+ for col1 in 0 ..n {
544+ for col2 in ( col1 + 1 ) ..n {
545+ let var1 = bdd. mk_var ( var ( n, row, col1) ) ;
546+ let var2 = bdd. mk_var ( var ( n, row, col2) ) ;
547+ // ¬(var1 ∧ var2) = ¬var1 ∨ ¬var2
548+ let constraint = bdd. apply_or ( -var1, -var2) ;
549+ result = bdd. apply_and ( result, constraint) ;
550+ }
551+ }
552+ result
553+ }
554+
555+ /// Constraints between current row and previous row to avoid column conflicts
556+ fn column_conflicts_with_previous ( bdd : & Bdd , n : usize , current_row : usize , prev_row : usize ) -> Ref {
557+ let mut result = bdd. one ( ) ;
558+ for col in 0 ..n {
559+ let current_var = bdd. mk_var ( var ( n, current_row, col) ) ;
560+ let prev_var = bdd. mk_var ( var ( n, prev_row, col) ) ;
561+ // ¬(current ∧ prev) = ¬current ∨ ¬prev
562+ let constraint = bdd. apply_or ( -current_var, -prev_var) ;
563+ result = bdd. apply_and ( result, constraint) ;
564+ }
565+ result
566+ }
567+
568+ /// Constraints between current row and previous row to avoid diagonal conflicts
569+ fn diagonal_conflicts_with_previous ( bdd : & Bdd , n : usize , current_row : usize , prev_row : usize ) -> Ref {
570+ let mut result = bdd. one ( ) ;
571+ let row_diff = current_row as i32 - prev_row as i32 ;
572+
573+ for prev_col in 0 ..n {
574+ // Main diagonal: col difference equals row difference
575+ let diag1_col = prev_col as i32 + row_diff;
576+ if diag1_col >= 0 && diag1_col < n as i32 {
577+ let current_var = bdd. mk_var ( var ( n, current_row, diag1_col as usize ) ) ;
578+ let prev_var = bdd. mk_var ( var ( n, prev_row, prev_col) ) ;
579+ let constraint = bdd. apply_or ( -current_var, -prev_var) ;
580+ result = bdd. apply_and ( result, constraint) ;
581+ }
582+
583+ // Anti-diagonal: col difference equals negative row difference
584+ let diag2_col = prev_col as i32 - row_diff;
585+ if diag2_col >= 0 && diag2_col < n as i32 {
586+ let current_var = bdd. mk_var ( var ( n, current_row, diag2_col as usize ) ) ;
587+ let prev_var = bdd. mk_var ( var ( n, prev_row, prev_col) ) ;
588+ let constraint = bdd. apply_or ( -current_var, -prev_var) ;
589+ result = bdd. apply_and ( result, constraint) ;
590+ }
591+ }
592+ result
593+ }
0 commit comments