Пример #1
0
    def helper_test_evaluate_raises(self,
                                    expr,
                                    expected_exc_type=None,
                                    **kwargs):
        """Helper for testing the improper use of the ``evaluate`` function.


        :param expr: The expression to attempt to evaluate.
        :type expr: :class:`str <python:str>`

        :param expected_exc_type: The exception type expected to be raised.
        :type expected_exc_type: Exception

        :param kwargs: The values to pass to the ``evaluate`` function.

        """
        did_catch = False

        try:
            b = BooleanExpression(expr)
            b.evaluate(**kwargs)
        except expected_exc_type:
            did_catch = True
        except Exception as e:
            traceback.print_exc()
            self.fail('Received exception of type ' + type(e).__name__ +
                      ' but was expecting type ' + expected_exc_type.__name__ +
                      '.')
            did_catch = True

        if not did_catch:
            self.fail('No exception thrown.')
    def test_iter_dnf_clauses_not_dnf(self):
        """Test attempting iter_dnf_clauses when not in DNF."""
        b = BooleanExpression('(A or B) and (C or D)')
        self.assertFalse(b.is_dnf)

        with self.assertRaises(RequiresNormalFormError):
            for clause in b.iter_dnf_clauses():
                pass
    def test_iter_cnf_clauses_not_cnf(self):
        """Test attempting iter_cnf_clauses when not in CNF."""
        b = BooleanExpression('A or !!B')
        self.assertFalse(b.is_cnf)

        with self.assertRaises(RequiresNormalFormError):
            for clause in b.iter_cnf_clauses():
                pass
    def test_iter_clauses_not_normal_form(self):
        """Test attempting iter_clauses when not in normal form."""
        b = BooleanExpression('A nand B')
        self.assertFalse(b.is_cnf)
        self.assertFalse(b.is_dnf)

        with self.assertRaises(RequiresNormalFormError):
            for clause in b.iter_clauses():
                pass
Пример #5
0
    def test_single_conflicting_nested_constraint(self):
        """Test single conflicting constraint in nested constrain()'s."""
        b = BooleanExpression('A or B')
        with self.assertRaises(AlreadyConstrainedSymbolError) as cm:
            with b.constrain(A=1):
                with b.constrain(A=1):
                    pass

        self.assertIn('Symbol "A"', str(cm.exception))
Пример #6
0
    def test_multiple_conflicting_constraints(self):
        """Test multiple conflicting constraint in nested constrain()'s."""
        b = BooleanExpression('A xor B xor C')
        with self.assertRaises(AlreadyConstrainedSymbolError) as cm:
            with b.constrain(A=1, B=0, C=1):
                with b.constrain(B=0, A=1):
                    pass

        self.assertIn('Symbols "A", "B"', str(cm.exception))
Пример #7
0
def distribute_ors(expr):
    """Convert an expression to distribute ORs over ANDed clauses.

    :param expr: The expression to transform.
    :type expr: :class:`str <python:str>` or :class:`BooleanExpression \
    <tt.expressions.bexpr.BooleanExpression>`

    :returns: A new expression object, transformed to distribute ORs over ANDed
        clauses.
    :rtype: :class:`BooleanExpression <tt.expressions.bexpr.BooleanExpression>`

    :raises InvalidArgumentTypeError: If ``expr`` is not a valid type.

    Here's a couple of simple examples::

        >>> from tt import distribute_ors
        >>> distribute_ors('A or (B and C and D and E)')
        <BooleanExpression "(A or B) and (A or C) and (A or D) and (A or E)">
        >>> distribute_ors('(A and B) or C')
        <BooleanExpression "(A or C) and (B or C)">

    And an example involving distributing a sub-expression::

        >>> distribute_ors('(A or B) or (C and D)')
        <BooleanExpression "(A or B or C) and (A or B or D)">

    """
    bexpr = ensure_bexpr(expr)
    return BooleanExpression(bexpr.tree.distribute_ors())
