简体   繁体   中英

How to turn a Boolean Function into a Binary Decision Diagram

Say I have the following Boolean functions:

or(x, y) := x || y
and(x, y) := x && y
not(x) := !x
foo(x, y, z) := and(x, or(y, z))
bar(x, y, z, a) := or(foo(x, y, z), not(a))
baz(x, y) := and(x, not(y))

Now I would like to construct a Binary Decision Diagram from them. I have looked through several papers but haven't been able to find how to construct them from nested logic formulas like these.

It is said that a Boolean function is a rooted, directed, acyclic graph. It has several nonterminal and terminal nodes. Then it says that each nonterminal node is labeled by a Boolean variable (not a function), which has two child nodes. I don't know what a Boolean variable is from my function definitions above. An edge from the node to child a or b represents assigment of the node to 0 or 1 respectively. It is called reduced if isomorphic subgraphs have been merged, and nodes whose two children are isomorphic are removed. This is a Reduced Ordered Binary Decision Diagram (ROBDD).

From that, and from all of the resources I've encountered, I haven't been able to figure out how to convert this these functions into BDDs/ROBDDs:

foo(1, 0, 1)
bar(1, 0, 1, 0)
baz(1, 0)

Or perhaps it's these that need to be converted:

foo(x, y, z)
bar(x, y, z, a)
baz(x, y)

Looking for help on an explanation of what I need to do in order to make this into the rooted, directed, acyclic graph. Knowing what the data structure looks like would also be helpful. It seems that it is just this:

var nonterminal = {
  a: child,
  b: child,
  v: some variable, not sure what
}

But then the question is how to go about constructing the graph from these functions foo , bar , and baz .

The basic logic operations AND, OR, XOR etc can all be computed between functions that are in BDD representation to yield a new function in BDD representation. The algorithms for these are all similar apart from how they handle terminals, and roughly goes like this:

  • if the result is a terminal, return that terminal.
  • if (op, A, B) is cached, return the cached result.
  • distinguish 3 cases (actually you can generalize this..)

    1. A.var == B.var , create a node (A.var, OP(A.lo, B.lo), OP(A.hi, B.hi)) where OP represents recursively invoking this procedure.
    2. A.var < B.var , create a node (A.var, OP(A.lo, B), OP(A.hi, B))
    3. A.var > B.var , create a node (B.var, OP(A, B.lo), OP(A, B.hi))
  • cache the result

"Create a node" should of course deduplicate itself, to fulfill the "reduced" requirement. The split in 3 cases takes care of the ordering requirement.

Complex functions that are a tree of simple operations can be turned in a BDD by applying this bottom up, at every turn only doing a simple combination of BDDs. This of course does tend generate nodes that are not part of the final result. Variables and constants have trivial BDDs.

For example, and(x, or(y, z)) is created by going depth-first into that tree, creating a BDD for the variable x (a trivial node, (x, 0, 1) ), then for y and z , performing OR (an instance of the algorithm described above, where only the first step really cares that the operation is OR ) on the BDDs that represent y and z , and then performing AND on the result and the BDD the represents the variable x . The exact result depends on your choice of variable ordering.

Functions that evaluate other functions inside of themselves require either function composition (if representing the called function by a BDD already) which is possible with BDDs but has some bad worst cases, or just inline the definition of the called function.

You can do it by evaluation of all variable assignments, eg in case of

foo(0,0,0) = 0
foo(0,0,1) = 0
foo(0,1,0) = 0
...

Then create the graph, start from the root. Each function argument gets an edge labeled with its assignment, the leaf node gets labeled with the result value:

x0 -0-> y0 -0-> z0 -0-> 0
x0 -0-> y1 -0-> z1 -1-> 0
x0 -0-> y2 -1-> z2 -0-> 0
...

merge the nodes (y0 = y1 = y2, z0 = z1):

x0 -0-> y0 -0-> z0 -0-> 0
x0 -0-> y0 -0-> z0 -1-> 0
x0 -0-> y0 -1-> z1 -0-> 0
...

reduce the nodes (There are some rules that allow to join nodes or to skip nodes). Eg since a 0 from the root always leads to the leaf 0 you can skip the later decisions:

x0 -0-> 0
...

Note that the nodes have to be labeled with the variable names to be assigned on the following graph edges. The algorithm is not really sophisticated (certainly there exists a more efficient one), but I hope it visualizes the strategy.

A data structure for storing BDDs can be based on triplets of the form (level, u, v) where u the node for what the Boolean function is when the variable at level is FALSE, and v the node for what the Boolean function is when the variable at level is TRUE.

The example described can be programmed using the Python package dd ( pip install dd to install the pure Python implementation). The code would be

from dd import autoref as _bdd

bdd = _bdd.BDD()
bdd.declare('x', 'y', 'z', 'a')
x_or_y = bdd.add_expr('x \/ y')
x_and_y = bdd.add_expr('x /\ y')
not_x = bdd.add_expr('~ x')

# variable renaming: x to y, y to z
let = {'x': 'y', 'y': 'z'}
y_or_z = bdd.let(let, x_or_y)

# using the method `BDD.apply`
foo = bdd.apply('and', bdd.var('x'), y_or_z)

# using a formula
foo_ = bdd.add_expr('x /\ (y \/ z)')
assert foo == foo_

# using the string representation of BDD node references
foo_ = bdd.add_expr('x /\ {u}'.format(u=y_or_z))
assert foo == foo_

bar = bdd.apply('or', foo, ~ bdd.var('a'))
bar_ = bdd.add_expr('{u} \/ ~ a'.format(u=foo))

assert bar == bar_

let = {'y': ~ bdd.var('y')}
baz = bdd.let(let, x_and_y)
baz_ = bdd.add_expr('x /\ ~ y')
assert baz == baz_
# plotting of the diagram using GraphViz
bdd.dump('foo.png', [foo])

This example includes both direct application of operators between BDDs (using the method BDD.apply or the parsing of Boolean formulas that calls these operators) and function composition for functions represented as BDDs (using the method BDD.let for renaming variables and for substituting a BDD for a variable, and the syntax '{u}'.format(u=...) for the string representation of a reference to a BDD node).

The result of plotting the Boolean function foo is shown below (produced by the bdd.dump statement in the code above).

绘制 foo 的结果

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM