Spaces:
Sleeping
Sleeping
| """ | |
| ======== | |
| Circuits | |
| ======== | |
| Convert a Boolean circuit to an equivalent Boolean formula. | |
| A Boolean circuit can be exponentially more expressive than an | |
| equivalent formula in the worst case, since the circuit can reuse | |
| subcircuits multiple times, whereas a formula cannot reuse subformulas | |
| more than once. Thus creating a Boolean formula from a Boolean circuit | |
| in this way may be infeasible if the circuit is large. | |
| """ | |
| import matplotlib.pyplot as plt | |
| import networkx as nx | |
| def circuit_to_formula(circuit): | |
| # Convert the circuit to an equivalent formula. | |
| formula = nx.dag_to_branching(circuit) | |
| # Transfer the operator or variable labels for each node from the | |
| # circuit to the formula. | |
| for v in formula: | |
| source = formula.nodes[v]["source"] | |
| formula.nodes[v]["label"] = circuit.nodes[source]["label"] | |
| return formula | |
| def formula_to_string(formula): | |
| def _to_string(formula, root): | |
| # If there are no children, this is a variable node. | |
| label = formula.nodes[root]["label"] | |
| if not formula[root]: | |
| return label | |
| # Otherwise, this is an operator. | |
| children = formula[root] | |
| # If one child, the label must be a NOT operator. | |
| if len(children) == 1: | |
| child = nx.utils.arbitrary_element(children) | |
| return f"{label}({_to_string(formula, child)})" | |
| # NB "left" and "right" here are a little misleading: there is | |
| # no order on the children of a node. That's okay because the | |
| # Boolean AND and OR operators are symmetric. It just means that | |
| # the order of the operands cannot be predicted and hence the | |
| # function does not necessarily behave the same way on every | |
| # invocation. | |
| left, right = formula[root] | |
| left_subformula = _to_string(formula, left) | |
| right_subformula = _to_string(formula, right) | |
| return f"({left_subformula} {label} {right_subformula})" | |
| root = next(v for v, d in formula.in_degree() if d == 0) | |
| return _to_string(formula, root) | |
| ############################################################################### | |
| # Create an example Boolean circuit. | |
| # ---------------------------------- | |
| # | |
| # This circuit has a ∧ at the output and two ∨s at the next layer. | |
| # The third layer has a variable x that appears in the left ∨, a | |
| # variable y that appears in both the left and right ∨s, and a | |
| # negation for the variable z that appears as the sole node in the | |
| # fourth layer. | |
| circuit = nx.DiGraph() | |
| # Layer 0 | |
| circuit.add_node(0, label="∧", layer=0) | |
| # Layer 1 | |
| circuit.add_node(1, label="∨", layer=1) | |
| circuit.add_node(2, label="∨", layer=1) | |
| circuit.add_edge(0, 1) | |
| circuit.add_edge(0, 2) | |
| # Layer 2 | |
| circuit.add_node(3, label="x", layer=2) | |
| circuit.add_node(4, label="y", layer=2) | |
| circuit.add_node(5, label="¬", layer=2) | |
| circuit.add_edge(1, 3) | |
| circuit.add_edge(1, 4) | |
| circuit.add_edge(2, 4) | |
| circuit.add_edge(2, 5) | |
| # Layer 3 | |
| circuit.add_node(6, label="z", layer=3) | |
| circuit.add_edge(5, 6) | |
| # Convert the circuit to an equivalent formula. | |
| formula = circuit_to_formula(circuit) | |
| print(formula_to_string(formula)) | |
| labels = nx.get_node_attributes(circuit, "label") | |
| options = { | |
| "node_size": 600, | |
| "alpha": 0.5, | |
| "node_color": "blue", | |
| "labels": labels, | |
| "font_size": 22, | |
| } | |
| plt.figure(figsize=(8, 8)) | |
| pos = nx.multipartite_layout(circuit, subset_key="layer") | |
| nx.draw_networkx(circuit, pos, **options) | |
| plt.title(formula_to_string(formula)) | |
| plt.axis("equal") | |
| plt.show() | |