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:
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 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:
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 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 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:
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:
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:
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!