Skip to content

Commit ab1ffbb

Browse files
authored
Merge pull request #74 from ccnmtl/lib-update
Library updates
2 parents 8a79bcb + ac8b83b commit ab1ffbb

File tree

3 files changed

+312
-56
lines changed

3 files changed

+312
-56
lines changed

logictools/expression_parser.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,18 @@ def paren_expr(self, expr):
6868

6969
class Frontier(Transformer):
7070

71-
def __init__(self, in_str):
71+
def __init__(self, in_str, allowed_ops=allowed_operations):
7272
super().__init__()
7373
self.in_str = in_str
7474
self.frontier = set()
7575
self.checked_tokens = set()
7676
self.tts = TreeToString()
77+
self.allowed_ops = allowed_ops
7778

7879
def _get_token_variants(self, token: Token): # necessary since Lark doesn't have meta for tokens. Ugh.
7980
sp, ep = token.start_pos, token.end_pos
8081
transforms = set()
81-
for op in allowed_operations[token.type]:
82+
for op in self.allowed_ops[token.type]:
8283
new_node = op(token)
8384
if type(new_node) is not list:
8485
new_node = [new_node]
@@ -90,7 +91,7 @@ def _get_token_variants(self, token: Token): # necessary since Lark doesn't hav
9091

9192
def _get_transformations(self, node: Tree) -> set:
9293
transforms = set()
93-
for op in allowed_operations[node.data]:
94+
for op in self.allowed_ops[node.data]:
9495
new_node = deepcopy(node) # necessary because some transformations are in-place. Change this.
9596
new_node = op(new_node)
9697
if type(new_node) is not list:
@@ -170,11 +171,11 @@ def paren_expr(self, children):
170171
return tr
171172

172173

173-
def get_frontier(in_str: str, simplify_parentheses=True) -> list:
174+
def get_frontier(in_str: str, simplify_parentheses=True, allowed_ops=allowed_operations) -> list:
174175
ep, tts = ExpressionParser(), TreeToString()
175176
tree = ep.parse(in_str)
176177
linted_str = tts.transform(tree)
177-
fr = Frontier(linted_str)
178+
fr = Frontier(linted_str, allowed_ops=allowed_ops)
178179
fr.transform(tree)
179180
frontier = fr.frontier
180181
if simplify_parentheses:
@@ -206,7 +207,6 @@ def validate(current_frontier: list, new_expr: str, new_rule: str) -> str:
206207

207208

208209
def check_success(new_linted: str, target: str) -> bool:
209-
print("Expression: {}, Target: {}".format(new_linted, target), new_linted.casefold() == target.casefold())
210210
return new_linted.casefold() == target.casefold()
211211

212212

logictools/logic_rule_transforms.py

Lines changed: 88 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import itertools
2-
from collections import defaultdict
2+
from collections import OrderedDict
33

44
from lark import Tree, Token
55
from itertools import permutations
@@ -36,14 +36,19 @@ def safe_paren(node: object):
3636

3737
def idempotence(tree: Tree):
3838
assert tree.data == "term" or tree.data == "expr"
39-
unique_exprs = set()
40-
new_children = []
41-
for i, c in enumerate(tree.children):
42-
if c not in unique_exprs:
43-
unique_exprs.add(c)
44-
new_children.append(c)
45-
tree.children = new_children
46-
return tree
39+
return Tree(tree.data, list({k: None for k in tree.children}.keys())) # dicts remember insertion order now!
40+
41+
42+
def reverse_idempotence(node):
43+
if is_tree(node, "term") or is_tree(node, "expr"):
44+
new_trees = [
45+
Tree(node.data, node.children[:i] + [c] + node.children[i:]) for i, c in enumerate(node.children)
46+
]
47+
else:
48+
new_trees = [
49+
Tree("expr", [node, node]), Tree("term", [node, node])
50+
]
51+
return new_trees
4752

4853

4954
def simplify_multiple_negation(tree: Tree):
@@ -101,16 +106,19 @@ def commutativity(tree: Tree): # p^q^r == q^p^r == ... r^p^q, returns list of p
101106
def associativity_LR(tree: Tree): # only for (a^b)^c or with V, i.e. 2-node 3-var ops. Somewhat Hacky. Expand later
102107
assert tree.data == "expr" or tree.data == "term"
103108
ch = tree.children
104-
if len(ch) == 2 and type(ch[0]) == Tree and ch[0].data == "paren_expr":
105-
par = ch[0].children[0]
106-
if is_tree(par, tree.data) and len(par.children) == 2:
107-
tree.children = [
108-
par.children[0],
109-
Tree("paren_expr", [
110-
Tree(tree.data, [par.children[1], ch[1]])
111-
])
112-
]
113-
return tree
109+
new_trees = []
110+
for i, c in enumerate(ch):
111+
if is_tree(c, "paren_expr") and is_tree(c.children[0], tree.data):
112+
swap_ch = c.children[0].children
113+
if i > 0:
114+
for j in range(len(swap_ch)-1):
115+
new_paren = parenthesize(Tree(tree.data, ch[:i] + swap_ch[:j+1]))
116+
new_trees.append(Tree(tree.data, [new_paren] + swap_ch[j+1:] + ch[i+1:]))
117+
if i < len(ch)-1:
118+
for j in range(1, len(swap_ch)):
119+
new_paren = parenthesize(Tree(tree.data, swap_ch[j:] + ch[i+1:]))
120+
new_trees.append(Tree(tree.data, ch[:i] + swap_ch[:j] + [new_paren]))
121+
return new_trees
114122

