Fix bug with observation expression DNF transformer, where it was

not preserving operand order when distributing FOLLOWEDBY.
pull/1/head
Michael Chisholm 2021-02-04 16:45:39 -05:00
parent bfc47e73f5
commit 9e9a61c71c
2 changed files with 29 additions and 12 deletions

View File

@ -282,6 +282,7 @@ class AbsorptionTransformer(
A or (A and B) = A A or (A and B) = A
A or (A followedby B) = A A or (A followedby B) = A
A or (B followedby A) = A
Other variants do not hold for observation expressions. Other variants do not hold for observation expressions.
""" """
@ -435,28 +436,35 @@ class DNFTransformer(ObservationExpressionTransformer):
A and (B or C) => (A and B) or (A and C) A and (B or C) => (A and B) or (A and C)
A followedby (B or C) => (A followedby B) or (A followedby C) A followedby (B or C) => (A followedby B) or (A followedby C)
(A or B) followedby C => (A followedby C) or (B followedby C)
""" """
def __transform(self, ast): def __transform(self, ast):
root_type = type(ast) # will be AST class for AND or FOLLOWEDBY # If no OR children, nothing to do
changed = False if any(
or_children = [] isinstance(child, OrObservationExpression)
other_children = [] for child in ast.operands
for child in ast.operands: ):
if isinstance(child, OrObservationExpression): # When we distribute FOLLOWEDBY over OR, it is important to
or_children.append(child.operands) # preserve the original FOLLOWEDBY order! We don't need to do that
else: # for AND, but we do it anyway because it doesn't hurt, and we can
other_children.append(child) # use the same code for both.
iterables = []
for child in ast.operands:
if isinstance(child, OrObservationExpression):
iterables.append(child.operands)
else:
iterables.append((child,))
if or_children: root_type = type(ast) # will be AST class for AND or FOLLOWEDBY
distributed_children = [ distributed_children = [
root_type([ root_type([
_dupe_ast(sub_ast) for sub_ast in itertools.chain( _dupe_ast(sub_ast) for sub_ast in itertools.chain(
other_children, prod_seq, prod_seq,
) )
]) ])
for prod_seq in itertools.product(*or_children) for prod_seq in itertools.product(*iterables)
] ]
# Need to recursively continue to distribute AND/FOLLOWEDBY over OR # Need to recursively continue to distribute AND/FOLLOWEDBY over OR
@ -470,6 +478,7 @@ class DNFTransformer(ObservationExpressionTransformer):
else: else:
result = ast result = ast
changed = False
return result, changed return result, changed

View File

@ -223,6 +223,10 @@ def test_obs_absorb_not_equivalent(patt1, patt2):
"([a:b=1] OR [a:b=2]) FOLLOWEDBY ([a:b=3] OR [a:b=4])", "([a:b=1] OR [a:b=2]) FOLLOWEDBY ([a:b=3] OR [a:b=4])",
"([a:b=1] FOLLOWEDBY [a:b=3]) OR ([a:b=1] FOLLOWEDBY [a:b=4]) OR ([a:b=2] FOLLOWEDBY [a:b=3]) OR ([a:b=2] FOLLOWEDBY [a:b=4])", "([a:b=1] FOLLOWEDBY [a:b=3]) OR ([a:b=1] FOLLOWEDBY [a:b=4]) OR ([a:b=2] FOLLOWEDBY [a:b=3]) OR ([a:b=2] FOLLOWEDBY [a:b=4])",
), ),
(
"([a:b=1] OR [a:b=2]) FOLLOWEDBY ([a:b=5] AND [a:b=6])",
"([a:b=1] FOLLOWEDBY ([a:b=5] AND [a:b=6])) OR ([a:b=2] FOLLOWEDBY ([a:b=5] AND [a:b=6]))"
)
], ],
) )
def test_obs_dnf_equivalent(patt1, patt2): def test_obs_dnf_equivalent(patt1, patt2):
@ -243,6 +247,10 @@ def test_obs_dnf_equivalent(patt1, patt2):
"[a:b=1] WITHIN 2 SECONDS", "[a:b=1] WITHIN 2 SECONDS",
"[a:b=1] REPEATS 2 TIMES", "[a:b=1] REPEATS 2 TIMES",
), ),
(
"[a:b=1] FOLLOWEDBY ([a:b=2] OR [a:b=3])",
"([a:b=2] FOLLOWEDBY [a:b=1]) OR ([a:b=1] FOLLOWEDBY [a:b=3])"
)
], ],
) )
def test_obs_not_equivalent(patt1, patt2): def test_obs_not_equivalent(patt1, patt2):