Пример #8
0
def apply_identity_law(expr):
    """Convert an expression to a form with the Identity Law applied.

    It should be noted that this transformation will also annihilate terms
    when possible. One such case where this would be applicable is the
    expression ``A and 0``, which would be transformed to the constant value
    ``0``.

    :returns: A new expression object, transformed so that the Identity Law
        has been applied to applicable *ANDs* and *ORs*.
    :rtype: :class:`BooleanExpression <tt.expressions.bexpr.BooleanExpression>`

    :raises InvalidArgumentTypeError: If ``expr`` is not a valid type.

    Here are a few simple examples showing the behavior of this transformation
    across all two-operand scenarios::

        >>> from tt import apply_identity_law
        >>> apply_identity_law('A and 1')
        <BooleanExpression "A">
        >>> apply_identity_law('A and 0')
        <BooleanExpression "0">
        >>> apply_identity_law('A or 0')
        <BooleanExpression "A">
        >>> apply_identity_law('A or 1')
        <BooleanExpression "1">

    """
    bexpr = ensure_bexpr(expr)
    return BooleanExpression(bexpr.tree.apply_identity_law())
Пример #9
0
def to_cnf(expr):
    """Convert an expression to conjunctive normal form (CNF).

    This transformation only guarantees to produce an equivalent form of the
    passed expression in conjunctive normal form; the transformed expression
    may be an inefficent representation of the passed expression.

    :param expr: The expression to transform.
    :type expr: :class:`str <python:str>` or :class:`BooleanExpression \
    <tt.expressions.bexpr.BooleanExpression>`

    :returns: A new expression object, transformed to be in CNF.
    :rtype: :class:`BooleanExpression <tt.expressions.bexpr.BooleanExpression>`

    :raises InvalidArgumentTypeError: If ``expr`` is not a valid type.

    Here are a few examples::

        >>> from tt import to_cnf
        >>> b = to_cnf('(A nor B) impl C')
        >>> b
        <BooleanExpression "A or B or C">
        >>> b.is_cnf
        True
        >>> b = to_cnf(r'~(~(A /\\ B) /\\ C /\\ D)')
        >>> b
        <BooleanExpression "(A \\/ ~C \\/ ~D) /\\ (B \\/ ~C \\/ ~D)">
        >>> b.is_cnf
        True

    """
    bexpr = ensure_bexpr(expr)
    return BooleanExpression(bexpr.tree.to_cnf())
Пример #10
0
def to_primitives(expr):
    """Convert an expression to a form with only primitive operators.

    All operators will be transformed equivalent form composed only of the
    logical AND, OR,and NOT operators. Symbolic operators in the passed
    expression will remain symbolic in the transformed expression and the same
    applies for plain English operators.

    :param expr: The expression to transform.
    :type expr: :class:`str <python:str>` or :class:`BooleanExpression \
    <tt.expressions.bexpr.BooleanExpression>`

    :returns: A new expression object, transformed to contain only primitive
        operators.
    :rtype: :class:`BooleanExpression <tt.expressions.bexpr.BooleanExpression>`

    :raises InvalidArgumentTypeError: If ``expr`` is not a valid type.

    Here's a simple transformation of exclusive-or::

        >>> from tt import to_primitives
        >>> to_primitives('A xor B')
        <BooleanExpression "(A and not B) or (not A and B)">

    And another example of if-and-only-if (using symbolic operators)::

        >>> to_primitives('A <-> B')
        <BooleanExpression "(A /\\ B) \\/ (~A /\\ ~B)">

    """
    bexpr = ensure_bexpr(expr)
    return BooleanExpression(bexpr.tree.to_primitives())
Пример #11
0
def coalesce_negations(expr):
    """Convert an expression to a form with all negations condensed.

    :returns: A new expression object, transformed so that all "runs" of
        logical *NOTs* are condensed into the minimal equivalent number.
    :rtype: :class:`BooleanExpression <tt.expressions.bexpr.BooleanExpression>`

    :raises InvalidArgumentTypeError: If ``expr`` is not a valid type.

    Here's a simple example showing the basic premise of this transformation::

        >>> from tt import coalesce_negations
        >>> coalesce_negations('~~A or ~B or ~~~C or ~~~~D')
        <BooleanExpression "A or ~B or ~C or D">

    This transformation works on more complex expressions, too::

        >>> coalesce_negations('!!(A -> not not B) or ~(~(A xor B))')
        <BooleanExpression "(A -> B) or (A xor B)">

    It should be noted that this transformation will also apply negations
    to constant operands, as well. The behavior for this functionality is as
    follows::

        >>> coalesce_negations('~0')
        <BooleanExpression "1">
        >>> coalesce_negations('~1')
        <BooleanExpression "0">
        >>> coalesce_negations('~~~0 -> ~1 -> not 1')
        <BooleanExpression "1 -> 0 -> 0">

    """
    bexpr = ensure_bexpr(expr)
    return BooleanExpression(bexpr.tree.coalesce_negations())
