plot_circuits.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. """
  2. ========
  3. Circuits
  4. ========
  5. Convert a Boolean circuit to an equivalent Boolean formula.
  6. A Boolean circuit can be exponentially more expressive than an
  7. equivalent formula in the worst case, since the circuit can reuse
  8. subcircuits multiple times, whereas a formula cannot reuse subformulas
  9. more than once. Thus creating a Boolean formula from a Boolean circuit
  10. in this way may be infeasible if the circuit is large.
  11. """
  12. import matplotlib.pyplot as plt
  13. import networkx as nx
  14. def circuit_to_formula(circuit):
  15. # Convert the circuit to an equivalent formula.
  16. formula = nx.dag_to_branching(circuit)
  17. # Transfer the operator or variable labels for each node from the
  18. # circuit to the formula.
  19. for v in formula:
  20. source = formula.nodes[v]["source"]
  21. formula.nodes[v]["label"] = circuit.nodes[source]["label"]
  22. return formula
  23. def formula_to_string(formula):
  24. def _to_string(formula, root):
  25. # If there are no children, this is a variable node.
  26. label = formula.nodes[root]["label"]
  27. if not formula[root]:
  28. return label
  29. # Otherwise, this is an operator.
  30. children = formula[root]
  31. # If one child, the label must be a NOT operator.
  32. if len(children) == 1:
  33. child = nx.utils.arbitrary_element(children)
  34. return f"{label}({_to_string(formula, child)})"
  35. # NB "left" and "right" here are a little misleading: there is
  36. # no order on the children of a node. That's okay because the
  37. # Boolean AND and OR operators are symmetric. It just means that
  38. # the order of the operands cannot be predicted and hence the
  39. # function does not necessarily behave the same way on every
  40. # invocation.
  41. left, right = formula[root]
  42. left_subformula = _to_string(formula, left)
  43. right_subformula = _to_string(formula, right)
  44. return f"({left_subformula} {label} {right_subformula})"
  45. root = next(v for v, d in formula.in_degree() if d == 0)
  46. return _to_string(formula, root)
  47. ###############################################################################
  48. # Create an example Boolean circuit.
  49. # ----------------------------------
  50. #
  51. # This circuit has a ∧ at the output and two ∨s at the next layer.
  52. # The third layer has a variable x that appears in the left ∨, a
  53. # variable y that appears in both the left and right ∨s, and a
  54. # negation for the variable z that appears as the sole node in the
  55. # fourth layer.
  56. circuit = nx.DiGraph()
  57. # Layer 0
  58. circuit.add_node(0, label="∧", layer=0)
  59. # Layer 1
  60. circuit.add_node(1, label="∨", layer=1)
  61. circuit.add_node(2, label="∨", layer=1)
  62. circuit.add_edge(0, 1)
  63. circuit.add_edge(0, 2)
  64. # Layer 2
  65. circuit.add_node(3, label="x", layer=2)
  66. circuit.add_node(4, label="y", layer=2)
  67. circuit.add_node(5, label="¬", layer=2)
  68. circuit.add_edge(1, 3)
  69. circuit.add_edge(1, 4)
  70. circuit.add_edge(2, 4)
  71. circuit.add_edge(2, 5)
  72. # Layer 3
  73. circuit.add_node(6, label="z", layer=3)
  74. circuit.add_edge(5, 6)
  75. # Convert the circuit to an equivalent formula.
  76. formula = circuit_to_formula(circuit)
  77. print(formula_to_string(formula))
  78. labels = nx.get_node_attributes(circuit, "label")
  79. options = {
  80. "node_size": 600,
  81. "alpha": 0.5,
  82. "node_color": "blue",
  83. "labels": labels,
  84. "font_size": 22,
  85. }
  86. plt.figure(figsize=(8, 8))
  87. pos = nx.multipartite_layout(circuit, subset_key="layer")
  88. nx.draw_networkx(circuit, pos, **options)
  89. plt.title(formula_to_string(formula))
  90. plt.axis("equal")
  91. plt.show()