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.

242 lines
8.2KB

  1. # -*- coding: utf-8 -*-
  2. """
  3. This program generates and caches lookup tables, and handles the Clifford group.
  4. It provides tables for Clifford group multiplication and conjugation,
  5. as well as CZ and decompositions of the 2x2 Cliffords.
  6. """
  7. import os, json, tempfile, os, sys, json, string
  8. from functools import reduce
  9. import itertools as it
  10. import numpy as np
  11. from tqdm import tqdm
  12. import qi
  13. decompositions = ("xxxx", "xx", "zzxx", "zz", "zxx", "z", "zzz", "xxz",
  14. "xzx", "xzxxx", "xzzzx", "xxxzx", "xzz", "zzx", "xxx", "x",
  15. "zzzx", "xxzx", "zx", "zxxx", "xxxz", "xzzz", "xz", "xzxx")
  16. def conjugate(operator, unitary):
  17. """ Returns transform * vop * transform^dagger and a phase in {+1, -1} """
  18. return measurement_table[operator, unitary]
  19. def use_old_cz():
  20. """ Use the CZ table from A&B's code """
  21. global cz_table
  22. from anders_cz import cz_table
  23. def get_name(i):
  24. """ Get the human-readable name of this clifford """
  25. return "IXYZ"[i & 0x03] + "ABCDEF"[i / 4]
  26. def find_clifford(needle, haystack):
  27. """ Find the index of a given u within a list of unitaries, up to a global phase """
  28. needle = qi.normalize_global_phase(needle)
  29. for i, t in enumerate(haystack):
  30. if np.allclose(t, needle):
  31. return i
  32. raise IndexError
  33. def find_cz(bond, c1, c2, commuters, state_table):
  34. """ Find the output of a CZ operation """
  35. # Figure out the target state
  36. target = qi.cz.dot(state_table[bond, c1, c2])
  37. target = qi.normalize_global_phase(target)
  38. # Choose the sets to search over
  39. s1 = commuters if c1 in commuters else xrange(24)
  40. s2 = commuters if c2 in commuters else xrange(24)
  41. # Find a match
  42. for bondp, c1p, c2p in it.product([0, 1], s1, s2):
  43. if np.allclose(target, state_table[bondp, c1p, c2p]):
  44. return bondp, c1p, c2p
  45. # Didn't find anything - this should never happen
  46. raise IndexError
  47. def compose_u(decomposition):
  48. """ Get the unitary representation of a particular decomposition """
  49. matrices = ({"x": qi.msqx, "z": qi.sqz}[c] for c in decomposition)
  50. output = reduce(np.dot, matrices, np.eye(2, dtype=complex))
  51. return qi.normalize_global_phase(output)
  52. def get_unitaries():
  53. """ The Clifford group """
  54. return [compose_u(d) for d in decompositions]
  55. def get_by_name(unitaries):
  56. """ Get a lookup table of cliffords by name """
  57. a = {name: find_clifford(u, unitaries)
  58. for name, u in qi.by_name.items()}
  59. a.update({get_name(i): i for i in range(24)})
  60. a.update({i: i for i in range(24)})
  61. return a
  62. def get_conjugation_table(unitaries):
  63. """ Construct the conjugation table """
  64. return np.array([find_clifford(qi.hermitian_conjugate(u), unitaries) for u in unitaries], dtype=int)
  65. def get_times_table(unitaries):
  66. """ Construct the times-table """
  67. return np.array([[find_clifford(u.dot(v), unitaries) for v in unitaries]
  68. for u in tqdm(unitaries, desc="Building times-table")], dtype=int)
  69. def get_state_table(unitaries):
  70. """ Cache a table of state to speed up a little bit """
  71. state_table = np.zeros((2, 24, 24, 4), dtype=complex)
  72. params = list(it.product([0, 1], range(24), range(24)))
  73. for bond, i, j in tqdm(params, desc="Building state table"):
  74. state = qi.bond if bond else qi.nobond
  75. kp = np.kron(unitaries[i], unitaries[j])
  76. state_table[bond, i, j, :] = qi.normalize_global_phase(
  77. np.dot(kp, state).T)
  78. return state_table
  79. def get_measurement_entry(operator, unitary):
  80. """
  81. Any Clifford group unitary will map an operator A in {I, X, Y, Z}
  82. to an operator B in +-{I, X, Y, Z}. This finds that mapping.
  83. """
  84. matrices = ({"x": qi.msqx, "z": qi.sqz}[c]
  85. for c in decompositions[unitary])
  86. unitary = reduce(np.dot, matrices, np.eye(2, dtype=complex))
  87. operator = qi.operators[operator]
  88. new_operator = reduce(
  89. np.dot, (unitary, operator, qi.hermitian_conjugate(unitary)))
  90. for i, o in enumerate(qi.operators):
  91. if np.allclose(o, new_operator):
  92. return i, 1
  93. elif np.allclose(o, -new_operator):
  94. return i, -1
  95. raise IndexError
  96. def get_measurement_table():
  97. """
  98. Compute a table of transform * operation * transform^dagger
  99. This is pretty unintelligible right now, we should probably compute the phase from unitaries instead
  100. """
  101. measurement_table = np.zeros((4, 24, 2), dtype=complex)
  102. for operator, unitary in it.product(range(4), range(24)):
  103. measurement_table[operator, unitary] = get_measurement_entry(
  104. operator, unitary)
  105. return measurement_table
  106. def get_commuters(unitaries):
  107. """ Get the indeces of gates which commute with CZ """
  108. commuters = (qi.id, qi.pz, qi.ph, qi.hermitian_conjugate(qi.ph))
  109. return [find_clifford(u, unitaries) for u in commuters]
  110. def get_cz_table(unitaries):
  111. """ Compute the lookup table for the CZ (A&B eq. 9) """
  112. # Get a cached state table and a list of gates which commute with CZ
  113. commuters = get_commuters(unitaries)
  114. state_table = get_state_table(unitaries)
  115. # And now build the CZ table
  116. cz_table = np.zeros((2, 24, 24, 3), dtype=int)
  117. rows = list(
  118. it.product([0, 1], it.combinations_with_replacement(range(24), 2)))
  119. # CZ is symmetric so we only need combinations
  120. for bond, (c1, c2) in tqdm(rows, desc="Building CZ table"):
  121. newbond, c1p, c2p = find_cz(
  122. bond, c1, c2, commuters, state_table)
  123. cz_table[bond, c1, c2] = [newbond, c1p, c2p]
  124. cz_table[bond, c2, c1] = [newbond, c2p, c1p]
  125. return cz_table
  126. def write_javascript_tables():
  127. """ Write the tables to javascript files for consumption in the browser """
  128. path = os.path.dirname(sys.argv[0])
  129. path = os.path.split(path)[0]
  130. with open(os.path.join(path, "static/scripts/tables.js"), "w") as f:
  131. f.write("var tables = {\n")
  132. f.write("\tdecompositions : {},\n"
  133. .format(json.dumps(decompositions)))
  134. f.write("\tconjugation_table : {},\n"
  135. .format(json.dumps(conjugation_table.tolist())))
  136. f.write("\ttimes_table : {},\n"
  137. .format(json.dumps(times_table.tolist())))
  138. f.write("\tcz_table : {},\n"
  139. .format(json.dumps(cz_table.tolist())))
  140. f.write("\tclifford : {}\n"
  141. .format(json.dumps(by_name)))
  142. f.write("};")
  143. def temp(filename):
  144. """ Get a temporary path """
  145. # TODO: this STILL fucking fails sometimes. WHY
  146. tempdir = tempfile.gettempdir()
  147. return os.path.join(tempdir, filename)
  148. def compute_everything():
  149. """ Compute all lookup tables """
  150. global unitaries, by_name, conjugation_table, times_table, cz_table, measurement_table
  151. unitaries = get_unitaries()
  152. by_name = get_by_name(unitaries)
  153. conjugation_table = get_conjugation_table(unitaries)
  154. times_table = get_times_table(unitaries)
  155. cz_table = get_cz_table(unitaries)
  156. measurement_table = get_measurement_table()
  157. def save_to_disk():
  158. """ Save all tables to disk """
  159. global unitaries, by_name, conjugation_table, times_table, cz_table, measurement_table
  160. np.save(temp("unitaries.npy"), unitaries)
  161. np.save(temp("conjugation_table.npy"), conjugation_table)
  162. np.save(temp("times_table.npy"), times_table)
  163. np.save(temp("cz_table.npy"), cz_table)
  164. np.save(temp("measurement_table.npy"), measurement_table)
  165. write_javascript_tables()
  166. with open(temp("by_name.json"), "wb") as f:
  167. json.dump(by_name, f)
  168. def load_from_disk():
  169. """ Load all the tables from disk """
  170. global unitaries, by_name, conjugation_table, times_table, cz_table, measurement_table
  171. unitaries = np.load(temp("unitaries.npy"))
  172. conjugation_table = np.load(temp("conjugation_table.npy"))
  173. times_table = np.load(temp("times_table.npy"))
  174. measurement_table = np.load(temp("measurement_table.npy"))
  175. cz_table = np.load(temp("cz_table.npy"))
  176. with open(temp("by_name.json")) as f:
  177. by_name = json.load(f)
  178. def is_diagonal(v):
  179. """ TODO: remove this. Checks if a VOP is diagonal or not """
  180. return v in {0, 3, 5, 6}
  181. if __name__ == "__main__":
  182. compute_everything()
  183. save_to_disk()
  184. else:
  185. try:
  186. load_from_disk()
  187. except IOError:
  188. compute_everything()
  189. save_to_disk()