Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Propositional Logic

Propositional logic, also known as propositional calculus or sentential logic, is a branch of logic that deals with propositions and their relationships through logical connectives. It is a foundational system in formal logic and is used to analyze and represent logical statements.

Problems statements

  • How to represent logical statements using symbols and connectives? And print statements from them?

  • How to evaluate the truth value of complex logical expressions?

  • How to construct relation chains like parent, grandparent etc from basic relations like parent and child? [BONUS]

Basic building block

Just printing out what a logic does...

First let’s define a simple class for storing our statements.

class Statement:
    def __init__(self, subject, predicate, singular=True):
        self.subject = subject
        self.predicate = predicate
        self.singular = singular

Now we will define our actual relation class with methods to pring natural language statements from logic...

class Relation:
    def __init__(self):
        self.map = {}

    def add(self, statement, subject, predicate, singular=True):
        self.map[statement] = Statement(subject, predicate, singular)

    def print_statement(self, statement):
        if self.map[statement].singular:
            print(self.map[statement].subject, " is ", self.map[statement].predicate, end="")
        else:
            print(self.map[statement].subject, " are ", self.map[statement].predicate, end="")

    def print_neg_statement(self, statement):
        if self.map[statement].singular:
            print(self.map[statement].subject, " is not ", self.map[statement].predicate, end="")
        else:
            print(self.map[statement].subject, " are not ", self.map[statement].predicate, end="")

    def build_statement(self, logic):
        """
        & = AND
        | = OR
        ! = NOT
        ~ = IMPLIES
        * = IF AND ONLY IF

        A = EVERY
        E = SOME
        """
        logic = logic.replace(" ", "")  # no space
        logic = logic.replace(",", "")  # no comma

        neg = False
        only_sub = False

        for i in range(len(logic)):
            if logic[i] == '!':
                neg = True
            elif logic[i] == '&':
                print(" and ", end="")
            elif logic[i] == '|':
                print(" or ", end="")
            elif logic[i] == '*':
                print(" if and only if, ", end="")
            elif logic[i] == '~':
                print(" implies, ", end="")
            elif logic[i] == 'A':
                print(" For all ", end="")
                only_sub = True
            elif logic[i] == 'E':
                print(" For some ", end="")
                only_sub = True
            elif logic[i] in ['(', ')', ',']:
                continue
            else:
                if only_sub:
                    print(self.map[logic[i]].subject, end=", ")
                    only_sub = False
                elif neg:
                    self.print_neg_statement(logic[i])
                    neg = False
                else:
                    self.print_statement(logic[i])
        print(".")

Let’s test our class,

relation = Relation()

relation.add("s", "students", "brilliant", singular=False)
relation.add("a", "Real CR", "student")
relation.add("b", "Real CR", "lazy")

relation.build_statement("A(s) s")
relation.build_statement("a & !b ~ a")
 For all students, students  are  brilliant.
Real CR  is  student and Real CR  is not  lazy implies, Real CR  is  student.

Truth Table

Let’s build truth tables for basic logical operations. We need to draw tables again and again, so let’s create a function to do that.

def draw_table(li, *args):
    length = len(" | ".join(args))
    print("-" * length)
    print(" | ".join(args))
    print("-" * length)
    for row in li:
        print(" | ".join(str(i) for i in row))
        # print(" | ".join(row))
    print("-" * length)

draw_table([["1", "1", "1"], 
            ["1", "0", "0"], 
            ["0", "1", "0"], 
            ["0", "0", "0"]], "A", "B", "A AND B")
---------------
A | B | A AND B
---------------
1 | 1 | 1
1 | 0 | 0
0 | 1 | 0
0 | 0 | 0
---------------

An another function to evaluate the result,

