identitysearch.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. from collections import deque
  2. from sympy.core.random import randint
  3. from sympy.external import import_module
  4. from sympy.core.basic import Basic
  5. from sympy.core.mul import Mul
  6. from sympy.core.numbers import Number
  7. from sympy.core.power import Pow
  8. from sympy.core.singleton import S
  9. from sympy.physics.quantum.represent import represent
  10. from sympy.physics.quantum.dagger import Dagger
  11. __all__ = [
  12. # Public interfaces
  13. 'generate_gate_rules',
  14. 'generate_equivalent_ids',
  15. 'GateIdentity',
  16. 'bfs_identity_search',
  17. 'random_identity_search',
  18. # "Private" functions
  19. 'is_scalar_sparse_matrix',
  20. 'is_scalar_nonsparse_matrix',
  21. 'is_degenerate',
  22. 'is_reducible',
  23. ]
  24. np = import_module('numpy')
  25. scipy = import_module('scipy', import_kwargs={'fromlist': ['sparse']})
  26. def is_scalar_sparse_matrix(circuit, nqubits, identity_only, eps=1e-11):
  27. """Checks if a given scipy.sparse matrix is a scalar matrix.
  28. A scalar matrix is such that B = bI, where B is the scalar
  29. matrix, b is some scalar multiple, and I is the identity
  30. matrix. A scalar matrix would have only the element b along
  31. it's main diagonal and zeroes elsewhere.
  32. Parameters
  33. ==========
  34. circuit : Gate tuple
  35. Sequence of quantum gates representing a quantum circuit
  36. nqubits : int
  37. Number of qubits in the circuit
  38. identity_only : bool
  39. Check for only identity matrices
  40. eps : number
  41. The tolerance value for zeroing out elements in the matrix.
  42. Values in the range [-eps, +eps] will be changed to a zero.
  43. """
  44. if not np or not scipy:
  45. pass
  46. matrix = represent(Mul(*circuit), nqubits=nqubits,
  47. format='scipy.sparse')
  48. # In some cases, represent returns a 1D scalar value in place
  49. # of a multi-dimensional scalar matrix
  50. if (isinstance(matrix, int)):
  51. return matrix == 1 if identity_only else True
  52. # If represent returns a matrix, check if the matrix is diagonal
  53. # and if every item along the diagonal is the same
  54. else:
  55. # Due to floating pointing operations, must zero out
  56. # elements that are "very" small in the dense matrix
  57. # See parameter for default value.
  58. # Get the ndarray version of the dense matrix
  59. dense_matrix = matrix.todense().getA()
  60. # Since complex values can't be compared, must split
  61. # the matrix into real and imaginary components
  62. # Find the real values in between -eps and eps
  63. bool_real = np.logical_and(dense_matrix.real > -eps,
  64. dense_matrix.real < eps)
  65. # Find the imaginary values between -eps and eps
  66. bool_imag = np.logical_and(dense_matrix.imag > -eps,
  67. dense_matrix.imag < eps)
  68. # Replaces values between -eps and eps with 0
  69. corrected_real = np.where(bool_real, 0.0, dense_matrix.real)
  70. corrected_imag = np.where(bool_imag, 0.0, dense_matrix.imag)
  71. # Convert the matrix with real values into imaginary values
  72. corrected_imag = corrected_imag * complex(1j)
  73. # Recombine the real and imaginary components
  74. corrected_dense = corrected_real + corrected_imag
  75. # Check if it's diagonal
  76. row_indices = corrected_dense.nonzero()[0]
  77. col_indices = corrected_dense.nonzero()[1]
  78. # Check if the rows indices and columns indices are the same
  79. # If they match, then matrix only contains elements along diagonal
  80. bool_indices = row_indices == col_indices
  81. is_diagonal = bool_indices.all()
  82. first_element = corrected_dense[0][0]
  83. # If the first element is a zero, then can't rescale matrix
  84. # and definitely not diagonal
  85. if (first_element == 0.0 + 0.0j):
  86. return False
  87. # The dimensions of the dense matrix should still
  88. # be 2^nqubits if there are elements all along the
  89. # the main diagonal
  90. trace_of_corrected = (corrected_dense/first_element).trace()
  91. expected_trace = pow(2, nqubits)
  92. has_correct_trace = trace_of_corrected == expected_trace
  93. # If only looking for identity matrices
  94. # first element must be a 1
  95. real_is_one = abs(first_element.real - 1.0) < eps
  96. imag_is_zero = abs(first_element.imag) < eps
  97. is_one = real_is_one and imag_is_zero
  98. is_identity = is_one if identity_only else True
  99. return bool(is_diagonal and has_correct_trace and is_identity)
  100. def is_scalar_nonsparse_matrix(circuit, nqubits, identity_only, eps=None):
  101. """Checks if a given circuit, in matrix form, is equivalent to
  102. a scalar value.
  103. Parameters
  104. ==========
  105. circuit : Gate tuple
  106. Sequence of quantum gates representing a quantum circuit
  107. nqubits : int
  108. Number of qubits in the circuit
  109. identity_only : bool
  110. Check for only identity matrices
  111. eps : number
  112. This argument is ignored. It is just for signature compatibility with
  113. is_scalar_sparse_matrix.
  114. Note: Used in situations when is_scalar_sparse_matrix has bugs
  115. """
  116. matrix = represent(Mul(*circuit), nqubits=nqubits)
  117. # In some cases, represent returns a 1D scalar value in place
  118. # of a multi-dimensional scalar matrix
  119. if (isinstance(matrix, Number)):
  120. return matrix == 1 if identity_only else True
  121. # If represent returns a matrix, check if the matrix is diagonal
  122. # and if every item along the diagonal is the same
  123. else:
  124. # Added up the diagonal elements
  125. matrix_trace = matrix.trace()
  126. # Divide the trace by the first element in the matrix
  127. # if matrix is not required to be the identity matrix
  128. adjusted_matrix_trace = (matrix_trace/matrix[0]
  129. if not identity_only
  130. else matrix_trace)
  131. is_identity = matrix[0] == 1.0 if identity_only else True
  132. has_correct_trace = adjusted_matrix_trace == pow(2, nqubits)
  133. # The matrix is scalar if it's diagonal and the adjusted trace
  134. # value is equal to 2^nqubits
  135. return bool(
  136. matrix.is_diagonal() and has_correct_trace and is_identity)
  137. if np and scipy:
  138. is_scalar_matrix = is_scalar_sparse_matrix
  139. else:
  140. is_scalar_matrix = is_scalar_nonsparse_matrix
  141. def _get_min_qubits(a_gate):
  142. if isinstance(a_gate, Pow):
  143. return a_gate.base.min_qubits
  144. else:
  145. return a_gate.min_qubits
  146. def ll_op(left, right):
  147. """Perform a LL operation.
  148. A LL operation multiplies both left and right circuits
  149. with the dagger of the left circuit's leftmost gate, and
  150. the dagger is multiplied on the left side of both circuits.
  151. If a LL is possible, it returns the new gate rule as a
  152. 2-tuple (LHS, RHS), where LHS is the left circuit and
  153. and RHS is the right circuit of the new rule.
  154. If a LL is not possible, None is returned.
  155. Parameters
  156. ==========
  157. left : Gate tuple
  158. The left circuit of a gate rule expression.
  159. right : Gate tuple
  160. The right circuit of a gate rule expression.
  161. Examples
  162. ========
  163. Generate a new gate rule using a LL operation:
  164. >>> from sympy.physics.quantum.identitysearch import ll_op
  165. >>> from sympy.physics.quantum.gate import X, Y, Z
  166. >>> x = X(0); y = Y(0); z = Z(0)
  167. >>> ll_op((x, y, z), ())
  168. ((Y(0), Z(0)), (X(0),))
  169. >>> ll_op((y, z), (x,))
  170. ((Z(0),), (Y(0), X(0)))
  171. """
  172. if (len(left) > 0):
  173. ll_gate = left[0]
  174. ll_gate_is_unitary = is_scalar_matrix(
  175. (Dagger(ll_gate), ll_gate), _get_min_qubits(ll_gate), True)
  176. if (len(left) > 0 and ll_gate_is_unitary):
  177. # Get the new left side w/o the leftmost gate
  178. new_left = left[1:len(left)]
  179. # Add the leftmost gate to the left position on the right side
  180. new_right = (Dagger(ll_gate),) + right
  181. # Return the new gate rule
  182. return (new_left, new_right)
  183. return None
  184. def lr_op(left, right):
  185. """Perform a LR operation.
  186. A LR operation multiplies both left and right circuits
  187. with the dagger of the left circuit's rightmost gate, and
  188. the dagger is multiplied on the right side of both circuits.
  189. If a LR is possible, it returns the new gate rule as a
  190. 2-tuple (LHS, RHS), where LHS is the left circuit and
  191. and RHS is the right circuit of the new rule.
  192. If a LR is not possible, None is returned.
  193. Parameters
  194. ==========
  195. left : Gate tuple
  196. The left circuit of a gate rule expression.
  197. right : Gate tuple
  198. The right circuit of a gate rule expression.
  199. Examples
  200. ========
  201. Generate a new gate rule using a LR operation:
  202. >>> from sympy.physics.quantum.identitysearch import lr_op
  203. >>> from sympy.physics.quantum.gate import X, Y, Z
  204. >>> x = X(0); y = Y(0); z = Z(0)
  205. >>> lr_op((x, y, z), ())
  206. ((X(0), Y(0)), (Z(0),))
  207. >>> lr_op((x, y), (z,))
  208. ((X(0),), (Z(0), Y(0)))
  209. """
  210. if (len(left) > 0):
  211. lr_gate = left[len(left) - 1]
  212. lr_gate_is_unitary = is_scalar_matrix(
  213. (Dagger(lr_gate), lr_gate), _get_min_qubits(lr_gate), True)
  214. if (len(left) > 0 and lr_gate_is_unitary):
  215. # Get the new left side w/o the rightmost gate
  216. new_left = left[0:len(left) - 1]
  217. # Add the rightmost gate to the right position on the right side
  218. new_right = right + (Dagger(lr_gate),)
  219. # Return the new gate rule
  220. return (new_left, new_right)
  221. return None
  222. def rl_op(left, right):
  223. """Perform a RL operation.
  224. A RL operation multiplies both left and right circuits
  225. with the dagger of the right circuit's leftmost gate, and
  226. the dagger is multiplied on the left side of both circuits.
  227. If a RL is possible, it returns the new gate rule as a
  228. 2-tuple (LHS, RHS), where LHS is the left circuit and
  229. and RHS is the right circuit of the new rule.
  230. If a RL is not possible, None is returned.
  231. Parameters
  232. ==========
  233. left : Gate tuple
  234. The left circuit of a gate rule expression.
  235. right : Gate tuple
  236. The right circuit of a gate rule expression.
  237. Examples
  238. ========
  239. Generate a new gate rule using a RL operation:
  240. >>> from sympy.physics.quantum.identitysearch import rl_op
  241. >>> from sympy.physics.quantum.gate import X, Y, Z
  242. >>> x = X(0); y = Y(0); z = Z(0)
  243. >>> rl_op((x,), (y, z))
  244. ((Y(0), X(0)), (Z(0),))
  245. >>> rl_op((x, y), (z,))
  246. ((Z(0), X(0), Y(0)), ())
  247. """
  248. if (len(right) > 0):
  249. rl_gate = right[0]
  250. rl_gate_is_unitary = is_scalar_matrix(
  251. (Dagger(rl_gate), rl_gate), _get_min_qubits(rl_gate), True)
  252. if (len(right) > 0 and rl_gate_is_unitary):
  253. # Get the new right side w/o the leftmost gate
  254. new_right = right[1:len(right)]
  255. # Add the leftmost gate to the left position on the left side
  256. new_left = (Dagger(rl_gate),) + left
  257. # Return the new gate rule
  258. return (new_left, new_right)
  259. return None
  260. def rr_op(left, right):
  261. """Perform a RR operation.
  262. A RR operation multiplies both left and right circuits
  263. with the dagger of the right circuit's rightmost gate, and
  264. the dagger is multiplied on the right side of both circuits.
  265. If a RR is possible, it returns the new gate rule as a
  266. 2-tuple (LHS, RHS), where LHS is the left circuit and
  267. and RHS is the right circuit of the new rule.
  268. If a RR is not possible, None is returned.
  269. Parameters
  270. ==========
  271. left : Gate tuple
  272. The left circuit of a gate rule expression.
  273. right : Gate tuple
  274. The right circuit of a gate rule expression.
  275. Examples
  276. ========
  277. Generate a new gate rule using a RR operation:
  278. >>> from sympy.physics.quantum.identitysearch import rr_op
  279. >>> from sympy.physics.quantum.gate import X, Y, Z
  280. >>> x = X(0); y = Y(0); z = Z(0)
  281. >>> rr_op((x, y), (z,))
  282. ((X(0), Y(0), Z(0)), ())
  283. >>> rr_op((x,), (y, z))
  284. ((X(0), Z(0)), (Y(0),))
  285. """
  286. if (len(right) > 0):
  287. rr_gate = right[len(right) - 1]
  288. rr_gate_is_unitary = is_scalar_matrix(
  289. (Dagger(rr_gate), rr_gate), _get_min_qubits(rr_gate), True)
  290. if (len(right) > 0 and rr_gate_is_unitary):
  291. # Get the new right side w/o the rightmost gate
  292. new_right = right[0:len(right) - 1]
  293. # Add the rightmost gate to the right position on the right side
  294. new_left = left + (Dagger(rr_gate),)
  295. # Return the new gate rule
  296. return (new_left, new_right)
  297. return None
  298. def generate_gate_rules(gate_seq, return_as_muls=False):
  299. """Returns a set of gate rules. Each gate rules is represented
  300. as a 2-tuple of tuples or Muls. An empty tuple represents an arbitrary
  301. scalar value.
  302. This function uses the four operations (LL, LR, RL, RR)
  303. to generate the gate rules.
  304. A gate rule is an expression such as ABC = D or AB = CD, where
  305. A, B, C, and D are gates. Each value on either side of the
  306. equal sign represents a circuit. The four operations allow
  307. one to find a set of equivalent circuits from a gate identity.
  308. The letters denoting the operation tell the user what
  309. activities to perform on each expression. The first letter
  310. indicates which side of the equal sign to focus on. The
  311. second letter indicates which gate to focus on given the
  312. side. Once this information is determined, the inverse
  313. of the gate is multiplied on both circuits to create a new
  314. gate rule.
  315. For example, given the identity, ABCD = 1, a LL operation
  316. means look at the left value and multiply both left sides by the
  317. inverse of the leftmost gate A. If A is Hermitian, the inverse
  318. of A is still A. The resulting new rule is BCD = A.
  319. The following is a summary of the four operations. Assume
  320. that in the examples, all gates are Hermitian.
  321. LL : left circuit, left multiply
  322. ABCD = E -> AABCD = AE -> BCD = AE
  323. LR : left circuit, right multiply
  324. ABCD = E -> ABCDD = ED -> ABC = ED
  325. RL : right circuit, left multiply
  326. ABC = ED -> EABC = EED -> EABC = D
  327. RR : right circuit, right multiply
  328. AB = CD -> ABD = CDD -> ABD = C
  329. The number of gate rules generated is n*(n+1), where n
  330. is the number of gates in the sequence (unproven).
  331. Parameters
  332. ==========
  333. gate_seq : Gate tuple, Mul, or Number
  334. A variable length tuple or Mul of Gates whose product is equal to
  335. a scalar matrix
  336. return_as_muls : bool
  337. True to return a set of Muls; False to return a set of tuples
  338. Examples
  339. ========
  340. Find the gate rules of the current circuit using tuples:
  341. >>> from sympy.physics.quantum.identitysearch import generate_gate_rules
  342. >>> from sympy.physics.quantum.gate import X, Y, Z
  343. >>> x = X(0); y = Y(0); z = Z(0)
  344. >>> generate_gate_rules((x, x))
  345. {((X(0),), (X(0),)), ((X(0), X(0)), ())}
  346. >>> generate_gate_rules((x, y, z))
  347. {((), (X(0), Z(0), Y(0))), ((), (Y(0), X(0), Z(0))),
  348. ((), (Z(0), Y(0), X(0))), ((X(0),), (Z(0), Y(0))),
  349. ((Y(0),), (X(0), Z(0))), ((Z(0),), (Y(0), X(0))),
  350. ((X(0), Y(0)), (Z(0),)), ((Y(0), Z(0)), (X(0),)),
  351. ((Z(0), X(0)), (Y(0),)), ((X(0), Y(0), Z(0)), ()),
  352. ((Y(0), Z(0), X(0)), ()), ((Z(0), X(0), Y(0)), ())}
  353. Find the gate rules of the current circuit using Muls:
  354. >>> generate_gate_rules(x*x, return_as_muls=True)
  355. {(1, 1)}
  356. >>> generate_gate_rules(x*y*z, return_as_muls=True)
  357. {(1, X(0)*Z(0)*Y(0)), (1, Y(0)*X(0)*Z(0)),
  358. (1, Z(0)*Y(0)*X(0)), (X(0)*Y(0), Z(0)),
  359. (Y(0)*Z(0), X(0)), (Z(0)*X(0), Y(0)),
  360. (X(0)*Y(0)*Z(0), 1), (Y(0)*Z(0)*X(0), 1),
  361. (Z(0)*X(0)*Y(0), 1), (X(0), Z(0)*Y(0)),
  362. (Y(0), X(0)*Z(0)), (Z(0), Y(0)*X(0))}
  363. """
  364. if isinstance(gate_seq, Number):
  365. if return_as_muls:
  366. return {(S.One, S.One)}
  367. else:
  368. return {((), ())}
  369. elif isinstance(gate_seq, Mul):
  370. gate_seq = gate_seq.args
  371. # Each item in queue is a 3-tuple:
  372. # i) first item is the left side of an equality
  373. # ii) second item is the right side of an equality
  374. # iii) third item is the number of operations performed
  375. # The argument, gate_seq, will start on the left side, and
  376. # the right side will be empty, implying the presence of an
  377. # identity.
  378. queue = deque()
  379. # A set of gate rules
  380. rules = set()
  381. # Maximum number of operations to perform
  382. max_ops = len(gate_seq)
  383. def process_new_rule(new_rule, ops):
  384. if new_rule is not None:
  385. new_left, new_right = new_rule
  386. if new_rule not in rules and (new_right, new_left) not in rules:
  387. rules.add(new_rule)
  388. # If haven't reached the max limit on operations
  389. if ops + 1 < max_ops:
  390. queue.append(new_rule + (ops + 1,))
  391. queue.append((gate_seq, (), 0))
  392. rules.add((gate_seq, ()))
  393. while len(queue) > 0:
  394. left, right, ops = queue.popleft()
  395. # Do a LL
  396. new_rule = ll_op(left, right)
  397. process_new_rule(new_rule, ops)
  398. # Do a LR
  399. new_rule = lr_op(left, right)
  400. process_new_rule(new_rule, ops)
  401. # Do a RL
  402. new_rule = rl_op(left, right)
  403. process_new_rule(new_rule, ops)
  404. # Do a RR
  405. new_rule = rr_op(left, right)
  406. process_new_rule(new_rule, ops)
  407. if return_as_muls:
  408. # Convert each rule as tuples into a rule as muls
  409. mul_rules = set()
  410. for rule in rules:
  411. left, right = rule
  412. mul_rules.add((Mul(*left), Mul(*right)))
  413. rules = mul_rules
  414. return rules
  415. def generate_equivalent_ids(gate_seq, return_as_muls=False):
  416. """Returns a set of equivalent gate identities.
  417. A gate identity is a quantum circuit such that the product
  418. of the gates in the circuit is equal to a scalar value.
  419. For example, XYZ = i, where X, Y, Z are the Pauli gates and
  420. i is the imaginary value, is considered a gate identity.
  421. This function uses the four operations (LL, LR, RL, RR)
  422. to generate the gate rules and, subsequently, to locate equivalent
  423. gate identities.
  424. Note that all equivalent identities are reachable in n operations
  425. from the starting gate identity, where n is the number of gates
  426. in the sequence.
  427. The max number of gate identities is 2n, where n is the number
  428. of gates in the sequence (unproven).
  429. Parameters
  430. ==========
  431. gate_seq : Gate tuple, Mul, or Number
  432. A variable length tuple or Mul of Gates whose product is equal to
  433. a scalar matrix.
  434. return_as_muls: bool
  435. True to return as Muls; False to return as tuples
  436. Examples
  437. ========
  438. Find equivalent gate identities from the current circuit with tuples:
  439. >>> from sympy.physics.quantum.identitysearch import generate_equivalent_ids
  440. >>> from sympy.physics.quantum.gate import X, Y, Z
  441. >>> x = X(0); y = Y(0); z = Z(0)
  442. >>> generate_equivalent_ids((x, x))
  443. {(X(0), X(0))}
  444. >>> generate_equivalent_ids((x, y, z))
  445. {(X(0), Y(0), Z(0)), (X(0), Z(0), Y(0)), (Y(0), X(0), Z(0)),
  446. (Y(0), Z(0), X(0)), (Z(0), X(0), Y(0)), (Z(0), Y(0), X(0))}
  447. Find equivalent gate identities from the current circuit with Muls:
  448. >>> generate_equivalent_ids(x*x, return_as_muls=True)
  449. {1}
  450. >>> generate_equivalent_ids(x*y*z, return_as_muls=True)
  451. {X(0)*Y(0)*Z(0), X(0)*Z(0)*Y(0), Y(0)*X(0)*Z(0),
  452. Y(0)*Z(0)*X(0), Z(0)*X(0)*Y(0), Z(0)*Y(0)*X(0)}
  453. """
  454. if isinstance(gate_seq, Number):
  455. return {S.One}
  456. elif isinstance(gate_seq, Mul):
  457. gate_seq = gate_seq.args
  458. # Filter through the gate rules and keep the rules
  459. # with an empty tuple either on the left or right side
  460. # A set of equivalent gate identities
  461. eq_ids = set()
  462. gate_rules = generate_gate_rules(gate_seq)
  463. for rule in gate_rules:
  464. l, r = rule
  465. if l == ():
  466. eq_ids.add(r)
  467. elif r == ():
  468. eq_ids.add(l)
  469. if return_as_muls:
  470. convert_to_mul = lambda id_seq: Mul(*id_seq)
  471. eq_ids = set(map(convert_to_mul, eq_ids))
  472. return eq_ids
  473. class GateIdentity(Basic):
  474. """Wrapper class for circuits that reduce to a scalar value.
  475. A gate identity is a quantum circuit such that the product
  476. of the gates in the circuit is equal to a scalar value.
  477. For example, XYZ = i, where X, Y, Z are the Pauli gates and
  478. i is the imaginary value, is considered a gate identity.
  479. Parameters
  480. ==========
  481. args : Gate tuple
  482. A variable length tuple of Gates that form an identity.
  483. Examples
  484. ========
  485. Create a GateIdentity and look at its attributes:
  486. >>> from sympy.physics.quantum.identitysearch import GateIdentity
  487. >>> from sympy.physics.quantum.gate import X, Y, Z
  488. >>> x = X(0); y = Y(0); z = Z(0)
  489. >>> an_identity = GateIdentity(x, y, z)
  490. >>> an_identity.circuit
  491. X(0)*Y(0)*Z(0)
  492. >>> an_identity.equivalent_ids
  493. {(X(0), Y(0), Z(0)), (X(0), Z(0), Y(0)), (Y(0), X(0), Z(0)),
  494. (Y(0), Z(0), X(0)), (Z(0), X(0), Y(0)), (Z(0), Y(0), X(0))}
  495. """
  496. def __new__(cls, *args):
  497. # args should be a tuple - a variable length argument list
  498. obj = Basic.__new__(cls, *args)
  499. obj._circuit = Mul(*args)
  500. obj._rules = generate_gate_rules(args)
  501. obj._eq_ids = generate_equivalent_ids(args)
  502. return obj
  503. @property
  504. def circuit(self):
  505. return self._circuit
  506. @property
  507. def gate_rules(self):
  508. return self._rules
  509. @property
  510. def equivalent_ids(self):
  511. return self._eq_ids
  512. @property
  513. def sequence(self):
  514. return self.args
  515. def __str__(self):
  516. """Returns the string of gates in a tuple."""
  517. return str(self.circuit)
  518. def is_degenerate(identity_set, gate_identity):
  519. """Checks if a gate identity is a permutation of another identity.
  520. Parameters
  521. ==========
  522. identity_set : set
  523. A Python set with GateIdentity objects.
  524. gate_identity : GateIdentity
  525. The GateIdentity to check for existence in the set.
  526. Examples
  527. ========
  528. Check if the identity is a permutation of another identity:
  529. >>> from sympy.physics.quantum.identitysearch import (
  530. ... GateIdentity, is_degenerate)
  531. >>> from sympy.physics.quantum.gate import X, Y, Z
  532. >>> x = X(0); y = Y(0); z = Z(0)
  533. >>> an_identity = GateIdentity(x, y, z)
  534. >>> id_set = {an_identity}
  535. >>> another_id = (y, z, x)
  536. >>> is_degenerate(id_set, another_id)
  537. True
  538. >>> another_id = (x, x)
  539. >>> is_degenerate(id_set, another_id)
  540. False
  541. """
  542. # For now, just iteratively go through the set and check if the current
  543. # gate_identity is a permutation of an identity in the set
  544. for an_id in identity_set:
  545. if (gate_identity in an_id.equivalent_ids):
  546. return True
  547. return False
  548. def is_reducible(circuit, nqubits, begin, end):
  549. """Determines if a circuit is reducible by checking
  550. if its subcircuits are scalar values.
  551. Parameters
  552. ==========
  553. circuit : Gate tuple
  554. A tuple of Gates representing a circuit. The circuit to check
  555. if a gate identity is contained in a subcircuit.
  556. nqubits : int
  557. The number of qubits the circuit operates on.
  558. begin : int
  559. The leftmost gate in the circuit to include in a subcircuit.
  560. end : int
  561. The rightmost gate in the circuit to include in a subcircuit.
  562. Examples
  563. ========
  564. Check if the circuit can be reduced:
  565. >>> from sympy.physics.quantum.identitysearch import is_reducible
  566. >>> from sympy.physics.quantum.gate import X, Y, Z
  567. >>> x = X(0); y = Y(0); z = Z(0)
  568. >>> is_reducible((x, y, z), 1, 0, 3)
  569. True
  570. Check if an interval in the circuit can be reduced:
  571. >>> is_reducible((x, y, z), 1, 1, 3)
  572. False
  573. >>> is_reducible((x, y, y), 1, 1, 3)
  574. True
  575. """
  576. current_circuit = ()
  577. # Start from the gate at "end" and go down to almost the gate at "begin"
  578. for ndx in reversed(range(begin, end)):
  579. next_gate = circuit[ndx]
  580. current_circuit = (next_gate,) + current_circuit
  581. # If a circuit as a matrix is equivalent to a scalar value
  582. if (is_scalar_matrix(current_circuit, nqubits, False)):
  583. return True
  584. return False
  585. def bfs_identity_search(gate_list, nqubits, max_depth=None,
  586. identity_only=False):
  587. """Constructs a set of gate identities from the list of possible gates.
  588. Performs a breadth first search over the space of gate identities.
  589. This allows the finding of the shortest gate identities first.
  590. Parameters
  591. ==========
  592. gate_list : list, Gate
  593. A list of Gates from which to search for gate identities.
  594. nqubits : int
  595. The number of qubits the quantum circuit operates on.
  596. max_depth : int
  597. The longest quantum circuit to construct from gate_list.
  598. identity_only : bool
  599. True to search for gate identities that reduce to identity;
  600. False to search for gate identities that reduce to a scalar.
  601. Examples
  602. ========
  603. Find a list of gate identities:
  604. >>> from sympy.physics.quantum.identitysearch import bfs_identity_search
  605. >>> from sympy.physics.quantum.gate import X, Y, Z
  606. >>> x = X(0); y = Y(0); z = Z(0)
  607. >>> bfs_identity_search([x], 1, max_depth=2)
  608. {GateIdentity(X(0), X(0))}
  609. >>> bfs_identity_search([x, y, z], 1)
  610. {GateIdentity(X(0), X(0)), GateIdentity(Y(0), Y(0)),
  611. GateIdentity(Z(0), Z(0)), GateIdentity(X(0), Y(0), Z(0))}
  612. Find a list of identities that only equal to 1:
  613. >>> bfs_identity_search([x, y, z], 1, identity_only=True)
  614. {GateIdentity(X(0), X(0)), GateIdentity(Y(0), Y(0)),
  615. GateIdentity(Z(0), Z(0))}
  616. """
  617. if max_depth is None or max_depth <= 0:
  618. max_depth = len(gate_list)
  619. id_only = identity_only
  620. # Start with an empty sequence (implicitly contains an IdentityGate)
  621. queue = deque([()])
  622. # Create an empty set of gate identities
  623. ids = set()
  624. # Begin searching for gate identities in given space.
  625. while (len(queue) > 0):
  626. current_circuit = queue.popleft()
  627. for next_gate in gate_list:
  628. new_circuit = current_circuit + (next_gate,)
  629. # Determines if a (strict) subcircuit is a scalar matrix
  630. circuit_reducible = is_reducible(new_circuit, nqubits,
  631. 1, len(new_circuit))
  632. # In many cases when the matrix is a scalar value,
  633. # the evaluated matrix will actually be an integer
  634. if (is_scalar_matrix(new_circuit, nqubits, id_only) and
  635. not is_degenerate(ids, new_circuit) and
  636. not circuit_reducible):
  637. ids.add(GateIdentity(*new_circuit))
  638. elif (len(new_circuit) < max_depth and
  639. not circuit_reducible):
  640. queue.append(new_circuit)
  641. return ids
  642. def random_identity_search(gate_list, numgates, nqubits):
  643. """Randomly selects numgates from gate_list and checks if it is
  644. a gate identity.
  645. If the circuit is a gate identity, the circuit is returned;
  646. Otherwise, None is returned.
  647. """
  648. gate_size = len(gate_list)
  649. circuit = ()
  650. for i in range(numgates):
  651. next_gate = gate_list[randint(0, gate_size - 1)]
  652. circuit = circuit + (next_gate,)
  653. is_scalar = is_scalar_matrix(circuit, nqubits, False)
  654. return circuit if is_scalar else None