Simplification#
We can write a program in Racket that will symbolically manipulate a Boolean expression. We will simplify an expression to CNF form.
Preliminaries#
We need a few of the functions from our boolean evaluator to make the simplification program.
;Get the first operand of an expression
;input-contract: A valid expression
;output-contact: The first operand
(define (op1 exp)
(second exp)
)
;Get the second operand of an expression
;input-contract: A valid expression
;output-contact: The second operand
(define (op2 exp)
(third exp)
)
;Detect Expression Type
;Given an operator and list length
;creates a procedure to detect expressions of that format
;input-contract: (and (symbol? op) (integer? len))
;output-contract: (procedure? (isOperator op len))
(define (isOperator op len)
(lambda (expr)
(and
(list? expr)
(equal? (length expr) len)
(equal? op (first expr))
)
)
)
;Define the formats we need
;Negation only takes 1 input
;Other Boolean Operators are 2 inputs and the operator
(define isNot? (isOperator '~ 2) )
(define isAnd? (isOperator '& 3) )
(define isOr? (isOperator 'v 3) )
(define isImplies? (isOperator '-> 3))
(define isBicond? (isOperator '<-> 3))
Negative Normal Form#
First, we need to convert the expression to NNF. This is a prerequisite of NNF.
NNF Rules#
There were a number of simplification rules used for NNF. We implement a function for each of them.
We need to replace conditional statements with their definition.
\( \begin{align} (A \implies B) = (\neg A \vee B) \end{align} \)
;Rule 1: Definition of Implies
;(A->B) = (~A v B)
;input-contract: A valid implies expression
;output-contract: The expression rewritten as an or
(define (defOfImplies expr)
(list 'v (list '~ (op1 expr)) (op2 expr))
)
(display "Def of Implies Example:\n")
(display (defOfImplies '(-> X Y)))(newline)
; Returns '(v (~ X) Y)
We also have a definition for the Bi-Conditional.
\( \begin{align} (A \iff B) = (A \implies B) \wedge (B \implies A) \end{align} \)
;Rule 2: Definition of Bi-Conditional
;(A <-> B) = (A->B) & (B->A)
;input-contract: A valid bi-conditional expression
;output-contract: The expression rewritten as an or+implies
(define (defOfBicond expr)
(list '&
(list '-> (op1 expr) (op2 expr))
(list '-> (op2 expr) (op1 expr))
)
)
(display "Def of Bi-Conditional Example:\n")
(display (defOfBicond '(<-> X Y)))(newline)
; Returns '(v (-> X Y) (-> Y X))
We also need to apply DeMorgan’s. This version works on a disjunction.
\( \begin{align} \neg(A \vee B) = (\neg A) \wedge (\neg B) \end{align} \)
;Rule 3: DeMorgan on Disjunction
;~(A v B) = (~A & ~B)
;input-contract: A valid boolean expression with negative applied to or
;output-contract: The expression rewritten using DeMorgan's Law
(define (deMorganDis expr)
(list '&
(list '~ (op1 (op1 expr)))
(list '~ (op2 (op1 expr)))
)
)
(display "DeMorgan on Disjunction Example:\n")
(display (deMorganDis '(~ (v X Y))))(newline)
; Returns '(& (~ X) (~ Y))
The DeMorgan’s Law version for conjunctions is also needed.
\( \begin{align} \neg(A \wedge B) = (\neg A) \vee (\neg B) \end{align} \)
;Rule 4: DeMorgan on Conjunction
;~(A & B) = (~A v ~B)
;input-contract: A valid boolean expression with negative applied to and
;output-contract: The expression rewritten using DeMorgan's Law
(define (deMorganConj expr)
(list 'v
(list '~ (op1 (op1 expr)))
(list '~ (op2 (op1 expr)))
)
)
(display "DeMorgan on Conjunction Example:\n")
(display (deMorganConj '(~ (& X Y))))(newline)
; Returns '(v (~ X) (~ Y))
The last rule we need is double negative elimination.
\( \begin{align} \neg \neg A = A \end{align} \)
;Rule 5: Double Negative Elimination
;~ ~ A = A
;input-contract: A valid boolean expression with a double negative
;output-contract: The expression with double negative removed
(define (doubleNeg expr)
(op1 (op1 expr))
)
(display "Double Negative Elimination Example:\n")
(display (doubleNeg '(~ (~ (v A B)))))(newline)
; Returns '(v A B)
Is NNF?#
To know when we should apply the rules, we need to know if the expression is in NNF form or not. We need to search the expression recursively. If we hit a point where a negation is above another operator, we know the expression is not in NNF.
;Determine if a Function is in NNF form or not
;input-contract: A valid boolean expression
;output-contract: #t if in NNF and #f otherwise
(define (isNNF? expr)
(cond
;These operators are fine, but check operands
[(or (isAnd? expr) (isOr? expr))
(and (isNNF? (op1 expr)) (isNNF? (op2 expr)))]
;These operators are not allowed
[(isImplies? expr) #f]
[(isBicond? expr) #f]
;Not cannot be applied to operators
[(isNot? expr) (not (list? (op1 expr)))]
;None of the above
[else #t]
)
)
Apply Rules#
We have a number of rules. We need to determine which to apply when. This function tests an expression to see if any rules apply. If not, it searches recursively.
;Apply Rules of NNF
;Applies any rules it finds to the expression
;input-contract: A boolean expression
;output-contract: an equivalent boolean expression with rules applied
(define (simplifyNNF expr)
(cond
;Operator Defs
[(isImplies? expr) (defOfImplies expr)]
[(isBicond? expr) (defOfBicond expr)]
;DeMorgan
[(and (isNot? expr) (isOr? (op1 expr)))
(deMorganDis expr)]
[(and (isNot? expr) (isAnd? (op1 expr)))
(deMorganConj expr)]
;Double Negative
[(and (isNot? expr) (isNot? (op1 expr)))
(doubleNeg expr)]
;Basic Operators
[(isNot? expr) (list '~ (simplifyNNF (op1 expr)))]
[(isAnd? expr) (list '& (simplifyNNF (op1 expr))
(simplifyNNF (op2 expr)))]
[(isOr? expr) (list 'v (simplifyNNF (op1 expr))
(simplifyNNF (op2 expr)))]
;None of the above
[else expr]
)
)
Convert to NNF#
To convert an expression to NNF, we just apply the rules repeatedly. If we apply them over and over again, eventually we will make it to NNF form.
;Convert to NNF
;Apply Rules until the expression is in NNF
;input-contract: a valid boolean expression
;output-contract: the same expression in NNF
(define (nnf expr)
;(display expr)(newline) ;Uncomment to see steps
(if (isNNF? expr)
expr
(nnf (simplifyNNF expr))
)
)
(display "Convert to NNF Examples:\n")
(display (nnf '(<-> A B)))(newline)
; Returns '(v (v (~ A) B) (v (~ B) A))
(display (nnf '(~ (<-> A B))))(newline)
; Returns '(& (& A (~ B)) (& B (~ A)))
(display (nnf '(~ (& (<-> A B) (-> (~ C) D)))))(newline)
; Returns '(v (v (& A (~ B)) (& B (~ A))) (& (~ C) (~ D)))
(display (nnf '(~ (& A (v B C)))))(newline)
; Returns (v ~A (& (~ B) (~ C)))
Conjunctive Normal Form#
Once and expression is in NNF, we can apply one additional rule to convert it to CNF.
CNF Rules#
We need to distribute disjunctions into conjunctions. We don’t need to worry about cases where both sides have a conjunction. That is just two applications of the rule. Notice that computers don’t understand rules can be flipped around the operator. We need to implement both versions.
\( \begin{align} (A \wedge B) \vee C =& (A \vee C) \wedge (B \vee C)\\ A \vee (B \wedge C) =& (A \vee B) \wedge (A \vee C) \end{align} \)
;Rule 1: Distribute Or
;(A & B) v C = (A v C) & (B v C)
;A v (B & C) = (A v B) & (A v C)
;input-contract: A boolean expression that needs to be
; distributed
;output-contract: The expression after distribution
(define (distributeOr expr)
(if (isAnd? (op1 expr))
(list '&
(list 'v (op1 (op1 expr))
(op2 expr))
(list 'v (op2 (op1 expr))
(op2 expr))
)
(list '&
(list 'v (op1 expr)
(op1 (op2 expr)))
(list 'v (op1 expr)
(op2 (op2 expr)))
)
)
)
(display "Distribution Examples:\n")
(display (distributeOr '(v (& A B) C)))(newline)
; Returns '(& (v A C) (v B C))
(display (distributeOr '(v A (& B C))))(newline)
; Returns '(& (v A B) (v A C))
Is CNF?#
We need to check if an expression is in CNF form. This way we know we can stop simplifying.
;Check if an expression is in CNF form
;input-contract: A valid boolean expression
;output-contract: #t if in CNF and #f otherwise
(define (isCNF? expr)
(cond
[(not (isNNF? expr)) #f]
[(and (isOr? expr)
(or (isAnd? (op1 expr)) (isAnd? (op2 expr))))
#f]
[(or (isAnd? expr) (isOr? expr))
(and (isCNF? (op1 expr)) (isCNF? (op2 expr)))]
[else #t]
)
Apply CNF rules#
This function checks to see if the CNF rules apply. If they do, it simplifies the expression.
;Apply Rules of CNF Form
;Applies any rules it finds once
;input-contract: a valid boolean expression
;output-contract: the expression simplified using rules
(define (simplifyCNF expr)
(cond
[(not (isNNF? expr)) (nnf expr)]
[(and (isOr? expr)
(or (isAnd? (op1 expr)) (isAnd? (op2 expr))))
(distributeOr expr)]
[(isAnd? expr) (list '&
(simplifyCNF (op1 expr))
(simplifyCNF (op2 expr)))]
[(isOr? expr) (list 'v
(simplifyCNF (op1 expr))
(simplifyCNF (op2 expr)))]
[(isNot? expr) (list '~
(simplifyCNF (op1 expr)))]
[else expr]
)
)
Simplify CNF#
To simplify an expression to CNF, we just apply the rules until the expression is in CNF form.
;Convert to CNF
;Repeatedly apply the rules until you reach CNF form
;input-contract: a valid boolean expression
;output-contract: an equivalent expression in CNF Form
(define (cnf expr)
(if (isCNF? expr)
expr
(cnf (simplifyCNF expr))))
(display "CNF Examples:\n")
(display (cnf '(v (& A B) (& C D))))(newline)
; Returns '(& (& (v A C) (v A D)) (& (v B C) (v B D)))
(display (cnf '(~ (<-> A B))))(newline)
; Returns '(& (& (v A B) (v A (~ A))) (& (v (~ B) B) (v (~ B) (~ A))))
Full Code Example#
The full file is provided below.
#lang racket
;Mark Boady
;Drexel University 2021
;This code simplifies an expression to CNF.
;----------------------------------------
;Section 1: Basic Predicates and Helpers
;----------------------------------------
;Get the first operand of an expression
;input-contract: A valid expression
;output-contact: The first operand
(define (op1 exp)
(second exp)
)
;Get the second operand of an expression
;input-contract: A valid expression
;output-contact: The second operand
(define (op2 exp)
(third exp)
)
;Detect Expression Type
;Given an operator and list length
;creates a procedure to detect expressions of that format
;input-contract: (and (symbol? op) (integer? len))
;output-contract: (procedure? (isOperator op len))
(define (isOperator op len)
(lambda (expr)
(and
(list? expr)
(equal? (length expr) len)
(equal? op (first expr))
)
)
)
;Define the formats we need
;Negation only takes 1 input
;Other Boolean Operators are 2 inputs and the operator
(define isNot? (isOperator '~ 2) )
(define isAnd? (isOperator '& 3) )
(define isOr? (isOperator 'v 3) )
(define isImplies? (isOperator '-> 3))
(define isBicond? (isOperator '<-> 3))
;----------------------------------------
;Section 2: Convert to NNF
;----------------------------------------
;Rule 1: Definition of Implies
;(A->B) = (~A v B)
;input-contract: A valid implies expression
;output-contract: The expression rewritten as an or
(define (defOfImplies expr)
(list 'v (list '~ (op1 expr)) (op2 expr))
)
(display "Def of Implies Example:\n")
(display (defOfImplies '(-> X Y)))(newline)
; Returns '(v (~ X) Y)
;Rule 2: Definition of Bi-Conditional
;(A <-> B) = (A->B) & (B->A)
;input-contract: A valid bi-conditional expression
;output-contract: The expression rewritten as an or+implies
(define (defOfBicond expr)
(list '&
(list '-> (op1 expr) (op2 expr))
(list '-> (op2 expr) (op1 expr))
)
)
(display "Def of Bi-Conditional Example:\n")
(display (defOfBicond '(<-> X Y)))(newline)
; Returns '(v (-> X Y) (-> Y X))
;Rule 3: DeMorgan on Disjunction
;~(A v B) = (~A & ~B)
;input-contract: A valid boolean expression with negative applied to or
;output-contract: The expression rewritten using DeMorgan's Law
(define (deMorganDis expr)
(list '&
(list '~ (op1 (op1 expr)))
(list '~ (op2 (op1 expr)))
)
)
(display "DeMorgan on Disjunction Example:\n")
(display (deMorganDis '(~ (v X Y))))(newline)
; Returns '(& (~ X) (~ Y))
;Rule 4: DeMorgan on Conjunction
;~(A & B) = (~A v ~B)
;input-contract: A valid boolean expression with negative applied to and
;output-contract: The expression rewritten using DeMorgan's Law
(define (deMorganConj expr)
(list 'v
(list '~ (op1 (op1 expr)))
(list '~ (op2 (op1 expr)))
)
)
(display "DeMorgan on Conjunction Example:\n")
(display (deMorganConj '(~ (& X Y))))(newline)
; Returns '(v (~ X) (~ Y))
;Rule 5: Double Negative Elimination
;~ ~ A = A
;input-contract: A valid boolean expression with a double negative
;output-contract: The expression with double negative removed
(define (doubleNeg expr)
(op1 (op1 expr))
)
(display "Double Negative Elimination Example:\n")
(display (doubleNeg '(~ (~ (v A B)))))(newline)
; Returns '(v A B)
;Determine if a Function is in NNF form or not
;input-contract: A valid boolean expression
;output-contract: #t if in NNF and #f otherwise
(define (isNNF? expr)
(cond
;These operators are fine, but check operands
[(or (isAnd? expr) (isOr? expr))
(and (isNNF? (op1 expr)) (isNNF? (op2 expr)))]
;These operators are not allowed
[(isImplies? expr) #f]
[(isBicond? expr) #f]
;Not cannot be applied to operators
[(isNot? expr) (not (list? (op1 expr)))]
;None of the above
[else #t]
)
)
;Apply Rules of NNF
;Applies any rules it finds to the expression
;input-contract: A boolean expression
;output-contract: an equivalent boolean expression with rules applied
(define (simplifyNNF expr)
(cond
;Operator Defs
[(isImplies? expr) (defOfImplies expr)]
[(isBicond? expr) (defOfBicond expr)]
;DeMorgan
[(and (isNot? expr) (isOr? (op1 expr)))
(deMorganDis expr)]
[(and (isNot? expr) (isAnd? (op1 expr)))
(deMorganConj expr)]
;Double Negative
[(and (isNot? expr) (isNot? (op1 expr)))
(doubleNeg expr)]
;Basic Operators
[(isNot? expr) (list '~ (simplifyNNF (op1 expr)))]
[(isAnd? expr) (list '& (simplifyNNF (op1 expr))
(simplifyNNF (op2 expr)))]
[(isOr? expr) (list 'v (simplifyNNF (op1 expr))
(simplifyNNF (op2 expr)))]
;None of the above
[else expr]
)
)
;Convert to NNF
;Apply Rules until the expression is in NNF
;input-contract: a valid boolean expression
;output-contract: the same expression in NNF
(define (nnf expr)
;(display expr)(newline) ;Uncomment to see steps
(if (isNNF? expr)
expr
(nnf (simplifyNNF expr))
)
)
(display "Convert to NNF Examples:\n")
(display (nnf '(<-> A B)))(newline)
; Returns '(v (v (~ A) B) (v (~ B) A))
(display (nnf '(~ (<-> A B))))(newline)
; Returns '(& (& A (~ B)) (& B (~ A)))
(display (nnf '(~ (& (<-> A B) (-> (~ C) D)))))(newline)
; Returns '(v (v (& A (~ B)) (& B (~ A))) (& (~ C) (~ D)))
(display (nnf '(~ (& A (v B C)))))(newline)
; Returns (v ~A (& (~ B) (~ C)))
;----------------------------------------
; Section 3: Convert CNF
;----------------------------------------
;Rule 1: Distribute Or
;(A & B) v C = (A v C) & (B v C)
;A v (B & C) = (A v B) & (A v C)
;input-contract: A boolean expression that needs to be
; distributed
;output-contract: The expression after distribution
(define (distributeOr expr)
(if (isAnd? (op1 expr))
(list '&
(list 'v (op1 (op1 expr))
(op2 expr))
(list 'v (op2 (op1 expr))
(op2 expr))
)
(list '&
(list 'v (op1 expr)
(op1 (op2 expr)))
(list 'v (op1 expr)
(op2 (op2 expr)))
)
)
)
(display "Distribution Examples:\n")
(display (distributeOr '(v (& A B) C)))(newline)
; Returns '(& (v A C) (v B C))
(display (distributeOr '(v A (& B C))))(newline)
; Returns '(& (v A B) (v A C))
;Check if an expression is in CNF form
;input-contract: A valid boolean expression
;output-contract: #t if in CNF and #f otherwise
(define (isCNF? expr)
(cond
[(not (isNNF? expr)) #f]
[(and (isOr? expr)
(or (isAnd? (op1 expr)) (isAnd? (op2 expr))))
#f]
[(or (isAnd? expr) (isOr? expr))
(and (isCNF? (op1 expr)) (isCNF? (op2 expr)))]
[else #t]
)
)
;Apply Rules of CNF Form
;Applies any rules it finds once
;input-contract: a valid boolean expression
;output-contract: the expression simplified using rules
(define (simplifyCNF expr)
(cond
[(not (isNNF? expr)) (nnf expr)]
[(and (isOr? expr)
(or (isAnd? (op1 expr)) (isAnd? (op2 expr))))
(distributeOr expr)]
[(isAnd? expr) (list '&
(simplifyCNF (op1 expr))
(simplifyCNF (op2 expr)))]
[(isOr? expr) (list 'v
(simplifyCNF (op1 expr))
(simplifyCNF (op2 expr)))]
[(isNot? expr) (list '~
(simplifyCNF (op1 expr)))]
[else expr]
)
)
;Convert to CNF
;Repeatedly apply the rules until you reach CNF form
;input-contract: a valid boolean expression
;output-contract: an equivalent expression in CNF Form
(define (cnf expr)
(if (isCNF? expr)
expr
(cnf (simplifyCNF expr))))
(display "CNF Examples:\n")
(display (cnf '(v (& A B) (& C D))))(newline)
; Returns '(& (& (v A C) (v A D)) (& (v B C) (v B D)))
(display (cnf '(~ (<-> A B))))(newline)
; Returns '(& (& (v A B) (v A (~ A))) (& (v (~ B) B) (v (~ B) (~ A))))
Conclusion#
Any boolean expression can be converted to CNF form. This is a purely mechanical process. It can be implemented recursively. We can let a computer apply the rules for us. This code every produces a value as a result. It takes symbolic expressions and rewrites them into different but equivalent expressions.