Anders and Briegel in Python
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

216 lines
7.1KB

  1. """
  2. Provides an extremely basic graph structure, based on neighbour lists
  3. """
  4. from collections import defaultdict
  5. import itertools as it
  6. import clifford
  7. import json
  8. import qi
  9. try:
  10. import networkx as nx
  11. except ImportError:
  12. print "Could not import networkx: layout will not work"
  13. class GraphState(object):
  14. def __init__(self):
  15. self.ngbh = defaultdict(set)
  16. self.vops = defaultdict(int)
  17. self.meta = defaultdict(dict)
  18. def add_node(self, v):
  19. """ Add a node if it doesn't already exist """
  20. if not v in self.ngbh:
  21. self.ngbh[v] = set()
  22. self.vops[v] = clifford.by_name["hadamard"]
  23. def add_edge(self, v1, v2):
  24. """ Add an edge between two vertices in the self """
  25. if not v1 in self.ngbh:
  26. self.vops[v1] = clifford.by_name["hadamard"]
  27. if not v2 in self.ngbh:
  28. self.vops[v2] = clifford.by_name["hadamard"]
  29. self.ngbh[v1].add(v2)
  30. self.ngbh[v2].add(v1)
  31. def del_edge(self, v1, v2):
  32. """ Delete an edge between two vertices in the self """
  33. self.ngbh[v1].remove(v2)
  34. self.ngbh[v2].remove(v1)
  35. def has_edge(self, v1, v2):
  36. """ Test existence of an edge between two vertices in the self """
  37. return v2 in self.ngbh[v1]
  38. def toggle_edge(self, v1, v2):
  39. """ Toggle an edge between two vertices in the self """
  40. if self.has_edge(v1, v2):
  41. self.del_edge(v1, v2)
  42. else:
  43. self.add_edge(v1, v2)
  44. def edgelist(self):
  45. """ Describe a graph as an edgelist """
  46. edges = frozenset(tuple(sorted((i, n)))
  47. for i, v in self.ngbh.items()
  48. for n in v)
  49. return [tuple(e) for e in edges]
  50. def remove_vop(self, a, avoid):
  51. """ Reduces VOP[a] to the identity """
  52. others = self.ngbh[a] - {avoid}
  53. swap_qubit = others.pop() if others else avoid
  54. for v in reversed(clifford.decompositions[self.vops[a]]):
  55. self.local_complementation(a if v == "x" else swap_qubit)
  56. def local_complementation(self, v):
  57. """ As defined in LISTING 1 of Anders & Briegel """
  58. for i, j in it.combinations(self.ngbh[v], 2):
  59. self.toggle_edge(i, j)
  60. # Update VOPs: TODO check ordering and replace by self.act_local_rotation
  61. self.vops[v] = clifford.times_table[
  62. self.vops[v]][clifford.by_name["sqx"]]
  63. for i in self.ngbh[v]:
  64. self.vops[i] = clifford.times_table[
  65. self.vops[i]][clifford.by_name["msqz"]]
  66. def act_local_rotation(self, a, op):
  67. """ Act a local rotation """
  68. self.vops[a] = clifford.times_table[op,self.vops[a]]
  69. def act_local_rotation_by_name(self, qubit, name):
  70. """ Shorthand """
  71. rotation = clifford.by_name[name]
  72. self.act_local_rotation(qubit, rotation)
  73. def act_hadamard(self, qubit):
  74. """ Shorthand """
  75. self.act_local_rotation(qubit, 10)
  76. def act_cz(self, a, b):
  77. """ Act a controlled-phase gate on two qubits """
  78. if self.ngbh[a] - {b}:
  79. self.remove_vop(a, b)
  80. if self.ngbh[b] - {a}:
  81. self.remove_vop(b, a)
  82. if self.ngbh[a] - {b}:
  83. self.remove_vop(a, b)
  84. edge = self.has_edge(a, b)
  85. new_edge, self.vops[a], self.vops[b] = clifford.cz_table[edge, self.vops[a], self.vops[b]]
  86. if new_edge != edge:
  87. self.toggle_edge(a, b)
  88. def measure_z(self, node, force = None):
  89. """ Measure the graph in the Z-basis """
  90. res = force if force else np.random.choice([0,1])
  91. # Disconnect
  92. for neighbour in self.ngbh[node]:
  93. self.del_edge(node, neighbour)
  94. if res:
  95. self.act_local_rotation_by_name(neighbour, "pz")
  96. # Set final state as appropriate
  97. if res:
  98. self.act_local_rotation_by_name(node, "px")
  99. self.act_local_rotation_by_name(node, "hadamard")
  100. else:
  101. self.act_local_rotation_by_name(node, "hadamard")
  102. return res
  103. def measure_x(self, i):
  104. """ Measure the graph in the X-basis """
  105. #TODO
  106. pass
  107. def measure_y(self, i):
  108. """ Measure the graph in the Y-basis """
  109. #TODO
  110. pass
  111. def order(self):
  112. """ Get the number of qubits """
  113. return len(self.vops)
  114. def __str__(self):
  115. """ Represent as a string for quick debugging """
  116. vopstr = {key: clifford.get_name(value) for key,value in self.vops.items()}
  117. nbstr = str(dict(self.ngbh))
  118. return "graph:\n vops: {}\n ngbh: {}\n".format(vopstr, nbstr)
  119. def to_json(self):
  120. """ Convert the graph to JSON form """
  121. #ngbh = {key: tuple(value) for key, value in self.ngbh.items()}
  122. meta = {key: value for key, value in self.meta.items()}
  123. edge = self.edgelist()
  124. return json.dumps({"vops": self.vops, "edge": edge, "meta": meta})
  125. def to_networkx(self):
  126. """ Convert the graph to a networkx graph """
  127. g = nx.Graph()
  128. g.edge = {node: {neighbour: {} for neighbour in neighbours}
  129. for node, neighbours in self.ngbh.items()}
  130. g.node = {node: {"vop": vop} for node, vop in self.vops.items()}
  131. for node, metadata in self.meta.items():
  132. g.node[node].update(metadata)
  133. return g
  134. def to_state_vector(self):
  135. """ Get the full state vector """
  136. if not len(self.vops)<10:
  137. raise ValueError("Cannot build state vector: too many qubits")
  138. output = qi.CircuitModel(len(self.vops))
  139. #print output
  140. for i in range(len(self.vops)):
  141. output.act_hadamard(i)
  142. #print output
  143. for i, j in self.edgelist():
  144. output.act_cz(i, j)
  145. #print output
  146. for i, u in self.vops.items():
  147. output.act_local_rotation(i, clifford.unitaries[u])
  148. return output
  149. def layout(self):
  150. """ Automatically lay out the graph """
  151. g = self.to_networkx()
  152. pos = nx.spring_layout(g, dim=3, scale=10)
  153. average = lambda axis: sum(p[axis] for p in pos.values())/float(len(pos))
  154. ax, ay, az = average(0), average(1), average(2)
  155. for key, (x, y, z) in pos.items():
  156. self.meta[key]["pos"] = {"x": round(x-ax, 0), "y": round(y-ay, 0), "z": round(z-az, 0)}
  157. def to_stabilizer(self):
  158. """ Get the stabilizer of this graph """
  159. # TODO: VOPs are not implemented yet
  160. output = ""
  161. for a in self.ngbh:
  162. for b in self.ngbh:
  163. if a == b:
  164. output += " X "
  165. elif a in self.ngbh[b]:
  166. output += " Z "
  167. else:
  168. output += " I "
  169. output += "\n"
  170. return output
  171. def adj_list(self):
  172. """ For comparison with Anders and Briegel's C++ implementation """
  173. rows = []
  174. for key, vop in self.vops.items():
  175. ngbh = " ".join(map(str, sorted(self.ngbh[key])))
  176. vop = clifford.get_name(vop)
  177. s = "Vertex {}: VOp {}, neighbors {}".format(key, vop, ngbh)
  178. rows.append(s)
  179. return " \n".join(rows)+ " \n"