115123

116124
def associativity_expand(tree: Tree): # remove all parenthesized expressions
@@ -125,6 +133,16 @@ def associativity_expand(tree: Tree): # remove all parenthesized expressions
125133
return tree
126134

127135

136+
def reverse_associativity_expand(tree: Tree): # add parentheses around arbitrary sequences of expressions
137+
assert tree.data == "expr" or tree.data == "term"
138+
ch = tree.children
139+
new_trees = []
140+
for i in range(len(ch)-1):
141+
for j in range(i+2, len(ch)+1):
142+
new_trees.append(Tree(tree.data, ch[:i] + [parenthesize(Tree(tree.data, ch[i:j]))] + ch[j:]))
143+
return new_trees
144+
145+
128146
def impl_to_disj(tree: Tree): # p->q == ~pVq
129147
assert tree.data == "dbl_expr"
130148
if len(tree.children) > 1:
@@ -178,8 +196,9 @@ def impl_to_dblimpl(tree: Tree): # (p->q)^(q->p) == p<=>q, any adjacent pair ca
178196
if ch0 == c2.children[0].children[1] and ch1 == c2.children[0].children[0]:
179197
pre_exclude = tree.children[:i]
180198
post_exclude = tree.children[i+2:]
181-
tr = Tree("eqn", [ch0, ch1])
182-
new_trees.append(Tree("term", pre_exclude + [tr] + post_exclude))
199+
tr_fwd, tr_bkwd = Tree("eqn", [ch0, ch1]), Tree("eqn", [ch1, ch0])
200+
new_trees.append(Tree("term", pre_exclude + [tr_fwd] + post_exclude))
201+
new_trees.append(Tree("term", pre_exclude + [tr_bkwd] + post_exclude))
183202
return new_trees if len(new_trees) > 0 else [tree]
184203

185204

@@ -195,6 +214,18 @@ def negation(tree: Tree): # pv~p=T, p^~p=F
195214
return tree
196215

197216

217+
def reverse_negation(token: Token, additional_ids=('p', 'q', 'r', 's')):
218+
assert is_token(token, "TRUE") or is_token(token, "FALSE")
219+
new_trees = []
220+
for i in additional_ids:
221+
tok = Token("ID", i)
222+
if is_token(token, "TRUE"):
223+
new_trees.append(parenthesize(Tree("expr", [tok, negate(tok)])))
224+
else:
225+
new_trees.append(parenthesize(Tree("term", [tok, negate(tok)])))
226+
return new_trees
227+
228+
198229
def demorgan(tree: Tree): # ~(pvq) == ~p^~q, ~(p^q) == ~pV~q
199230
assert tree.data == "literal"
200231
if is_tree(tree.children[1], "paren_expr"):
@@ -233,19 +264,17 @@ def absorption(tree: Tree): # pV(p^q) == p, p^(pVq) == p
233264
return tree
234265

235266

236-
def reverse_absorption(tree: Tree): # pvq == pv(p^q), p^q == p^(pvq), consecutive pairs in order
237-
assert tree.data == "expr" or tree.data == "term"
238-
if len(tree.children) == 1:
239-
return [tree]
240-
dual = "expr" if tree.data == "term" else "term"
267+
def reverse_absorption(node, additional_ids=('p', 'q', 'r', 's')): # p == pv(p^ID), p == p^(pvID)
241268
new_trees = []
242-
for i, c in enumerate(tree.children[:-1]):
243-
tr = parenthesize(Tree(dual, [c, tree.children[i+1]]))
244-
new_trees.append(Tree(tree.data, tree.children[:i+1] + [tr] + tree.children[i+2:])) # :i+1 keeps p in pv(p^q)
269+
if is_token(node, "ID"):
270+
additional_ids = [v for v in additional_ids if node.value != v]
271+
for v in additional_ids:
272+
new_trees.append(Tree("expr", [node, parenthesize(Tree("term", [node, v]))]))
273+
new_trees.append(Tree("term", [node, parenthesize(Tree("expr", [node, v]))]))
245274
return new_trees
246275