def evaluate_expression(a, b, ex):
    """
    & = AND
    | = OR
    ! = NOT
    ~ = IMPLIES
    * = IF AND ONLY IF
    """
    if (ex == '!'):
        return 0 if a == 1 else 1
    elif (ex == '&'):
        return 1 if a == 1 and b == 1 else 0
    elif (ex == '|'):
        return 1 if a == 1 or b == 1 else 0
    elif (ex == '~'):
        return 0 if a == 1 and b == 0 else 1
    elif (ex == '*'):
        return 1 if (a == 1 and b == 1) or (a == 0 and b == 0) else 0

[OPTIONAL] With our evaluate_expression and draw_table function, let’s draw some truth tables...

N = 2 # Number of variables
arr = [list(map(int, bin(i)[2:].zfill(N))) for i in range(2**N)]

for i in arr:
    i.append(int(str(evaluate_expression(i[0], i[1], '*'))))
draw_table(arr, "A", "B", "ONLY IF")
---------------
A | B | ONLY IF
---------------
0 | 0 | 1
0 | 1 | 0
1 | 0 | 0
1 | 1 | 1
---------------

Extended Proposition!

Let’s inherit our previous Relation class and extend it to evaluate complex logical expressions.

from collections import deque

class Relation(Relation):
    def __init__(self):
        super().__init__()
        self.operand_val = {}

    def map_operands(self, op, val):
        self.operand_val[op] = val

    def evaluate_expression(self, exp):
        """
        & = AND
        | = OR
        ! = NOT
        ~ = IMPLIES
        * = IF AND ONLY IF
        """
        exp = exp.replace(" ", "")  # no space
        exp = exp.replace(",", "")  # no comma

        operators = deque()
        operands = deque()

        negative = False
        for i in exp:
            if i in ['&', '|', '~', '*']:
                operators.append(i)
            elif i == '!':
                negative = True
            else:
                if negative:
                    operands.append(evaluate_expression(self.operand_val[i], 0, '!'))
                    negative = False
                else:
                    operands.append(i)
        
        operands = deque(self.operand_val[i] if (i != 0 and i != 1) else i for i in operands )
        while operators:
            op = operators.popleft()
            a = operands.popleft()
            b = operands.popleft()
            
            operands.appendleft(evaluate_expression(a, b, op))
        
        return operands.popleft()

And let’s test it,

relation = Relation()

relation.add("a", "sensei", "student")
relation.add("b", "sensei", "brilliant")

relation.map_operands("a", 1)
relation.map_operands("b", 0)

print(relation.evaluate_expression("a | b & !a ~ b"))
1

And now’s the fun part! Let’s draw truth tables for a and b,

array = [list(map(int, bin(i)[2:].zfill(2))) for i in range(2**2)]
for i in array:
    relation.map_operands("a", i[0])
    relation.map_operands("b", i[1])
    i.append(relation.evaluate_expression("a | !b ~ b"))

draw_table(array, "a", "b", "a OR NOT(b) IMPLIES b")
-----------------------------
a | b | a OR NOT(b) IMPLIES b
-----------------------------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 0
1 | 1 | 1
-----------------------------

Relation Builder (BONUS)

Suppose we have a list of relations like, someone is someones parent or children and we want to build relation chains like grandparent, grandchild, cousin etc. We can do that with a simple class.

class People:
    def __init__(self, name, parent=None, children=None):
        self.name = name
        self.parent = parent
        self.children = [children]

class Relation:
    def __init__(self):
        self.map = {}

    def add(self, person):
        self.map[person.name] = person

    def parent(self, parent, child):
        self.map[child].parent = parent
        if self.map[parent].children == [None]:
            self.map[parent].children = [child]
        else:
            self.map[parent].children.append(child)
    
    def find_grandparent(self, name):
        parent = self.map[name].parent
        if parent is None:
            return None
        grandparent = self.map[parent].parent
        return grandparent

Now, let’s test it out...

relation = Relation()

john = People("John")
mary = People("Mary")
joe = People("Joe")

relation.add(john)
relation.add(mary)
relation.add(joe)

relation.parent("John", "Mary")
relation.parent("Mary", "Joe")

print("Joe's grandparent is:", relation.find_grandparent("Joe"))
Joe's grandparent is: John