-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrobdd_graph.py
169 lines (131 loc) · 5.66 KB
/
robdd_graph.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import re
from boolean_parser import *
class BDDNode:
def __init__(self, var=None, high=None, low=None, terminal=None):
self.var = var # The variable for this node
self.high = high # The high branch (when the variable is True)
self.low = low # The low branch (when the variable is False)
self.terminal = terminal # Terminal value (True or False)
def __eq__(self, other):
if not isinstance(other, BDDNode):
return False
return (self.var == other.var and
self.low == other.low and
self.high == other.high and
self.terminal == other.terminal)
def __hash__(self):
# Use a tuple of the node's key properties to compute the hash
return hash((self.var, self.high, self.low, self.terminal))
def __repr__(self):
if self.terminal is not None:
return f"Terminal({self.terminal})"
return f"Node({self.var}, High={self.high}, Low={self.low})"
# Apply Shannon expansion to build the BDD
def shannon_expansion(expr, var):
positive_expr = re.sub(rf'\b{var}\b', 'True', expr)
negative_expr = re.sub(rf'\b{var}\b', 'False', expr)
return positive_expr, negative_expr
# Evaluate an expression (used for terminal cases)
def evaluate_expr(expr):
try:
return eval(expr)
except Exception:
return None
node_cache = {}
def reduction(node):
""" Apply reduction rules and cache the node. """
# Rule 1: Eliminate redundant nodes
if node.low == node.high:
return node.low
# Rule 2: Merge equivalent subtrees (Memoization)
node_key = (node.var, id(node.low), id(node.high))
if node_key in node_cache:
return node_cache[node_key]
node_cache[node_key] = node
return node
# Recursive function to construct BDD using Shannon expansion
def construct_robdd_from_expression(parsed_expr, variables):
# Base case: if the expression simplifies to True/False
simplified = evaluate_expr(parsed_expr)
if simplified is not None:
return BDDNode(terminal=simplified)
# Apply Shannon expansion for the current variable
var = variables[0]
rest_vars = variables[1:]
pos_expr, neg_expr = shannon_expansion(parsed_expr, var)
# Recursively construct the high and low branches
high_node = construct_robdd_from_expression(pos_expr, rest_vars)
low_node = construct_robdd_from_expression(neg_expr, rest_vars)
return reduction(BDDNode(var=var, high=high_node, low=low_node))
# Main function to generate and print the ROBDD
def generate_robdd(expression):
# Extract variables and parse the expression
variables = extract_variables(expression)
parsed_expr = parse_boolean_expression(expression)
# Construct the BDD using Shannon expansion
return construct_robdd_from_expression(parsed_expr, variables)
# Function to compare two ROBDDs while considering variable renaming
def compare_bdds_with_variable_mapping(bdd1, bdd2, var_map=None):
if var_map is None:
var_map = {}
# If both are terminal nodes, compare their values
if bdd1.terminal is not None and bdd2.terminal is not None:
return bdd1.terminal == bdd2.terminal
# If only one is terminal and the other is not, they are not equivalent
if (bdd1.terminal is not None) or (bdd2.terminal is not None):
return False
# Check if the variables are already mapped
if bdd1.var in var_map:
if var_map[bdd1.var] != bdd2.var:
return False # Inconsistent mapping
else:
var_map[bdd1.var] = bdd2.var # Map the variable in BDD1 to BDD2
# Recursively compare the high and low branches
high_eq = compare_bdds_with_variable_mapping(bdd1.high, bdd2.high, var_map)
low_eq = compare_bdds_with_variable_mapping(bdd1.low, bdd2.low, var_map)
return high_eq and low_eq
# Function to generate the DOT representation of the ROBDD
def robdd_to_dot(robdd):
dot = ['digraph ROBDD {']
node_counter = [0] # Use a list to keep node IDs mutable within recursive functions
node_map = {} # Map to hold node to ID mapping
def get_node_id(node):
"""Returns a unique node ID for a given node."""
if node in node_map:
return node_map[node]
node_id = f"node{node_counter[0]}"
node_map[node] = node_id
node_counter[0] += 1
return node_id
def process_node(node):
"""Recursively process each node and generate DOT format for it."""
node_id = get_node_id(node)
if node.terminal is not None:
# Terminal node (True or False)
label = f"1" if node.terminal else f"0"
dot.append(f'{node_id} [label="{label}", shape=box];')
else:
# Decision node
dot.append(f'{node_id} [label="{node.var}"];')
# Process low (False) branch with a dashed line
low_id = get_node_id(node.low)
process_node(node.low)
dot.append(f'{node_id} -> {low_id} [label="0", style=dashed];')
# Process high (True) branch with a solid line
high_id = get_node_id(node.high)
process_node(node.high)
dot.append(f'{node_id} -> {high_id} [label="1"];')
process_node(robdd)
dot.append('}')
return '\n'.join(dot)
# Function to print the BDD
def print_bdd(node, indent=0):
prefix = " " * indent
if node.terminal is not None:
print(f"{prefix}Terminal({node.terminal})")
else:
print(f"{prefix}Node({node.var})")
print(f"{prefix} High:")
print_bdd(node.high, indent + 2)
print(f"{prefix} Low:")
print_bdd(node.low, indent + 2)