circuitutils.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. """Primitive circuit operations on quantum circuits."""
  2. from functools import reduce
  3. from sympy.core.sorting import default_sort_key
  4. from sympy.core.containers import Tuple
  5. from sympy.core.mul import Mul
  6. from sympy.core.symbol import Symbol
  7. from sympy.core.sympify import sympify
  8. from sympy.utilities import numbered_symbols
  9. from sympy.physics.quantum.gate import Gate
  10. __all__ = [
  11. 'kmp_table',
  12. 'find_subcircuit',
  13. 'replace_subcircuit',
  14. 'convert_to_symbolic_indices',
  15. 'convert_to_real_indices',
  16. 'random_reduce',
  17. 'random_insert'
  18. ]
  19. def kmp_table(word):
  20. """Build the 'partial match' table of the Knuth-Morris-Pratt algorithm.
  21. Note: This is applicable to strings or
  22. quantum circuits represented as tuples.
  23. """
  24. # Current position in subcircuit
  25. pos = 2
  26. # Beginning position of candidate substring that
  27. # may reappear later in word
  28. cnd = 0
  29. # The 'partial match' table that helps one determine
  30. # the next location to start substring search
  31. table = list()
  32. table.append(-1)
  33. table.append(0)
  34. while pos < len(word):
  35. if word[pos - 1] == word[cnd]:
  36. cnd = cnd + 1
  37. table.append(cnd)
  38. pos = pos + 1
  39. elif cnd > 0:
  40. cnd = table[cnd]
  41. else:
  42. table.append(0)
  43. pos = pos + 1
  44. return table
  45. def find_subcircuit(circuit, subcircuit, start=0, end=0):
  46. """Finds the subcircuit in circuit, if it exists.
  47. Explanation
  48. ===========
  49. If the subcircuit exists, the index of the start of
  50. the subcircuit in circuit is returned; otherwise,
  51. -1 is returned. The algorithm that is implemented
  52. is the Knuth-Morris-Pratt algorithm.
  53. Parameters
  54. ==========
  55. circuit : tuple, Gate or Mul
  56. A tuple of Gates or Mul representing a quantum circuit
  57. subcircuit : tuple, Gate or Mul
  58. A tuple of Gates or Mul to find in circuit
  59. start : int
  60. The location to start looking for subcircuit.
  61. If start is the same or past end, -1 is returned.
  62. end : int
  63. The last place to look for a subcircuit. If end
  64. is less than 1 (one), then the length of circuit
  65. is taken to be end.
  66. Examples
  67. ========
  68. Find the first instance of a subcircuit:
  69. >>> from sympy.physics.quantum.circuitutils import find_subcircuit
  70. >>> from sympy.physics.quantum.gate import X, Y, Z, H
  71. >>> circuit = X(0)*Z(0)*Y(0)*H(0)
  72. >>> subcircuit = Z(0)*Y(0)
  73. >>> find_subcircuit(circuit, subcircuit)
  74. 1
  75. Find the first instance starting at a specific position:
  76. >>> find_subcircuit(circuit, subcircuit, start=1)
  77. 1
  78. >>> find_subcircuit(circuit, subcircuit, start=2)
  79. -1
  80. >>> circuit = circuit*subcircuit
  81. >>> find_subcircuit(circuit, subcircuit, start=2)
  82. 4
  83. Find the subcircuit within some interval:
  84. >>> find_subcircuit(circuit, subcircuit, start=2, end=2)
  85. -1
  86. """
  87. if isinstance(circuit, Mul):
  88. circuit = circuit.args
  89. if isinstance(subcircuit, Mul):
  90. subcircuit = subcircuit.args
  91. if len(subcircuit) == 0 or len(subcircuit) > len(circuit):
  92. return -1
  93. if end < 1:
  94. end = len(circuit)
  95. # Location in circuit
  96. pos = start
  97. # Location in the subcircuit
  98. index = 0
  99. # 'Partial match' table
  100. table = kmp_table(subcircuit)
  101. while (pos + index) < end:
  102. if subcircuit[index] == circuit[pos + index]:
  103. index = index + 1
  104. else:
  105. pos = pos + index - table[index]
  106. index = table[index] if table[index] > -1 else 0
  107. if index == len(subcircuit):
  108. return pos
  109. return -1
  110. def replace_subcircuit(circuit, subcircuit, replace=None, pos=0):
  111. """Replaces a subcircuit with another subcircuit in circuit,
  112. if it exists.
  113. Explanation
  114. ===========
  115. If multiple instances of subcircuit exists, the first instance is
  116. replaced. The position to being searching from (if different from
  117. 0) may be optionally given. If subcircuit cannot be found, circuit
  118. is returned.
  119. Parameters
  120. ==========
  121. circuit : tuple, Gate or Mul
  122. A quantum circuit.
  123. subcircuit : tuple, Gate or Mul
  124. The circuit to be replaced.
  125. replace : tuple, Gate or Mul
  126. The replacement circuit.
  127. pos : int
  128. The location to start search and replace
  129. subcircuit, if it exists. This may be used
  130. if it is known beforehand that multiple
  131. instances exist, and it is desirable to
  132. replace a specific instance. If a negative number
  133. is given, pos will be defaulted to 0.
  134. Examples
  135. ========
  136. Find and remove the subcircuit:
  137. >>> from sympy.physics.quantum.circuitutils import replace_subcircuit
  138. >>> from sympy.physics.quantum.gate import X, Y, Z, H
  139. >>> circuit = X(0)*Z(0)*Y(0)*H(0)*X(0)*H(0)*Y(0)
  140. >>> subcircuit = Z(0)*Y(0)
  141. >>> replace_subcircuit(circuit, subcircuit)
  142. (X(0), H(0), X(0), H(0), Y(0))
  143. Remove the subcircuit given a starting search point:
  144. >>> replace_subcircuit(circuit, subcircuit, pos=1)
  145. (X(0), H(0), X(0), H(0), Y(0))
  146. >>> replace_subcircuit(circuit, subcircuit, pos=2)
  147. (X(0), Z(0), Y(0), H(0), X(0), H(0), Y(0))
  148. Replace the subcircuit:
  149. >>> replacement = H(0)*Z(0)
  150. >>> replace_subcircuit(circuit, subcircuit, replace=replacement)
  151. (X(0), H(0), Z(0), H(0), X(0), H(0), Y(0))
  152. """
  153. if pos < 0:
  154. pos = 0
  155. if isinstance(circuit, Mul):
  156. circuit = circuit.args
  157. if isinstance(subcircuit, Mul):
  158. subcircuit = subcircuit.args
  159. if isinstance(replace, Mul):
  160. replace = replace.args
  161. elif replace is None:
  162. replace = ()
  163. # Look for the subcircuit starting at pos
  164. loc = find_subcircuit(circuit, subcircuit, start=pos)
  165. # If subcircuit was found
  166. if loc > -1:
  167. # Get the gates to the left of subcircuit
  168. left = circuit[0:loc]
  169. # Get the gates to the right of subcircuit
  170. right = circuit[loc + len(subcircuit):len(circuit)]
  171. # Recombine the left and right side gates into a circuit
  172. circuit = left + replace + right
  173. return circuit
  174. def _sympify_qubit_map(mapping):
  175. new_map = {}
  176. for key in mapping:
  177. new_map[key] = sympify(mapping[key])
  178. return new_map
  179. def convert_to_symbolic_indices(seq, start=None, gen=None, qubit_map=None):
  180. """Returns the circuit with symbolic indices and the
  181. dictionary mapping symbolic indices to real indices.
  182. The mapping is 1 to 1 and onto (bijective).
  183. Parameters
  184. ==========
  185. seq : tuple, Gate/Integer/tuple or Mul
  186. A tuple of Gate, Integer, or tuple objects, or a Mul
  187. start : Symbol
  188. An optional starting symbolic index
  189. gen : object
  190. An optional numbered symbol generator
  191. qubit_map : dict
  192. An existing mapping of symbolic indices to real indices
  193. All symbolic indices have the format 'i#', where # is
  194. some number >= 0.
  195. """
  196. if isinstance(seq, Mul):
  197. seq = seq.args
  198. # A numbered symbol generator
  199. index_gen = numbered_symbols(prefix='i', start=-1)
  200. cur_ndx = next(index_gen)
  201. # keys are symbolic indices; values are real indices
  202. ndx_map = {}
  203. def create_inverse_map(symb_to_real_map):
  204. rev_items = lambda item: tuple([item[1], item[0]])
  205. return dict(map(rev_items, symb_to_real_map.items()))
  206. if start is not None:
  207. if not isinstance(start, Symbol):
  208. msg = 'Expected Symbol for starting index, got %r.' % start
  209. raise TypeError(msg)
  210. cur_ndx = start
  211. if gen is not None:
  212. if not isinstance(gen, numbered_symbols().__class__):
  213. msg = 'Expected a generator, got %r.' % gen
  214. raise TypeError(msg)
  215. index_gen = gen
  216. if qubit_map is not None:
  217. if not isinstance(qubit_map, dict):
  218. msg = ('Expected dict for existing map, got ' +
  219. '%r.' % qubit_map)
  220. raise TypeError(msg)
  221. ndx_map = qubit_map
  222. ndx_map = _sympify_qubit_map(ndx_map)
  223. # keys are real indices; keys are symbolic indices
  224. inv_map = create_inverse_map(ndx_map)
  225. sym_seq = ()
  226. for item in seq:
  227. # Nested items, so recurse
  228. if isinstance(item, Gate):
  229. result = convert_to_symbolic_indices(item.args,
  230. qubit_map=ndx_map,
  231. start=cur_ndx,
  232. gen=index_gen)
  233. sym_item, new_map, cur_ndx, index_gen = result
  234. ndx_map.update(new_map)
  235. inv_map = create_inverse_map(ndx_map)
  236. elif isinstance(item, (tuple, Tuple)):
  237. result = convert_to_symbolic_indices(item,
  238. qubit_map=ndx_map,
  239. start=cur_ndx,
  240. gen=index_gen)
  241. sym_item, new_map, cur_ndx, index_gen = result
  242. ndx_map.update(new_map)
  243. inv_map = create_inverse_map(ndx_map)
  244. elif item in inv_map:
  245. sym_item = inv_map[item]
  246. else:
  247. cur_ndx = next(gen)
  248. ndx_map[cur_ndx] = item
  249. inv_map[item] = cur_ndx
  250. sym_item = cur_ndx
  251. if isinstance(item, Gate):
  252. sym_item = item.__class__(*sym_item)
  253. sym_seq = sym_seq + (sym_item,)
  254. return sym_seq, ndx_map, cur_ndx, index_gen
  255. def convert_to_real_indices(seq, qubit_map):
  256. """Returns the circuit with real indices.
  257. Parameters
  258. ==========
  259. seq : tuple, Gate/Integer/tuple or Mul
  260. A tuple of Gate, Integer, or tuple objects or a Mul
  261. qubit_map : dict
  262. A dictionary mapping symbolic indices to real indices.
  263. Examples
  264. ========
  265. Change the symbolic indices to real integers:
  266. >>> from sympy import symbols
  267. >>> from sympy.physics.quantum.circuitutils import convert_to_real_indices
  268. >>> from sympy.physics.quantum.gate import X, Y, H
  269. >>> i0, i1 = symbols('i:2')
  270. >>> index_map = {i0 : 0, i1 : 1}
  271. >>> convert_to_real_indices(X(i0)*Y(i1)*H(i0)*X(i1), index_map)
  272. (X(0), Y(1), H(0), X(1))
  273. """
  274. if isinstance(seq, Mul):
  275. seq = seq.args
  276. if not isinstance(qubit_map, dict):
  277. msg = 'Expected dict for qubit_map, got %r.' % qubit_map
  278. raise TypeError(msg)
  279. qubit_map = _sympify_qubit_map(qubit_map)
  280. real_seq = ()
  281. for item in seq:
  282. # Nested items, so recurse
  283. if isinstance(item, Gate):
  284. real_item = convert_to_real_indices(item.args, qubit_map)
  285. elif isinstance(item, (tuple, Tuple)):
  286. real_item = convert_to_real_indices(item, qubit_map)
  287. else:
  288. real_item = qubit_map[item]
  289. if isinstance(item, Gate):
  290. real_item = item.__class__(*real_item)
  291. real_seq = real_seq + (real_item,)
  292. return real_seq
  293. def random_reduce(circuit, gate_ids, seed=None):
  294. """Shorten the length of a quantum circuit.
  295. Explanation
  296. ===========
  297. random_reduce looks for circuit identities in circuit, randomly chooses
  298. one to remove, and returns a shorter yet equivalent circuit. If no
  299. identities are found, the same circuit is returned.
  300. Parameters
  301. ==========
  302. circuit : Gate tuple of Mul
  303. A tuple of Gates representing a quantum circuit
  304. gate_ids : list, GateIdentity
  305. List of gate identities to find in circuit
  306. seed : int or list
  307. seed used for _randrange; to override the random selection, provide a
  308. list of integers: the elements of gate_ids will be tested in the order
  309. given by the list
  310. """
  311. from sympy.core.random import _randrange
  312. if not gate_ids:
  313. return circuit
  314. if isinstance(circuit, Mul):
  315. circuit = circuit.args
  316. ids = flatten_ids(gate_ids)
  317. # Create the random integer generator with the seed
  318. randrange = _randrange(seed)
  319. # Look for an identity in the circuit
  320. while ids:
  321. i = randrange(len(ids))
  322. id = ids.pop(i)
  323. if find_subcircuit(circuit, id) != -1:
  324. break
  325. else:
  326. # no identity was found
  327. return circuit
  328. # return circuit with the identity removed
  329. return replace_subcircuit(circuit, id)
  330. def random_insert(circuit, choices, seed=None):
  331. """Insert a circuit into another quantum circuit.
  332. Explanation
  333. ===========
  334. random_insert randomly chooses a location in the circuit to insert
  335. a randomly selected circuit from amongst the given choices.
  336. Parameters
  337. ==========
  338. circuit : Gate tuple or Mul
  339. A tuple or Mul of Gates representing a quantum circuit
  340. choices : list
  341. Set of circuit choices
  342. seed : int or list
  343. seed used for _randrange; to override the random selections, give
  344. a list two integers, [i, j] where i is the circuit location where
  345. choice[j] will be inserted.
  346. Notes
  347. =====
  348. Indices for insertion should be [0, n] if n is the length of the
  349. circuit.
  350. """
  351. from sympy.core.random import _randrange
  352. if not choices:
  353. return circuit
  354. if isinstance(circuit, Mul):
  355. circuit = circuit.args
  356. # get the location in the circuit and the element to insert from choices
  357. randrange = _randrange(seed)
  358. loc = randrange(len(circuit) + 1)
  359. choice = choices[randrange(len(choices))]
  360. circuit = list(circuit)
  361. circuit[loc: loc] = choice
  362. return tuple(circuit)
  363. # Flatten the GateIdentity objects (with gate rules) into one single list
  364. def flatten_ids(ids):
  365. collapse = lambda acc, an_id: acc + sorted(an_id.equivalent_ids,
  366. key=default_sort_key)
  367. ids = reduce(collapse, ids, [])
  368. ids.sort(key=default_sort_key)
  369. return ids