Mastering Logic for AI - Converting Natural Language Statements to Propositional Logic
Generating using AI - Propositional Logic Sample Code

Mastering Logic for AI - Converting Natural Language Statements to Propositional Logic

Propositional logic, also known as Boolean logic, is foundational for reasoning in mathematics, computer science, and artificial intelligence. Whether you’re building AI systems or solving complex logical problems, understanding how to break down and represent natural language statements into propositional logic is an essential skill.

In this blog, I will walk through a Python solution to convert natural language statements containing logical conditions (like "if", "and", "or", "not", etc.) into symbolic propositional logic. The goal is to map statements into their logical counterparts such as:

  • "If P then Q" → (P → Q)
  • "P and Q" → (P ∧ Q)
  • "P or Q" → (P ∨ Q)
  • "Not P" → ?P
  • "P if and only if Q" → (P ? Q)

Let’s dive into the code and understand how it works step-by-step.

Propositional Logic Conversion Function

Here’s the complete Python function that handles the conversion:

import re

def propositional_logic(s: str) -> str:
    # Step 1: Replace logical symbols with corresponding characters
    s = s.lower()
    s = re.sub(r"\bthen\b", "→", s, flags=re.IGNORECASE)  # Implication
    s = re.sub(r"\bif\s+and\s+only\s+if\b", "%", s, flags=re.IGNORECASE)  # "if and only if" replaced with %
    s = re.sub(r"\bif\b", "$", s, flags=re.IGNORECASE)  # If replaced with $
    s = re.sub(r"\band\b", "∧", s, flags=re.IGNORECASE)  # Conjunction
    s = re.sub(r"\bor\b", "∨", s, flags=re.IGNORECASE)  # Disjunction
    s = re.sub(r"\bnot\b", "?", s, flags=re.IGNORECASE)  # Negation
    operators = ['→', '∧', '∨', '?', '?']
    if not any(op in s for op in operators):
        return 'invalid input'
    
    # Step 2: Replace all sentence parts with sequential letters
    sentence_parts = re.findall(r"\b\w+[^→∧∨??1]*\b", s)
    
    alphabet = 'PQRSTUVWXYZABCDEFGHIJKLMNO'
    
    for idx, part in enumerate(sentence_parts):
        letter = alphabet[idx % 26]  # Reuse letters if there are more than 26 parts
        s = re.sub(r'\b{}\b'.format(re.escape(part)), letter, s, count=1)

    # Combine, process, and clean up the logic symbols
    s = combine_negatives(s)
    s = process_string(s)
    s = omit_symbols(s)
    s = add_brackets(s)
    
    return s        

Breaking Down the Code

Let’s break down each part of this code to understand its functionality and why it's essential for converting natural language to propositional logic.

1. Handling Logical Operators (Step 1)

s = s.lower()
s = re.sub(r"\bthen\b", "→", s, flags=re.IGNORECASE)  # Implication
s = re.sub(r"\bif\s+and\s+only\s+if\b", "%", s, flags=re.IGNORECASE)  # "if and only if" replaced with %
s = re.sub(r"\bif\b", "$", s, flags=re.IGNORECASE)  # If replaced with $
s = re.sub(r"\band\b", "∧", s, flags=re.IGNORECASE)  # Conjunction
s = re.sub(r"\bor\b", "∨", s, flags=re.IGNORECASE)  # Disjunction
s = re.sub(r"\bnot\b", "?", s, flags=re.IGNORECASE)  # Negation        

Explanation:

  • This part of the function identifies and replaces the logical operators (like "if", "and", "or", "then", "not") with their symbolic counterparts (→, ∧, ∨, ?).
  • It uses regular expressions (re.sub()) to find these words, ensuring they are only replaced when they appear as standalone words (using \b boundary markers).

This is critical because it allows the program to process natural language and convert it into a more concise symbolic format that’s easier to work with.

2. Validating Input

operators = ['→', '∧', '∨', '?', '?']
if not any(op in s for op in operators):
    return 'invalid input'        

Explanation:

  • This block checks if the input string contains any logical operators. If none are found, the function returns "invalid input".
  • This prevents unnecessary processing on strings that don’t have any logical structure.

3. Mapping Sentence Parts to Variables (Step 2)

sentence_parts = re.findall(r"\b\w+[^→∧∨??1]*\b", s)
alphabet = 'PQRSTUVWXYZABCDEFGHIJKLMNO'

for idx, part in enumerate(sentence_parts):
    letter = alphabet[idx % 26]  # Reuse letters if there are more than 26 parts
    s = re.sub(r'\b{}\b'.format(re.escape(part)), letter, s, count=1)        

Explanation:

  • This part extracts the different sentence parts (like “It is raining” or “The ground is wet”) and maps them to unique propositional variables (P, Q, R, etc.).
  • By using the alphabet string, the function assigns letters sequentially to propositions. If there are more than 26 propositions, it starts reusing letters.

This mapping allows the logical relationships to be expressed in a compact format that’s easy to interpret and manipulate.

4. Combining Negations

