| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103 |
- """
- ========
- 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()
|