Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Wiki Markup
h1. Homework 6: Symbolic Evaluation of Boolean Expressions

...



*Due:* Friday, Feburary 26, 2010

...



*Extra

...

Overview

Write a Scheme function reduce that reduces boolean expressions (represented in Scheme notation) to simplified form. For the purposes of this assignment, boolean expressions are Scheme expressions constructed from:

  • the boolean constants true and false ;
  • boolean variables (represented by symbols other than true, false, not, and, or, implies, and if) that can be bound to either true or false.
  • the unary operator not .
  • the binary operators and, or, and implies, and
  • the ternary operator if.

The course staff is providing function parse and unparse in the file parse.ss that convert boolean expressions in Scheme notation to a simple inductively defined type called boolExp and vice-versa. The coding of parse and unparse is not difficult, but it is tedious (like most parsing) so the course staff is providing this code rather than asking students to write it. The Scheme primitive read: -> SchemeExp is a procedure of no arguments that reads a Scheme expression from the console. DrScheme pops up an input box to serve as the console when (read) is executed.

These parsing functions rely on the following Scheme data definitions:

Given

Code Block
 Credit*

h1. Overview

Write a Scheme function {{reduce}} that reduces boolean expressions (represented in Scheme notation) to simplified form.  For the purposes of this assignment, boolean expressions are Scheme expressions constructed from:
* the boolean constants {{true}} and {{false}} ;
* boolean variables (represented by symbols other than {{true}}, {{false}}, {{not}}, {{and}}, {{or}}, {{implies}}, and {{if}}) that can be bound to either {{true}} or {{false}}.
* the unary operator {{not}} .
* the binary operators {{and}}, {{or}}, and {{implies}}, and
* the ternary operator {{if}}.

The course staff is providing function {{parse}} and {{unparse}} in the file [parse.ss|^parse.ss.txt] that convert boolean expressions in Scheme notation to a simple inductively defined type called {{boolExp}} and vice-versa.  The coding of {{parse}} and {{unparse}} is not difficult, but it is tedious (like most parsing) so the course staff is providing this code rather than asking students to write it.  The Scheme primitive {{read: -> SchemeExp}} is a procedure of no arguments that reads a Scheme expression from the console.  DrScheme pops up an input box to serve as the console when {{(read)}} is executed.

These parsing functions rely on the following Scheme data definitions:

Given
{code}
(define-struct Not (arg))
(define-struct And (left right))
(define-struct Or (left right))
(define-struct Implies (left right))
(define-struct If (test conseq alt))

a boolExp is either:

...

{code}
a {{boolExp}} is either:
* a boolean constant {{true}} and {{false}} ;
* a symbol {{S}} representing a boolean variable;
* {{(make-Not X)}} where {{X}} is a {{boolExp

...

}} ;
* {{(make-And X Y) where X}} and {{Y}} are {{boolExps

...

}};
* {{(make-Or X Y) where X}} and {{Y}} are {{boolExps

...

}};
* {{(make-Implies X Y)}} where {{X}} and {{Y}} are {{boolExps}}; or

...


* {{(make-If X Y Z)}} where {{X}} , {{Y}} , and {{Z}} are {{boolExps}}.

...



A bool-SchemeExp is either:

...


* a boolean constant {{true}} or {{false}};

...


