Navigation

Source code for sympy.physics.quantum.qubit

"""Qubits for quantum computing.Todo:* Finish implementing measurement logic. This should include POVM.* Update docstrings.* Update tests."""from__future__importprint_function,divisionimportmathfromsympyimportInteger,log,Mul,Add,Pow,conjugatefromsympy.core.basicimportsympifyfromsympy.core.compatibilityimportstring_types,xrangefromsympy.matricesimportMatrix,zerosfromsympy.printing.pretty.stringpictimportprettyFormfromsympy.physics.quantum.hilbertimportComplexSpacefromsympy.physics.quantum.stateimportKet,Bra,Statefromsympy.physics.quantum.qexprimportQuantumErrorfromsympy.physics.quantum.representimportrepresentfromsympy.physics.quantum.matrixutilsimport(numpy_ndarray,scipy_sparse_matrix)fromsympy.mpmath.libmp.libintmathimportbitcount__all__=['Qubit','QubitBra','IntQubit','IntQubitBra','qubit_to_matrix','matrix_to_qubit','matrix_to_density','measure_all','measure_partial','measure_partial_oneshot','measure_all_oneshot']#-----------------------------------------------------------------------------# Qubit Classes#-----------------------------------------------------------------------------classQubitState(State):"""Base class for Qubit and QubitBra."""#-------------------------------------------------------------------------# Initialization/creation#-------------------------------------------------------------------------@classmethoddef_eval_args(cls,args):# If we are passed a QubitState or subclass, we just take its qubit# values directly.iflen(args)==1andisinstance(args[0],QubitState):returnargs[0].qubit_values# Turn strings into tuple of stringsiflen(args)==1andisinstance(args[0],string_types):args=tuple(args[0])args=sympify(args)# Validate input (must have 0 or 1 input)forelementinargs:ifnot(element==1orelement==0):raiseValueError("Qubit values must be 0 or 1, got: %r"%element)returnargs@classmethoddef_eval_hilbert_space(cls,args):returnComplexSpace(2)**len(args)#-------------------------------------------------------------------------# Properties#-------------------------------------------------------------------------@propertydefdimension(self):"""The number of Qubits in the state."""returnlen(self.qubit_values)@propertydefnqubits(self):returnself.dimension@propertydefqubit_values(self):"""Returns the values of the qubits as a tuple."""returnself.label#-------------------------------------------------------------------------# Special methods#-------------------------------------------------------------------------def__len__(self):returnself.dimensiondef__getitem__(self,bit):returnself.qubit_values[int(self.dimension-bit-1)]#-------------------------------------------------------------------------# Utility methods#-------------------------------------------------------------------------defflip(self,*bits):"""Flip the bit(s) given."""newargs=list(self.qubit_values)foriinbits:bit=int(self.dimension-i-1)ifnewargs[bit]==1:newargs[bit]=0else:newargs[bit]=1returnself.__class__(*tuple(newargs))

