3

In one of my classes at university, we are learning functional programming through SML/NJ.

I have been given an assignment which requires us to perform a number of operations on Binary Decision Diagrams. (conjunction, disjunction, not, etc.)

Based on this truth table

table

I have the following function defined

fun IfThenElse(p,q,r) = 
  if p then q else r;

Then, we have the BDD (ROBDD) datatype declared as such

datatype ROBDD = true
               | false
               | IfThenElse of string * ROBDD * ROBDD;

So far this is all fairly straightforward. I'm getting lost on actually operating on BDD's, for instance, creating an ROBDD which represents the conjunction of two ROBDD's.

My function declaration so far looks like this

infix bddAnd;
fun op bddAnd(first:ROBDD,second:ROBDD) = 
   ...

It will be called with two ROBDD's, something like this

val conjunction = IfThenElse("p", true, false) bddAnd IfThenElse("q", true, false);

From here, I'm not really sure where to start. My professor has given us this hint:

Of course, True bddAnd anyROBDD will be just anyROBDD. To get the ordering: If you’re asked to compute (IfThenElse(p, ϕ, ψ) bddAnd IfThenElse(q, χ, θ)) the proposition letter at the root of the resultant ROBDD will be either p or q – whichever is less. So you’ll need 3 cases: p < q, p = q, and p > q. Having determined the root, you recurse down the branches

The first part makes sense, but I'm left with two questions.

1. How do I determine the root of any ROBDD?

If it's just true or false, it doesn't have one, right? So there should be a special case for just being given a boolean? If we are given a more fleshed out ROBDD, like IfThenElse("p", true, false), how do I gain access to p, in the ROBDD structure? Note that the first argument of an IfThenElse will always be a letter.

2. How do I recurse through the ROBDD's?

I understand the basics of recursive functions in SML, but I'm confused on how to do it within the ROBDD structure, compared to say, lists. I'm guessing I need to build some sort of curried functions that operate on each argument in the ROBDD, but I'm really not sure how to structure it.

Apologies for the long winded question, but I'm really having a hard time understanding how to operate on the ROBDD structure. Any explanation would be very helpful, thank you!

EDIT:

After some syntax and renaming, my bddAnd function now looks like this

infix bddAnd;
fun op bddAnd (true, second) = second
  | op bddAnd (first, true) = first
  | op bddAnd (false, _) = false
  | op bddAnd (_, false) = false
  | op bddAnd ((left as (IfThenElse (prop1, true1, else1))), (right as (IfThenElse (prop2, true2, else2)))) = 
        if prop1 < prop2 then 
          IfThenElse (prop1, true1 bddAnd right, else1 bddAnd right)
        else if prop1 > prop2 then
          IfThenElse (prop2, true2 bddAnd left, else2 bddAnd left)
        else
          IfThenElse (prop1, true1 bddAnd right, else1 bddAnd left);
Andrew Brooke
  • 12,073
  • 8
  • 39
  • 55

1 Answers1

4

Pattern matching is usually a good starting point.

The cases involving True and False are simple:

fun op bddAnd (True, second) = second
     | bddAnd (first, True) = first
     | bddAnd (False, _) = False
     | bddAnd (_, False) = False

the last one is more interesting:

 | bddAnd (IfThenElse (v1, t1, e1)) (IfThenElse (v2, t2, e2)) = ... what? ...

As your professor hinted, you need to consider three cases for v1 and v2:

if v1 < v2 then ...
else if v1 > v2 then ...
else ...

Looking at the first one, v1 < v2, we should pick v1 as the "root".

It's not very difficult to convince yourself that

(IfThenElse p T1 E1) bddAnd (IfThenElse q T2 E2)

is equivalent to

IfThenElse p (T1 bddAnd (IfThenElse q T2 E2)) (E1 bddAnd (IfThenElse q T2 E2))

that is, you create one "tree" from two by recursing into both branches of the IfThenElse you chose, bringing the other tree along.
The recursion will terminate since bddAnd is applied to smaller and smaller arguments, and the result will be ordered as long as the input was ordered (which I assume we are allowed to assume).

Matching the code above,

 | bddAnd (left as (IfThenElse (v1, t1, e1))) (right as (IfThenElse (v2, t2, e2))) = 
        if v1 < v2 then IfThenElse (v1, t1 bddAnd right, e1 bddAnd right)
        else ...

(Using as-patterns to make it easier to refer to the parameters as wholes.)

The remaining two cases left as an exercise.

molbdnilo
  • 64,751
  • 3
  • 43
  • 82
  • This definition for 'ordered' is in accordance with the one for [OBDD](https://en.wikipedia.org/wiki/Binary_decision_diagram)s. – sshine Feb 03 '16 at 14:47
  • 1
    I'm pretty sure it's `as` instead of `@` for as-patterns. – Ionuț G. Stan Feb 03 '16 at 14:53
  • @SimonShine It's a good day when you get to learn something :-) – molbdnilo Feb 03 '16 at 14:59
  • @IonuțG.Stan I always get that wrong. It's probably not correct now either. – molbdnilo Feb 03 '16 at 15:05
  • Thanks for the answer, you explained this really nicely! Am I correct in my understanding of how the other cases work? (in the edited part of my question) – Andrew Brooke Feb 03 '16 at 22:05
  • @AndrewBrooke The equal case isn't quite correct – since the proposition letter is the same, you'll choose the same branch from both if-then-else's regardless of its truth, and you only need the respective "then" and "else" parts for the recursion, not the entire trees. (I hope that made some kind of sense.) – molbdnilo Feb 03 '16 at 22:18
  • Ah whoops `true1` should be `then1`. Like this? `IfThenElse (prop1, then1 bddAnd then2, else1 bddAnd else2);` Or am I still misunderstanding? – Andrew Brooke Feb 03 '16 at 22:24
  • @AndrewBrooke That looks correct to me. (Disclaimer: I haven't tested any of this, and I may be completely wrong.) – molbdnilo Feb 03 '16 at 23:39
  • Thanks, in the few test cases I've worked through it seems to be producing the right answer – Andrew Brooke Feb 03 '16 at 23:41