def combine_negatives(sentence: str) -> str:
    if "→" not in sentence:
        sentence = re.sub(r"([A-Z])\s*?\s*([A-Z])", lambda m: "?" + m.group(1), sentence)
    return sentence        

Explanation:

  • This function handles negations by combining propositions that are negated. For example, if the sentence contains A ? B, it combines them into ?A.

This ensures that negations are applied correctly to the propositions in the logical expression.

5. Processing Symbols

def process_string(s: str):
    symbols = get_symbol_indices(s)
    pairs = create_pairs(symbols)
    result = transform_string(s, pairs)
    return result        

Explanation:

  • This function handles complex relationships like bi-conditionals (?) and implications. It identifies pairs of symbols and transforms the string accordingly.

6. Cleaning Up and Adding Brackets

def omit_symbols(s: str) -> str:
    s = re.sub(r"[^A-Z→∧∨??]", " ", s)
    s = re.sub(r"\s{2,}", " ", s).strip()
    return s

def add_brackets(s: str) -> str:
    s = re.sub(r"(\w)\s*∧\s*(\w|\(?\w\))", r"(\1 ∧ \2)", s)
    s = re.sub(r"(\w|\(?\w\)|\(\w ∧ \w\))\s*∨\s*(\w|\(?\w\)|\(\w ∧ \w\))", r"(\1 ∨ \2)", s)
    s = re.sub(r"(?<!\()(\w|\(?\w\)|\(\w ∧ \w\)|\(\w ∨ \w\))\s*→\s*(\w|\(?\w\)|\(\w ∧ \w\)|\(\w ∨ \w\))(?!\))", r"(\1 → \2)", s)
    s = re.sub(r"(\w|\(?\w\)|\(\w ∧ \w\)|\(\w ∨ \w\))\s*?\s*(\w|\(?\w\)|\(\w ∧ \w\)|\(\w ∨ \w\))", r"(\1 ? \2)", s)

    if not s.startswith("(") or not s.endswith(")"):
        s = "(" + s + ")"
    return s        

Explanation:

  • omit_symbols: Cleans the string by removing unnecessary characters and reducing multiple spaces.
  • add_brackets: Adds parentheses around logical operations to ensure the correct precedence. It processes conjunctions (∧), disjunctions (∨), and implications (→) to ensure they follow logical order.

Brackets are critical for maintaining the correct order of operations in logic, just as parentheses are essential in mathematical expressions.


Running the Code with Examples

Let’s apply this logic conversion to some test cases:

s1 = "If it is cold or if it is raining then it is snowing."
s2 = "It is sunny and it is not raining."
s3 = "If it is raining then the ground is wet."
s4 = "If and only if it is raining and the ground is wet, then there is a flood."
s5 = "If it rains then the ground is wet and if the ground is wet then there will be a flood."
s6 = "The sky is clear if and only if it is not raining."
s7 = "Hello there."
s8 = "if and only if i tell you to go there, then you should go there."

result1 = propositional_logic(s1)
result2 = propositional_logic(s2)
result3 = propositional_logic(s3)
result4 = propositional_logic(s4)
result5 = propositional_logic(s5)
result6 = propositional_logic(s6)
result7 = propositional_logic(s7)
result8 = propositional_logic(s8)

print(f"s1 = {result1}")
print(f"s2 = {result2}")
print(f"s3 = {result3}")
print(f"s4 = {result4}")
print(f"s5 = {result5}")
print(f"s6 = {result6}")
print(f"s7 = {result7}")
print(f"s8 = {result8}")        

Output

s1 = ((P ∨ Q) → R)
s2 = (P ∧ ?Q)
s3 = (P → Q)
s4 = ((P ∧ Q) ? R)
s5 = ((P → Q) ∧ (R → S))
s6 = (P ? ?Q)
s7 = invalid input
s8 = (P ? Q)        

While the provided solution effectively converts a range of natural language statements into propositional logic, there's always room for enhancement and optimization. Here are some thought-provoking questions and challenges for enthusiasts looking to delve deeper:

  1. Handling Nested Conditions
  2. Incorporating Quantifiers
  3. Optimizing for Large Inputs
  4. Dynamic Variable Assignment
  5. User-Friendly Output with Explanation
  6. GUI Integration
  7. Error Handling and Validation


Conclusion

Converting natural language statements into propositional logic is a powerful tool for formalizing and analyzing logical relationships. Through the Python-based solution presented, we've demonstrated how regular expressions and systematic processing can effectively translate complex sentences into their symbolic counterparts. While the current implementation serves as a solid foundation, the challenges and questions posed encourage continuous improvement and exploration, paving the way for more sophisticated and versatile logic processing tools.

Whether you're a student delving into logic, a developer working on AI systems, or simply a curious mind, understanding and enhancing such tools can significantly bolster your logical reasoning and computational capabilities. Happy coding and logical reasoning!


Connect with me on LinkedIn for more engaging challenges and to enhance your LLM's ability to tackle complex problems!

要查看或添加评论,请登录

Elvin B.的更多文章

社区洞察

其他会员也浏览了