[docs]classQubit(QubitState,Ket):"""A multi-qubit ket in the computational (z) basis. We use the normal convention that the least significant qubit is on the right, so ``|00001>`` has a 1 in the least significant qubit. Parameters ========== values : list, str The qubit values as a list of ints ([0,0,0,1,1,]) or a string ('011'). Examples ======== Create a qubit in a couple of different ways and look at their attributes: >>> from sympy.physics.quantum.qubit import Qubit >>> Qubit(0,0,0) |000> >>> q = Qubit('0101') >>> q |0101> >>> q.nqubits 4 >>> len(q) 4 >>> q.dimension 4 >>> q.qubit_values (0, 1, 0, 1) We can flip the value of an individual qubit: >>> q.flip(1) |0111> We can take the dagger of a Qubit to get a bra: >>> from sympy.physics.quantum.dagger import Dagger >>> Dagger(q) <0101| >>> type(Dagger(q)) <class 'sympy.physics.quantum.qubit.QubitBra'> Inner products work as expected: >>> ip = Dagger(q)*q >>> ip <0101|0101> >>> ip.doit() 1 """@classmethoddefdual_class(self):returnQubitBradef_eval_innerproduct_QubitBra(self,bra,**hints):ifself.label==bra.label:returnInteger(1)else:returnInteger(0)def_represent_default_basis(self,**options):returnself._represent_ZGate(None,**options)def_represent_ZGate(self,basis,**options):"""Represent this qubits in the computational basis (ZGate). """format=options.get('format','sympy')n=1definite_state=0foritinreversed(self.qubit_values):definite_state+=n*itn=n*2result=[0]*(2**self.dimension)result[int(definite_state)]=1ifformat=='sympy':returnMatrix(result)elifformat=='numpy':importnumpyasnpreturnnp.matrix(result,dtype='complex').transpose()elifformat=='scipy.sparse':fromscipyimportsparsereturnsparse.csr_matrix(result,dtype='complex').transpose()def_eval_trace(self,bra,**kwargs):indices=kwargs.get('indices',[])#sort index list to begin trace from most-significant#qubitsorted_idx=list(indices)iflen(sorted_idx)==0:sorted_idx=list(range(0,self.nqubits))sorted_idx.sort()#trace out for each of indexnew_mat=self*braforiinxrange(len(sorted_idx)-1,-1,-1):# start from tracing out from leftmost qubitnew_mat=self._reduced_density(new_mat,int(sorted_idx[i]))if(len(sorted_idx)==self.nqubits):#in case full trace was requestedreturnnew_mat[0]else:returnmatrix_to_density(new_mat)def_reduced_density(self,matrix,qubit,**options):"""Compute the reduced density matrix by tracing out one qubit. The qubit argument should be of type python int, since it is used in bit operations """deffind_index_that_is_projected(j,k,qubit):bit_mask=2**qubit-1return((j>>qubit)<<(1+qubit))+(j&bit_mask)+(k<<qubit)old_matrix=represent(matrix,**options)old_size=old_matrix.cols#we expect the old_size to be evennew_size=old_size//2new_matrix=Matrix().zeros(new_size)foriinxrange(new_size):forjinxrange(new_size):forkinrange(2):col=find_index_that_is_projected(j,k,qubit)row=find_index_that_is_projected(i,k,qubit)new_matrix[i,j]+=old_matrix[row,col]returnnew_matrix

[docs]classQubitBra(QubitState,Bra):"""A multi-qubit bra in the computational (z) basis. We use the normal convention that the least significant qubit is on the right, so ``|00001>`` has a 1 in the least significant qubit. Parameters ========== values : list, str The qubit values as a list of ints ([0,0,0,1,1,]) or a string ('011'). See also ======== Qubit: Examples using qubits """@classmethoddefdual_class(self):returnQubit

classIntQubitState(QubitState):"""A base class for qubits that work with binary representations."""@classmethoddef_eval_args(cls,args):# The case of a QubitState instanceiflen(args)==1andisinstance(args[0],QubitState):returnQubitState._eval_args(args)# For a single argument, we construct the binary representation of# that integer with the minimal number of bits.iflen(args)==1andargs[0]>1:#rvalues is the minimum number of bits needed to express the numberrvalues=reversed(range(bitcount(abs(args[0]))))qubit_values=[(args[0]>>i)&1foriinrvalues]returnQubitState._eval_args(qubit_values)# For two numbers, the second number is the number of bits# on which it is expressed, so IntQubit(0,5) == |00000>.eliflen(args)==2andargs[1]>1:need=bitcount(abs(args[0]))ifargs[1]<need:raiseValueError('cannot represent %s with %s bits'%(args[0],args[1]))qubit_values=[(args[0]>>i)&1foriinreversed(range(args[1]))]returnQubitState._eval_args(qubit_values)else:returnQubitState._eval_args(args)defas_int(self):"""Return the numerical value of the qubit."""number=0n=1foriinreversed(self.qubit_values):number+=n*in=n<<1returnnumberdef_print_label(self,printer,*args):returnstr(self.as_int())def_print_label_pretty(self,printer,*args):label=self._print_label(printer,*args)returnprettyForm(label)_print_label_repr=_print_label_print_label_latex=_print_label