247276

248-
def TF_negation(tree: Tree):
277+
def TF_negation(tree: Tree): # ~T == F, ~F == T
249278
assert tree.data == "literal"
250279
if len(tree.children) == 2:
251280
if is_token(tree.children[1], "TRUE"):
@@ -311,13 +340,13 @@ def double_negate(tree: Tree): # p == ~~p
311340
"Double Negation": [double_negate, simplify_multiple_negation],
312341
"Implication as Disjunction": [impl_to_disj, disj_to_impl],
313342
"Iff as Implication": [dblimpl_to_impl, impl_to_dblimpl],
314-
"Idempotence": [idempotence],
343+
"Idempotence": [idempotence, reverse_idempotence],
315344
"Identity": [identity, reverse_identity],
316345
"Domination": [domination],
317346
"Commutativity": [commutativity],
318-
"Associativity": [associativity_LR, associativity_expand],
319-
"Negation": [negation, TF_negation],
320-
"Absorption": [absorption],
347+
"Associativity": [associativity_LR, associativity_expand, reverse_associativity_expand],
348+
"Negation": [negation, TF_negation, reverse_negation],
349+
"Absorption": [absorption, reverse_absorption],
321350
"Distributivity": [distributivity, reverse_distributivity],
322351
"De Morgan's Law": [demorgan, reverse_demorgan]
323352
}
@@ -339,25 +368,31 @@ def get_operation_name(op):
339368
impl_to_disj, reverse_identity
340369
],
341370
'expr': [
342-
idempotence, identity, domination, commutativity, associativity_LR, associativity_expand, negation, absorption,
343-
distributivity, reverse_distributivity, double_negate, reverse_demorgan, disj_to_impl, reverse_identity
371+
idempotence, identity, domination, commutativity, associativity_LR, associativity_expand,
372+
reverse_associativity_expand, negation, absorption, distributivity, reverse_distributivity, double_negate,
373+
reverse_demorgan, disj_to_impl, reverse_identity, reverse_idempotence
344374
],
345375
'term': [
346-
idempotence, identity, domination, commutativity, associativity_LR, associativity_expand, negation, absorption,
347-
distributivity, reverse_distributivity, double_negate, reverse_demorgan, impl_to_dblimpl, reverse_identity
376+
idempotence, identity, domination, commutativity, associativity_LR, associativity_expand,
377+
reverse_associativity_expand, negation, absorption, distributivity, reverse_distributivity, double_negate,
378+
reverse_demorgan, impl_to_dblimpl, reverse_identity, reverse_idempotence
348379
],
349380
'literal': [
350-
simplify_multiple_negation, TF_negation, demorgan, reverse_identity
381+
simplify_multiple_negation, TF_negation, demorgan, reverse_identity, reverse_idempotence
351382
],
352383
'variable': [],
353384
'paren_expr': [
354-
double_negate, reverse_identity
385+
double_negate, reverse_identity, reverse_idempotence, reverse_absorption
355386
],
356387
'ID': [
357-
reverse_identity
388+
reverse_identity, reverse_idempotence, reverse_absorption
389+
],
390+
"TRUE": [
391+
reverse_negation
392+
],
393+
"FALSE": [
394+
reverse_negation
358395
],
359-
"TRUE": [],
360-
"FALSE": [],
361396
"_LPAR": [],
362397
"_RPAR": [],
363398
"NOT": [],
@@ -378,13 +413,16 @@ def get_operation_name(op):
378413
ep = ExpressionParser()
379414
tts = TreeToString()
380415

381-
tr1 = ep.parse('pvqvr').children[0]
382-
tr2 = disj_to_impl(tr1)
416+
tr1 = Token("TRUE", "True") #ep.parse('p').children[0]
417+
tr2 = reverse_negation(tr1)
383418
print([tts.transform(t) for t in tr2])
384-
tr1 = ep.parse('a->q->c').children[0]
385-
tr2 = impl_to_disj(tr1)
419+
tr1 = Token("FALSE", "F") #ep.parse('(pvq)').children[0]
420+
tr2 = reverse_negation(tr1)
386421
print([tts.transform(t) for t in tr2])
387422

388-
tr3 = ep.parse('(pvq)').children[0]
389-
tr4 = simplify_paren_expr(tr3)
390-
print(tr4 if type(tr4) == Token else tts.transform(tr4), sep="\n")
423+
t1 = ep.parse('a^b^c^a^b^a').children[0]
424+
t1 = idempotence(t1)
425+
print(t1 if type(t1) == Token else tts.transform(t1), sep="\n")
426+
t3 = ep.parse('(a^b^c)v(a^b^c)').children[0]
427+
t3 = idempotence(t3)
428+
print(t3 if type(t3) == Token else tts.transform(t3), sep="\n")

0 commit comments

Comments
 (0)