Skip to content

Commit ddde28d

Browse files
committed
Add alternative incremental encoding for n-queens
1 parent cf490d3 commit ddde28d

File tree

1 file changed

+134
-1
lines changed

1 file changed

+134
-1
lines changed

examples/queens.rs

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

189193
fn 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

Comments
 (0)