Skip to main content
Glama
nqueens.py8.9 kB
""" N-Queens Problem Example This module solves the classic N-Queens problem using constraint programming. The problem involves placing N queens on an NxN chessboard such that no two queens can attack each other (no two queens share the same row, column, or diagonal). The constraint programming problem involves: - N queen positions on an NxN board - Each row must contain exactly one queen - Each column must contain exactly one queen - No two queens can be on the same diagonal - Optional: find all solutions or just one solution This is a classic constraint satisfaction problem suitable for OR-Tools CP-SAT. """ from returns.result import Success from usolver_mcp.models.ortools_models import ( Constraint, Objective, ObjectiveType, Problem, Variable, VariableType, ) from usolver_mcp.solvers.ortools_solver import solve_problem def create_nqueens_problem(n=8, find_all_solutions=False): """ Create an N-Queens constraint programming problem. Args: n: Size of the chessboard (nxn) and number of queens find_all_solutions: Whether to enumerate all solutions Returns: Problem: The OR-Tools constraint programming problem """ # Variables: queens[i] represents the column position of the queen in row i # Domain: each queen can be in any column from 0 to n-1 queens = Variable( name="queens", type=VariableType.INTEGER, domain=(0, n - 1), shape=[n], description=f"Column position of queen in each row (0 to {n-1})", ) constraints = [] # Constraint 1: All queens must be in different columns # This is automatically handled by the AllDifferent constraint constraints.append( Constraint( expression=f"model.AddAllDifferent([queens[i] for i in range({n})])", description="All queens must be in different columns", ) ) # Constraint 2: No two queens on the same diagonal # For diagonal constraints, we need: # - No two queens on the same positive diagonal: queens[i] + i ≠ queens[j] + j for i ≠ j # - No two queens on the same negative diagonal: queens[i] - i ≠ queens[j] - j for i ≠ j # Positive diagonal constraint constraints.append( Constraint( expression=f"model.AddAllDifferent([queens[i] + i for i in range({n})])", description="No two queens on the same positive diagonal", ) ) # Negative diagonal constraint constraints.append( Constraint( expression=f"model.AddAllDifferent([queens[i] - i for i in range({n})])", description="No two queens on the same negative diagonal", ) ) # Objective: Just find a feasible solution (or all solutions) objective = Objective( type=ObjectiveType.FEASIBILITY, description="Find feasible queen placement(s)" ) return Problem( variables=[queens], constraints=constraints, objective=objective, description=f"{n}-Queens problem: place {n} queens on {n}x{n} board with no attacks", parameters={"enumerate_all_solutions": find_all_solutions, "n": n}, ) def solve_nqueens(n=8, find_all_solutions=False): """ Solve the N-Queens problem and return results. Args: n: Size of the chessboard find_all_solutions: Whether to find all solutions Returns: dict: Solution results including queen positions and board representation """ problem = create_nqueens_problem(n, find_all_solutions) result = solve_problem(problem) match result: case Success(solution): if solution.is_feasible: queens_positions = solution.values["queens"] # Create board representation board = create_board_representation(queens_positions, n) # Count attacks (should be 0 for valid solution) attack_count = count_attacks(queens_positions) return { "status": "feasible", "n": n, "queens": queens_positions, "board": board, "attack_count": attack_count, "is_valid": attack_count == 0, "statistics": solution.statistics, } else: return { "status": solution.status, "error": f"No solution found for {n}-Queens", } case failure: error_msg = ( failure.failure() if hasattr(failure, "failure") else str(failure) ) return { "status": "error", "error": f"Failed to solve {n}-Queens problem: {error_msg}", } def create_board_representation(queens, n): """Create a visual representation of the chessboard with queens.""" board = [] for row in range(n): board_row = ["." for _ in range(n)] queen_col = queens[row] board_row[queen_col] = "Q" board.append(board_row) return board def count_attacks(queens): """Count the number of attacking pairs of queens.""" n = len(queens) attacks = 0 for i in range(n): for j in range(i + 1, n): # Same column if queens[i] == queens[j]: attacks += 1 # Same diagonal elif abs(queens[i] - queens[j]) == abs(i - j): attacks += 1 return attacks def print_results(results) -> None: """Print N-Queens results in a formatted way.""" if results["status"] != "feasible": print(f"Problem Status: {results['status']}") if "error" in results: print(f"Error: {results['error']}") return n = results["n"] queens = results["queens"] board = results["board"] print(f"{n}-Queens Problem Solution") print("=" * (max(30, n * 4))) # Print queen positions print("\nQueen positions (row, column):") for row, col in enumerate(queens): print(f" Row {row}: Column {col}") # Print board print("\nChessboard:") print(" " + " ".join([str(i) for i in range(n)])) for row in range(n): print(f"{row} " + " ".join(board[row])) # Validation if results["is_valid"]: print("\n✓ Valid solution: No queens attack each other") else: print(f"\n✗ Invalid solution: {results['attack_count']} attacking pairs") # Print solver statistics if "statistics" in results: print("\nSolver Statistics:") for key, value in results["statistics"].items(): display_key = " ".join(word.title() for word in key.split("_")) print(f" {display_key}: {value}") def validate_solution(queens) -> bool: """Validate that the queens placement is a valid N-Queens solution.""" n = len(queens) # Check that all queens are in different columns if len(set(queens)) != n: return False # Check diagonals for i in range(n): for j in range(i + 1, n): # Check if queens are on the same diagonal if abs(queens[i] - queens[j]) == abs(i - j): return False return True def benchmark_nqueens(max_n=12) -> None: """Benchmark N-Queens for different board sizes.""" print("N-Queens Benchmark") print("=" * 40) print("Size | Status | Time (if available)") print("-" * 40) for n in range(4, max_n + 1): try: results = solve_nqueens(n) status = "✓ Solved" if results["status"] == "feasible" else "✗ Failed" print(f"{n:4d} | {status:9s} | -") except Exception as e: print(f"{n:4d} | ✗ Error | {str(e)[:20]}...") def main() -> None: """Main function to run the N-Queens example.""" print(__doc__) # Solve classic 8-Queens print("Solving classic 8-Queens problem...") results = solve_nqueens(8) print_results(results) # Optional: benchmark different sizes print("\n" + "=" * 50) print("Benchmark different board sizes:") benchmark_nqueens(10) def test_nqueens() -> None: """Test function for pytest.""" # Test 4-Queens (smallest non-trivial case) results = solve_nqueens(4) assert results["status"] == "feasible" assert results["n"] == 4 assert len(results["queens"]) == 4 assert results["is_valid"] assert validate_solution(results["queens"]) # Test 8-Queens (classic case) results = solve_nqueens(8) assert results["status"] == "feasible" assert results["n"] == 8 assert len(results["queens"]) == 8 assert results["is_valid"] assert validate_solution(results["queens"]) # Test that solution has no attacks assert results["attack_count"] == 0 if __name__ == "__main__": main()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sdiehl/usolver'

If you have feedback or need assistance with the MCP directory API, please join our Discord server