Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/logic/boolformula.py
4048 views
1
r"""
2
Module associated with booleval, logictable, and logicparser, to do
3
boolean evaluation of boolean formulas.
4
Formulas consist of the operators &, |, ~, ^, ->, <->, corresponding
5
to and, or, not, xor, if...then, if and only if. Operators can
6
be applied to variables that consist of a leading letter and trailing
7
underscores and alphanumerics. Parentheses may be used to
8
explicitly show order of operation.
9
10
AUTHORS:
11
-- Chris Gorecki
12
13
EXAMPLES:
14
sage: import sage.logic.propcalc as propcalc
15
sage: f = propcalc.formula("a&((b|c)^a->c)<->b")
16
sage: g = propcalc.formula("boolean<->algebra")
17
sage: (f&~g).ifthen(f)
18
((a&((b|c)^a->c)<->b)&(~(boolean<->algebra)))->(a&((b|c)^a->c)<->b)
19
20
EXAMPLES:
21
sage: import sage.logic.propcalc as propcalc
22
sage: f = propcalc.formula("a&((b|c)^a->c)<->b")
23
sage: g = propcalc.formula("boolean<->algebra")
24
sage: (f&~g).ifthen(f)
25
((a&((b|c)^a->c)<->b)&(~(boolean<->algebra)))->(a&((b|c)^a->c)<->b)
26
27
We can create a truth table from a formula.
28
sage: f.truthtable()
29
a b c value
30
False False False True
31
False False True True
32
False True False False
33
False True True False
34
True False False True
35
True False True False
36
True True False True
37
True True True True
38
sage: f.truthtable(end=3)
39
a b c value
40
False False False True
41
False False True True
42
False True False False
43
sage: f.truthtable(start=4)
44
a b c value
45
True False False True
46
True False True False
47
True True False True
48
True True True True
49
sage: propcalc.formula("a").truthtable()
50
a value
51
False False
52
True True
53
54
Now we can evaluate the formula for a given set of inputs.
55
sage: f.evaluate({'a':True, 'b':False, 'c':True})
56
False
57
sage: f.evaluate({'a':False, 'b':False, 'c':True})
58
True
59
60
And we can convert a boolean formula to conjunctive normal form.
61
sage: f.convert_cnf_table()
62
sage: f
63
(a|~b|c)&(a|~b|~c)&(~a|b|~c)
64
sage: f.convert_cnf_recur()
65
sage: f
66
(a|~b|c)&(a|~b|~c)&(~a|b|~c)
67
68
Or determine if an expression is satisfiable, a contradiction, or a tautology.
69
sage: f = propcalc.formula("a|b")
70
sage: f.is_satisfiable()
71
True
72
sage: f = f & ~f
73
sage: f.is_satisfiable()
74
False
75
sage: f.is_contradiction()
76
True
77
sage: f = f | ~f
78
sage: f.is_tautology()
79
True
80
81
The equality operator compares semantic equivalence.
82
sage: f = propcalc.formula("(a|b)&c")
83
sage: g = propcalc.formula("c&(b|a)")
84
sage: f == g
85
True
86
sage: g = propcalc.formula("a|b&c")
87
sage: f == g
88
False
89
90
It is an error to create a formula with bad syntax.
91
sage: propcalc.formula("")
92
Traceback (most recent call last):
93
...
94
SyntaxError: malformed statement
95
sage: propcalc.formula("a&b~(c|(d)")
96
Traceback (most recent call last):
97
...
98
SyntaxError: malformed statement
99
sage: propcalc.formula("a&&b")
100
Traceback (most recent call last):
101
...
102
SyntaxError: malformed statement
103
sage: propcalc.formula("a&b a")
104
Traceback (most recent call last):
105
...
106
SyntaxError: malformed statement
107
108
It is also an error to not abide by the naming conventions.
109
sage: propcalc.formula("~a&9b")
110
Traceback (most recent call last):
111
...
112
NameError: invalid variable name 9b: identifiers must begin with a letter and contain only alphanumerics and underscores
113
114
"""
115
#*******************************************************************************************
116
#copyright (C) 2006 William Stein <[email protected]>
117
#copyright (C) 2006 Chris Gorecki <[email protected]>
118
#Distributed under the terms of the GNU General Public License (GPL)
119
#http://www.gnu.org/licenses/
120
#*******************************************************************************************
121
import booleval
122
import logictable
123
import logicparser
124
#import boolopt
125
from types import *
126
127
latex_operators = [('&', '\\wedge '),
128
('|', '\\vee '),
129
('~', '\\neg '),
130
('^', '\\oplus '),
131
('<->', '\\leftrightarrow '),
132
('->', '\\rightarrow ')]
133
134
class BooleanFormula:
135
__expression = ""
136
__tree = []
137
__vars_order = []
138
139
def __init__(self, exp, tree, vo):
140
r"""
141
This function initializes the data fields and is called when a
142
new statement is created.
143
144
INPUT:
145
self -- the calling object.
146
exp -- a string containing the logic expression to be manipulated.
147
tree -- a list containing the parse tree of the expression.
148
vo -- a list of the variables in the expression in order,
149
with each variable occurring only once.
150
151
OUTPUT:
152
Effectively returns an instance of this class.
153
154
EXAMPLES:
155
This example illustrates the creation of a statement.
156
sage: import sage.logic.propcalc as propcalc
157
sage: s = propcalc.formula("a&b|~(c|a)")
158
sage: s
159
a&b|~(c|a)
160
"""
161
self.__expression = ""
162
#remove white space from expression
163
for c in exp:
164
if(c != ' '):
165
self.__expression += c
166
self.__tree = tree
167
self.__vars_order = vo
168
169
def __repr__(self):
170
r"""
171
Returns a string representation of this statement.
172
173
INPUT:
174
self -- the calling object.
175
176
OUTPUT:
177
Returns the string representation of this statement.
178
179
EXAMPLES:
180
sage: import sage.logic.propcalc as propcalc
181
sage: propcalc.formula("man->monkey&human")
182
man->monkey&human
183
"""
184
return self.__expression
185
186
def _latex_(self):
187
r"""
188
Returns a LaTeX representation of this statement.
189
190
INPUT:
191
self -- the calling object.
192
193
OUTPUT:
194
Returns the latex representation of this statement.
195
196
EXAMPLES:
197
sage: import sage.logic.propcalc as propcalc
198
sage: s = propcalc.formula("man->monkey&human")
199
sage: latex(s)
200
man\rightarrow monkey\wedge human
201
202
sage: f = propcalc.formula("a & ((~b | c) ^ a -> c) <-> ~b")
203
sage: latex(f)
204
a\wedge ((\neg b\vee c)\oplus a\rightarrow c)\leftrightarrow \neg b
205
"""
206
latex_expression = self.__expression
207
for old, new in latex_operators:
208
latex_expression = latex_expression.replace(old, new)
209
return latex_expression
210
211
def tree(self):
212
r"""
213
Returns a list containing the parse tree of this boolean expression.
214
215
INPUT:
216
self -- the calling object.
217
218
OUTPUT:
219
A list containing the parse tree of self.
220
221
EXAMPLES:
222
sage: import sage.logic.propcalc as propcalc
223
sage: s = propcalc.formula("man -> monkey & human")
224
sage: s.tree()
225
['->', 'man', ['&', 'monkey', 'human']]
226
sage: f = propcalc.formula("a & ((~b | c) ^ a -> c) <-> ~b")
227
sage: f.tree()
228
<BLANKLINE>
229
['<->',
230
['&', 'a', ['->', ['^', ['|', ['~', 'b', None], 'c'], 'a'], 'c']],
231
['~', 'b', None]]
232
"""
233
return self.__tree
234
235
def __or__(self, other):
236
r"""
237
Overloads the | operator to 'or' two statements together.
238
239
INPUT:
240
self -- the right hand side statement.
241
other -- the left hand side statement.
242
243
OUTPUT:
244
Returns a new statement that is the first statement logically
245
or'ed together.
246
EXAMPLES:
247
sage: import sage.logic.propcalc as propcalc
248
sage: s = propcalc.formula("a&b")
249
sage: f = propcalc.formula("c^d")
250
sage: s | f
251
(a&b)|(c^d)
252
"""
253
return self.add_statement(other, '|')
254
255
def __and__(self, other):
256
r"""
257
Overloads the & operator to 'and' two statements together.
258
259
INPUT:
260
self -- the right hand side statement.
261
other -- the left hand side statement.
262
263
OUTPUT:
264
Returns a new statement that is the first statement logically
265
and'ed together.
266
267
EXAMPLES:
268
sage: import sage.logic.propcalc as propcalc
269
sage: s = propcalc.formula("a&b")
270
sage: f = propcalc.formula("c^d")
271
sage: s & f
272
(a&b)&(c^d)
273
"""
274
return self.add_statement(other, '&')
275
276
def __xor__(self, other):
277
r"""
278
Overloads the ^ operator to xor two statements together.
279
280
INPUT:
281
self -- the right hand side statement.
282
other -- the left hand side statement.
283
284
OUTPUT:
285
Returns a new statement that is the first statement logically
286
xor'ed together.
287
288
EXAMPLES:
289
sage: import sage.logic.propcalc as propcalc
290
sage: s = propcalc.formula("a&b")
291
sage: f = propcalc.formula("c^d")
292
sage: s ^ f
293
(a&b)^(c^d)
294
"""
295
return self.add_statement(other, '^')
296
297
def __pow__(self, other):
298
r"""
299
Overloads the ^ operator to xor two statements together.
300
301
INPUT:
302
self -- the right hand side statement.
303
other -- the left hand side statement.
304
305
OUTPUT:
306
Returns a new statement that is the first statement logically
307
xor'ed together.
308
309
EXAMPLES:
310
sage: import sage.logic.propcalc as propcalc
311
sage: s = propcalc.formula("a&b")
312
sage: f = propcalc.formula("c^d")
313
sage: s ^ f
314
(a&b)^(c^d)
315
"""
316
return self.add_statement(other, '^')
317
318
def __invert__(self):
319
r"""
320
Overloads the ~ operator to not a statement.
321
322
INPUT:
323
self -- the right hand side statement.
324
other -- the left hand side statement.
325
326
OUTPUT:
327
Returns a new statement that is the first statement logically
328
not'ed.
329
330
EXAMPLES:
331
sage: import sage.logic.propcalc as propcalc
332
sage: s = propcalc.formula("a&b")
333
sage: ~s
334
~(a&b)
335
"""
336
exp = '~(' + self.__expression + ')'
337
parse_tree, vars_order = logicparser.parse(exp)
338
return BooleanFormula(exp, parse_tree, vars_order)
339
340
def ifthen(self, other):
341
r"""
342
Returns two statements attached by the -> operator.
343
344
INPUT:
345
self -- the right hand side statement.
346
other -- the left hand side statement.
347
348
OUTPUT:
349
Returns a new statement that is the first statement logically
350
ifthen'ed together.
351
352
EXAMPLES:
353
sage: import sage.logic.propcalc as propcalc
354
sage: s = propcalc.formula("a&b")
355
sage: f = propcalc.formula("c^d")
356
sage: s.ifthen(f)
357
(a&b)->(c^d)
358
"""
359
return self.add_statement(other, '->')
360
361
def iff(self, other):
362
r"""
363
Returns two statements attached by the <-> operator.
364
365
INPUT:
366
self -- the right hand side statement.
367
other -- the left hand side statement.
368
369
OUTPUT:
370
Returns a new statement that is the first statement logically
371
ifandonlyif'ed together.
372
373
EXAMPLES:
374
sage: import sage.logic.propcalc as propcalc
375
sage: s = propcalc.formula("a&b")
376
sage: f = propcalc.formula("c^d")
377
sage: s.iff(f)
378
(a&b)<->(c^d)
379
"""
380
return self.add_statement(other, '<->')
381
382
def __eq__(self, other):
383
r"""
384
Overloads the == operator to determine if two expressions
385
are logically equivalent.
386
387
INPUT:
388
self -- the right hand side statement.
389
other -- the left hand side statement.
390
391
OUTPUT:
392
Returns true if the left hand side is equivalent to the
393
right hand side, and false otherwise.
394
395
EXAMPLES:
396
sage: import sage.logic.propcalc as propcalc
397
sage: f = propcalc.formula("(a|b)&c")
398
sage: g = propcalc.formula("c&(b|a)")
399
sage: f == g
400
True
401
sage: g = propcalc.formula("a|b&c")
402
sage: f == g
403
False
404
"""
405
return self.equivalent(other)
406
407
def truthtable(self, start = 0, end = -1):
408
r"""
409
This function returns a truthtable object corresponding to the given
410
statement. Each row of the table corresponds to a binary number, with
411
each variable associated to a column of the number, and taking on
412
a true value if that column has a value of 1. Please see the
413
logictable module for details. The function returns a table that
414
start inclusive and end exclusive so truthtable(0, 2) will include
415
row 0, but not row 2.
416
417
INPUT:
418
self -- the calling object.
419
start -- an integer representing the row of the truth
420
table from which to start initialized to 0, which
421
is the first row when all the variables are
422
false.
423
end -- an integer representing the last row of the truthtable
424
to be created. It is initialized to the last row of the
425
full table.
426
427
OUTPUT:
428
Returns the truthtable (a 2-D array with the creating statement
429
tacked on the front) corresponding to the statement.
430
431
EXAMPLES:
432
This example illustrates the creation of a statement.
433
sage: import sage.logic.propcalc as propcalc
434
sage: s = propcalc.formula("a&b|~(c|a)")
435
sage: s.truthtable()
436
a b c value
437
False False False True
438
False False True False
439
False True False True
440
False True True False
441
True False False False
442
True False True False
443
True True False True
444
True True True True
445
446
We can now create truthtable of rows 1 to 4, inclusive
447
sage: s.truthtable(1, 5)
448
a b c value
449
False False True False
450
False True False True
451
False True True False
452
True False False False
453
454
There should be no errors.
455
456
NOTES:
457
When sent with no start or end parameters, this is an
458
exponential time function requiring O(2**n) time, where
459
n is the number of variables in the expression.
460
"""
461
max = 2 ** len(self.__vars_order)
462
if(end < 0):
463
end = max
464
if(end > max):
465
end = max
466
if(start < 0):
467
start = 0
468
if(start > max):
469
start = max
470
keys, table = [], []
471
vars = {}
472
for var in self.__vars_order:
473
vars[var] = False
474
keys.insert(0, var)
475
keys = list(keys)
476
for i in range(start, end):
477
j = 0
478
row = []
479
for key in keys:
480
bit = self.get_bit(i, j)
481
vars[key] = bit
482
j += 1
483
row.insert(0, bit)
484
row.append(booleval.eval_formula(self.__tree, vars))
485
table.append(row)
486
keys.reverse()
487
table = logictable.Truthtable(table, keys)
488
return table
489
490
def evaluate(self, var_values):
491
r"""
492
Evaluates a formula for the given input values.
493
494
INPUT:
495
self -- the calling object.
496
var_values -- a dictionary containing pairs of
497
variables and their boolean values.
498
All variable must be present.
499
500
OUTPUT:
501
Return the evaluation of the formula with the given
502
inputs, either True or False.
503
504
EXAMPLES:
505
sage: import sage.logic.propcalc as propcalc
506
sage: f = propcalc.formula("a&b|c")
507
sage: f.evaluate({'a':False, 'b':False, 'c':True})
508
True
509
sage: f.evaluate({'a':True, 'b':False, 'c':False})
510
False
511
"""
512
return booleval.eval_formula(self.__tree, var_values)
513
514
def is_satisfiable(self):
515
r"""
516
Is_satisfiable determines if there is some assignment of
517
variables for which the formula will be true.
518
519
INPUT:
520
self -- the calling object.
521
522
OUTPUT:
523
True if the formula can be satisfied, False otherwise.
524
525
EXAMPLES:
526
sage: import sage.logic.propcalc as propcalc
527
sage: f = propcalc.formula("a|b")
528
sage: f.is_satisfiable()
529
True
530
sage: g = f & (~f)
531
sage: g.is_satisfiable()
532
False
533
"""
534
table = self.truthtable().get_table_list()
535
for row in table[1:]:
536
if row[-1] == True:
537
return True
538
return False
539
540
def is_tautology(self):
541
r"""
542
Is_tautology determines if the formula is always
543
true.
544
545
INPUT:
546
self -- the calling object.
547
548
OUTPUT:
549
True if the formula is a tautology, False otherwise.
550
551
EXAMPLES:
552
sage: import sage.logic.propcalc as propcalc
553
sage: f = propcalc.formula("a|~a")
554
sage: f.is_tautology()
555
True
556
sage: f = propcalc.formula("a&~a")
557
sage: f.is_tautology()
558
False
559
sage: f = propcalc.formula("a&b")
560
sage: f.is_tautology()
561
False
562
563
"""
564
return not (~self).is_satisfiable()
565
566
def is_contradiction(self):
567
r"""
568
Is_contradiction determines if the formula is always
569
false.
570
571
INPUT:
572
self -- the calling object.
573
574
OUTPUT:
575
True if the formula is a contradiction, False otherwise.
576
577
EXAMPLES:
578
sage: import sage.logic.propcalc as propcalc
579
sage: f = propcalc.formula("a&~a")
580
sage: f.is_contradiction()
581
True
582
sage: f = propcalc.formula("a|~a")
583
sage: f.is_contradiction()
584
False
585
sage: f = propcalc.formula("a|b")
586
sage: f.is_contradiction()
587
False
588
"""
589
return not self.is_satisfiable()
590
591
def equivalent(self, other):
592
r"""
593
This function determines if two formulas are semantically
594
equivalent.
595
596
INPUT:
597
self -- the calling object.
598
other -- a boolformula instance.
599
600
OUTPUT:
601
True if the two formulas are logically equivalent, False
602
otherwise.
603
604
EXAMPLES:
605
sage: import sage.logic.propcalc as propcalc
606
sage: f = propcalc.formula("(a|b)&c")
607
sage: g = propcalc.formula("c&(a|b)")
608
sage: f.equivalent(g)
609
True
610
sage: g = propcalc.formula("a|b&c")
611
sage: f.equivalent(g)
612
False
613
"""
614
return self.iff(other).is_tautology()
615
616
def convert_cnf(self):
617
r"""
618
This function converts an instance of boolformula to conjunctive
619
normal form. It does so by calling the function convert_cnf_table().
620
Hence convert_cnf() and convert_cnf_table() are functionally
621
identical, with the latter performing the actual conversion.
622
623
INPUT:
624
self -- the calling object.
625
626
OUTPUT:
627
An instance of boolformula with an identical truth table that is in
628
conjunctive normal form.
629
630
EXAMPLES:
631
632
Converting a boolean expression to conjunctive normal form.
633
sage: import sage.logic.propcalc as propcalc
634
sage: s = propcalc.formula("a ^ b <-> c")
635
sage: s.convert_cnf(); s
636
(a|b|~c)&(a|~b|c)&(~a|b|c)&(~a|~b|~c)
637
638
Ensure that convert_cnf() and convert_cnf_table() produce the same result.
639
sage: t = propcalc.formula("a ^ b <-> c")
640
sage: t.convert_cnf_table(); t
641
(a|b|~c)&(a|~b|c)&(~a|b|c)&(~a|~b|~c)
642
sage: t == s
643
True
644
645
SEE ALSO:
646
convert_cnf_table()
647
"""
648
self.convert_cnf_table()
649
650
def convert_cnf_table(self):
651
r"""
652
This function converts an instance of boolformula to conjunctive
653
normal form. It does this by examining the truthtable of the formula,
654
and thus takes O(2^n) time, where n is the number of variables.
655
656
INPUT:
657
self -- the calling object.
658
659
OUTPUT:
660
An instance of boolformula with an identical truth table that is in
661
conjunctive normal form.
662
663
EXAMPLES:
664
sage: import sage.logic.propcalc as propcalc
665
sage: s = propcalc.formula("a ^ b <-> c")
666
sage: s.convert_cnf()
667
sage: s
668
(a|b|~c)&(a|~b|c)&(~a|b|c)&(~a|~b|~c)
669
670
Ensure that convert_cnf() and convert_cnf_table() produce the same result.
671
sage: t = propcalc.formula("a ^ b <-> c")
672
sage: t.convert_cnf_table(); t
673
(a|b|~c)&(a|~b|c)&(~a|b|c)&(~a|~b|~c)
674
sage: t == s
675
True
676
677
NOTES:
678
This method creates the cnf parse tree by examining the logic
679
table of the formula. Creating the table requires O(2^n) time
680
where n is the number of variables in the formula.
681
"""
682
str = ''
683
t = self.truthtable()
684
table = t.get_table_list()
685
vars = table[0]
686
for row in table[1:]:
687
if(row[-1] == False):
688
str += '('
689
for i in range(len(row) - 1):
690
if(row[i] == True):
691
str += '~'
692
str += vars[i]
693
str += '|'
694
str = str[:-1] + ')&'
695
self.__expression = str[:-1]
696
#in case of tautology
697
if(len(self.__expression) == 0):
698
self.__expression = '(' + self.__vars_order[0] + '|~' + self.__vars_order[0] + ')'
699
self.__tree, self.__vars_order = logicparser.parse(self.__expression)
700
701
def convert_cnf_recur(self):
702
r"""
703
This function converts an instance of boolformula to conjunctive
704
normal form. It does this by applying a set of rules that are
705
guaranteed to convert the formula. Worst case the converted
706
expression has an O(2^n) increase in size (and time as well), but if
707
the formula is already in CNF (or close to) it is only O(n).
708
709
INPUT:
710
self -- the calling object.
711
712
OUTPUT:
713
An instance of boolformula with an identical truth table that is in
714
conjunctive normal form.
715
716
EXAMPLES:
717
sage: import sage.logic.propcalc as propcalc
718
sage: s = propcalc.formula("a^b<->c")
719
sage: s.convert_cnf_recur()
720
sage: s
721
(~a|a|c)&(~b|a|c)&(~a|b|c)&(~b|b|c)&(~c|a|b)&(~c|~a|~b)
722
723
NOTES:
724
This function can require an exponential blow up in space from the
725
original expression. This in turn can require large amounts of time.
726
Unless a formula is already in (or close to) being in cnf convert_cnf()
727
is typically preferred, but results can vary.
728
"""
729
self.__tree = logicparser.apply_func(self.__tree, self.reduce_op)
730
self.__tree = logicparser.apply_func(self.__tree, self.dist_not)
731
self.__tree = logicparser.apply_func(self.__tree, self.dist_ors)
732
self.convert_expression()
733
734
def satformat(self):
735
r"""
736
This function returns the satformat representation of a boolean formula.
737
See www.cs.ubc.ca/~hoos/SATLIB/Benchmarks/SAT/satformat.ps for a
738
description of satformat.
739
740
INPUT:
741
self -- the calling object.
742
743
OUTPUT:
744
A string representing the satformat representation of this object.
745
746
EXAMPLES:
747
sage: import sage.logic.propcalc as propcalc
748
sage: f = propcalc.formula("a&((b|c)^a->c)<->b")
749
sage: f.convert_cnf()
750
sage: f
751
(a|~b|c)&(a|~b|~c)&(~a|b|~c)
752
sage: f.satformat()
753
'p cnf 3 0\n1 -2 3 0 1 -2 -3 \n0 -1 2 -3'
754
755
NOTES:
756
If the instance of boolean formula has not been converted to
757
CNF form by a call to convert_cnf() or convert_cnf_recur()
758
satformat() will call convert_cnf(). Please see the notes for
759
convert_cnf() and convert_cnf_recur() for performance issues.
760
"""
761
self.convert_cnf_table()
762
s = ''
763
vars_num = {}
764
i = 0
765
clauses = 0
766
for e in self.__vars_order:
767
vars_num[e] = str(i + 1)
768
i += 1
769
i = 0
770
w = 1
771
while i < len(self.__expression):
772
c = self.__expression[i]
773
if(c == ')'):
774
clauses += 1
775
if(c in '()|'):
776
i += 1
777
continue
778
if(c == '~'):
779
s += '-'
780
elif(c == '&'):
781
s += '0 '
782
else:
783
varname = ''
784
while(i < self.__expression[i] not in '|) '):
785
varname += self.__expression[i]
786
i += 1
787
s += vars_num[varname] + ' '
788
if(len(s) >= (w * 15) and s[-1] != '-'):
789
s += '\n'
790
w += 1
791
i += 1
792
s = 'p cnf ' + str(len(self.__vars_order)) + ' ' + str(clauses) + '\n' + s
793
return s[:-1]
794
795
# def simplify(self):
796
# r"""
797
# This function uses the propcalc package to simplify an expression to
798
# its minimal form.
799
#
800
# INPUT:
801
# self -- the calling object.
802
#
803
# OUTPUT:
804
# A simplified expression.
805
#
806
# EXAMPLES:
807
# sage: import sage.logic.propcalc as propcalc
808
# sage: f = propcalc.formula("a&((b|c)^a->c)<->b")
809
# sage: f.truthtable()
810
# a b c value
811
# False False False True
812
# False False True True
813
# False True False False
814
# False True True False
815
# True False False True
816
# True False True False
817
# True True False True
818
# True True True True
819
# sage: f.simplify()
820
# (~a&~b)|(a&~b&~c)|(a&b)
821
# sage: f.truthtable()
822
# a b c value
823
# False False False True
824
# False False True True
825
# False True False False
826
# False True True False
827
# True False False True
828
# True False True False
829
# True True False True
830
# True True True True
831
#
832
# NOTES:
833
# If the instance of boolean formula has not been converted to
834
# cnf form by a call to convert_cnf() or convert_cnf_recur()
835
# satformat() will call convert_cnf(). Please see the notes for
836
# convert_cnf() and convert_cnf_recur() for performance issues.
837
# """
838
# exp = ''
839
# self.__tree = logicparser.apply_func(self.__tree, self.reduce_op)
840
# plf = logicparser.apply_func(self.__tree, self.convert_opt)
841
# wff = boolopt.PLFtoWFF()(plf) # convert to positive-normal form
842
# wtd = boolopt.WFFtoDNF()
843
# dnf = wtd(wff)
844
# dnf = wtd.clean(dnf)
845
# if(dnf == [] or dnf == [[]]):
846
# exp = self.__vars_order[0] + '&~' + self.__vars_order[0] + ' '
847
# opt = boolopt.optimize(dnf)
848
# if(exp == '' and (opt == [] or opt == [[]])):
849
# exp = self.__vars_order[0] + '|~' + self.__vars_order[0] + ' '
850
# if(exp == ''):
851
# for con in opt:
852
# s = '('
853
# for prop in con:
854
# if(prop[0] == 'notprop'):
855
# s += '~'
856
# s += prop[1] + '&'
857
# exp += s[:-1] + ')|'
858
# self.__expression = exp[:-1]
859
# self.__tree, self.__vars_order = logicparser.parse(self.__expression)
860
# return BooleanFormula(self.__expression, self.__tree, self.__vars_order)
861
862
def convert_opt(self, tree):
863
r"""
864
This function can be applied to a parse tree to convert to the tuple
865
form used by bool opt. The expression must only contain '&', '|', and
866
'~' operators.
867
868
INPUT:
869
self -- the calling object.
870
tree -- a list of three elements corresponding to a branch of a
871
parse tree.
872
OUTPUT:
873
A tree branch that does not contain ^, ->, or <-> operators.
874
875
EXAMPLES:
876
sage: import sage.logic.propcalc as propcalc, sage.logic.logicparser as logicparser
877
sage: s = propcalc.formula("a&(b|~c)")
878
sage: tree = ['&', 'a', ['|', 'b', ['~', 'c', None]]]
879
sage: logicparser.apply_func(tree, s.convert_opt)
880
('and', ('prop', 'a'), ('or', ('prop', 'b'), ('not', ('prop', 'c'))))
881
"""
882
if(type(tree[1]) is not TupleType and tree[1] != None):
883
lval = ('prop', tree[1])
884
else:
885
lval = tree[1]
886
if(type(tree[2]) is not TupleType and tree[2] != None):
887
rval = ('prop', tree[2])
888
else:
889
rval = tree[2]
890
if(tree[0] == '~'):
891
return ('not', lval)
892
if(tree[0] == '&'):
893
op = 'and'
894
if(tree[0] == '|'):
895
op = 'or'
896
return (op, lval, rval)
897
898
def add_statement(self, other, op):
899
r"""
900
This function takes two statements and combines them
901
together with the given operator.
902
903
INPUT:
904
self -- the left hand side statement object.
905
other -- the right hand side statement object.
906
op -- the character of the operation to be performed.
907
908
OUTPUT:
909
Returns a new statement that is the first statement attached to
910
the second statement by the operator op.
911
912
EXAMPLES:
913
sage: import sage.logic.propcalc as propcalc
914
sage: s = propcalc.formula("a&b")
915
sage: f = propcalc.formula("c^d")
916
sage: s.add_statement(f, '|')
917
(a&b)|(c^d)
918
sage: s.add_statement(f, '->')
919
(a&b)->(c^d)
920
"""
921
exp = '(' + self.__expression + ')' + op + '(' + other.__expression + ')'
922
parse_tree, vars_order = logicparser.parse(exp)
923
return BooleanFormula(exp, parse_tree, vars_order)
924
925
def get_bit(self, x, c):
926
r"""
927
This function returns bit c of the number x. The 0 bit is the
928
low order bit. Errors should be handled gracefully by a return
929
of false, and negative numbers x always return false while a
930
negative c will index from the high order bit.
931
932
INPUT:
933
self -- the calling object.
934
x -- an integer, the number from which to take the bit.
935
c -- an integer, the bit number to be taken, where 0 is
936
the low order bit.
937
938
OUTPUT:
939
returns True if bit c of number x is 1, False otherwise.
940
941
EXAMPLES:
942
sage: import sage.logic.propcalc as propcalc
943
sage: s = propcalc.formula("a&b")
944
sage: s.get_bit(2, 1)
945
True
946
sage: s.get_bit(8, 0)
947
False
948
949
It is not an error to have a bit out of range.
950
sage: s.get_bit(64, 7)
951
False
952
953
Nor is it an error to use a negative number.
954
sage: s.get_bit(-1, 3)
955
False
956
sage: s.get_bit(64, -1)
957
True
958
sage: s.get_bit(64, -2)
959
False
960
"""
961
bits = []
962
while(x > 0):
963
if(x % 2 == 0):
964
b = False
965
else:
966
b = True
967
x = int(x / 2)
968
bits.append(b)
969
if(c > len(bits) - 1):
970
return False
971
else:
972
return bits[c]
973
974
def reduce_op(self, tree):
975
r"""
976
This function can be applied to a parse tree to convert if-and-only-if,
977
if-then, and xor operations to operations only involving and/or operations.
978
979
INPUT:
980
self -- the calling object.
981
tree -- a list of three elements corresponding to a branch of a
982
parse tree.
983
OUTPUT:
984
A tree branch that does not contain ^, ->, or <-> operators.
985
986
EXAMPLES:
987
sage: import sage.logic.propcalc as propcalc, sage.logic.logicparser as logicparser
988
sage: s = propcalc.formula("a->b^c")
989
sage: tree = ['->', 'a', ['^', 'b', 'c']]
990
sage: logicparser.apply_func(tree, s.reduce_op)
991
['|', ['~', 'a', None], ['&', ['|', 'b', 'c'], ['~', ['&', 'b', 'c'], None]]]
992
"""
993
if(tree[0] == '<->'):
994
#parse tree for (~tree[1]|tree[2])&(~tree[2]|tree[1])
995
new_tree = ['&', ['|', ['~', tree[1], None], tree[2]], \
996
['|', ['~', tree[2], None], tree[1]]]
997
elif(tree[0] == '^'):
998
#parse tree for (tree[1]|tree[2])&~(tree[1]&tree[2])
999
new_tree = ['&', ['|', tree[1], tree[2]], \
1000
['~', ['&', tree[1], tree[2]], None]]
1001
elif(tree[0] == '->'):
1002
#parse tree for ~tree[1]|tree[2]
1003
new_tree = ['|', ['~', tree[1], None], tree[2]]
1004
else:
1005
new_tree = tree
1006
return new_tree
1007
1008
def dist_not(self, tree):
1009
r"""
1010
This function can be applied to a parse tree to distribute not operators
1011
over other operators.
1012
1013
INPUT:
1014
self -- the calling object.
1015
tree -- a list of three elements corresponding to a branch of a
1016
parse tree.
1017
OUTPUT:
1018
A tree branch that does not contain un-distributed nots.
1019
1020
EXAMPLES:
1021
sage: import sage.logic.propcalc as propcalc, sage.logic.logicparser as logicparser
1022
sage: s = propcalc.formula("~(a&b)")
1023
sage: tree = ['~', ['&', 'a', 'b'], None]
1024
sage: logicparser.apply_func(tree, s.dist_not) #long time
1025
['|', ['~', 'a', None], ['~', 'b', None]]
1026
"""
1027
if(tree[0] == '~' and type(tree[1]) is ListType):
1028
op = tree[1][0]
1029
if(op != '~'):
1030
if(op == '&'):
1031
op = '|'
1032
else:
1033
op = '&'
1034
new_tree = [op, ['~', tree[1][1], None], ['~', tree[1][2], None]]
1035
return logicparser.apply_func(new_tree, self.dist_not)
1036
else:
1037
#cancel double negative
1038
return tree[1][1]
1039
else:
1040
return tree
1041
1042
def dist_ors(self, tree):
1043
r"""
1044
This function can be applied to a parse tree to distribute or over and.
1045
1046
INPUT:
1047
self -- the calling object.
1048
tree -- a list of three elements corresponding to a branch of a
1049
parse tree.
1050
OUTPUT:
1051
A tree branch that does not contain un-distributed ors.
1052
1053
EXAMPLES:
1054
sage: import sage.logic.propcalc as propcalc, sage.logic.logicparser as logicparser
1055
sage: s = propcalc.formula("(a&b)|(a&c)")
1056
sage: tree = ['|', ['&', 'a', 'b'], ['&', 'a', 'c']]
1057
sage: logicparser.apply_func(tree, s.dist_ors) #long time
1058
['&', ['&', ['|', 'a', 'a'], ['|', 'b', 'a']], ['&', ['|', 'a', 'c'], ['|', 'b', 'c']]]
1059
1060
"""
1061
if(tree[0] == '|' and type(tree[2]) is ListType and tree[2][0] == '&'):
1062
new_tree = ['&', ['|', tree[1], tree[2][1]], ['|', tree[1], tree[2][2]]]
1063
return logicparser.apply_func(new_tree, self.dist_ors)
1064
if(tree[0] == '|' and type(tree[1]) is ListType and tree[1][0] == '&'):
1065
new_tree = ['&', ['|', tree[1][1], tree[2]], ['|', tree[1][2], tree[2]]]
1066
return logicparser.apply_func(new_tree, self.dist_ors)
1067
return tree
1068
1069
def to_infix(self, tree):
1070
r"""
1071
This function can be applied to a parse tree to convert it from prefix to
1072
infix.
1073
1074
INPUT:
1075
self -- the calling object.
1076
tree -- a list of three elements corresponding to a branch of a
1077
parse tree.
1078
1079
OUTPUT:
1080
A tree branch in infix form.
1081
1082
EXAMPLES:
1083
sage: import sage.logic.propcalc as propcalc, sage.logic.logicparser as logicparser
1084
sage: s = propcalc.formula("(a&b)|(a&c)")
1085
sage: tree = ['|', ['&', 'a', 'b'], ['&', 'a', 'c']]
1086
sage: logicparser.apply_func(tree, s.to_infix)
1087
[['a', '&', 'b'], '|', ['a', '&', 'c']]
1088
"""
1089
if(tree[0] != '~'):
1090
return [tree[1], tree[0], tree[2]]
1091
return tree
1092
1093
def convert_expression(self):
1094
r"""
1095
This function converts the string expression associated with an instance
1096
of boolformula to match with its tree representation after being converted
1097
to conjunctive normal form.
1098
1099
INPUT:
1100
self -- the calling object.
1101
1102
OUTPUT:
1103
None.
1104
1105
EXAMPLES:
1106
sage: import sage.logic.propcalc as propcalc
1107
sage: s = propcalc.formula("a^b<->c")
1108
sage: s.convert_cnf_recur(); s #long time
1109
(~a|a|c)&(~b|a|c)&(~a|b|c)&(~b|b|c)&(~c|a|b)&(~c|~a|~b)
1110
1111
"""
1112
ttree = self.__tree[:]
1113
ttree = logicparser.apply_func(ttree, self.to_infix)
1114
self.__expression = ''
1115
str_tree = str(ttree)
1116
open_flag = False
1117
put_flag = False
1118
i = 0
1119
for c in str_tree:
1120
if(i < len(str_tree) - 1):
1121
op = self.get_next_op(str_tree[i:])
1122
if(op == '|' and not open_flag):
1123
self.__expression += '('
1124
open_flag = True
1125
if(i < len(str_tree) - 2 and str_tree[i + 1] == '&' and open_flag):
1126
open_flag = False
1127
self.__expression += ')'
1128
if(str_tree[i:i + 4] == 'None'):
1129
i += 4
1130
if(i < len(str_tree) and str_tree[i] not in ' \',[]'):
1131
self.__expression += str_tree[i]
1132
i += 1
1133
if(open_flag == True):
1134
self.__expression += ')'
1135
1136
def get_next_op(self, str):
1137
r"""
1138
This function returns the next operator in a string.
1139
1140
INPUT:
1141
self -- the calling object.
1142
tree -- a string containing a logical expression.
1143
1144
OUTPUT:
1145
The next operator in the string.
1146
1147
EXAMPLES:
1148
sage: import sage.logic.propcalc as propcalc
1149
sage: s = propcalc.formula("f&p")
1150
sage: s.get_next_op("abra|cadabra")
1151
'|'
1152
"""
1153
i = 0
1154
while(i < len(str) - 1 and str[i] != '&' and str[i] != '|'):
1155
i += 1
1156
return str[i]
1157
1158