def _eliminate_biconditional(node: syntax.Node, *args) -> syntax.Node: """Eliminates biconditional. :param node: The node to rewrite. :returns: Rewritten node. Rewrites expressions of type `A <=> B` into `(A => B) & (B => A)`. """ if not node.is_equivalence(): return node a, b = node.children child1 = syntax.make_formula(syntax.IMPLICATION, [a, b]) child2 = syntax.make_formula(syntax.IMPLICATION, [b, a]) children = [child1, child2] return syntax.make_formula(syntax.CONJUNCTION, children)
def _standardize_quantified_variables(node: syntax.Node, state: syntax.WalkState) -> syntax.Node: """Standardizes quantified variables by giving them unique names. :param node: The node to rewrite. :param state: Rewriting state. :returns: Rewritten node. Rewrites expressions of type: - `*x: A(x)` into `*var_1: A(var_1)`, - `?x: A(x)` into `?var_1: A(var_1)`. """ seen: List[syntax.Node] = state.context.setdefault('seen', []) replaced: syntax.T_Substitution = state.context.setdefault('replaced', {}) # todo: replaced would not work with nested quantified formulas that # reuse the same symbol, but actually never mind - it's a corner case # I don't want to fiddle with now if node in seen: return node if node.is_quantified(): old = node.get_quantified_variable().value if old.startswith('_'): return node # already renamed new = _new_variable_name() qtype = node.get_quantifier_type() quant = syntax.make_quantifier(qtype, new) rv = syntax.make_formula(quant, node.children) replaced[new] = node.get_quantified_variable() state.stack.append((old, new)) seen.append(rv) return rv elif node.is_variable(): # reversed, because we want to rename symbol to the last seen value. # Example: We want to rewrite `?x, ?x: x` into `?a: ?b: b`. for old, new in reversed(state.stack): if old == node.value: rv = syntax.make_variable(new) seen.append(rv) return rv return node else: return node
def _distribute_conjunction(node: syntax.Node, *args) -> syntax.Node: """Distributes conjunctions over disjunctions. :param node: The node to rewrite. :returns: Rewritten node. Rewrites expressions of type `(A & B) | C` into `(A | C) & (B | C)`. """ if not node.is_disjunction(): return node for child in node.children: other = next(x for x in node.children if x is not child) if child.is_conjunction(): a, b = child.children rv1 = syntax.make_formula(syntax.DISJUNCTION, [a, other]) rv2 = syntax.make_formula(syntax.DISJUNCTION, [b, other]) rv = syntax.make_formula(syntax.CONJUNCTION, [rv1, rv2]) return rv return node
def _eliminate_implication(node: syntax.Node, *args) -> syntax.Node: """Eliminates implication. :param node: The node to rewrite. :returns: Rewritten node. Rewrites expressions of type `A => B` into `!A | B`. """ if not node.is_implication(): return node a, b = node.children children = [a.negate(), b] return syntax.make_formula(syntax.DISJUNCTION, children)
def _propagate_negation(node: syntax.Node, *args) -> syntax.Node: """Propagates negations down the syntax tree. :param node: The node to rewrite. :returns: Rewritten node. Rewrites expressions of type: - `!!A` into `A`, - `!(A & B)` into `!A | !B`, - `!(A | B)` into `!A & !B`, - `!(*x: A)` into `?x: !A`,` - `!(?x: A)` into `*x: !A`. """ if not node.is_negation(): return node rv = None child = node.children[0] # Double negation if child.is_negation(): return child.children[0] # De Morgan elif child.is_conjunction(): rv = syntax.DISJUNCTION # De Morgan elif child.is_disjunction(): rv = syntax.CONJUNCTION # Flip Quantifiers elif child.is_quantified(): qtype = next( k for k in [syntax.UNIVERSAL_QUANTIFIER, syntax.EXISTENTIAL_QUANTIFIER] if k != child.get_quantifier_type()) qname = child.get_quantified_variable().value rv = syntax.make_quantifier(qtype, qname) if rv is None: return node else: children = [x.negate() for x in child.children] return syntax.make_formula(rv, children)