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.

331 lines
11KB

  1. """
  2. Provides an extremely basic graph structure, based on key/value pairs
  3. """
  4. import itertools as it
  5. import json
  6. import qi, clifford, util
  7. import random
  8. class GraphState(object):
  9. def __init__(self, nodes=[]):
  10. self.adj, self.node = {}, {}
  11. self.add_nodes(nodes)
  12. def add_node(self, v, **kwargs):
  13. """ Add a node """
  14. assert not v in self.node
  15. self.adj[v] = {}
  16. self.node[v] = {"vop": clifford.by_name["hadamard"]}
  17. self.node[v].update(kwargs)
  18. def add_nodes(self, nodes):
  19. """ Add a buncha nodes """
  20. for n in nodes:
  21. self.add_node(n)
  22. def add_edge(self, v1, v2, data={}):
  23. """ Add an edge between two vertices in the self """
  24. assert v1 != v2
  25. self.adj[v1][v2] = data
  26. self.adj[v2][v1] = data
  27. def add_edges(self, edges):
  28. """ Add a buncha edges """
  29. for (v1, v2) in edges:
  30. self.add_edge(v1, v2)
  31. def del_edge(self, v1, v2):
  32. """ Delete an edge between two vertices in the self """
  33. del self.adj[v1][v2]
  34. del self.adj[v2][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.adj[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. # TODO: inefficient
  47. edges = set(tuple(sorted((i, n)))
  48. for i, v in self.adj.items()
  49. for n in v)
  50. return tuple(edges)
  51. def remove_vop(self, a, avoid):
  52. """ Reduces VOP[a] to the identity """
  53. others = set(self.adj[a]) - {avoid}
  54. #TODO: this is a hack for determinsim. remove
  55. swap_qubit = min(others) if others else avoid
  56. #swap_qubit = others.pop() if others else avoid # TODO: maybe this is the only problematic part
  57. #print "SWAPPING WITH {} (options were {})".format(swap_qubit, tuple(others))
  58. for v in reversed(clifford.decompositions[self.node[a]["vop"]]):
  59. if v == "x":
  60. self.local_complementation(a, "U ->")
  61. else:
  62. self.local_complementation(swap_qubit, "V ->")
  63. def local_complementation(self, v, prefix=""):
  64. """ As defined in LISTING 1 of Anders & Briegel """
  65. for i, j in it.combinations(self.adj[v], 2):
  66. self.toggle_edge(i, j)
  67. self.node[v]["vop"] = clifford.times_table[
  68. self.node[v]["vop"], clifford.by_name["msqx_h"]]
  69. for i in self.adj[v]:
  70. self.node[i]["vop"] = clifford.times_table[
  71. self.node[i]["vop"], clifford.by_name["sqz_h"]]
  72. def act_local_rotation(self, v, op):
  73. """ Act a local rotation """
  74. rotation = clifford.by_name[str(op)]
  75. self.node[v]["vop"] = clifford.times_table[
  76. rotation, self.node[v]["vop"]]
  77. def act_local_rotation2(self, v, op):
  78. """ Act a local rotation """
  79. rotation = clifford.by_name[str(op)]
  80. self.node[v]["vop"] = clifford.times_table[
  81. self.node[v]["vop"], rotation]
  82. def act_hadamard(self, qubit):
  83. """ Shorthand """
  84. self.act_local_rotation(qubit, 10)
  85. def lonely(self, a, b):
  86. """ Is this qubit lonely ? """
  87. return len(self.adj[a]) > (b in self.adj[a])
  88. def act_cz(self, a, b):
  89. """ Act a controlled-phase gate on two qubits """
  90. if self.lonely(a, b):
  91. self.remove_vop(a, b)
  92. if self.lonely(b, a):
  93. self.remove_vop(b, a)
  94. if self.lonely(a, b) and not clifford.is_diagonal(self.node[a]["vop"]):
  95. self.remove_vop(a, b)
  96. edge = self.has_edge(a, b)
  97. va = self.node[a]["vop"]
  98. vb = self.node[b]["vop"]
  99. new_edge, self.node[a]["vop"], self.node[b]["vop"] = \
  100. clifford.cz_table[int(edge), va, vb]
  101. if new_edge != edge:
  102. self.toggle_edge(a, b)
  103. def measure(self, node, basis, force=None):
  104. """ Measure in an arbitrary basis """
  105. basis = clifford.by_name[basis]
  106. ha = clifford.conjugation_table[self.node[node]["vop"]]
  107. basis, phase = clifford.conjugate(basis, ha)
  108. #print "MEASURE"
  109. #print "Op: {} Phase: {}".format(basis, phase)
  110. # Flip a coin
  111. result = force if force!=None else random.choice([0, 1])
  112. # Flip the result if we have negative phase
  113. if phase == -1:
  114. result = not result
  115. if basis == clifford.by_name["px"]:
  116. result = self.measure_x(node, result)
  117. elif basis == clifford.by_name["py"]:
  118. result = self.measure_y(node, result)
  119. elif basis == clifford.by_name["pz"]:
  120. result = self.measure_z(node, result)
  121. else:
  122. raise ValueError("You can only measure in {X,Y,Z}")
  123. # Flip the result if we have negative phase
  124. if phase == -1:
  125. result = not result
  126. return result
  127. def toggle_edges(self, a, b):
  128. """ Toggle edges between vertex sets a and b """
  129. # TODO: i'm pretty sure this is just a single-line it.combinations or equiv
  130. done = set()
  131. for i, j in it.product(a, b):
  132. if i != j and not (i, j) in done:
  133. done.add((i, j))
  134. done.add((j, i))
  135. self.toggle_edge(i, j)
  136. def measure_x(self, node, result):
  137. """ Measure the graph in the X-basis """
  138. if len(self.adj[node]) == 0:
  139. print "gXm{},D".format(node)
  140. return 0
  141. print "gXm{},{:d}".format(node, result)
  142. # Pick a vertex
  143. #friend = next(self.adj[node].iterkeys())
  144. # TODO this is enforced determinism for testing purposes
  145. friend = sorted(self.adj[node].keys())[0]
  146. print "Friend: {}".format(friend)
  147. # Update the VOPs. TODO: pretty ugly
  148. if result:
  149. # Do a z on all ngb(vb) \ ngb(v) \ {v}, and some other stuff
  150. self.act_local_rotation2(friend, "msqy")
  151. self.act_local_rotation2(node, "pz")
  152. print "sq(-Y) on", friend
  153. print "Z on", node
  154. for n in set(self.adj[friend]) - set(self.adj[node]) - {node}:
  155. print "Z on", n
  156. self.act_local_rotation2(n, "pz")
  157. else:
  158. # Do a z on all ngb(v) \ ngb(vb) \ {vb}, and sqy on the friend
  159. self.act_local_rotation2(friend, "sqy")
  160. print "sq(Y) on", friend
  161. for n in set(self.adj[node]) - set(self.adj[friend]) - {friend}:
  162. print "Z on", n
  163. self.act_local_rotation2(n, "pz")
  164. # Toggle the edges. TODO: Yuk. Just awful!
  165. a = set(self.adj[node].keys())
  166. b = set(self.adj[friend].keys())
  167. self.toggle_edges(a, b)
  168. intersection = a & b
  169. for i, j in it.combinations(intersection, 2):
  170. self.toggle_edge(i, j)
  171. for n in a - {friend}:
  172. self.toggle_edge(friend, n)
  173. return result
  174. def measure_y(self, node, result):
  175. """ Measure the graph in the Y-basis """
  176. print "gYm{},{:d}".format(node, result)
  177. # Do some rotations
  178. for neighbour in self.adj[node]:
  179. # NB: should these be hermitian_conjugated?
  180. self.act_local_rotation2(neighbour, "sqz" if result else "msqz")
  181. # A sort of local complementation
  182. vngbh = set(self.adj[node]) | {node}
  183. for i, j in it.combinations(vngbh, 2):
  184. self.toggle_edge(i, j)
  185. # lcoS.herm_adjoint() if result else lcoS
  186. # else smiZ
  187. # 5 else 6
  188. #print "RESULT is {:d}, op is {} doing {}".format(result, self.node[node]["vop"], 5 if result else 6)
  189. self.act_local_rotation2(node, 5 if result else 6)
  190. #print "BECAME ", self.node[node]["vop"]
  191. return result
  192. def measure_z(self, node, result):
  193. """ Measure the graph in the Z-basis """
  194. print "gZm{},{:d}".format(node, result)
  195. # Disconnect
  196. for neighbour in tuple(self.adj[node]):
  197. self.del_edge(node, neighbour)
  198. if result:
  199. self.act_local_rotation2(neighbour, "pz")
  200. # Rotate
  201. if result:
  202. self.act_local_rotation2(node, "px")
  203. self.act_local_rotation2(node, "hadamard")
  204. else:
  205. self.act_local_rotation2(node, "hadamard")
  206. return result
  207. def order(self):
  208. """ Get the number of qubits """
  209. return len(self.node)
  210. def __str__(self):
  211. """ Represent as a string for quick debugging """
  212. s = ""
  213. for key in sorted(self.node.keys()):
  214. s += "{}: {}\t".format(key, clifford.get_name(self.node[key]["vop"]).replace("YC", "-"))
  215. if self.adj[key]:
  216. s += str(tuple(self.adj[key].keys())).replace(" ", "")
  217. else:
  218. s += "-"
  219. s += "\n"
  220. return s
  221. def to_json(self, stringify=False):
  222. """
  223. Convert the graph to JSON form.
  224. JSON keys must be strings, But sometimes it is useful to have
  225. a JSON-like object whose keys are tuples!
  226. """
  227. if stringify:
  228. node = {str(key): value for key, value in self.node.items()}
  229. adj = {str(key): {str(key): value for key, value in ngbh.items()}
  230. for key, ngbh in self.adj.items()}
  231. return {"node": node, "adj": adj}
  232. else:
  233. return {"node": self.node, "adj": self.adj}
  234. def from_json(self, data):
  235. """ Reconstruct from JSON """
  236. self.__init__([])
  237. # TODO
  238. def to_state_vector(self):
  239. """ Get the full state vector """
  240. if len(self.node) > 15:
  241. raise ValueError("Cannot build state vector: too many qubits")
  242. state = qi.CircuitModel(len(self.node))
  243. for i in range(len(self.node)):
  244. state.act_hadamard(i)
  245. for i, j in self.edgelist():
  246. state.act_cz(i, j)
  247. for i, n in self.node.items():
  248. state.act_local_rotation(i, clifford.unitaries[n["vop"]])
  249. return state
  250. def to_stabilizer(self):
  251. """ Get the stabilizer of this graph """
  252. return
  253. output = {a: {} for a in self.node}
  254. for a, b in it.product(self.node, self.node):
  255. if a == b:
  256. output[a][b] = "X"
  257. elif a in self.adj[b]:
  258. output[a][b] = "Z"
  259. else:
  260. output[a][b] = "I"
  261. # TODO: signs
  262. return output
  263. def __eq__(self, other):
  264. """ Check equality between graphs """
  265. if str(type(other)) == "<class 'anders_briegel.graphsim.GraphRegister'>":
  266. return self.to_json() == other.to_json()
  267. return self.adj == other.adj and self.node == other.node
  268. if __name__ == '__main__':
  269. g = GraphState()
  270. g.add_nodes(range(10))
  271. g.add_edge(0, 5)
  272. g.act_local_rotation(6, 10)
  273. print g
  274. print g.to_state_vector()