Пример #12
0
    def helper_test_tokenization(self,
                                 expr,
                                 expected_tokens=None,
                                 expected_postfix_tokens=None,
                                 expected_symbols=None,
                                 expected_tree_str=None):
        """Helper for testing tokenization on valid expressions.

        :param expr: The expression for which to create a new
            ``BooleanExpression`` object, which should be of a valid form.
        :type expr: BooleanExpression

        :param expected_tokens: The list of expected tokens for the passed
            expression.
        :type expected_tokens: List[str]

        :param expected_postfix_tokens: The list of expected postfix tokens for
            the passed expression.
        :type expected_postfix_tokens: List[str]

        :param expected_symbols: The list of expected symbols for the passed
            expression.
        :type expected_symbols: List[str]

        :param expected_tree_str: The expected string representation of the
            constructed expression tree.
        :type expected_tree_str: str

        """
        b = BooleanExpression(expr)
        self.assertEqual(expected_tokens, b.tokens)
        self.assertEqual(expected_postfix_tokens, b.postfix_tokens)
        self.assertEqual(expected_symbols, b.symbols)
        self.assertEqual(expected_tree_str, str(b.tree))
Пример #13
0
def apply_inverse_law(expr):
    """Convert an expression to a form with the Inverse Law applied.

    :returns: A new expression object, transformed so that the Inverse Law
        has been applied to applicable *ANDs* and *ORs*.
    :rtype: :class:`BooleanExpression <tt.expressions.bexpr.BooleanExpression>`

    :raises InvalidArgumentTypeError: If ``expr`` is not a valid type.

    This transformation will apply the Identity Law to simple binary
    expressions consisting of negated and non-negated forms of the same
    operand. Let's take a look::

        >>> from tt.transformations import apply_inverse_law
        >>> apply_inverse_law('A and ~A')
        <BooleanExpression "0">
        >>> apply_inverse_law('A or B or ~B or C')
        <BooleanExpression "1">

    This transformation will also apply the behavior expected of the Inverse
    Law when negated and non-negated forms of the same operand appear in the
    same CNF or DNF clause in an expression::

        >>> from tt.transformations import apply_inverse_law
        >>> apply_inverse_law('(A or B or ~A) -> (C and ~C)')
        <BooleanExpression "1 -> 0">
        >>> apply_inverse_law('(A or !!!A) xor (not C or not not C)')
        <BooleanExpression "1 xor 1">

    """
    bexpr = ensure_bexpr(expr)
    return BooleanExpression(bexpr.tree.apply_inverse_law())
Пример #14
0
    def helper_test_evaluate(self, expr, expected_result=None, **kwargs):
        """Helper for testing the evaluation of expressions.

        :param expr: The expression to attempt to evaluate.
        :type expr: :class:`str <python:str>`

        :param expected_result: The truthy value expected to be the result of
            evaluation the expression.
        :type expected_result: :class:`bool <python:bool>` or
            :class:`int <python:int>`

        :param kwargs: The values to pass to the ``evaluate`` function.

        """
        b = BooleanExpression(expr)
        result = b.evaluate(**kwargs)
        self.assertEqual(result, expected_result)
Пример #15
0
    def test_simple_iter_cnf(self):
        """Test basic expression iter_cnf_clauses functionality."""
        b = BooleanExpression('(A or B) and (C or D) and (E or F)')
        self.assertTrue(b.is_cnf)
        self.assertFalse(b.is_dnf)

        clauses = b.iter_cnf_clauses()
        self.assertEqual(
            repr(next(clauses)),
            '<BooleanExpression "A or B">')
        self.assertEqual(
            repr(next(clauses)),
            '<BooleanExpression "C or D">')
        self.assertEqual(
            repr(next(clauses)),
            '<BooleanExpression "E or F">')
        with self.assertRaises(StopIteration):
            next(clauses)