[docs]classIntQubit(IntQubitState,Qubit):"""A qubit ket that store integers as binary numbers in qubit values. The differences between this class and ``Qubit`` are: * The form of the constructor. * The qubit values are printed as their corresponding integer, rather than the raw qubit values. The internal storage format of the qubit values in the same as ``Qubit``. Parameters ========== values : int, tuple If a single argument, the integer we want to represent in the qubit values. This integer will be represented using the fewest possible number of qubits. If a pair of integers, the first integer gives the integer to represent in binary form and the second integer gives the number of qubits to use. Examples ======== Create a qubit for the integer 5: >>> from sympy.physics.quantum.qubit import IntQubit >>> from sympy.physics.quantum.qubit import Qubit >>> q = IntQubit(5) >>> q |5> We can also create an ``IntQubit`` by passing a ``Qubit`` instance. >>> q = IntQubit(Qubit('101')) >>> q |5> >>> q.as_int() 5 >>> q.nqubits 3 >>> q.qubit_values (1, 0, 1) We can go back to the regular qubit form. >>> Qubit(q) |101> """@classmethoddefdual_class(self):returnIntQubitBradef_eval_innerproduct_IntQubitBra(self,bra,**hints):returnQubit._eval_innerproduct_QubitBra(self,bra)

[docs]defmatrix_to_qubit(matrix):"""Convert from the matrix repr. to a sum of Qubit objects. Parameters ---------- matrix : Matrix, numpy.matrix, scipy.sparse The matrix to build the Qubit representation of. This works with sympy matrices, numpy matrices and scipy.sparse sparse matrices. Examples -------- Represent a state and then go back to its qubit form: >>> from sympy.physics.quantum.qubit import matrix_to_qubit, Qubit >>> from sympy.physics.quantum.gate import Z >>> from sympy.physics.quantum.represent import represent >>> q = Qubit('01') >>> matrix_to_qubit(represent(q)) |01> """# Determine the format based on the type of the input matrixformat='sympy'ifisinstance(matrix,numpy_ndarray):format='numpy'ifisinstance(matrix,scipy_sparse_matrix):format='scipy.sparse'# Make sure it is of correct dimensions for a Qubit-matrix representation.# This logic should work with sympy, numpy or scipy.sparse matrices.ifmatrix.shape[0]==1:mlistlen=matrix.shape[1]nqubits=log(mlistlen,2)ket=Falsecls=QubitBraelifmatrix.shape[1]==1:mlistlen=matrix.shape[0]nqubits=log(mlistlen,2)ket=Truecls=Qubitelse:raiseQuantumError('Matrix must be a row/column vector, got %r'%matrix)ifnotisinstance(nqubits,Integer):raiseQuantumError('Matrix must be a row/column vector of size ''2**nqubits, got: %r'%matrix)# Go through each item in matrix, if element is non-zero, make it into a# Qubit item times the element.result=0foriinrange(mlistlen):ifket:element=matrix[i,0]else:element=matrix[0,i]ifformat=='numpy'orformat=='scipy.sparse':element=complex(element)ifelement!=0.0:# Form Qubit array; 0 in bit-locations where i is 0, 1 in# bit-locations where i is 1qubit_array=[int(i&(1<<x)!=0)forxinrange(nqubits)]qubit_array.reverse()result=result+element*cls(*qubit_array)# If sympy simplified by pulling out a constant coefficient, undo that.ifisinstance(result,(Mul,Add,Pow)):result=result.expand()returnresult

[docs]defmatrix_to_density(mat):""" Works by finding the eigenvectors and eigenvalues of the matrix. We know we can decompose rho by doing: sum(EigenVal*|Eigenvect><Eigenvect|) """fromsympy.physics.quantum.densityimportDensityeigen=mat.eigenvects()args=[[matrix_to_qubit(Matrix([vector,])),x[0]]forxineigenforvectorinx[2]ifx[0]!=0]if(len(args)==0):return0else:returnDensity(*args)

