1
1
"""
2
- Davis–Putnam–Logemann–Loveland (DPLL) algorithm is a complete, backtracking-based search algorithm
3
- for deciding the satisfiability of propositional logic formulae in conjunctive normal form, i.e. for solving the CNF-SAT problem.
2
+ Davis–Putnam–Logemann–Loveland (DPLL) algorithm is a complete,
3
+ backtracking-based search algorithm for deciding the satisfiability of
4
+ propositional logic formulae in conjunctive normal form,
5
+ i.e, for solving the CNF-SAT problem.
4
6
5
- For more information about the algorithm: https://en.wikipedia.org/wiki/DPLL_algorithm
7
+ For more information about the algorithm:
8
+ https://en.wikipedia.org/wiki/DPLL_algorithm
6
9
"""
7
10
8
11
import random
9
12
10
13
11
- class Clause () :
14
+ class Clause :
12
15
"""
13
16
A clause represented in CNF.
14
17
A clause is a set of literals, either complemented or otherwise.
@@ -17,7 +20,7 @@ class Clause():
17
20
{A5', A2', A1} is the clause (A5' v A2' v A1)
18
21
19
22
Create a set of literals and a clause with them
20
- >>> literals = ["A1", "A2'", "A3"]
23
+ >>> literals = ["A1", "A2'", "A3"]
21
24
>>> clause = Clause(literals)
22
25
>>> print(clause)
23
26
{ A1 , A2' , A3 }
@@ -32,23 +35,24 @@ def __init__(self, literals):
32
35
"""
33
36
Represent the literals and an assignment in a clause."
34
37
"""
35
- self .literals = {literal : None for literal in literals } # Assign all literals to None initially
38
+ # Assign all literals to None initially
39
+ self .literals = {literal : None for literal in literals }
36
40
self .no_of_literals = len (self .literals )
37
-
41
+
38
42
def __str__ (self ):
39
43
"""
40
- To print a clause as in CNF.
41
- Variable clause holds the string representation.
44
+ To print a clause as in CNF.
45
+ Variable clause holds the string representation.
42
46
"""
43
47
clause = "{ "
44
48
for i in range (0 , self .no_of_literals ):
45
49
clause += str (list (self .literals .keys ())[i ]) + " "
46
50
if i != self .no_of_literals - 1 :
47
51
clause += ", "
48
52
clause += "}"
49
-
53
+
50
54
return clause
51
-
55
+
52
56
def assign (self , model ):
53
57
"""
54
58
Assign values to literals of the clause as given by model.
@@ -59,51 +63,53 @@ def assign(self, model):
59
63
if symbol in model .keys ():
60
64
val = model [symbol ]
61
65
else :
62
- i += 1
63
- continue
64
- if val != None :
66
+ i += 1
67
+ continue
68
+ if val is not None :
69
+ # Complement assignment if literal is in complemented form
65
70
if list (self .literals .keys ())[i ][- 1 ] == "'" :
66
- val = not val #Complement assignment if literal is in complemented form
71
+ val = not val
67
72
self .literals [list (self .literals .keys ())[i ]] = val
68
73
i += 1
69
-
74
+
70
75
def evaluate (self , model ):
71
76
"""
72
- Evaluates the clause till the extent possible with the current assignments in model.
77
+ Evaluates the clause with the assignments in model.
73
78
This has the following steps:
74
79
1. Return True if both a literal and its complement exist in the clause.
75
80
2. Return True if a single literal has the assignment True.
76
81
3. Return None(unable to complete evaluation) if a literal has no assignment.
77
82
4. Compute disjunction of all values assigned in clause.
78
83
"""
79
- for l in list (self .literals .keys ()):
80
- if len (l ) == 2 :
81
- symbol = l + "'"
84
+ for literal in list (self .literals .keys ()):
85
+ if len (literal ) == 2 :
86
+ symbol = literal + "'"
82
87
if symbol in list (self .literals .keys ()):
83
88
return True
84
89
else :
85
- symbol = l [:2 ]
90
+ symbol = literal [:2 ]
86
91
if symbol in list (self .literals .keys ()):
87
- return True
92
+ return True
88
93
89
94
self .assign (model )
90
95
result = False
91
96
for j in self .literals .values ():
92
- if j == True :
97
+ if j is True :
93
98
return True
94
- elif j == None :
99
+ elif j is None :
95
100
return None
96
101
for j in self .literals .values ():
97
102
result = result or j
98
103
return result
99
104
100
- class Formula ():
105
+
106
+ class Formula :
101
107
"""
102
108
A formula represented in CNF.
103
109
A formula is a set of clauses.
104
110
For example,
105
- {{A1, A2, A3'}, {A5', A2', A1}} is the formula ((A1 v A2 v A3') and (A5' v A2' v A1))
106
-
111
+ {{A1, A2, A3'}, {A5', A2', A1}} is ((A1 v A2 v A3') and (A5' v A2' v A1))
112
+
107
113
Create two clauses and a formula with them
108
114
>>> c1 = Clause(["A1", "A2'", "A3"])
109
115
>>> c2 = Clause(["A5'", "A2'", "A1"])
@@ -112,27 +118,29 @@ class Formula():
112
118
>>> print(f)
113
119
{ { A1 , A2' , A3 } , { A5' , A2' , A1 } }
114
120
"""
121
+
115
122
def __init__ (self , clauses ):
116
123
"""
117
124
Represent the number of clauses and the clauses themselves.
118
125
"""
119
126
self .clauses = [c for c in clauses ]
120
127
self .no_of_clauses = len (self .clauses )
121
-
128
+
122
129
def __str__ (self ):
123
130
"""
124
- To print a formula as in CNF.
125
- Variable formula holds the string representation.
131
+ To print a formula as in CNF.
132
+ Variable formula holds the string representation.
126
133
"""
127
134
formula = "{ "
128
135
for i in range (0 , self .no_of_clauses ):
129
- formula += ( str (self .clauses [i ]) + " " )
136
+ formula += str (self .clauses [i ]) + " "
130
137
if i != self .no_of_clauses - 1 :
131
138
formula += ", "
132
139
formula += "}"
133
-
140
+
134
141
return formula
135
142
143
+
136
144
def generate_clause ():
137
145
"""
138
146
Randomly generate a clause.
@@ -144,18 +152,19 @@ def generate_clause():
144
152
i = 0
145
153
while i < no_of_literals :
146
154
var_no = random .randint (1 , 5 )
147
- var_name = base_var + str (var_no )
155
+ var_name = base_var + str (var_no )
148
156
var_complement = random .randint (0 , 1 )
149
157
if var_complement == 1 :
150
158
var_name += "'"
151
159
if var_name in literals :
152
160
i -= 1
153
161
else :
154
162
literals .append (var_name )
155
- i += 1
163
+ i += 1
156
164
clause = Clause (literals )
157
165
return clause
158
166
167
+
159
168
def generate_formula ():
160
169
"""
161
170
Randomly generate a formula.
@@ -173,6 +182,7 @@ def generate_formula():
173
182
formula = Formula (set (clauses ))
174
183
return formula
175
184
185
+
176
186
def generate_parameters (formula ):
177
187
"""
178
188
Return the clauses and symbols from a formula.
@@ -198,26 +208,30 @@ def generate_parameters(formula):
198
208
for literal in clause .literals .keys ():
199
209
symbol = literal [:2 ]
200
210
if symbol not in symbols_set :
201
- symbols_set .append (symbol )
211
+ symbols_set .append (symbol )
202
212
return clauses , symbols_set
203
213
214
+
204
215
def find_pure_symbols (clauses , symbols , model ):
205
216
"""
206
- Return pure symbols and their assignments to satisfy the clause, if figurable.
207
- Pure symbols are symbols in a formula that exist only in one form, either complemented or otherwise.
217
+ Return pure symbols and their values to satisfy clause.
218
+ Pure symbols are symbols in a formula that exist only
219
+ in one form, either complemented or otherwise.
208
220
For example,
209
- { { A4 , A3 , A5' , A1 , A3' } , { A4 } , { A3 } } has the pure symbols A4, A5' and A1.
221
+ { { A4 , A3 , A5' , A1 , A3' } , { A4 } , { A3 } } has
222
+ pure symbols A4, A5' and A1.
210
223
This has the following steps:
211
- 1. Ignore clauses that have already evaluated to be True.
224
+ 1. Ignore clauses that have already evaluated to be True.
212
225
2. Find symbols that occur only in one form in the rest of the clauses.
213
- 3. Assign value True or False depending on whether the symbols occurs in normal or complemented form respectively.
214
-
226
+ 3. Assign value True or False depending on whether the symbols occurs
227
+ in normal or complemented form respectively.
228
+
215
229
>>> c1 = Clause(["A1", "A2'", "A3"])
216
230
>>> c2 = Clause(["A5'", "A2'", "A1"])
217
231
218
232
>>> f = Formula([c1, c2])
219
233
>>> c, s = generate_parameters(f)
220
-
234
+
221
235
>>> model = {}
222
236
>>> p, v = find_pure_symbols(c, s, model)
223
237
>>> print(p, v)
@@ -226,38 +240,42 @@ def find_pure_symbols(clauses, symbols, model):
226
240
pure_symbols = []
227
241
assignment = dict ()
228
242
literals = []
229
-
243
+
230
244
for clause in clauses :
231
- if clause .evaluate (model ) == True :
245
+ if clause .evaluate (model ) is True :
232
246
continue
233
- for l in clause .literals .keys ():
234
- literals .append (l )
247
+ for literal in clause .literals .keys ():
248
+ literals .append (literal )
235
249
236
250
for s in symbols :
237
- sym = (s + "'" )
238
- if (s in literals and sym not in literals ) or (s not in literals and sym in literals ):
251
+ sym = s + "'"
252
+ if (s in literals and sym not in literals ) or (
253
+ s not in literals and sym in literals
254
+ ):
239
255
pure_symbols .append (s )
240
256
for p in pure_symbols :
241
257
assignment [p ] = None
242
258
for s in pure_symbols :
243
- sym = ( s + "'" )
259
+ sym = s + "'"
244
260
if s in literals :
245
261
assignment [s ] = True
246
262
elif sym in literals :
247
263
assignment [s ] = False
248
264
return pure_symbols , assignment
249
265
266
+
250
267
def find_unit_clauses (clauses , model ):
251
268
"""
252
- Returns the unit symbols and their assignments to satisfy the clause, if figurable .
269
+ Returns the unit symbols and their values to satisfy clause.
253
270
Unit symbols are symbols in a formula that are:
254
- - Either the only symbol in a clause
255
- - Or all other literals in that clause have been assigned False
271
+ - Either the only symbol in a clause
272
+ - Or all other literals in that clause have been assigned False
256
273
This has the following steps:
257
274
1. Find symbols that are the only occurences in a clause.
258
- 2. Find symbols in a clause where all other literals are assigned to be False.
259
- 3. Assign True or False depending on whether the symbols occurs in normal or complemented form respectively.
260
-
275
+ 2. Find symbols in a clause where all other literals are assigned False.
276
+ 3. Assign True or False depending on whether the symbols occurs in
277
+ normal or complemented form respectively.
278
+
261
279
>>> c1 = Clause(["A4", "A3", "A5'", "A1", "A3'"])
262
280
>>> c2 = Clause(["A4"])
263
281
>>> c3 = Clause(["A3"])
@@ -277,11 +295,11 @@ def find_unit_clauses(clauses, model):
277
295
unit_symbols .append (list (clause .literals .keys ())[0 ])
278
296
else :
279
297
Fcount , Ncount = 0 , 0
280
- for l , v in clause .literals .items ():
281
- if v == False :
298
+ for literal , value in clause .literals .items ():
299
+ if value is False :
282
300
Fcount += 1
283
- elif v == None :
284
- sym = l
301
+ elif value is None :
302
+ sym = literal
285
303
Ncount += 1
286
304
if Fcount == clause .no_of_literals - 1 and Ncount == 1 :
287
305
unit_symbols .append (sym )
@@ -296,6 +314,7 @@ def find_unit_clauses(clauses, model):
296
314
297
315
return unit_symbols , assignment
298
316
317
+
299
318
def dpll_algorithm (clauses , symbols , model ):
300
319
"""
301
320
Returns the model if the formula is satisfiable, else None
@@ -321,35 +340,35 @@ def dpll_algorithm(clauses, symbols, model):
321
340
check_clause_all_true = True
322
341
for clause in clauses :
323
342
clause_check = clause .evaluate (model )
324
- if clause_check == False :
343
+ if clause_check is False :
325
344
return False , None
326
- elif clause_check == None :
345
+ elif clause_check is None :
327
346
check_clause_all_true = False
328
347
continue
329
348
330
349
if check_clause_all_true :
331
350
return True , model
332
-
351
+
333
352
pure_symbols , assignment = find_pure_symbols (clauses , symbols , model )
334
353
P = None
335
354
if len (pure_symbols ) > 0 :
336
355
P , value = pure_symbols [0 ], assignment [pure_symbols [0 ]]
337
-
356
+
338
357
if P :
339
358
tmp_model = model
340
359
tmp_model [P ] = value
341
360
tmp_symbols = [i for i in symbols ]
342
361
if P in tmp_symbols :
343
362
tmp_symbols .remove (P )
344
363
return dpll_algorithm (clauses , tmp_symbols , tmp_model )
345
-
346
- unit_symbols , assignment = find_unit_clauses (clauses , model )
364
+
365
+ unit_symbols , assignment = find_unit_clauses (clauses , model )
347
366
P = None
348
367
if len (unit_symbols ) > 0 :
349
368
P , value = unit_symbols [0 ], assignment [unit_symbols [0 ]]
350
369
if P :
351
370
tmp_model = model
352
- tmp_model [P ]= value
371
+ tmp_model [P ] = value
353
372
tmp_symbols = [i for i in symbols ]
354
373
if P in tmp_symbols :
355
374
tmp_symbols .remove (P )
@@ -361,12 +380,14 @@ def dpll_algorithm(clauses, symbols, model):
361
380
362
381
return dpll_algorithm (clauses , rest , tmp1 ) or dpll_algorithm (clauses , rest , tmp2 )
363
382
383
+
364
384
if __name__ == "__main__" :
365
385
import doctest
386
+
366
387
doctest .testmod ()
367
388
368
389
formula = generate_formula ()
369
- print (f' The formula { formula } is' , end = " " )
390
+ print (f" The formula { formula } is" , end = " " )
370
391
371
392
clauses , symbols = generate_parameters (formula )
372
393
0 commit comments