Пример #16
0
    def test_simple_iter_dnf(self):
        """Test basic expression iter_dnf_clauses functionality."""
        b = BooleanExpression('(A and B) or (C and D) or (E and F)')
        self.assertTrue(b.is_dnf)
        self.assertFalse(b.is_cnf)

        clauses = b.iter_dnf_clauses()
        self.assertEqual(
            repr(next(clauses)),
            '<BooleanExpression "A and B">')
        self.assertEqual(
            repr(next(clauses)),
            '<BooleanExpression "C and D">')
        self.assertEqual(
            repr(next(clauses)),
            '<BooleanExpression "E and F">')
        with self.assertRaises(StopIteration):
            next(clauses)
Пример #17
0
 def test_deep_conflicting_nested_constraint(self):
     """Test a conflict that occurs in a deeply nested context manager."""
     b = BooleanExpression('A and B and C and D and E')
     with self.assertRaises(AlreadyConstrainedSymbolError) as cm:
         with b.constrain(A=0):
             with b.constrain(B=0):
                 with b.constrain(C=0):
                     with b.constrain(D=0):
                         with b.constrain(E=0):
                             with b.constrain(B=0, D=0, E=1):
                                 pass
     self.assertIn('Symbols "B", "D", "E"', str(cm.exception))
Пример #18
0
Файл: bexpr.py Проект: GDApsy/tt
def _ensure_bexpr(expr):
    """Return a BooleanExpression object or raise an error."""
    if isinstance(expr, str):
        return BooleanExpression(expr)
    elif isinstance(expr, BooleanExpression):
        return expr
    else:
        raise InvalidArgumentTypeError(
            'Transformations accept either a string or BooleanExpression '
            'argument')
Пример #19
0
    def test_simple_iter_clauses(self):
        """Test basic expression iter_clauses functionality."""
        # ensure defaults to CNF
        b = BooleanExpression('A or B or C or D')
        self.assertTrue(b.is_cnf)
        self.assertTrue(b.is_dnf)

        clauses = b.iter_clauses()
        self.assertEqual(
            repr(next(clauses)),
            '<BooleanExpression "A or B or C or D">')
        with self.assertRaises(StopIteration):
            next(clauses)

        # ensure falls back to DNF
        b = BooleanExpression('(A and B and C) or (D and E and F)')
        self.assertFalse(b.is_cnf)
        self.assertTrue(b.is_dnf)

        clauses = b.iter_clauses()
        self.assertEqual(
            repr(next(clauses)),
            '<BooleanExpression "A and B and C">')
        self.assertEqual(
            repr(next(clauses)),
            '<BooleanExpression "D and E and F">')
        with self.assertRaises(StopIteration):
            next(clauses)
Пример #20
0
 def test_from_expression_object(self):
     """Test creating a truth table from an existing expression object."""
     self.helper_test_truth_table(
         BooleanExpression('A'),
         expected_table_str='\n'.join((
             '+---+---+',
             '| A |   |',
             '+---+---+',
             '| 0 | 0 |',
             '+---+---+',
             '| 1 | 1 |',
             '+---+---+'
         )))
Пример #21
0
    def _init_from_expression(self, expr, fill_all, ordering):
        if isinstance(expr, str):
            self._expr = BooleanExpression(expr)
        elif isinstance(expr, BooleanExpression):
            self._expr = expr
        else:
            raise InvalidArgumentTypeError(
                'Arg `expr` must be of type `str` or `BooleanExpression`')

        if ordering is None:
            self._ordering = self._expr.symbols
        else:
            assert_iterable_contains_all_expr_symbols(
                ordering, set(self._expr.symbols))
            self._ordering = ordering

        if not self._ordering:
            raise NoEvaluationVariationError(
                'This expression is composed only of constant values')

        self._results = [None for _ in range(2**len(self._ordering))]
        if fill_all:
            self.fill()