* a symbol {{S

...

}};
* {{(list 'not X)}} where {{X}} is a {{bool\-SchemeExp

...

}} ;
* {{(list op X Y)}} where {{op}} is {{'and}} , {{'or

...

}} , or {{'implies}} where {{X}} and {{Y}} are {{bool\-SchemeExp}}s;

...


* {{(list 'if X Y Z)}} where {{X}}, {{Y}}, and {{Z}} are {{bool\-SchemeExp}}s.

...



The provided functions {{parse

...

Code Block
}} and {{unparse}} have the following signatures.
{code}
parse: bool-SchemeExp -> boolExp
unparse: boolExp -> bool-SchemeExp
{code}
Given a parsed input of type {{boolExp}} , the simplification process

...


consists of following four phases:

...


* Conversion to {{if}} form implemented by the function {{convert\-to\-if

...

A description of each of these phases follows. The reduce function has type bool-SchemeExp -> bool-SchemeExp .

Conversion to if form

A boolean expression can be converted to if form by repeatedlyapplying the following rewrite rules in any order until no rule is applicable.

Code Block
}} .
* Normalization implemented by the function {{normalize}} .
* Symbolic evaluation implemented by the function {{eval}} .
* Conversion back to conventional {{boolean}} form implemented by the function {{convert\-to\-bool}} .


A description of each of these phases follows.  The {{reduce}} function has type {{bool\-SchemeExp \-> bool\-SchemeExp}} .

h1. Conversion to {{if}} form

A boolean expression can be converted to {{if}} form by repeatedlyapplying the following rewrite rules in any order until no rule is applicable.
{code}
(make-Not  X)   	=>	(make-If  X  false true)
(make-And  X  Y)	=>	(make-If  X  Y  false)
(make-Or  X  Y)		=>	(make-If  X  true  Y)
(make-Implies  X  Y)	=>	(make-If  X  Y  true)
{code}
The conversion process always terminates (since each rewrite strictly

...


reduces the number of logical connectives in the expression) and yields

...


a unique answer independent of the order in which the rewrites are performed.

...


This property is called the Church-Rosser property, after the logicians

...


(Alonzo Church and Barkley Rosser) who invented the concept.

...



Since the reduction rules for this phase are Church-Rosser, you can write the function {{convert-to-if}} using simple structural recursion.  For each of the boolean operators {{And}} , {{Or}} , {{Not}} , and {{Implies}} , reduce the component expressions first and then applying the matching reduction (except for {{if}} for which there is no top-level reduction).

...



The following examples illustrate the conversion process:

...


{code
}
(check-expect  (convert-to-if (make-Or (make-And 'x 'y) 'z))    (make-If (make-If 'x 'y false) true 'z))
(check-expect  (convert-to-if (make-Implies 'x (make-Not 'y))   (make-If 'x (make-If 'y false  true) true))
{code}

We suggest simply traversing the tree using the structural recursion template for type {{boolExp}} and converting all structures (other than {{if}}s) to the corresponding {{if}} structures.

...



Write an inductive data definition and template for boolean formulas in {{if}} form, naming this type {{ifExp}}. (Note: {{make-If}} is the only constructor, other than variables and constants, for

...

Code Block

a {{ifExp}}.

A {{boolExp}} is either:
   *  a boolean value {{true}} and {{false}};
   *  a symbol {{s}} representing a boolean variable;
   *  {{(make-Not M)}} where {{M}} is a {{boolExp}};
   *  {{(make-And M N)}} where {{M}} and {{N}} are {{boolExps}};
   *  {{(make-Or M N)}} where {{M}} and {{N}} are {{boolExps}};
   *  {{(make-Implies M N)}} where {{M}} and {{N}} are {{boolExps=}}; or
   *  {{(make-If M N P)}} where {{M}}, {{N}}, and {{P}} are {{boolExpboolExps}}s.

The provided function {{parse: input -> boolExp}} takes a Scheme expression and returns the corresponding {{boolExp}}.

h1. Normalization

An {{ifExp}} is _normalized_ iff every sub-expression in ={{test=}} position is either a variable (symbol) or a constant ({{true}} or {{false}}).  We call this type {{norm-ifExp}}    .

For example, the =ifExp=
{{ifExp}} {{(make-If (make-If X Y Z) U V))

...

Code Block

}} is not a {{norm-ifExp}} because it has an {{If}} construction in {{test}} position.  In contrast, the equivalent {{ifExp}}
 {{(make-If X (make-If Y U V) (make-If Z U V))

...

Code Block

}} is normalized and hence is an {{norm-ifExp}}.

The normalization process, implemented by the function
= {{normalize: ifExp -> norm-ifExp=}} eliminates all ={{if=}} constructions that
 appear in the _test_ position of other =if={{test}} positions inside {{if}} constructions.
  We perform this transformation by
repeatedly applying the following rewrite rule
 (to any portion of the expression) until it is inapplicable:
{code}
(make-If  (make-If  X  Y  Z)  U  V)  =>	 (make-If  X  (make-If  Y  U  V) (make-If Z  U  V)).

...


{code
}
This transformation always terminates and yields a unique answer
 independent of the order in which rewrites are performed.
 The proof of this fact is left as an optional exercise.

In the {{normalize}} function, it is critically important not to duplicate any work, so
 the order in which reductions are made really matters.  Do *NOT* apply
 the normalization rule above unless {{U}} and {{V}} are already
 normalized, because the rule duplicates both {{U}} and {{V}}.  If you
 reduce the _{{consequent_}} and the _{{alternative_}} (={{U=}} and ={{V=}} in the left
 hand side of the rule above) before reducing the _{{test_}}, =normalize=
{{normalize}} runs in linear time (in the number of nodes in the input); if done in
 the wrong order it runs in exponential time in the worst case.  (And
 some of our test cases will exhibit this worst case behavior.)

Hint: define a sub-function head-normalize that takes three ={{norm-ifExp=s =X=, =Y=ifExps}} {{X}}, {{Y}}, and =Z=
{{Z}} and constructs a ={{norm-ifExp=}} equivalent to ={{(makeIf X Y Z)=}}.  This help function processes
={{X=}} because the ={{test=}} position must be a variable or a constant, yet ={{X=}} can be an
arbitrary ={{norm-ifExp=}}. In contrast, ={{(head-normalize X Y Z)=}} never even inspects ={{Y=}}
and ={{Z=}} because they are already normalized and the normalizing transformations performed
in ={{head-normalize=}} never place these expressions in ={{test=}} position.

The following examples illustrate how the ={{normalize=}} and ={{head-normalize=}} functions behave:
{code}
(check-expect (head-normalize 'x 'y 'z) (make-If 'x 'y 'z))

...


(check-expect (head-normalize true 'y 'z) (make-If true 'y 'z))

...


(check-expect (head-normalize false 'y 'z) (make-If false 'y 'z))

...


(check-expect (head-normalize (make-If 'x 'y 'z) 'u 'v) (make-If 'x (make-If 'y 'u 'v) (make-If 'z 'u 'v)))

...


(check-expect (head-normalize (make-If 'x (make-If 'yt 'yc 'ya) (make-If 'zt 'zc 'za)) 'u 'v)

...


(make-If 'x (make-If 'yt (make-If 'yc 'u 'v) (make-If 'ya 'u 'v)) (make-If 'zt (make-If 'zc 'u 'v) (make-If 'za 'u 'v))))

...



(check-expect (normalize true) true)

...


(check-expect (normalize false) false)

...


(check-expect (normalize 'x) 'x)

...


(check-expect (normalize (make-If 'x 'y 'z)) (make-If 'x 'y 'z))

...


(check-expect (normalize (make-If (make-If 'x 'y 'z) 'u 'v)) (make-If 'x (make-If 'y 'u 'v) (make-If 'z 'u 'v)))

...


{code
}

Once a large formula has been normalized, do not try to print it unless you know that the formula is small!
The printed form can be exponentially larger than the internal representation (because the internal representation can share subtrees).

Before you start writing ={{normalize=}}, write the template corresponding to the inductive data definition of ={{norm-ifExp=}}.

----+h1. Symbolic Evaluation

The symbolic evaluation process, implemented by the function ={{eval: norm-if-form environment \-> norm-if-form=}}, reduces a ={{norm\-if\-form=
}} to simple form.  In particular, it reduces all tautologies
 (expressions that are always true) to ={{true=}} and
 all contradictions (expressions that are always false) to ={{false=}}.
<p>
Symbolic evaluation applies the following rewrite rules to
 an expression until
 none is applicable (with one exception
discussed below):

...

{code}
(make-If  true  X  Y)	=>	X
(make-If  false  X  Y)	=>	Y
(make-If  X  true  false)  =>	X
(make-If  X  Y  Y) 	=>	Y
(make-If  X  Y  Z)	   =>	(make-If  X  Y\[X <\- true\]  Z\[X <\- false\])

...


{code
}
The notation ={{M[X <- N]=}} means ={{M=}} with all occurrences
 of the symbol ={{X=}} replaced by the expression ={{N=}}.  It is very costly to actually
 perform these subtitutions on =norm-if-form= data.  To avoid this computational
 expense, we simply maintain a list of bindings which are pairs consisting
 of symbols (variable names) and boolean values {{ ={true=}}, ={{false= }}.  The
 following data definition definition formally defines the ={{binding=}} type.

A ={{binding=}} is a pair
 {{(make-binding s v)
 v)}} where s is a symbol (a variable) and v is a boolean value (an element of { =true=, =false= }.

An =environment= is a =(listOf binding)=.

When the =eval= function encounters a variable (symbol), it looks up the symbol in the environment and replaces the symbol it's boolean value if it exists.
<p>
These rewrite rules do not have the Church-Rosser property.  The
last two rewrite rules are the spoilers; the relative order
in which they are applied can affect the result in some cases.
However, the rewrite rules
do have the Church-Rosser property on expressions which are
tautologies or contradictions.
<p>
If the final rule is applied only when
=X= actually occurs in either
=Y= or =Z=,
then the symbolic evaluation process
is guaranteed to terminate.  In this case, every rule
either reduces the size of the expression or the number of variable
occurrences in it.

We recommend applying the rules in the order shown from the top down
until no more reductions
are possible (using the constraint on the final rule).  Note that the
last rule should only be applied once to a given sub-expression.

----+Conversion to Boolean Form

The final phase converts an expression in (not necessarily reduced) =If= form
to an equivalent expression constructed from
variables and { =true=, =false=, =And=, =Or=, =Not=,
=Implies=, =If=}.
This process
eliminates every expression of the form
{code}
(make-If X Y Z)

...


{code
}
where one of the arguments {=X=, =Y=, =Z=} is a constant
{ =true=, =false=}.

Use the following set of reduction rules to perform this conversion
{code}
(make-If  X  false true)  =>  (make-Not X)

...


(make-If  X  Y  false)    =>  (make-And  X  Y)

...


(make-If  X  true  Y)     =>  (make-Or

...

  X  Y)

...


(make-If  X  Y  true)     =>  (make-Implies  X  Y)

...


YYYcodeYYY

...


where {{X}} , {{Y}} , and {{Z}} are arbitrary {{If}} forms.

...


This set of rules is Church-Rosser, so the rules can safely be applied

...


using simple structural recursion.