# 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`

in`and`

simplification[`(and ,@(list-no-order (list-rest 'and inside) outside ...)) `(and ,@inside ,@outside)]

`or`

in`or`

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))
```