Symbolic Computation¶
LGA Discussion¶
- What questions did you have on the reading? Can your group members answer, or you can ask me.
- Define symbolic computation in your own words.
- What structures in Racket would you find useful for symbolic computation?
- Share what other applications you came up with for symbolic computation. Formulate some more with your group.
Symbolic Computation Defined¶
- Wikipedia considers symbolic computation to be simply computer algebra.
- While computer algebra is a form of symbolic computation, there are plenty of
other applications.
- Programming languages
- Compilers
- Artificial intelligence
- …
Lisp & Symbolic Computation¶
Lisp dialects have a homoiconic syntax: the code is data, and data is code. Lists being the structure of the language syntax, code can be manipulated just like lists.
- The concept of “quoting” is fairly unique to just Lisp.
- It leads to a natural way to manipulate and work on code in the language.
- Key point: we can manipulate code before it is evaluated!
John McCarthy (1958)
Recursive Functions of Symbolic Expressions and their Computation by Machine
- Today we will explore a practical application of symbolic computation in artificial intelligence.
Boolean Expressions as S-Expressions¶
To represent boolean expressions in Racket, we need to formalize an s-expression syntax for them:
Conjunction | \(a \land b \land c \ldots\) | (and a b c ...) |
Disjunction | \(a \lor b \lor c \ldots\) | (or a b c ...) |
Negation | \(\lnot a\) | (not a) |
Practice: convert to s-expression
- \(a \land (b \lor c \lor d) \land d\)
- \(\lnot a \land (a \lor \lnot b) \land \lnot (a \lor b)\)
Conjunctive Normal Form¶
Note
Depending on your background, you may already know this. Bear with me while I explain it to everyone else.
A boolean expression is in conjunctive normal form (CNF) if and only if all of the below are true:
- It only contains conjunctions, disjunctions, and negations.
- Negations only contain a variable, not a conjunction or disjunction.
- Disjunctions only contain variables and negations.
Example:
Learning Group Activity
Come up with an expression in CNF (not the example), and one not in CNF.
Verifying CNF in Racket¶
(define/match (in-cnf? expr [level 'root])
[((? symbol?) _) #t]
[(`(not ,(? symbol?)) _) #t]
[((list-rest 'or args) (or 'root 'and))
(andmap (λ (x) (in-cnf? x 'or)) args)]
[((list-rest 'and args) 'root)
(andmap (λ (x) (in-cnf? x 'and)) args)]
[(_ _) #f])
Conversion to CNF¶
We can convert any boolean expression composed of just conjunctions, disjunctions, and negations to CNF using the following mathematical properties:
- Elimination of double-negation: \(\lnot \lnot a \to a\)
- DeMorgan’s Law (Conjunction): \(\lnot (a \land b) \to (\lnot a \lor \lnot b)\)
- DeMorgan’s Law (Disjunction): \(\lnot (a \lor b) \to (\lnot a \land \lnot b)\)
- Distributive Property: \(a \lor (b \land c) \to (a \lor b) \land (a \lor c)\)
Practice: Convert to CNF¶
Convert each expression to CNF:
- \(\lnot (a \land \lnot b)\)
- \(\lnot ((a \lor b) \land \lnot (c \lor d))\)
- \(\lnot ((a \lor b) \land (c \lor d))\)
Racket: Convert to CNF¶
Here’s the base structure we want our code to follow:
(define (boolean->cnf expr)
(if (in-cnf? expr)
expr
(boolean->cnf
(match expr
...)))) ;; cases for the conversions we know
Double Negation Pattern Match¶
[`(not (not ,e)) e]
Simplify and/or of single argument¶
[`(or ,e) e]
[`(and ,e) e]
DeMorgan’s Law¶
DeMorgan’s Law for Conjunction
[`(not (and ,@(list-rest args))) `(or ,@(map (curry list 'not) args))]
DeMorgan’s Law for Disjunction
[`(not (or ,@(list-rest args))) `(and ,@(map (curry list 'not) args))]
Explosion of and/or with nested expression¶
and
inand
simplification[`(and ,@(list-no-order (list-rest 'and inside) outside ...)) `(and ,@inside ,@outside)]
or
inor
simplification[`(or ,@(list-no-order (list-rest 'or inside) outside ...)) `(or ,@inside ,@outside)]
Distributive Law¶
[`(or ,@(list-no-order (list-rest 'and and-args) args ...))
`(or ,@(cdr args)
(and ,@(map
(λ (x) (list 'or (car args) x))
and-args)))]
Recurse otherwise…¶
[(list-rest sym args)
(cons sym (map boolean->cnf args))]
Putting it all together¶
> (boolean->cnf '(or (and a b) (and (not c) d) (and (not e) f)))
'(and (or (not c) a (not e))
(or (not c) b (not e))
(or d a (not e))
(or d b (not e))
(or (not c) a f)
(or (not c) b f)
(or d a f)
(or d b f))
SAT Solving¶
The satisfiability problem [1] in computer science asks:
Given a boolean expression, is there any set of assignments to the variables which results in the equation evaluating to true?
For example:
(and a (not a))
: not satisfiable(and a a)
: satisfiable
(you could imagine much larger inputs)
[1] | If you’ve taken algorithms, you probably know that this problem is NP-complete |
Davis-Puntam-Lodgemann-Loveland Algorithm¶
procedure DPLL(\(e\)): if \(e\) is true: return true if \(e\) is false: return false \(v \gets\) select-variable(\(e\)) \(e_1 \gets\) simplify(assume-true(\(v\), \(e\))) if DPLL(\(e_1\)): return true \(e_2 \gets\) simplify(assume-false(\(v\), \(e\))) return DPLL(\(e_2\))
Note
DPLL will work with any variable selection from select-variable
, but
certain selections may lead to a more efficent solution on average than
others.
DPLL: Exercise¶
Draw the DPLL tree for the following expression, and determine whether the equation is satisfiable or not:
DPLL in Racket¶
(define (solve-cnf expr)
(define (solve-rec expr bindings)
(case expr
[(#t) bindings]
[(#f) #f]
[else
(let ([sym (choose-symbol expr)])
(define (solve-assume value)
(solve-rec (assume sym value expr)
(cons (cons sym value) bindings)))
(let ([sym-true (solve-assume #t)])
(if sym-true
sym-true
(solve-assume #f))))]))
(solve-rec expr '()))
choose-symbol¶
Not a good heuristic, but it works!
(define (choose-symbol expr)
(if (symbol? expr)
expr
(choose-symbol (cadr expr))))
Assuming and Simplifying¶
(define (assume var value expr)
(cond
[(eq? var expr) value]
[(equal? `(not ,var) expr) (not value)]
[(symbol? expr) expr]
[else
(match expr
[`(not ,_) expr]
[(list-rest sym args)
...])])) ;; handle conjunction/disjunction
Handling Conjunction/Disjunction¶
(let ([look-for (case sym
[(and) #f]
[(or) #t])])
(define (f item acc)
(if (eq? acc look-for)
acc
(let ([result (assume var value item)])
(cond
[(eq? result look-for) result]
[(eq? result (not look-for)) acc]
[else (cons result acc)]))))
(let ([result (foldl f '() args)])
(cond
[(null? result) (not look-for)]
[(eq? result look-for) result]
[else (cons sym result)])))
Putting It All Together¶
(define (solve expr)
(solve-cnf (boolean->cnf expr)))
> (solve '(and a b))
'((b . #t) (a . #t))
> (solve '(or (and a b) (and c d) (and e f)))
'((d . #t) (f . #t) (c . #t))
> (solve '(and a (not a)))
#f
> (solve '(and (or (not a) b) (or a (not b))))
'((b . #t) (a . #t))