Spaces:
Sleeping
Sleeping
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, equal_valued | |
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 = equal_valued(matrix[0], 1) 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 | |
def circuit(self): | |
return self._circuit | |
def gate_rules(self): | |
return self._rules | |
def equivalent_ids(self): | |
return self._eq_ids | |
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 | |