Пример #22
0
def apply_idempotent_law(expr):
    """Convert an expression to a form with the Idempotent Law applied.

    :returns: A new expression object, transformed so that the Idempotent Law
        has been applied to applicable clauses.
    :rtype: :class:`BooleanExpression <tt.expressions.bexpr.BooleanExpression>`

    :raises InvalidArgumentTypeError: If ``expr`` is not a valid data type.

    This transformation will apply the Idempotent Law to clauses of *AND* and
    *OR* operators containing redundant operands. Here are a couple of simple
    examples::

        >>> from tt import apply_idempotent_law
        >>> apply_idempotent_law('A and A')
        <BooleanExpression "A">
        >>> apply_idempotent_law('B or B')
        <BooleanExpression "B">

    This transformation will consider similarly-negated operands to be
    redundant; for example::

        >>> from tt import apply_idempotent_law
        >>> apply_idempotent_law('~A and ~~~A')
        <BooleanExpression "~A">
        >>> apply_idempotent_law('B or ~B or ~~B or ~~~B or ~~~~B or ~~~~~B')
        <BooleanExpression "B or ~B">

    Let's also take a quick look at this transformation's ability to prune
    redundant operands from CNF and DNF clauses::

        >>> from tt import apply_idempotent_law
        >>> apply_idempotent_law('(A and B and C and C and B) or (A and A)')
        <BooleanExpression "(A and B and C) or A">

    Of important note is that this transformation will not recursively apply
    the Idempotent Law to operands that bubble up. Here's an example
    illustrating this case::

        >>> from tt import apply_idempotent_law
        >>> apply_idempotent_law('(A or A) and (A or A)')
        <BooleanExpression "A and A">

    """
    bexpr = ensure_bexpr(expr)
    return BooleanExpression(bexpr.tree.apply_idempotent_law())
Пример #23
0
def apply_de_morgans(expr):
    """Convert an expression to a form with De Morgan's Law applied.

    :returns: A new expression object, transformed so that De Morgan's Law has
        been applied to negated *ANDs* and *ORs*.
    :rtype: :class:`BooleanExpression <tt.expressions.bexpr.BooleanExpression>`

    :raises InvalidArgumentTypeError: If ``expr`` is not a valid type.

    Here's a couple of simple examples showing De Morgan's Law being applied
    to a negated AND and a negated OR::

        >>> from tt import apply_de_morgans
        >>> apply_de_morgans('~(A /\\ B)')
        <BooleanExpression "~A \\/ ~B">
        >>> apply_de_morgans('~(A \\/ B)')
        <BooleanExpression "~A /\\ ~B">

    """
    bexpr = ensure_bexpr(expr)
    return BooleanExpression(bexpr.tree.apply_de_morgans())
Пример #24
0
    def helper_test_tokenization_raises(self,
                                        expr,
                                        expected_exc_type=None,
                                        expected_error_pos=None):
        """Helper for testing tokenization on invalid expressions.

        :param expr: The expression for which to create a new
            ``BooleanExpression`` object, which should be of a valid form.
        :type expr: str

        :param expected_exc_type: The type of exception expected to be thrown
            during processing of the expression.
        :type expected_exc_type: Exception

        :param expected_error_pos: The position within the expression where the
            troublesome area began; if omitted, this optional argument will not
            be checked on the caught exception.
        :type expected_error_pos: int, optional

        """
        did_catch = False

        try:
            BooleanExpression(expr)
        except expected_exc_type as e:
            if expected_error_pos is not None:
                self.assertEqual(expected_error_pos, e.error_pos)
            did_catch = True
        except Exception as e:
            traceback.print_exc()
            self.fail('Received exception of type ' + type(e).__name__ +
                      ' but was expecting type ' + expected_exc_type.__name__ +
                      '.')
            did_catch = True

        if not did_catch:
            self.fail('No exception thrown.')
Пример #25
0
 def test_str(self):
     """Test the expression __str__ implementation."""
     b = BooleanExpression('(A or B) nand C')
     self.assertEqual(str(b), '(A or B) nand C')
Пример #26
0
 def test_repr(self):
     """Test the expression __repr__ implementation."""
     b = BooleanExpression('A -> B -> C -> D')
     self.assertEqual(repr(b), '<BooleanExpression "A -> B -> C -> D">')
Пример #27
0
 def test_from_boolean_expression_object(self):
     """Test passing an expression object as argument."""
     self.assert_apply_idempotent_law_transformation(
         BooleanExpression('A and A'), 'A')
Пример #28
0
 def test_from_boolean_expression_object(self):
     """Test transformation when passing an expr object as the argument."""
     self.assert_distribute_ors_tranformation(BooleanExpression('A and B'),
                                              'A and B')
Пример #29
0
def _tree(opts):
    """Run the ``tree`` command."""
    b = BooleanExpression(opts.expression)
    print_info(b.tree)
Пример #30
0
def _postfix_tokens(opts):
    """Run the ``postfix-tokens`` command."""
    b = BooleanExpression(opts.expression)
    print_info('\n'.join(b.postfix_tokens))