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.

274 lines
8.9KB

  1. """
  2. Provides an extremely basic graph structure, based on neighbour lists
  3. """
  4. import itertools as it
  5. import json
  6. import qi, clifford, util
  7. import random
  8. def debug(x):
  9. pass
  10. class GraphState(object):
  11. def __init__(self, nodes=[]):
  12. self.adj, self.node = {}, {}
  13. self.add_nodes(nodes)
  14. def add_node(self, v, **kwargs):
  15. """ Add a node """
  16. assert not v in self.node
  17. self.adj[v] = {}
  18. self.node[v] = {"vop": clifford.by_name["hadamard"]}
  19. self.node[v].update(kwargs)
  20. def add_nodes(self, nodes):
  21. """ Add a buncha nodes """
  22. for n in nodes:
  23. self.add_node(n)
  24. def add_edge(self, v1, v2, data = {}):
  25. """ Add an edge between two vertices in the self """
  26. assert v1 != v2
  27. self.adj[v1][v2] = data
  28. self.adj[v2][v1] = data
  29. def add_edges(self, edges):
  30. """ Add a buncha edges """
  31. for (v1, v2) in edges:
  32. self.add_edge(v1, v2)
  33. def del_edge(self, v1, v2):
  34. """ Delete an edge between two vertices in the self """
  35. del self.adj[v1][v2]
  36. del self.adj[v2][v1]
  37. def has_edge(self, v1, v2):
  38. """ Test existence of an edge between two vertices in the self """
  39. return v2 in self.adj[v1]
  40. def toggle_edge(self, v1, v2):
  41. """ Toggle an edge between two vertices in the self """
  42. if self.has_edge(v1, v2):
  43. self.del_edge(v1, v2)
  44. else:
  45. self.add_edge(v1, v2)
  46. def edgelist(self):
  47. """ Describe a graph as an edgelist """
  48. # TODO: inefficient
  49. edges = set(tuple(sorted((i, n)))
  50. for i, v in self.adj.items()
  51. for n in v)
  52. return tuple(edges)
  53. def remove_vop(self, a, avoid):
  54. """ Reduces VOP[a] to the identity """
  55. others = set(self.adj[a]) - {avoid}
  56. swap_qubit = others.pop() if others else avoid
  57. debug("remove_byprod_op called: (v, avoid, vb):")
  58. self.print_adj_list_line(a)
  59. self.print_adj_list_line(avoid)
  60. self.print_adj_list_line(swap_qubit)
  61. debug("using {}".format(clifford.decompositions[self.node[a]["vop"]]))
  62. for v in reversed(clifford.decompositions[self.node[a]["vop"]]):
  63. self.local_complementation(a if v == "x" else swap_qubit)
  64. assert self.node[a]["vop"]==0
  65. debug("remove_byprod_op: after (v, avoid, vb):")
  66. self.print_adj_list_line(a)
  67. self.print_adj_list_line(avoid)
  68. self.print_adj_list_line(swap_qubit)
  69. def local_complementation(self, v):
  70. """ As defined in LISTING 1 of Anders & Briegel """
  71. debug("V ->Inverting about {}".format(self.get_adj_list_line(v)))
  72. for i, j in it.combinations(self.adj[v], 2):
  73. self.toggle_edge(i, j)
  74. msqx_h = clifford.conjugation_table[clifford.by_name["msqx"]]
  75. sqz_h = clifford.conjugation_table[clifford.by_name["sqz"]]
  76. self.node[v]["vop"] = clifford.times_table[self.node[v]["vop"], msqx_h]
  77. for i in self.adj[v]:
  78. self.node[i]["vop"] = clifford.times_table[self.node[i]["vop"], sqz_h]
  79. def act_local_rotation(self, v, op):
  80. """ Act a local rotation """
  81. rotation = clifford.by_name[str(op)]
  82. self.node[v]["vop"] = clifford.times_table[rotation, self.node[v]["vop"]]
  83. def act_hadamard(self, qubit):
  84. """ Shorthand """
  85. self.act_local_rotation(qubit, 10)
  86. def act_cz(self, a, b):
  87. """ Act a controlled-phase gate on two qubits """
  88. debug("before cphase between {} and {}".format(a, b))
  89. self.print_adj_list_line(a)
  90. self.print_adj_list_line(b)
  91. ci = self.get_connection_info(a, b)
  92. if ci["non1"]:
  93. debug("cphase: left vertex has NONs -> putting it to Id")
  94. self.remove_vop(a, b)
  95. ci = self.get_connection_info(a, b)
  96. if ci["non2"]:
  97. debug("cphase: right vertex has NONs -> putting it to Id")
  98. self.remove_vop(b, a)
  99. ci = self.get_connection_info(a, b)
  100. if ci["non1"] and not clifford.is_diagonal(self.node[a]["vop"]):
  101. debug("cphase: left one needs treatment again -> putting it to Id")
  102. self.remove_vop(b, a)
  103. self.cz_with_table(a, b)
  104. def get_connection_info(self, a, b):
  105. if self.has_edge(a, b):
  106. return {"was_edge": True,
  107. "non1": len(self.adj.get(a)) > 1,
  108. "non2": len(self.adj.get(b)) > 1}
  109. else:
  110. return {"was_edge": False,
  111. "non1": len(self.adj.get(a)) > 0,
  112. "non2": len(self.adj.get(b)) > 0}
  113. def cz_with_table(self, a, b):
  114. """ Run the table """
  115. debug("cphase_with_table called on:")
  116. self.print_adj_list_line(a)
  117. self.print_adj_list_line(b)
  118. ci = self.get_connection_info(a, b)
  119. try:
  120. assert ci["non1"]==False or clifford.is_diagonal(self.node[a]["vop"])
  121. assert ci["non2"]==False or clifford.is_diagonal(self.node[b]["vop"])
  122. except AssertionError:
  123. print ci
  124. print self.node[a]["vop"]
  125. print self.node[b]["vop"]
  126. edge = self.has_edge(a, b)
  127. new_edge, self.node[a]["vop"], self.node[
  128. b]["vop"] = clifford.cz_table[edge, self.node[a]["vop"], self.node[b]["vop"]]
  129. if new_edge != edge:
  130. self.toggle_edge(a, b)
  131. if new_edge:
  132. debug("adding edge")
  133. else:
  134. debug("deling edge")
  135. debug("cphase_with_table: after")
  136. self.print_adj_list_line(a)
  137. self.print_adj_list_line(b)
  138. ci = self.get_connection_info(a, b)
  139. assert ci["non1"]==False or clifford.is_diagonal(self.node[a]["vop"])
  140. assert ci["non2"]==False or clifford.is_diagonal(self.node[b]["vop"])
  141. def measure_z(self, node, force=None):
  142. """ Measure the graph in the Z-basis """
  143. res = force if force!=None else random.choice([0, 1])
  144. # Disconnect
  145. for neighbour in self.adj[node]:
  146. self.del_edge(node, neighbour)
  147. if res:
  148. self.act_local_rotation(neighbour, "pz")
  149. # Rotate
  150. if res:
  151. self.act_local_rotation(node, "px")
  152. self.act_local_rotation(node, "hadamard")
  153. else:
  154. self.act_local_rotation(node, "hadamard")
  155. return res
  156. def measure_x(self, i):
  157. """ Measure the graph in the X-basis """
  158. # TODO
  159. pass
  160. def measure_y(self, i):
  161. """ Measure the graph in the Y-basis """
  162. # TODO
  163. pass
  164. def order(self):
  165. """ Get the number of qubits """
  166. return len(self.node)
  167. def __str__(self):
  168. """ Represent as a string for quick debugging """
  169. node = {key: clifford.get_name(value["vop"])
  170. for key, value in self.node.items()}
  171. nbstr = str(self.adj)
  172. return "graph:\n node: {}\n adj: {}\n".format(node, nbstr)
  173. def to_json(self):
  174. """ Convert the graph to JSON form """
  175. return {"node": self.node, "adj": self.adj}
  176. def from_json(self, data):
  177. """ Reconstruct from JSON """
  178. self.__init__([])
  179. #TODO
  180. def to_state_vector(self):
  181. """ Get the full state vector """
  182. if len(self.node) > 15:
  183. raise ValueError("Cannot build state vector: too many qubits")
  184. state = qi.CircuitModel(len(self.node))
  185. for i in range(len(self.node)):
  186. state.act_hadamard(i)
  187. for i, j in self.edgelist():
  188. state.act_cz(i, j)
  189. for i, n in self.node.items():
  190. state.act_local_rotation(i, clifford.unitaries[n["vop"]])
  191. return state
  192. def to_stabilizer(self):
  193. """ Get the stabilizer of this graph """
  194. output = {a:{} for a in self.node}
  195. for a, b in it.product(self.node, self.node):
  196. if a == b:
  197. output[a][b] = "X"
  198. elif a in self.adj[b]:
  199. output[a][b] = "Z"
  200. else:
  201. output[a][b] = "I"
  202. return output
  203. def adj_list(self):
  204. """ For comparison with Anders and Briegel's C++ implementation """
  205. rows = []
  206. for key, node in self.node.items():
  207. adj = " ".join(map(str, sorted(self.adj[key])))
  208. vop = clifford.get_name(node["vop"])
  209. s = "Vertex {}: VOp {}, neighbors {}".format(key, vop, adj)
  210. rows.append(s)
  211. return " \n".join(rows) + " \n"
  212. def get_adj_list_line(self, key):
  213. """ TODO: delete """
  214. node = self.node[key]
  215. adj = " ".join(map(str, sorted(self.adj[key])))
  216. vop = clifford.get_name(node["vop"])
  217. s = "Vertex {}: VOp {}, neighbors {}".format(key, vop, adj)
  218. return s
  219. def print_adj_list_line(self, key):
  220. debug(self.get_adj_list_line(key))
  221. def __eq__(self, other):
  222. """ Check equality between graphs """
  223. return self.adj == other.adj and self.node == other.node