123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853 |
- from collections import deque
- from sympy.core.random import randint
- from sympy.external import import_module
- from sympy.core.basic import Basic
- from sympy.core.mul import Mul
- from sympy.core.numbers import Number
- from sympy.core.power import Pow
- from sympy.core.singleton import S
- from sympy.physics.quantum.represent import represent
- from sympy.physics.quantum.dagger import Dagger
- __all__ = [
- # Public interfaces
- 'generate_gate_rules',
- 'generate_equivalent_ids',
- 'GateIdentity',
- 'bfs_identity_search',
- 'random_identity_search',
- # "Private" functions
- 'is_scalar_sparse_matrix',
- 'is_scalar_nonsparse_matrix',
- 'is_degenerate',
- 'is_reducible',
- ]
- np = import_module('numpy')
- scipy = import_module('scipy', import_kwargs={'fromlist': ['sparse']})
- def is_scalar_sparse_matrix(circuit, nqubits, identity_only, eps=1e-11):
- """Checks if a given scipy.sparse matrix is a scalar matrix.
- A scalar matrix is such that B = bI, where B is the scalar
- matrix, b is some scalar multiple, and I is the identity
- matrix. A scalar matrix would have only the element b along
- it's main diagonal and zeroes elsewhere.
- Parameters
- ==========
- circuit : Gate tuple
- Sequence of quantum gates representing a quantum circuit
- nqubits : int
- Number of qubits in the circuit
- identity_only : bool
- Check for only identity matrices
- eps : number
- The tolerance value for zeroing out elements in the matrix.
- Values in the range [-eps, +eps] will be changed to a zero.
- """
- if not np or not scipy:
- pass
- matrix = represent(Mul(*circuit), nqubits=nqubits,
- format='scipy.sparse')
- # In some cases, represent returns a 1D scalar value in place
- # of a multi-dimensional scalar matrix
- if (isinstance(matrix, int)):
- return matrix == 1 if identity_only else True
- # If represent returns a matrix, check if the matrix is diagonal
- # and if every item along the diagonal is the same
- else:
- # Due to floating pointing operations, must zero out
- # elements that are "very" small in the dense matrix
- # See parameter for default value.
- # Get the ndarray version of the dense matrix
- dense_matrix = matrix.todense().getA()
- # Since complex values can't be compared, must split
- # the matrix into real and imaginary components
- # Find the real values in between -eps and eps
- bool_real = np.logical_and(dense_matrix.real > -eps,
- dense_matrix.real < eps)
- # Find the imaginary values between -eps and eps
- bool_imag = np.logical_and(dense_matrix.imag > -eps,
- dense_matrix.imag < eps)
- # Replaces values between -eps and eps with 0
- corrected_real = np.where(bool_real, 0.0, dense_matrix.real)
- corrected_imag = np.where(bool_imag, 0.0, dense_matrix.imag)
- # Convert the matrix with real values into imaginary values
- corrected_imag = corrected_imag * complex(1j)
- # Recombine the real and imaginary components
- corrected_dense = corrected_real + corrected_imag
- # Check if it's diagonal
- row_indices = corrected_dense.nonzero()[0]
- col_indices = corrected_dense.nonzero()[1]
- # Check if the rows indices and columns indices are the same
- # If they match, then matrix only contains elements along diagonal
- bool_indices = row_indices == col_indices
- is_diagonal = bool_indices.all()
- first_element = corrected_dense[0][0]
- # If the first element is a zero, then can't rescale matrix
- # and definitely not diagonal
- if (first_element == 0.0 + 0.0j):
- return False
- # The dimensions of the dense matrix should still
- # be 2^nqubits if there are elements all along the
- # the main diagonal
- trace_of_corrected = (corrected_dense/first_element).trace()
- expected_trace = pow(2, nqubits)
- has_correct_trace = trace_of_corrected == expected_trace
- # If only looking for identity matrices
- # first element must be a 1
- real_is_one = abs(first_element.real - 1.0) < eps
- imag_is_zero = abs(first_element.imag) < eps
- is_one = real_is_one and imag_is_zero
- is_identity = is_one if identity_only else True
- return bool(is_diagonal and has_correct_trace and is_identity)
- def is_scalar_nonsparse_matrix(circuit, nqubits, identity_only, eps=None):
- """Checks if a given circuit, in matrix form, is equivalent to
- a scalar value.
- Parameters
- ==========
- circuit : Gate tuple
- Sequence of quantum gates representing a quantum circuit
- nqubits : int
- Number of qubits in the circuit
- identity_only : bool
- Check for only identity matrices
- eps : number
- This argument is ignored. It is just for signature compatibility with
- is_scalar_sparse_matrix.
- Note: Used in situations when is_scalar_sparse_matrix has bugs
- """
- matrix = represent(Mul(*circuit), nqubits=nqubits)
- # In some cases, represent returns a 1D scalar value in place
- # of a multi-dimensional scalar matrix
- if (isinstance(matrix, Number)):
- return matrix == 1 if identity_only else True
- # If represent returns a matrix, check if the matrix is diagonal
- # and if every item along the diagonal is the same
- else:
- # Added up the diagonal elements
- matrix_trace = matrix.trace()
- # Divide the trace by the first element in the matrix
- # if matrix is not required to be the identity matrix
- adjusted_matrix_trace = (matrix_trace/matrix[0]
- if not identity_only
- else matrix_trace)
- is_identity = matrix[0] == 1.0 if identity_only else True
- has_correct_trace = adjusted_matrix_trace == pow(2, nqubits)
- # The matrix is scalar if it's diagonal and the adjusted trace
- # value is equal to 2^nqubits
- return bool(
- matrix.is_diagonal() and has_correct_trace and is_identity)
- if np and scipy:
- is_scalar_matrix = is_scalar_sparse_matrix
- else:
- is_scalar_matrix = is_scalar_nonsparse_matrix
- def _get_min_qubits(a_gate):
- if isinstance(a_gate, Pow):
- return a_gate.base.min_qubits
- else:
- return a_gate.min_qubits
- def ll_op(left, right):
- """Perform a LL operation.
- A LL operation multiplies both left and right circuits
- with the dagger of the left circuit's leftmost gate, and
- the dagger is multiplied on the left side of both circuits.
- If a LL is possible, it returns the new gate rule as a
- 2-tuple (LHS, RHS), where LHS is the left circuit and
- and RHS is the right circuit of the new rule.
- If a LL is not possible, None is returned.
- Parameters
- ==========
- left : Gate tuple
- The left circuit of a gate rule expression.
- right : Gate tuple
- The right circuit of a gate rule expression.
- Examples
- ========
- Generate a new gate rule using a LL operation:
- >>> from sympy.physics.quantum.identitysearch import ll_op
- >>> from sympy.physics.quantum.gate import X, Y, Z
- >>> x = X(0); y = Y(0); z = Z(0)
- >>> ll_op((x, y, z), ())
- ((Y(0), Z(0)), (X(0),))
- >>> ll_op((y, z), (x,))
- ((Z(0),), (Y(0), X(0)))
- """
- if (len(left) > 0):
- ll_gate = left[0]
- ll_gate_is_unitary = is_scalar_matrix(
- (Dagger(ll_gate), ll_gate), _get_min_qubits(ll_gate), True)
- if (len(left) > 0 and ll_gate_is_unitary):
- # Get the new left side w/o the leftmost gate
- new_left = left[1:len(left)]
- # Add the leftmost gate to the left position on the right side
- new_right = (Dagger(ll_gate),) + right
- # Return the new gate rule
- return (new_left, new_right)
- return None
- def lr_op(left, right):
- """Perform a LR operation.
- A LR operation multiplies both left and right circuits
- with the dagger of the left circuit's rightmost gate, and
- the dagger is multiplied on the right side of both circuits.
- If a LR is possible, it returns the new gate rule as a
- 2-tuple (LHS, RHS), where LHS is the left circuit and
- and RHS is the right circuit of the new rule.
- If a LR is not possible, None is returned.
- Parameters
- ==========
- left : Gate tuple
- The left circuit of a gate rule expression.
- right : Gate tuple
- The right circuit of a gate rule expression.
- Examples
- ========
- Generate a new gate rule using a LR operation:
- >>> from sympy.physics.quantum.identitysearch import lr_op
- >>> from sympy.physics.quantum.gate import X, Y, Z
- >>> x = X(0); y = Y(0); z = Z(0)
- >>> lr_op((x, y, z), ())
- ((X(0), Y(0)), (Z(0),))
- >>> lr_op((x, y), (z,))
- ((X(0),), (Z(0), Y(0)))
- """
- if (len(left) > 0):
- lr_gate = left[len(left) - 1]
- lr_gate_is_unitary = is_scalar_matrix(
- (Dagger(lr_gate), lr_gate), _get_min_qubits(lr_gate), True)
- if (len(left) > 0 and lr_gate_is_unitary):
- # Get the new left side w/o the rightmost gate
- new_left = left[0:len(left) - 1]
- # Add the rightmost gate to the right position on the right side
- new_right = right + (Dagger(lr_gate),)
- # Return the new gate rule
- return (new_left, new_right)
- return None
- def rl_op(left, right):
- """Perform a RL operation.
- A RL operation multiplies both left and right circuits
- with the dagger of the right circuit's leftmost gate, and
- the dagger is multiplied on the left side of both circuits.
- If a RL is possible, it returns the new gate rule as a
- 2-tuple (LHS, RHS), where LHS is the left circuit and
- and RHS is the right circuit of the new rule.
- If a RL is not possible, None is returned.
- Parameters
- ==========
- left : Gate tuple
- The left circuit of a gate rule expression.
- right : Gate tuple
- The right circuit of a gate rule expression.
- Examples
- ========
- Generate a new gate rule using a RL operation:
- >>> from sympy.physics.quantum.identitysearch import rl_op
- >>> from sympy.physics.quantum.gate import X, Y, Z
- >>> x = X(0); y = Y(0); z = Z(0)
- >>> rl_op((x,), (y, z))
- ((Y(0), X(0)), (Z(0),))
- >>> rl_op((x, y), (z,))
- ((Z(0), X(0), Y(0)), ())
- """
- if (len(right) > 0):
- rl_gate = right[0]
- rl_gate_is_unitary = is_scalar_matrix(
- (Dagger(rl_gate), rl_gate), _get_min_qubits(rl_gate), True)
- if (len(right) > 0 and rl_gate_is_unitary):
- # Get the new right side w/o the leftmost gate
- new_right = right[1:len(right)]
- # Add the leftmost gate to the left position on the left side
- new_left = (Dagger(rl_gate),) + left
- # Return the new gate rule
- return (new_left, new_right)
- return None
- def rr_op(left, right):
- """Perform a RR operation.
- A RR operation multiplies both left and right circuits
- with the dagger of the right circuit's rightmost gate, and
- the dagger is multiplied on the right side of both circuits.
- If a RR is possible, it returns the new gate rule as a
- 2-tuple (LHS, RHS), where LHS is the left circuit and
- and RHS is the right circuit of the new rule.
- If a RR is not possible, None is returned.
- Parameters
- ==========
- left : Gate tuple
- The left circuit of a gate rule expression.
- right : Gate tuple
- The right circuit of a gate rule expression.
- Examples
- ========
- Generate a new gate rule using a RR operation:
- >>> from sympy.physics.quantum.identitysearch import rr_op
- >>> from sympy.physics.quantum.gate import X, Y, Z
- >>> x = X(0); y = Y(0); z = Z(0)
- >>> rr_op((x, y), (z,))
- ((X(0), Y(0), Z(0)), ())
- >>> rr_op((x,), (y, z))
- ((X(0), Z(0)), (Y(0),))
- """
- if (len(right) > 0):
- rr_gate = right[len(right) - 1]
- rr_gate_is_unitary = is_scalar_matrix(
- (Dagger(rr_gate), rr_gate), _get_min_qubits(rr_gate), True)
- if (len(right) > 0 and rr_gate_is_unitary):
- # Get the new right side w/o the rightmost gate
- new_right = right[0:len(right) - 1]
- # Add the rightmost gate to the right position on the right side
- new_left = left + (Dagger(rr_gate),)
- # Return the new gate rule
- return (new_left, new_right)
- return None
- def generate_gate_rules(gate_seq, return_as_muls=False):
- """Returns a set of gate rules. Each gate rules is represented
- as a 2-tuple of tuples or Muls. An empty tuple represents an arbitrary
- scalar value.
- This function uses the four operations (LL, LR, RL, RR)
- to generate the gate rules.
- A gate rule is an expression such as ABC = D or AB = CD, where
- A, B, C, and D are gates. Each value on either side of the
- equal sign represents a circuit. The four operations allow
- one to find a set of equivalent circuits from a gate identity.
- The letters denoting the operation tell the user what
- activities to perform on each expression. The first letter
- indicates which side of the equal sign to focus on. The
- second letter indicates which gate to focus on given the
- side. Once this information is determined, the inverse
- of the gate is multiplied on both circuits to create a new
- gate rule.
- For example, given the identity, ABCD = 1, a LL operation
- means look at the left value and multiply both left sides by the
- inverse of the leftmost gate A. If A is Hermitian, the inverse
- of A is still A. The resulting new rule is BCD = A.
- The following is a summary of the four operations. Assume
- that in the examples, all gates are Hermitian.
- LL : left circuit, left multiply
- ABCD = E -> AABCD = AE -> BCD = AE
- LR : left circuit, right multiply
- ABCD = E -> ABCDD = ED -> ABC = ED
- RL : right circuit, left multiply
- ABC = ED -> EABC = EED -> EABC = D
- RR : right circuit, right multiply
- AB = CD -> ABD = CDD -> ABD = C
- The number of gate rules generated is n*(n+1), where n
- is the number of gates in the sequence (unproven).
- Parameters
- ==========
- gate_seq : Gate tuple, Mul, or Number
- A variable length tuple or Mul of Gates whose product is equal to
- a scalar matrix
- return_as_muls : bool
- True to return a set of Muls; False to return a set of tuples
- Examples
- ========
- Find the gate rules of the current circuit using tuples:
- >>> from sympy.physics.quantum.identitysearch import generate_gate_rules
- >>> from sympy.physics.quantum.gate import X, Y, Z
- >>> x = X(0); y = Y(0); z = Z(0)
- >>> generate_gate_rules((x, x))
- {((X(0),), (X(0),)), ((X(0), X(0)), ())}
- >>> generate_gate_rules((x, y, z))
- {((), (X(0), Z(0), Y(0))), ((), (Y(0), X(0), Z(0))),
- ((), (Z(0), Y(0), X(0))), ((X(0),), (Z(0), Y(0))),
- ((Y(0),), (X(0), Z(0))), ((Z(0),), (Y(0), X(0))),
- ((X(0), Y(0)), (Z(0),)), ((Y(0), Z(0)), (X(0),)),
- ((Z(0), X(0)), (Y(0),)), ((X(0), Y(0), Z(0)), ()),
- ((Y(0), Z(0), X(0)), ()), ((Z(0), X(0), Y(0)), ())}
- Find the gate rules of the current circuit using Muls:
- >>> generate_gate_rules(x*x, return_as_muls=True)
- {(1, 1)}
- >>> generate_gate_rules(x*y*z, return_as_muls=True)
- {(1, X(0)*Z(0)*Y(0)), (1, Y(0)*X(0)*Z(0)),
- (1, Z(0)*Y(0)*X(0)), (X(0)*Y(0), Z(0)),
- (Y(0)*Z(0), X(0)), (Z(0)*X(0), Y(0)),
- (X(0)*Y(0)*Z(0), 1), (Y(0)*Z(0)*X(0), 1),
- (Z(0)*X(0)*Y(0), 1), (X(0), Z(0)*Y(0)),
- (Y(0), X(0)*Z(0)), (Z(0), Y(0)*X(0))}
- """
- if isinstance(gate_seq, Number):
- if return_as_muls:
- return {(S.One, S.One)}
- else:
- return {((), ())}
- elif isinstance(gate_seq, Mul):
- gate_seq = gate_seq.args
- # Each item in queue is a 3-tuple:
- # i) first item is the left side of an equality
- # ii) second item is the right side of an equality
- # iii) third item is the number of operations performed
- # The argument, gate_seq, will start on the left side, and
- # the right side will be empty, implying the presence of an
- # identity.
- queue = deque()
- # A set of gate rules
- rules = set()
- # Maximum number of operations to perform
- max_ops = len(gate_seq)
- def process_new_rule(new_rule, ops):
- if new_rule is not None:
- new_left, new_right = new_rule
- if new_rule not in rules and (new_right, new_left) not in rules:
- rules.add(new_rule)
- # If haven't reached the max limit on operations
- if ops + 1 < max_ops:
- queue.append(new_rule + (ops + 1,))
- queue.append((gate_seq, (), 0))
- rules.add((gate_seq, ()))
- while len(queue) > 0:
- left, right, ops = queue.popleft()
- # Do a LL
- new_rule = ll_op(left, right)
- process_new_rule(new_rule, ops)
- # Do a LR
- new_rule = lr_op(left, right)
- process_new_rule(new_rule, ops)
- # Do a RL
- new_rule = rl_op(left, right)
- process_new_rule(new_rule, ops)
- # Do a RR
- new_rule = rr_op(left, right)
- process_new_rule(new_rule, ops)
- if return_as_muls:
- # Convert each rule as tuples into a rule as muls
- mul_rules = set()
- for rule in rules:
- left, right = rule
- mul_rules.add((Mul(*left), Mul(*right)))
- rules = mul_rules
- return rules
- def generate_equivalent_ids(gate_seq, return_as_muls=False):
- """Returns a set of equivalent gate identities.
- A gate identity is a quantum circuit such that the product
- of the gates in the circuit is equal to a scalar value.
- For example, XYZ = i, where X, Y, Z are the Pauli gates and
- i is the imaginary value, is considered a gate identity.
- This function uses the four operations (LL, LR, RL, RR)
- to generate the gate rules and, subsequently, to locate equivalent
- gate identities.
- Note that all equivalent identities are reachable in n operations
- from the starting gate identity, where n is the number of gates
- in the sequence.
- The max number of gate identities is 2n, where n is the number
- of gates in the sequence (unproven).
- Parameters
- ==========
- gate_seq : Gate tuple, Mul, or Number
- A variable length tuple or Mul of Gates whose product is equal to
- a scalar matrix.
- return_as_muls: bool
- True to return as Muls; False to return as tuples
- Examples
- ========
- Find equivalent gate identities from the current circuit with tuples:
- >>> from sympy.physics.quantum.identitysearch import generate_equivalent_ids
- >>> from sympy.physics.quantum.gate import X, Y, Z
- >>> x = X(0); y = Y(0); z = Z(0)
- >>> generate_equivalent_ids((x, x))
- {(X(0), X(0))}
- >>> generate_equivalent_ids((x, y, z))
- {(X(0), Y(0), Z(0)), (X(0), Z(0), Y(0)), (Y(0), X(0), Z(0)),
- (Y(0), Z(0), X(0)), (Z(0), X(0), Y(0)), (Z(0), Y(0), X(0))}
- Find equivalent gate identities from the current circuit with Muls:
- >>> generate_equivalent_ids(x*x, return_as_muls=True)
- {1}
- >>> generate_equivalent_ids(x*y*z, return_as_muls=True)
- {X(0)*Y(0)*Z(0), X(0)*Z(0)*Y(0), Y(0)*X(0)*Z(0),
- Y(0)*Z(0)*X(0), Z(0)*X(0)*Y(0), Z(0)*Y(0)*X(0)}
- """
- if isinstance(gate_seq, Number):
- return {S.One}
- elif isinstance(gate_seq, Mul):
- gate_seq = gate_seq.args
- # Filter through the gate rules and keep the rules
- # with an empty tuple either on the left or right side
- # A set of equivalent gate identities
- eq_ids = set()
- gate_rules = generate_gate_rules(gate_seq)
- for rule in gate_rules:
- l, r = rule
- if l == ():
- eq_ids.add(r)
- elif r == ():
- eq_ids.add(l)
- if return_as_muls:
- convert_to_mul = lambda id_seq: Mul(*id_seq)
- eq_ids = set(map(convert_to_mul, eq_ids))
- return eq_ids
- class GateIdentity(Basic):
- """Wrapper class for circuits that reduce to a scalar value.
- A gate identity is a quantum circuit such that the product
- of the gates in the circuit is equal to a scalar value.
- For example, XYZ = i, where X, Y, Z are the Pauli gates and
- i is the imaginary value, is considered a gate identity.
- Parameters
- ==========
- args : Gate tuple
- A variable length tuple of Gates that form an identity.
- Examples
- ========
- Create a GateIdentity and look at its attributes:
- >>> from sympy.physics.quantum.identitysearch import GateIdentity
- >>> from sympy.physics.quantum.gate import X, Y, Z
- >>> x = X(0); y = Y(0); z = Z(0)
- >>> an_identity = GateIdentity(x, y, z)
- >>> an_identity.circuit
- X(0)*Y(0)*Z(0)
- >>> an_identity.equivalent_ids
- {(X(0), Y(0), Z(0)), (X(0), Z(0), Y(0)), (Y(0), X(0), Z(0)),
- (Y(0), Z(0), X(0)), (Z(0), X(0), Y(0)), (Z(0), Y(0), X(0))}
- """
- def __new__(cls, *args):
- # args should be a tuple - a variable length argument list
- obj = Basic.__new__(cls, *args)
- obj._circuit = Mul(*args)
- obj._rules = generate_gate_rules(args)
- obj._eq_ids = generate_equivalent_ids(args)
- return obj
- @property
- def circuit(self):
- return self._circuit
- @property
- def gate_rules(self):
- return self._rules
- @property
- def equivalent_ids(self):
- return self._eq_ids
- @property
- def sequence(self):
- return self.args
- def __str__(self):
- """Returns the string of gates in a tuple."""
- return str(self.circuit)
- def is_degenerate(identity_set, gate_identity):
- """Checks if a gate identity is a permutation of another identity.
- Parameters
- ==========
- identity_set : set
- A Python set with GateIdentity objects.
- gate_identity : GateIdentity
- The GateIdentity to check for existence in the set.
- Examples
- ========
- Check if the identity is a permutation of another identity:
- >>> from sympy.physics.quantum.identitysearch import (
- ... GateIdentity, is_degenerate)
- >>> from sympy.physics.quantum.gate import X, Y, Z
- >>> x = X(0); y = Y(0); z = Z(0)
- >>> an_identity = GateIdentity(x, y, z)
- >>> id_set = {an_identity}
- >>> another_id = (y, z, x)
- >>> is_degenerate(id_set, another_id)
- True
- >>> another_id = (x, x)
- >>> is_degenerate(id_set, another_id)
- False
- """
- # For now, just iteratively go through the set and check if the current
- # gate_identity is a permutation of an identity in the set
- for an_id in identity_set:
- if (gate_identity in an_id.equivalent_ids):
- return True
- return False
- def is_reducible(circuit, nqubits, begin, end):
- """Determines if a circuit is reducible by checking
- if its subcircuits are scalar values.
- Parameters
- ==========
- circuit : Gate tuple
- A tuple of Gates representing a circuit. The circuit to check
- if a gate identity is contained in a subcircuit.
- nqubits : int
- The number of qubits the circuit operates on.
- begin : int
- The leftmost gate in the circuit to include in a subcircuit.
- end : int
- The rightmost gate in the circuit to include in a subcircuit.
- Examples
- ========
- Check if the circuit can be reduced:
- >>> from sympy.physics.quantum.identitysearch import is_reducible
- >>> from sympy.physics.quantum.gate import X, Y, Z
- >>> x = X(0); y = Y(0); z = Z(0)
- >>> is_reducible((x, y, z), 1, 0, 3)
- True
- Check if an interval in the circuit can be reduced:
- >>> is_reducible((x, y, z), 1, 1, 3)
- False
- >>> is_reducible((x, y, y), 1, 1, 3)
- True
- """
- current_circuit = ()
- # Start from the gate at "end" and go down to almost the gate at "begin"
- for ndx in reversed(range(begin, end)):
- next_gate = circuit[ndx]
- current_circuit = (next_gate,) + current_circuit
- # If a circuit as a matrix is equivalent to a scalar value
- if (is_scalar_matrix(current_circuit, nqubits, False)):
- return True
- return False
- def bfs_identity_search(gate_list, nqubits, max_depth=None,
- identity_only=False):
- """Constructs a set of gate identities from the list of possible gates.
- Performs a breadth first search over the space of gate identities.
- This allows the finding of the shortest gate identities first.
- Parameters
- ==========
- gate_list : list, Gate
- A list of Gates from which to search for gate identities.
- nqubits : int
- The number of qubits the quantum circuit operates on.
- max_depth : int
- The longest quantum circuit to construct from gate_list.
- identity_only : bool
- True to search for gate identities that reduce to identity;
- False to search for gate identities that reduce to a scalar.
- Examples
- ========
- Find a list of gate identities:
- >>> from sympy.physics.quantum.identitysearch import bfs_identity_search
- >>> from sympy.physics.quantum.gate import X, Y, Z
- >>> x = X(0); y = Y(0); z = Z(0)
- >>> bfs_identity_search([x], 1, max_depth=2)
- {GateIdentity(X(0), X(0))}
- >>> bfs_identity_search([x, y, z], 1)
- {GateIdentity(X(0), X(0)), GateIdentity(Y(0), Y(0)),
- GateIdentity(Z(0), Z(0)), GateIdentity(X(0), Y(0), Z(0))}
- Find a list of identities that only equal to 1:
- >>> bfs_identity_search([x, y, z], 1, identity_only=True)
- {GateIdentity(X(0), X(0)), GateIdentity(Y(0), Y(0)),
- GateIdentity(Z(0), Z(0))}
- """
- if max_depth is None or max_depth <= 0:
- max_depth = len(gate_list)
- id_only = identity_only
- # Start with an empty sequence (implicitly contains an IdentityGate)
- queue = deque([()])
- # Create an empty set of gate identities
- ids = set()
- # Begin searching for gate identities in given space.
- while (len(queue) > 0):
- current_circuit = queue.popleft()
- for next_gate in gate_list:
- new_circuit = current_circuit + (next_gate,)
- # Determines if a (strict) subcircuit is a scalar matrix
- circuit_reducible = is_reducible(new_circuit, nqubits,
- 1, len(new_circuit))
- # In many cases when the matrix is a scalar value,
- # the evaluated matrix will actually be an integer
- if (is_scalar_matrix(new_circuit, nqubits, id_only) and
- not is_degenerate(ids, new_circuit) and
- not circuit_reducible):
- ids.add(GateIdentity(*new_circuit))
- elif (len(new_circuit) < max_depth and
- not circuit_reducible):
- queue.append(new_circuit)
- return ids
- def random_identity_search(gate_list, numgates, nqubits):
- """Randomly selects numgates from gate_list and checks if it is
- a gate identity.
- If the circuit is a gate identity, the circuit is returned;
- Otherwise, None is returned.
- """
- gate_size = len(gate_list)
- circuit = ()
- for i in range(numgates):
- next_gate = gate_list[randint(0, gate_size - 1)]
- circuit = circuit + (next_gate,)
- is_scalar = is_scalar_matrix(circuit, nqubits, False)
- return circuit if is_scalar else None
|