Implement a Boolean Satisfiability (SAT) Solver
The Boolean Satisfiability Problem (SAT) is a fundamental problem in computer science, particularly in theoretical computer science and artificial intelligence. It asks whether there exists an interpretation (a truth assignment) of variables that satisfies a given Boolean formula. Implementing a SAT solver allows us to tackle a wide range of problems, from circuit verification to automated reasoning.
Problem Description
Your task is to implement a SAT solver in Python. The solver should take a Boolean formula in Conjunctive Normal Form (CNF) as input and determine if there is a truth assignment to its variables that makes the entire formula true. If a satisfying assignment exists, the solver should return one such assignment. If no satisfying assignment exists, it should indicate that the formula is unsatisfiable.
Key Requirements:
- Input Format: The input will be a list of clauses, where each clause is a list of literals. A literal is either a positive integer (representing a positive variable) or a negative integer (representing the negation of a variable). For example,
[[1, -2], [2, 3]]represents the CNF formula(x1 OR NOT x2) AND (x2 OR x3). Variables are numbered starting from 1. - Output Format:
- If the formula is satisfiable, return a dictionary mapping variable numbers (integers) to their truth values (booleans
TrueorFalse). For example,{1: True, 2: False, 3: True}. - If the formula is unsatisfiable, return
None.
- If the formula is satisfiable, return a dictionary mapping variable numbers (integers) to their truth values (booleans
- Algorithm: You should implement a basic backtracking search algorithm, often referred to as DPLL (Davis-Putnam-Logemann-Loveland). The core components of DPLL include:
- Unit Propagation: If a clause contains only one literal, that literal must be true. Assign the corresponding variable its truth value to satisfy the clause and simplify the remaining formula.
- Pure Literal Elimination: If a variable appears only positively or only negatively across all remaining clauses, assign it the truth value that satisfies those occurrences.
- Splitting Rule: If neither of the above rules can be applied, choose an unassigned variable and try assigning it
True. If this leads to a satisfying assignment, return it. Otherwise, backtrack and try assigning itFalse.
Expected Behavior:
- The solver should correctly identify satisfiable and unsatisfiable formulas.
- For satisfiable formulas, it should return a valid truth assignment.
- The solver should handle empty clauses (which make the formula unsatisfiable) and empty input (which is trivially satisfiable).
Edge Cases:
- Empty formula: An empty list of clauses
[]is considered satisfiable with an empty assignment{}. - Empty clause: A clause like
[]within the input signifies an immediate unsatisfiable state. - Formula with no variables: A formula like
[[]]is unsatisfiable. A formula like[]is satisfiable.
Examples
Example 1:
Input: [[1, -2], [-1, 2], [1, 2]]
Output: {1: True, 2: True}
Explanation:
Clause 1: (x1 OR NOT x2)
Clause 2: (NOT x1 OR x2)
Clause 3: (x1 OR x2)
If x1 is True:
Clause 1 becomes (True OR NOT x2) -> True
Clause 2 becomes (False OR x2) -> x2
Clause 3 becomes (True OR x2) -> True
Now we only need to satisfy x2. If x2 is True, then Clause 2 is satisfied.
So, {1: True, 2: True} satisfies all clauses.
Example 2:
Input: [[1], [-1]]
Output: None
Explanation:
Clause 1: (x1)
Clause 2: (NOT x1)
This is a contradiction. If x1 must be True (from Clause 1) and x1 must be False (from Clause 2), then no satisfying assignment exists.
Example 3: (Pure Literal Elimination)
Input: [[1, 2], [-1, 3], [2, 3]]
Output: {1: True, 2: True, 3: True} (or other valid assignment)
Explanation:
Clause 1: (x1 OR x2)
Clause 2: (NOT x1 OR x3)
Clause 3: (x2 OR x3)
Let's say we pick x1.
If x1 is True:
Clause 1: (True OR x2) -> True
Clause 2: (False OR x3) -> x3
Clause 3: (x2 OR x3)
Now we have x3 and (x2 OR x3) to satisfy. If we assign x3 = True, both are satisfied.
If x1 is False:
Clause 1: (False OR x2) -> x2
Clause 2: (True OR x3) -> True
Clause 3: (x2 OR x3)
Now we have x2 and (x2 OR x3) to satisfy. If we assign x2 = True, both are satisfied.
A valid assignment would be {1: True, 2: True, 3: True}.
Another: {1: False, 2: True, 3: True}.
(Note: The solver might find any valid assignment, not necessarily this specific one.)
Constraints
- The number of variables will be between 1 and 100.
- The number of clauses will be between 0 and 1000.
- Each clause will contain between 1 and 100 literals (unless it's an empty clause).
- The absolute value of any literal will be between 1 and the number of variables.
- The input will always be a list of lists of integers.
- The algorithm should be reasonably efficient for the given constraints, though a brute-force exponential solution might time out for larger inputs. DPLL with optimizations is expected.
Notes
- You will need to manage the state of assignments and the remaining clauses effectively.
- Consider how to represent the formula and assignments efficiently.
- Think about the order in which you pick variables for splitting. A simple heuristic is often sufficient for basic implementations.
- For unit propagation, you'll need to efficiently find clauses with only one literal.
- For pure literal elimination, you'll need to efficiently track the occurrences of positive and negative literals for each variable.
- Remember to handle the removal of satisfied clauses and the simplification of unsatisfied clauses after an assignment.