[docs]defqubit_to_matrix(qubit,format='sympy'):"""Coverts an Add/Mul of Qubit objects into it's matrix representation This function is the inverse of ``matrix_to_qubit`` and is a shorthand for ``represent(qubit)``. """returnrepresent(qubit,format=format)#-----------------------------------------------------------------------------# Measurement#-----------------------------------------------------------------------------

[docs]defmeasure_partial_oneshot(qubit,bits,format='sympy'):"""Perform a partial oneshot measurement on the specified qubits. A oneshot measurement is equivalent to performing a measurement on a quantum system. This type of measurement does not return the probabilities like an ensemble measurement does, but rather returns *one* of the possible resulting states. The exact state that is returned is determined by picking a state randomly according to the ensemble probabilities. Parameters ---------- qubits : Qubit The qubit to measure. This can be any Qubit or a linear combination of them. bits : tuple The qubits to measure. format : str The format of the intermediate matrices to use. Possible values are ('sympy','numpy','scipy.sparse'). Currently only 'sympy' is implemented. Returns ------- result : Qubit The qubit that the system collapsed to upon measurement. """importrandomm=qubit_to_matrix(qubit,format)ifformat=='sympy':m=m.normalized()possible_outcomes=_get_possible_outcomes(m,bits)# Form output from functionrandom_number=random.random()total_prob=0foroutcomeinpossible_outcomes:# Calculate probability of finding the specified bits# with given valuestotal_prob+=(outcome.H*outcome)[0]iftotal_prob>=random_number:returnmatrix_to_qubit(outcome.normalized())else:raiseNotImplementedError("This function can't handle non-sympy matrix formats yet")

def_get_possible_outcomes(m,bits):"""Get the possible states that can be produced in a measurement. Parameters ---------- m : Matrix The matrix representing the state of the system. bits : tuple, list Which bits will be measured. Returns ------- result : list The list of possible states which can occur given this measurement. These are un-normalized so we can derive the probability of finding this state by taking the inner product with itself """# This is filled with loads of dirty binary tricks...You have been warnedsize=max(m.shape)# Max of shape to account for bra or ketnqubits=int(math.log(size,2)+.1)# Number of qubits possible# Make the output states and put in output_matrices, nothing in them now.# Each state will represent a possible outcome of the measurement# Thus, output_matrices[0] is the matrix which we get when all measured# bits return 0. and output_matrices[1] is the matrix for only the 0th# bit being trueoutput_matrices=[]foriinrange(1<<len(bits)):output_matrices.append(zeros(2**nqubits,1))# Bitmasks will help sort how to determine possible outcomes.# When the bit mask is and-ed with a matrix-index,# it will determine which state that index belongs tobit_masks=[]forbitinbits:bit_masks.append(1<<bit)# Make possible outcome statesforiinrange(2**nqubits):trueness=0# This tells us to which output_matrix this value belongs# Find truenessforjinrange(len(bit_masks)):ifi&bit_masks[j]:trueness+=j+1# Put the value in the correct output matrixoutput_matrices[trueness][i]=m[i]returnoutput_matrices

[docs]defmeasure_all_oneshot(qubit,format='sympy'):"""Perform a oneshot ensemble measurement on all qubits. A oneshot measurement is equivalent to performing a measurement on a quantum system. This type of measurement does not return the probabilities like an ensemble measurement does, but rather returns *one* of the possible resulting states. The exact state that is returned is determined by picking a state randomly according to the ensemble probabilities. Parameters ---------- qubits : Qubit The qubit to measure. This can be any Qubit or a linear combination of them. format : str The format of the intermediate matrices to use. Possible values are ('sympy','numpy','scipy.sparse'). Currently only 'sympy' is implemented. Returns ------- result : Qubit The qubit that the system collapsed to upon measurement. """importrandomm=qubit_to_matrix(qubit)ifformat=='sympy':m=m.normalized()random_number=random.random()total=0result=0foriinm:total+=i*i.conjugate()iftotal>random_number:breakresult+=1returnQubit(IntQubit(result,int(math.log(max(m.shape),2)+.1)))else:raiseNotImplementedError("This function can't handle non-sympy matrix formats yet")