Boolean Satisfiability Problem
Overview
The Boolean Satisfiability Problem (SAT) determines whether there exists an assignment of truth values to variables that makes a given Boolean formula evaluate to true.
This example demonstrates:
- Formulating a SAT problem in Conjunctive Normal Form (CNF)
- Converting it to a tensor network
- Finding satisfying assignments
- Counting satisfiable solutions
We'll work with a CNF formula consisting of multiple clauses.
using GenericTensorNetworks, GenericTensorNetworks.ProblemReductions
Define boolean variables
@bools a b c d e f g
Create a CNF formula: (a ∨ b ∨ ¬d ∨ ¬e) ∧ (¬a ∨ d ∨ e ∨ ¬f) ∧ (f ∨ g) ∧ (¬b ∨ c)
cnf = ∧(∨(a, b, ¬d, ¬e), ∨(¬a, d, e, ¬f), ∨(f, g), ∨(¬b, c))
(a ∨ b ∨ ¬d ∨ ¬e) ∧ (¬a ∨ d ∨ e ∨ ¬f) ∧ (f ∨ g) ∧ (¬b ∨ c)
Manual Verification
For small problems, we can manually find and verify a satisfying assignment:
assignment = Dict([:a=>true, :b=>false, :c=>false, :d=>true, :e=>false, :f=>false, :g=>true])
Dict{Symbol, Bool} with 7 entries:
:a => 1
:b => 0
:f => 0
:d => 1
:e => 0
:c => 0
:g => 1
Check if this assignment satisfies the formula:
satisfiable(cnf, assignment)
true
Tensor Network Formulation
Define the satisfiability problem:
sat = Satisfiability(cnf)
Satisfiability{Symbol, Int64, UnitWeight, ProblemReductions.EXTREMA}([:a, :b, :d, :e, :f, :g, :c], [1, 1, 1, 1], (a ∨ b ∨ ¬d ∨ ¬e) ∧ (¬a ∨ d ∨ e ∨ ¬f) ∧ (f ∨ g) ∧ (¬b ∨ c))
The objective is to maximize the number of satisfied clauses:
objectives(sat)
4-element Vector{ProblemReductions.LocalSolutionSize{Int64}}:
LocalSolutionSize{Int64} on [1, 2, 3, 4]
┌───────────────┬──────┐
│ Configuration │ Size │
├───────────────┼──────┤
│ [0, 0, 0, 0] │ 1 │
│ [1, 0, 0, 0] │ 1 │
│ [0, 1, 0, 0] │ 1 │
│ [1, 1, 0, 0] │ 1 │
│ [0, 0, 1, 0] │ 1 │
│ [1, 0, 1, 0] │ 1 │
│ [0, 1, 1, 0] │ 1 │
│ [1, 1, 1, 0] │ 1 │
│ [0, 0, 0, 1] │ 1 │
│ [1, 0, 0, 1] │ 1 │
│ [0, 1, 0, 1] │ 1 │
│ [1, 1, 0, 1] │ 1 │
│ [0, 0, 1, 1] │ 0 │
│ [1, 0, 1, 1] │ 1 │
│ [0, 1, 1, 1] │ 1 │
│ [1, 1, 1, 1] │ 1 │
└───────────────┴──────┘
LocalSolutionSize{Int64} on [1, 3, 4, 5]
┌───────────────┬──────┐
│ Configuration │ Size │
├───────────────┼──────┤
│ [0, 0, 0, 0] │ 1 │
│ [1, 0, 0, 0] │ 1 │
│ [0, 1, 0, 0] │ 1 │
│ [1, 1, 0, 0] │ 1 │
│ [0, 0, 1, 0] │ 1 │
│ [1, 0, 1, 0] │ 1 │
│ [0, 1, 1, 0] │ 1 │
│ [1, 1, 1, 0] │ 1 │
│ [0, 0, 0, 1] │ 1 │
│ [1, 0, 0, 1] │ 0 │
│ [0, 1, 0, 1] │ 1 │
│ [1, 1, 0, 1] │ 1 │
│ [0, 0, 1, 1] │ 1 │
│ [1, 0, 1, 1] │ 1 │
│ [0, 1, 1, 1] │ 1 │
│ [1, 1, 1, 1] │ 1 │
└───────────────┴──────┘
LocalSolutionSize{Int64} on [5, 6]
┌───────────────┬──────┐
│ Configuration │ Size │
├───────────────┼──────┤
│ [0, 0] │ 0 │
│ [1, 0] │ 1 │
│ [0, 1] │ 1 │
│ [1, 1] │ 1 │
└───────────────┴──────┘
LocalSolutionSize{Int64} on [2, 7]
┌───────────────┬──────┐
│ Configuration │ Size │
├───────────────┼──────┤
│ [0, 0] │ 1 │
│ [1, 0] │ 0 │
│ [0, 1] │ 1 │
│ [1, 1] │ 1 │
└───────────────┴──────┘
Convert to tensor network representation:
problem = GenericTensorNetwork(sat)
GenericTensorNetwork{Satisfiability{Symbol, Int64, UnitWeight, ProblemReductions.EXTREMA}, OMEinsum.DynamicNestedEinsum{Int64}, Int64}
- open vertices: Int64[]
- fixed vertices: Dict{Int64, Int64}()
- contraction time = 2^5.248, space = 2^2.0, read-write = 2^5.728
Mathematical Background
For each boolean variable x, we assign a degree of freedom $s_x ∈ \{0,1\}$, where:
- 0 represents the value 'false'
- 1 represents the value 'true'
Each clause is mapped to a tensor. For example, the clause ¬x ∨ y ∨ ¬z
becomes a tensor labeled by $(s_x, s_y, s_z)$:
\[C_{s_x,s_y,s_z} = \begin{cases} 0 & \text{if } s_x = 1, s_y = 0, s_z = 1 \\ 1 & \text{otherwise} \end{cases}\]
Only the configuration $(s_x, s_y, s_z) = (1, 0, 1)$ makes this clause unsatisfied.
Solution Analysis
1. Satisfiability Check
Find the maximum number of clauses that can be satisfied:
num_satisfiable = solve(problem, SizeMax())[]
4.0ₜ
2. Counting Solutions
The graph polynomial counts assignments by number of satisfied clauses:
num_satisfiable_count = read_size_count(solve(problem, GraphPolynomial())[])
5-element Vector{Pair{Int64, BigInt}}:
0 => 0
1 => 0
2 => 12
3 => 56
4 => 60
3. Finding a Satisfying Assignment
Find one satisfying assignment:
single_config = read_config(solve(problem, SingleConfigMax())[])
1101011
Convert the bit vector to a variable assignment and verify:
solution_assignment = Dict(zip(ProblemReductions.symbols(problem.problem), single_config))
satisfiable(cnf, solution_assignment)
true
More APIs
The Independent Set Problem chapter has more examples on how to use the APIs.
This page was generated using Literate.jl.