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.

233 lines
7.3KB

  1. """
  2. Provides an extremely basic graph structure, based on neighbour lists
  3. """
  4. import itertools as it
  5. import clifford
  6. import json
  7. import qi
  8. try:
  9. from networkx import Graph as NXGraph
  10. except ImportError:
  11. NXGraph = object
  12. try:
  13. import websocket
  14. from socket import error as socket_error
  15. import time, atexit
  16. except ImportError:
  17. websocket = None
  18. class GraphState(NXGraph):
  19. def __init__(self, nodes=[], connect_to_server=True):
  20. self.adj, self.node = {}, {}
  21. self.add_nodes(nodes)
  22. if connect_to_server:
  23. self.connect_to_server()
  24. def add_node(self, v, **kwargs):
  25. """ Add a node """
  26. assert not v in self.node
  27. self.adj[v] = {}
  28. self.node[v] = {"vop": clifford.by_name["hadamard"]}
  29. self.node[v].update(kwargs)
  30. def add_nodes(self, nodes):
  31. """ Add a buncha nodes """
  32. for n in nodes:
  33. self.add_node(n)
  34. def add_edge(self, v1, v2, data = {}):
  35. """ Add an edge between two vertices in the self """
  36. assert v1 != v2
  37. self.adj[v1][v2] = data
  38. self.adj[v2][v1] = data
  39. def add_edges(self, edges):
  40. """ Add a buncha edges """
  41. for (v1, v2) in edges:
  42. self.add_edge(v1, v2)
  43. def del_edge(self, v1, v2):
  44. """ Delete an edge between two vertices in the self """
  45. del self.adj[v1][v2]
  46. del self.adj[v2][v1]
  47. def has_edge(self, v1, v2):
  48. """ Test existence of an edge between two vertices in the self """
  49. return v2 in self.adj[v1]
  50. def toggle_edge(self, v1, v2):
  51. """ Toggle an edge between two vertices in the self """
  52. if self.has_edge(v1, v2):
  53. self.del_edge(v1, v2)
  54. else:
  55. self.add_edge(v1, v2)
  56. def edgelist(self):
  57. """ Describe a graph as an edgelist """
  58. # TODO: inefficient
  59. edges = set(tuple(sorted((i, n)))
  60. for i, v in self.adj.items()
  61. for n in v)
  62. return tuple(edges)
  63. def remove_vop(self, a, avoid):
  64. """ Reduces VOP[a] to the identity """
  65. #TODO: inefficient
  66. others = set(self.adj[a]) - {avoid}
  67. swap_qubit = others.pop() if others else avoid
  68. for v in reversed(clifford.decompositions[self.node[a]["vop"]]):
  69. self.local_complementation(a if v == "x" else swap_qubit)
  70. def local_complementation(self, v):
  71. """ As defined in LISTING 1 of Anders & Briegel """
  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. #TODO: inefficient
  89. if set(self.adj[a]) - {b}:
  90. self.remove_vop(a, b)
  91. if set(self.adj[b]) - {a}:
  92. self.remove_vop(b, a)
  93. if set(self.adj[a]) - {b}:
  94. self.remove_vop(a, b)
  95. edge = int(self.has_edge(a, b))
  96. new_edge, self.node[a]["vop"], self.node[
  97. b]["vop"] = clifford.cz_table[edge, self.node[a]["vop"], self.node[b]["vop"]]
  98. if new_edge != edge:
  99. self.toggle_edge(a, b)
  100. def measure_z(self, node, force=None):
  101. """ Measure the graph in the Z-basis """
  102. res = force if force else np.random.choice([0, 1])
  103. # Disconnect
  104. for neighbour in self.adj[node]:
  105. self.del_edge(node, neighbour)
  106. if res:
  107. self.act_local_rotation(neighbour, "pz")
  108. # Rotate
  109. if res:
  110. self.act_local_rotation(node, "px")
  111. self.act_local_rotation(node, "hadamard")
  112. else:
  113. self.act_local_rotation(node, "hadamard")
  114. return res
  115. def measure_x(self, i):
  116. """ Measure the graph in the X-basis """
  117. # TODO
  118. pass
  119. def measure_y(self, i):
  120. """ Measure the graph in the Y-basis """
  121. # TODO
  122. pass
  123. def order(self):
  124. """ Get the number of qubits """
  125. return len(self.node)
  126. def __str__(self):
  127. """ Represent as a string for quick debugging """
  128. node = {key: clifford.get_name(value["vop"])
  129. for key, value in self.node.items()}
  130. nbstr = str(self.adj)
  131. return "graph:\n node: {}\n adj: {}\n".format(node, nbstr)
  132. def to_json(self):
  133. """ Convert the graph to JSON form """
  134. return {"node": self.node, "adj": self.adj}
  135. def from_json(self, data):
  136. """ Reconstruct from JSON """
  137. self.__init__([])
  138. #TODO
  139. def to_state_vector(self):
  140. """ Get the full state vector """
  141. if len(self.node) > 15:
  142. raise ValueError("Cannot build state vector: too many qubits")
  143. state = qi.CircuitModel(len(self.node))
  144. for i in range(len(self.node)):
  145. state.act_hadamard(i)
  146. for i, j in self.edgelist():
  147. state.act_cz(i, j)
  148. for i, n in self.node.items():
  149. state.act_local_rotation(i, clifford.unitaries[n["vop"]])
  150. return state
  151. def to_stabilizer(self):
  152. """ Get the stabilizer of this graph """
  153. # TODO: VOPs are not implemented yet
  154. output = ""
  155. for a in self.adj:
  156. for b in self.adj:
  157. if a == b:
  158. output += " X "
  159. elif a in self.adj[b]:
  160. output += " Z "
  161. else:
  162. output += " I "
  163. output += "\n"
  164. return output
  165. def adj_list(self):
  166. """ For comparison with Anders and Briegel's C++ implementation """
  167. rows = []
  168. for key, node in self.node.items():
  169. adj = " ".join(map(str, sorted(self.adj[key])))
  170. vop = clifford.get_name(node["vop"])
  171. s = "Vertex {}: VOp {}, neighbors {}".format(key, vop, adj)
  172. rows.append(s)
  173. return " \n".join(rows) + " \n"
  174. def __eq__(self, other):
  175. """ Check equality between graphs """
  176. return self.adj == other.adj and self.node == other.node
  177. def connect_to_server(self, uri = "ws://localhost:5000"):
  178. """ Attempt to connect to the websocket server """
  179. if not websocket:
  180. return
  181. try:
  182. self.ws = websocket.create_connection(uri)
  183. atexit.register(self.shutdown)
  184. except socket_error:
  185. #print "Could not establish connection to server ({}).".format(uri)
  186. self.ws = None
  187. def shutdown(self):
  188. """ Close the connection to the websocket """
  189. self.update()
  190. self.ws.close()
  191. def update(self, delay = 0.5):
  192. """ Call this function when you are ready to send data to the browser """
  193. if not self.ws:
  194. return
  195. data = json.dumps(self.to_json())
  196. self.ws.send(data)
  197. time.sleep(delay)