whiletruelearn
11/1/2016 - 10:45 PM

tautology.py

"""
A program to verify whether the proposition
entered is a tautology or not.

Run and Tested in Python 2.7

"""

import string
import itertools


class _Constants(object):
    """
    Constants for Tautology Verifier

    """
    BOOL_OPERATORS = ('!', '&', '|')
    PRECEDENCE = {'!': 3, '&': 2, '|': 1, '(': 0}
    TRUTH_DICT = {'True': True, 'False': False}
    BOOL_VALS = ('True', 'False')
    GRAMMAR = ('!', '&', '|', '(', ')')


def _clean_up(text):
    """
    Performs basic grammar check of the proposition after
    removing whitespaces.

    Throws error if variables are not part of the grammar defined.

    :param text: proposition
    :return : <List> of elements in cleaned up proposition
    """
    temp = []
    text = text.replace(' ', '')
    for t in text:
        if t in string.ascii_letters or t in _Constants.GRAMMAR:
            temp.append(t)
        else:
            raise ValueError("{0} in  proposition: {1} is invalid".format(t, text))
    return temp


def _get_postfix_expr(proposition, variables):
    """
    Generate postfix expression from the proposition
    list and variable list.

    Throws error if the infix expression is not correctly
    balanced.

    :param proposition: <List> infix expression
    :param variables : <Set> variables
    :return: the expression in postfix notation
    """

    temp = []
    postfix = []
    try:
        for i in proposition:
            if i == "(":
                temp.append(i)
            elif i == ")":
                elem = temp.pop()
                while elem is not "(":
                    postfix.append(elem)
                    elem = temp.pop()
            elif i in variables:
                postfix.append(i)
            elif i in _Constants.BOOL_OPERATORS:
                pre = _Constants.PRECEDENCE[i]
                while len(temp) != 0 and pre <= _Constants.PRECEDENCE.get(temp[-1], 4):
                    postfix.append(temp.pop())
                temp.append(i)

        while len(temp) > 0:
            postfix.append(temp.pop())
        postfix_expr = "".join(postfix)

    except IndexError:
        raise ValueError('Expression: {0} is not valid and unable to convert to postfix'.format("".join(proposition)))

    return postfix_expr


def _get_possibilities(variables):
    """
    Generate all the boolean possibilities for a set of variables

    2^n possibilities exist for n variables

    :param variables: Set of variables in the proposition
    :return: List of all boolean possibilities for the variables
    """
    possibilities = map(lambda x: zip(variables, x), list(itertools.product(['True', 'False'], repeat=len(variables))))
    return possibilities


def _evaluate_postfix_expr(t):
    """
    Evaluate the postfix expression.

    :param t: postfix expression
    :return: truth value of the post fix expression
    """
    pf = t.split(' ')
    eval_stack = []
    for i in pf:
        if i in _Constants.BOOL_VALS:
            eval_stack.append(i)
        elif i == '!':
            operand = eval_stack.pop()
            res = _logic_op(first_operand=operand, operator=i)
            eval_stack.append(res)
        else:
            first_operand = eval_stack.pop()
            second_operand = eval_stack.pop()
            res = _logic_op(first_operand=first_operand, operator=i, second_operand=second_operand)
            eval_stack.append(res)

    fin = eval_stack.pop()
    return fin


def _logic_op(first_operand, operator, second_operand=None):
    """
    For given operands , perform the boolean logic specified
    by the operator.

    :param first_operand: First operand  (True or False)
    :param operator: Boolean operator
    :param second_operand: Second operand which can be also None
    :return: Truth value of the expression
    """

    if operator == "!":
        return not _Constants.TRUTH_DICT[str(first_operand)]

    elif operator == '&':
        return _Constants.TRUTH_DICT[str(first_operand)] and _Constants.TRUTH_DICT[str(second_operand)]

    elif operator == '|':
        return _Constants.TRUTH_DICT[str(first_operand)] or _Constants.TRUTH_DICT[str(second_operand)]
    else:
        raise ValueError('Operator not defined')


def _eval_boolean(pf, p):
    """
    Method which evaluates the truth value for
    a given possibility combination of variables.

    :param pf: Proposition list
    :param p: Boolean possibilites for the variables
    :return: the boolean value obtained after postfix evaluation
    """
    t = " ".join(pf)
    for var, truth in p:
        t = t.replace(str(var), str(truth))
    res = _evaluate_postfix_expr(t)
    return res


def _calculate_tautology(proposition, possibilities):
    """

    Check whether the proposition is a tautology by
    checking the result for all the  boolean possible
    combinations.


    :param proposition: Proposition statement
    :param possibilities: Possibile boolean values
    :return: True if proposition is a tautology
    """
    truth = True
    for p in possibilities:

        bool_val = _eval_boolean(proposition, p)
        if bool_val == False:
            truth = False
            break
    return truth


def verify_tautology(proposition):
    """
    Verify whether a given propostion is tautology or not.

    :param proposition: Proposition statement
    :return: True if proposition is tautology, False if not
    """

    proposition = _clean_up(proposition)
    variables = set([proposition[i] for i in range(0, len(proposition)) if proposition[i] in string.ascii_letters])
    postfix_proposition = _get_postfix_expr(proposition, variables)
    possibilities = _get_possibilities(variables)
    res = _calculate_tautology(postfix_proposition, possibilities)
    print res


# Unit Test cases

verify_tautology('(!a | (a & a))')
verify_tautology('(!a | (b & !a)) ')
verify_tautology('(!a | a) ')
verify_tautology('((a & (!b | b)) | (!a & (!b | b))) ')

# Few extra test cases

# verify_tautology('a&b&c')
# verify_tautology('((a & (!b | b)) | (      !a & (!b | b))) ')
# verify_tautology('((a & (!b | b)) | (      !a & (!b | b)))) ')
# verify_tautology('#%^$^$#')