From d3b5fe96839fcb8176aec218a9c2685fd16966f1 Mon Sep 17 00:00:00 2001 From: Ashe Miller Date: Thu, 29 Feb 2024 15:49:24 -0700 Subject: [PATCH 01/11] Added Propagation Code for EOC Error Generators --- pygsti/propErrorGens/ErrorPropagator.py | 228 ++++++++++++ pygsti/propErrorGens/propagatableerrorgen.py | 361 +++++++++++++++++++ pygsti/propErrorGens/pyGSTiStimTranslator.py | 65 ++++ 3 files changed, 654 insertions(+) create mode 100644 pygsti/propErrorGens/ErrorPropagator.py create mode 100644 pygsti/propErrorGens/propagatableerrorgen.py create mode 100644 pygsti/propErrorGens/pyGSTiStimTranslator.py diff --git a/pygsti/propErrorGens/ErrorPropagator.py b/pygsti/propErrorGens/ErrorPropagator.py new file mode 100644 index 000000000..0ea24cdca --- /dev/null +++ b/pygsti/propErrorGens/ErrorPropagator.py @@ -0,0 +1,228 @@ +import stim +from pygsti.propErrorGens.propagatableerrorgen import * +from pygsti.propErrorGens.pyGSTiStimTranslator import * +from numpy import abs +from numpy.linalg import multi_dot +from scipy.linalg import expm + + +''' +takes a pygsti circuit where each gate has a defined error model and returns the errorgenerators necessary to create an +end of circuit error generator under a variety of scenarios + +circ: pygsti circuit +errorModel: Dictionary defined the small markovian error generators and their rates for each gate +BCHOrder: in cases where the BCH approximation is used, carries it out to the desired order (can currently only handle order 1 or 2) +BCHLayerWise: If true will push the errors through one layer of gatesand then combines them using the bch approximation at each layer +If false will simply push all errors to the end +NonMarkovian: Pushes the error generators to the end and then formats them to work with the cumulant expansion code +MultiGateDict: Containts the translation between a numbered gate Gxpi22 and the PyGSTi standard gate used when a singular gate has +multiple error iterations +MultiGate: lets the code know +returns: list of propagatableerrorgens +''' +def ErrorPropagator(circ,errorModel,MultiGateDict={},BCHOrder=1,BCHLayerwise=False,NonMarkovian=False,MultiGate=False): + qubits=len(circ.line_labels) + stim_layers=[] + for j in range(circ.depth): + layer = circ.layer(j) + stim_layer=pyGSTiLayer_to_stimLayer(layer,qubits,MultiGateDict,MultiGate) + stim_layers.append(stim_layer) + stim_layers.pop(0) #Immeditielty toss the first layer because it is not important, + + propagation_layers=[] + if not BCHLayerwise or NonMarkovian: + while len(stim_layers) != 0: + top_layer=stim_layers.pop(0) + for layer in stim_layers: + top_layer = layer*top_layer + propagation_layers.append(top_layer) + else: + propagation_layers = stim_layers + + errorLayers=buildErrorlayers(circ,errorModel,qubits) + + num_error_layers=len(errorLayers) + fully_propagated_layers=[] + for _ in range(0,num_error_layers-1): + err_layer=errorLayers.pop(0) + layer=propagation_layers.pop(0) + for err_order in err_layer: + for errorGenerator in err_order: + errorGenerator.propagate_error_gen_inplace_tableau(layer) + if BCHLayerwise and not NonMarkovian: + following_layer = errorLayers.pop(0) + new_errors=BCH_Handler(err_layer,following_layer,BCHOrder) + errorLayers.insert(new_errors,0) + else: + fully_propagated_layers.append(err_layer) + + fully_propagated_layers.append(errorLayers.pop(0)) + if BCHLayerwise and not NonMarkovian: + for order in errorLayers: + for error in order: + if len(fully_propagated_layers)==0: + fully_propagated_layers.append(error) + elif error in fully_propagated_layers: + idy=fully_propagated_layers.index(error) + new_error=error+fully_propagated_layers[idy] + fully_propagated_layers.pop(idy) + fully_propagated_layers.append(new_error) + else: + fully_propagated_layers.append(error) + return fully_propagated_layers + + elif not BCHLayerwise and not NonMarkovian: + simplified_EOC_errors=[] + if BCHOrder == 1: + for layer in fully_propagated_layers: + for order in layer: + for error in order: + if len(simplified_EOC_errors)==0: + simplified_EOC_errors.append(error) + elif error in simplified_EOC_errors: + idy=simplified_EOC_errors.index(error) + new_error=error+simplified_EOC_errors[idy] + simplified_EOC_errors.pop(idy) + if not (abs(new_error.get_Error_Rate()) <.000001): + simplified_EOC_errors.append(new_error) + else: + if not (abs(error.get_Error_Rate())<.000001): + simplified_EOC_errors.append(error) + else: + Exception("Higher propagated through Errors are not Implemented Yet") + return simplified_EOC_errors + + else: + return fully_propagated_layers + + +''' +takes two error layers (list of propagatableerrorgens) and find the bch combination of the two +err_layer: list lists of propagatableerrorgens +following_layer: list of propagatableerrorgens +BCHOrder: Order to carry the bch expansion out to, can currently be set to one or two +returns list of lists of propagatableerrorgens. The outer list contains each of them individual list denote order +''' +def BCH_Handler(err_layer,following_layer,BCHOrder): + new_errors=[] + for curr_order in range(0,BCHOrder): + working_order=[] + if curr_order == 0: + used_indexes=[] + for error in err_layer[curr_order]: + try: + idy=following_layer[curr_order].index(error) + working_order.append(error+following_layer[curr_order][idy]) + used_indexes.append(idy) + except: + working_order.append(error) + for idy,error in enumerate(following_layer[curr_order]): + if idy in used_indexes: + continue + else: + working_order.append(error) + + new_errors.append(working_order) + elif curr_order ==1: + working_order=[] + for error1 in err_layer[curr_order-1]: + for error2 in following_layer[curr_order-1]: + errorlist = commute_errors(error1,error2,BCHweight=1/2) + for error3 in errorlist: + if len(working_order)==0: + working_order.append(error3) + elif error3 in working_order: + idy=working_order.index(error3) + new_error=error3+working_order[idy] + working_order.pop(idy) + working_order.append(new_error) + else: + working_order.append(error3) + if len(err_layer)==2: + for error3 in err_layer[1]: + if len(working_order)==0: + working_order.append(error3) + elif error3 in working_order: + idy=working_order.index(error3) + new_error=error3+working_order[idy] + working_order.pop(idy) + working_order.append(new_error) + else: + working_order.append(error3) + if len(following_layer)==2: + for error3 in following_layer[1]: + if len(working_order)==0: + working_order.append(error3) + elif error3 in working_order: + idy=working_order.index(error3) + new_error=error3+working_order[idy] + working_order.pop(idy) + if new_error.get_Error_Rate() != 0j: + working_order.append(new_error) + else: + working_order.append(error3) + new_errors.append(working_order) + + else: + Exception("Higher Orders are not Implemented Yet") + + +''' +takes a pygst circuit object and error Dictionary and creates error layers + +inputs +circ: pygsti circuit +errorDict: Dictionary defined the small markovian error generators and their rates for each gate +qubits: number of qubits in the circuit + +output +ErrorGens, a list of error gen layers (which are list of propagatable errorgens) + +''' +def buildErrorlayers(circ,errorDict,qubits): + ErrorGens=[] + #For the jth layer of each circuit + for j in range(circ.depth): + l = circ.layer(j) # get the layer + errorLayer=[] + for _, g in enumerate(l): # for gate in layer l + gErrorDict = errorDict[g.name] #get the errors for the gate + p1=qubits*'I' # make some paulis why? + p2=qubits*'I' + for errs in gErrorDict: #for an error in the accompanying error dictionary + errType=errs[0] + paulis=[] + for ind,el in enumerate(g): #enumerate the gate ind =0 is name ind = 1 is first qubit ind = 2 is second qubit + if ind !=0: #if the gate element of concern is not the name + p1=p1[:el] + errs[1][ind-1] +p1[(el+1):] + + paulis.append(p1) + if errType in "CA": + for ind,el in enumerate(g): + if ind !=0: + p2=p2[:el] + errs[2][ind-1] +p2[(el+1):] + paulis.append(p2) + errorLayer.append(propagatableerrorgen(errType,paulis,gErrorDict[errs])) + ErrorGens.append([errorLayer]) + return ErrorGens + + + +# There's a factor of a half missing in here. +def nm_propagators(corr, Elist): + Kms = [] + for idm in range(len(Elist)): + Am = Elist[idm].toWeightedErrorBasisMatrix + # This assumes that Elist is in reverse chronological order + partials = [] + for idn in range(idm, len(Elist)): + An = Elist[idn].toWeightedErrorBasisMatrix() + partials += [corr[idm,idn] * Am @ An] + partials[0] = partials[0]/2 + Kms += [sum(partials,0)] + return Kms + +def averaged_evolution(corr, Elist): + Kms = nm_propagators(corr, Elist) + return multi_dot([expm(Km) for Km in Kms]) \ No newline at end of file diff --git a/pygsti/propErrorGens/propagatableerrorgen.py b/pygsti/propErrorGens/propagatableerrorgen.py new file mode 100644 index 000000000..1fa6febe2 --- /dev/null +++ b/pygsti/propErrorGens/propagatableerrorgen.py @@ -0,0 +1,361 @@ +from pygsti.baseobjs.errorgenlabel import ElementaryErrorgenLabel +from pygsti.propErrorGens.pyGSTiStimTranslator import * +import stim +from numpy import array,kron +from pygsti.tools import change_basis +from pygsti.tools.lindbladtools import create_elementary_errorgen +''' +Similar to errorgenlabel but has an errorrate included as well as additional classes +''' +class propagatableerrorgen(ElementaryErrorgenLabel): + ''' + Labels an elementary errorgen by a type, pauli and error rate + ''' + + @classmethod + def cast(cls, obj, sslbls=None, identity_label='I'): + raise NotImplementedError("TODO: Implement casts for this method") + + + ''' + Initiates the errorgen object + Inputs + errorgen_type: charecture can be set to 'H' Hamiltonian, 'S' Stochastic, 'C' Correlated or 'A' active following the conventions + of the taxonomy of small markovian errorgens paper + + Outputs: + propagatableerrorgen object + ''' + def __init__(self,errorgen_type,basis_element_labels,error_rate): + self.errorgen_type=str(errorgen_type) + self.basis_element_labels=tuple(basis_element_labels) + self.error_rate=error_rate + + ''' + hashes the error gen object + ''' + def __hash__(self): + return hash((self.errorgen_type,self.basis_element_labels)) + + ''' + checks and if two error gens have the same type and labels + ''' + def __eq__(self, other): + return (self.errorgen_type == other.errorgen_type + and self.basis_element_labels == other.basis_element_labels) + + ''' + displays the errorgens as strings + ''' + def __str__(self): + return self.errorgen_type + "(" + ",".join(map(str, self.basis_element_labels)) + ")" + ": " + self.error_rate + + + def __repr__(self): + return str((self.errorgen_type, self.basis_element_labels, self.error_rate)) + + ''' + adds the error rates together oftwo error generators of the same type and label + ''' + def __add__(self,other): + if self.errorgen_type == other.errorgen_type and self.basis_element_labels == other.basis_element_labels: + return propagatableerrorgen(self.errorgen_type,self.basis_element_labels,self.error_rate + other.error_rate) + else: + raise Exception("ErrorGens are not equal") + + ''' + returns the dictionary representation of the error generator inline with pygsti notation + ''' + def to_dict(self): + return {self: self.error_rate} + + + ''' + returns the error rate + ''' + def get_Error_Rate(self): + return self.error_rate + + ''' + returns the string representation of the first pauli label + ''' + def getP1(self): + return self.basis_element_labels[0] + + ''' + returns the string representation of the second pauli label + ''' + def getP2(self): + return self.basis_element_labels[1] + + ''' + propagates a propagatableerrorgen object through a clifford layer, returns the created error gen + ''' + def propagate_error_gen_inplace(self, player): + slayer = pyGSTiLayer_to_stimLayer(player) + new_basis_labels = [] + weightmod = 1 + for pauli in self.basis_element_labels: + temp=pyGSTiPauli_2_stimPauli(pauli) + temp = slayer(temp) + weightmod=weightmod*temp.sign + new_basis_labels.append(stimPauli_2_pyGSTiPauli(temp)) + + if self.errorgen_type in 'HCA': + self.error_rate=self.error_rate*weightmod + self.basis_element_labels =tuple(new_basis_labels) + + ''' + using stim propagates the associated pauli labels through a stim tableu object, the object is modified inplace + ''' + def propagate_error_gen_inplace_tableau(self, slayer): + new_basis_labels = [] + weightmod = 1 + for pauli in self.basis_element_labels: + temp=pyGSTiPauli_2_stimPauli(pauli) + temp = slayer(temp) + weightmod=weightmod*temp.sign + new_basis_labels.append(stimPauli_2_pyGSTiPauli(temp)) + + if self.errorgen_type in 'HCA': + self.error_rate=self.error_rate*weightmod + self.basis_element_labels =tuple(new_basis_labels) + + ''' + returns the strings representing the pauli labels in the pygsti representation of paulis as stim PauliStrings + ''' + def returnStimPaulis(self): + paulis_string=[] + for pauli in self.basis_element_labels: + paulis_string.append(stim.PauliString(pauli)) + return tuple(paulis_string) + + ''' + Returns the errorbasis matrix for the associated errorgenerator mulitplied by its error rate + + input: A pygsti defined matrix basis by default can be pauli-product, gellmann 'gm' or then pygsti standard basis 'std' + functions defaults to pauli product if not specified + ''' + def toWeightedErrorBasisMatrix(self,matrix_basis='pp'): + PauliDict={ + 'I' : array([[1.0,0.0],[0.0,1.0]]), + 'X' : array([[0.0j, 1.0+0.0j], [1.0+0.0j, 0.0j]]), + 'Y' : array([[0.0, -1.0j], [1.0j, 0.0]]), + 'Z' : array([[1.0, 0.0j], [0.0j, -1.0]]) + } + paulis=[] + for paulistring in self.basis_element_labels: + for idx,pauli in enumerate(paulistring): + if idx == 0: + pauliMat = PauliDict[pauli] + else: + pauliMat=kron(pauliMat,PauliDict[pauli]) + paulis.append(pauliMat) + if self.errorgen_type in 'HS': + return self.error_rate*change_basis(create_elementary_errorgen(self.errorgen_type,paulis[0]),'std',matrix_basis) + else: + return self.error_rate*change_basis(create_elementary_errorgen(self.errorgen_type,paulis[0],paulis[1]),'std',matrix_basis) + + + + + +''' +Returns the Commutator of two errors +''' +def commute_errors(ErG1,ErG2, weightFlip=1.0, BCHweight=1.0): + def com(p1,p2): + P1 = pyGSTiPauli_2_stimPauli(p1) + P2=pyGSTiPauli_2_stimPauli(p2) + P3=P1*P2-P2*P1 + return (P3.weight,stimPauli_2_pyGSTiPauli(P3)) + + def acom(p1,p2): + P1 = pyGSTiPauli_2_stimPauli(p1) + P2=pyGSTiPauli_2_stimPauli(p2) + P3=P1*P2+P2*P1 + return (P3.weight,stimPauli_2_pyGSTiPauli(P3)) + + def labelMultiply(p1,p2): + P1 = pyGSTiPauli_2_stimPauli(p1) + P2=pyGSTiPauli_2_stimPauli(p2) + P3=P1*P2 + return (P3.weight,stimPauli_2_pyGSTiPauli(P3)) + + errorGens=[] + + wT=ErG1.getWeight()*ErG2.getWeight()*weightFlip*BCHweight + + if ErG1.getType()=='H' and ErG2.getType()=='H': + pVec=com(ErG1.getP1() , ErG2.getP2()) + errorGens.append( propagatableerrorgen( 'H' , [pVec[1]] , -1j*wT *pVec[0] ) ) + + elif ErG1.getType()=='H' and ErG2.getType()=='S': + pVec=com(ErG2.getP1() , ErG1.getP1()) + errorGens.append( propagatableerrorgen( 'C' , [ErG2.getP1() , pVec[1]] , 1j*wT*pVec[0] ) ) + + elif ErG1.getType()=='S' and ErG2.getType()=='H': + pVec=com(ErG2.getP1() , ErG1.getP1()) + errorGens.append( propagatableerrorgen( 'C' , [ErG2.getP1() , pVec[1]] , -1j*wT *pVec[0] ) ) + + elif ErG1.getType()=='H' and ErG2.getType()=='C': + pVec1=com(ErG2.getP1() , ErG1.getP1()) + errorGens.append( propagatableerrorgen('C' , [pVec1[1], ErG2.getP2()] , 1j*wT*pVec1[0] ) ) + pVec2=com(ErG2.getP2() , ErG1.getP1()) + errorGens.append( propagatableerrorgen('C' , [pVec2[1] , ErG2.getP1()] , 1j*wT*pVec2[0] ) ) + + elif ErG1.getType()=='C' and ErG2.getType()=='H': + errorGens = commute_errors(ErG2,ErG1,weightFlip=-1.0,BCHweight=BCHweight) + + elif ErG1.getType()=='H' and ErG2.getType()=='A': + pVec1 = com(ErG1.getP1() , ErG2.getP1()) + errorGens.append( propagatableerrorgen('A' , [pVec1[1] , ErG2.getP2()] , -1j*wT*pVec1[0]) ) + pVec2 = com(ErG1.getP1() , ErG2.getP2()) + errorGens.append( propagatableerrorgen('A' , [ErG2.getP1(), pVec2[1]] , -1j*wT*pVec2[0] ) ) + + elif ErG1.getType()=='A' and ErG2.getType()=='H': + errorGens = commute_errors(ErG2,ErG1,weightFlip=-1.0,BCHweight=BCHweight) + + elif ErG1.getType()=='S' and ErG2.getType()=='S': + errorGens.append( propagatableerrorgen('H', ErG1.getP1(),0 )) + + elif ErG1.getType()=='S' and ErG2.getType()=='C': + pVec1=labelMultiply(ErG1.getP1() , ErG2.getP1()) + pVec2=labelMultiply(ErG2.getP2() , ErG1.getP1()) + errorGens.append( propagatableerrorgen( 'A' , [pVec1[1], pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0] )) + pVec1 = labelMultiply(ErG1.getP1() , ErG2.getP2()) + pVec2 = labelMultiply(ErG2.getP1() , ErG1.getP1()) + errorGens.append( propagatableerrorgen( 'A', [pVec1[1], pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0])) + pVec1 =acom(ErG2.getP1(), ErG2.getP2()) + pVec2 = labelMultiply(pVec1[1],ErG1.getP1()) + errorGens.append( propagatableerrorgen( 'A' ,[pVec2[1], ErG1.getP1()] , -1j*.5*wT*pVec1[0]*pVec2[0])) + pVec1=acom(ErG2.getP1(), ErG2.getP2()) + pVec2=labelMultiply(ErG1.getP1(),pVec1[1]) + errorGens.append( propagatableerrorgen( 'A', [ErG1.getP1() ,pVec2[1]],-1j*.5*wT*pVec1[0]*pVec2[0])) + + elif ErG1.getType() == 'C' and ErG2.getType() == 'S': + errorGens = commute_errors(ErG2,ErG1,weightFlip=-1.0,BCHweight=BCHweight) + + elif ErG1.getType() == 'S' and ErG2.getType() == 'A': + pVec1 =labelMultiply(ErG1.getP1() , ErG2.getP1()) + pVec2=labelMultiply(ErG2.getP2() , ErG1.getP1()) + errorGens.append( propagatableerrorgen( 'C', [pVec1[1], pVec2[1]] ,1j*wT*pVec1[0]*pVec2[0] )) + pVec1=labelMultiply(ErG1.getP1() , ErG2.getP2()) + pVec2=labelMultiply(ErG2.getP1() , ErG1.getP1()) + errorGens.append( propagatableerrorgen( 'C', [pVec1[1], pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0])) + pVec1 = com(ErG2.getP1() , ErG2.getP2()) + pVec2 = com(ErG1.getP1(),pVec1[1]) + errorGens.append( propagatableerrorgen( 'A', [ErG1.getP1(), pVec2[1]] ,-.5*wT*pVec1[0]*pVec2[0])) + + elif ErG1.getType() == 'A' and ErG1.getType() == 'S': + errorGens = commute_errors(ErG2,ErG1,weightFlip=-1.0,BCHweight=BCHweight) + + elif ErG1.getType() == 'C' and ErG2.getType() == 'C': + A=ErG1.getP1() + B=ErG1.getP2() + P=ErG2.getP1() + Q=ErG2.getP2() + pVec1 = labelMultiply(A,P) + pVec2 =labelMultiply(Q,B) + errorGens.append( propagatableerrorgen( 'A', [pVec1[1], pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0] )) + pVec1 = labelMultiply(A,Q) + pVec2 =labelMultiply(P,B) + errorGens.append( propagatableerrorgen( 'A' , [pVec1[1] , pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0] )) + pVec1 = labelMultiply(B,P) + pVec2 =labelMultiply(Q,A) + errorGens.append( propagatableerrorgen( 'A' , [pVec1[1] , pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0] )) + pVec1 = labelMultiply(B,Q) + pVec2 =labelMultiply(P,A) + errorGens.append( propagatableerrorgen( 'A' , [pVec1[1] , pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0])) + pVec1=acom(A,B) + pVec2=com(P,pVec1[1]) + errorGens.append( propagatableerrorgen( 'A' , [pVec2[1] , Q ], -.5*1j*wT*pVec1[0]*pVec2[0])) + pVec1=acom(A,B) + pVec2=com(Q,pVec1[1]) + errorGens.append( propagatableerrorgen( 'A' , [pVec2[1], P] , -.5*1j*wT*pVec1[0]*pVec2[0])) + pVec1=acom(P,Q) + pVec2=com(pVec1[1],A) + errorGens.append( propagatableerrorgen( 'A' , [pVec2[1] , B] , -.5*1j*wT*pVec1[0]*pVec2[0])) + pVec1=acom(P,Q) + pVec2=com(pVec1[1],B) + errorGens.append( propagatableerrorgen( 'A' , [pVec2[1] , A ] , -.5*1j*wT*pVec1[0]*pVec2[0])) + pVec1=acom(A,B) + pVec2=acom(P,Q) + pVec3=com(pVec1[1],pVec2[1]) + errorGens.append( propagatableerrorgen( 'H', [pVec3[1]] ,.25*1j*wT*pVec1[0]*pVec2[0]*pVec3[0])) + + elif ErG1.getType() == 'C' and ErG2.getType() == 'A': + A=ErG1.getP1() + B=ErG1.getP2() + P=ErG2.getP1() + Q=ErG2.getP2() + pVec1 = labelMultiply(A,P) + pVec2 =labelMultiply(Q,B) + errorGens.append( propagatableerrorgen('C' , [pVec1[1],pVec2[1]] , 1j*wT*pVec1[0]*pVec2[0])) + pVec1 = labelMultiply(A,Q) + pVec2 =labelMultiply(P,B) + errorGens.append( propagatableerrorgen('C' ,[pVec1[1],pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0])) + pVec1 = labelMultiply(B,P) + pVec2 =labelMultiply(Q,A) + errorGens.append( propagatableerrorgen('C' , [pVec1[1],pVec2[1]] , 1j*wT*pVec1[0]*pVec2[0])) + pVec1 = labelMultiply(P,A) + pVec2 =labelMultiply(B,Q) + errorGens.append( propagatableerrorgen('C' ,[pVec1[1],pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0])) + pVec1 = com(P,Q) + pVec2 =com(A,pVec1[1]) + errorGens.append( propagatableerrorgen('A' , [pVec2[1] , B] , .5*wT*pVec1[0]*pVec2[0] )) + pVec1 = com(P,Q) + pVec2 =com(B,pVec1[1]) + errorGens.append( propagatableerrorgen('A' , [pVec2[1], A ], .5*wT*pVec1[0]*pVec2[0] )) + pVec1 = acom(A,B) + pVec2 =com(P,pVec1[1]) + errorGens.append( propagatableerrorgen('C', [pVec2[1] , Q ], .5*1j*wT*pVec1[0]*pVec2[0] )) + pVec1 = acom(A,B) + pVec2 =com(Q,pVec1[1]) + errorGens.append( propagatableerrorgen('C',[pVec2[1],P ],-.5*1j*wT*pVec1[0]*pVec2[0] )) + pVec1 = com(P,Q) + pVec2 =acom(A,B) + pVec3=com(pVec1[1],pVec2[1]) + errorGens.append( propagatableerrorgen('H',[pVec3[1]],-.25*wT*pVec1[0]*pVec2[0]*pVec3[0])) + + elif ErG1.getType() == 'A' and ErG2.getType() == 'C': + errorGens = commute_errors(ErG2,ErG1,weightFlip=-1.0,BCHweight=BCHweight) + + elif ErG1.getType() == 'A' and ErG2.getType() == 'A': + A=ErG1.getP1() + B=ErG1.getP2() + P=ErG2.getP1() + Q=ErG2.getP2() + pVec1=labelMultiply(Q,B) + pVec2=labelMultiply(A,P) + errorGens.append(propagatableerrorgen('A',[pVec1[1],pVec2[1]] ,-1j*wT*pVec1[0]*pVec2[0])) + pVec1=labelMultiply(P,A) + pVec2=labelMultiply(B,Q) + errorGens.append(propagatableerrorgen('A',[pVec1[1],pVec2[1]],-1j*wT*pVec1[0]*pVec2[0])) + pVec1=labelMultiply(B,P) + pVec2=labelMultiply(Q,A) + errorGens.append(propagatableerrorgen('A',[pVec1[1],pVec2[1]],-1j*wT*pVec1[0]*pVec2[0])) + pVec1=labelMultiply(A,Q) + pVec2=labelMultiply(P,B) + errorGens.append(propagatableerrorgen('A',[pVec1[1],pVec2[1]],-1j*wT*pVec1[0]*pVec2[0])) + pVec1=com(P,Q) + pVec2=com(B,pVec1[1]) + errorGens.append(propagatableerrorgen('C',[pVec2[1],A],.5*wT*pVec1[0]*pVec2[0])) + pVec1=com(P,Q) + pVec2=com(A,pVec1[1]) + errorGens.append(propagatableerrorgen('C',[pVec2[1],B] ,-.5*wT*pVec1[0]*pVec2[0])) + pVec1=com(A,B) + pVec2=com(P,pVec1[1]) + errorGens.append(propagatableerrorgen('C', [pVec2[1],Q] ,.5*wT*pVec1[0]*pVec2[0])) + pVec1=com(A,B) + pVec2=com(Q,pVec1[1]) + errorGens.append(propagatableerrorgen('C', [pVec2[1],P] ,-.5*wT*pVec1[0]*pVec2[0])) + pVec1=com(P,Q) + pVec2=com(A,B) + pVec3=com(pVec1[1],pVec2[1]) + errorGens.append( propagatableerrorgen('H',[pVec3[1]] ,.25*wT*pVec1[0]*pVec2[0]*pVec3[0])) + + + return errorGens + + diff --git a/pygsti/propErrorGens/pyGSTiStimTranslator.py b/pygsti/propErrorGens/pyGSTiStimTranslator.py new file mode 100644 index 000000000..039ada10c --- /dev/null +++ b/pygsti/propErrorGens/pyGSTiStimTranslator.py @@ -0,0 +1,65 @@ +import stim + + + +''' +returns a dictionary capable of translating pygsti standard gate labels to stim tablue representations of gates +''' +def Gate_Translate_Dict_p_2_s(): + pyGSTi_to_stim_GateDict={ + 'Gi' : stim.Tableau.from_named_gate('I'), + 'Gxpi' : stim.Tableau.from_named_gate('X'), + 'Gypi' : stim.Tableau.from_named_gate('Y'), + 'Gzpi' : stim.Tableau.from_named_gate('Z'), + 'Gxpi2' : stim.Tableau.from_named_gate('SQRT_X'), + 'Gypi2' : stim.Tableau.from_named_gate('SQRT_Y'), + 'Gzpi2' : stim.Tableau.from_named_gate('SQRT_Z'), + 'Gxmpi2': stim.Tableau.from_named_gate('SQRT_X_DAG'), + 'Gympi2': stim.Tableau.from_named_gate('SQRT_Y_DAG'), + 'Gzmpi2': stim.Tableau.from_named_gate('SQRT_Z_DAG'), + 'Gh' : stim.Tableau.from_named_gate('H'), + 'Gxx' : stim.Tableau.from_named_gate('SQRT_XX'), + 'Gzz' : stim.Tableau.from_named_gate('SQRT_ZZ'), + 'Gcnot' : stim.Tableau.from_named_gate('CNOT'), + 'Gswap' : stim.Tableau.from_named_gate('SWAP') + } + return pyGSTi_to_stim_GateDict + + +''' +returns a dict translating the stim tableu (gate) key to pyGSTi gate keys +TODO: change the stim tablues to tablues keys +''' +def Gate_Translate_Dict_s_2_p(): + dict = Gate_Translate_Dict_p_2_s() + return {v: k for k, v in dict.items()} + +''' +Takes a layer of pyGSTi gates and composes them into a single stim Tableu +''' +def pyGSTiLayer_to_stimLayer(player,qubits,MultiGateDict={},MultiGate=False): + slayer=stim.Tableau(qubits) + started = False + stimDict=Gate_Translate_Dict_p_2_s() + for sub_lbl in player: + if not MultiGate: + temp = stimDict[sub_lbl.name] + else: + temp = stimDict[MultiGateDict[sub_lbl.name]] + slayer.append(temp,sub_lbl.qubits) + return slayer + +''' +Takes the typical pygsti label for paulis and returns a stim PauliString object +''' +def pyGSTiPauli_2_stimPauli(pauli): + return stim.PauliString(pauli) + + +''' +Converts a stim paulistring to the string typically used in pysti to label paulis +warning: stim ofter stores a pauli phase in the string (i.e +1,-1,+i,-i) this is assumed positive +in this function, if the weight is needed please store paulistring::weight prior to applying this function +''' +def stimPauli_2_pyGSTiPauli(pauliString): + return str(pauliString)[1:].replace('_',"I") \ No newline at end of file From fdd689d36c2261884b4cbca09ffe206cef5a4d58 Mon Sep 17 00:00:00 2001 From: ashenmill <156946147+ashenmill@users.noreply.github.com> Date: Mon, 18 Mar 2024 15:17:37 -0600 Subject: [PATCH 02/11] stim integration to pygsti Moved some files around, integrated stim translations into pyGSTi, Added a tutorial notebook to examples --- .../Propagatable error gens tutorial.ipynb | 167 ++++++++++++++++++ pygsti/circuits/circuit.py | 53 ++++++ .../errorgenpropagation/errorpropagator.py} | 18 +- .../propagatableerrorgen.py | 4 +- .../utilspygstistimtranslator.py} | 1 - pygsti/tools/internalgates.py | 30 ++++ 6 files changed, 262 insertions(+), 11 deletions(-) create mode 100644 jupyter_notebooks/Examples/Propagatable error gens tutorial.ipynb rename pygsti/{propErrorGens/ErrorPropagator.py => extras/errorgenpropagation/errorpropagator.py} (94%) rename pygsti/{propErrorGens => extras/errorgenpropagation}/propagatableerrorgen.py (98%) rename pygsti/{propErrorGens/pyGSTiStimTranslator.py => extras/errorgenpropagation/utilspygstistimtranslator.py} (99%) diff --git a/jupyter_notebooks/Examples/Propagatable error gens tutorial.ipynb b/jupyter_notebooks/Examples/Propagatable error gens tutorial.ipynb new file mode 100644 index 000000000..7489126d9 --- /dev/null +++ b/jupyter_notebooks/Examples/Propagatable error gens tutorial.ipynb @@ -0,0 +1,167 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from pygsti.extras.errorgenpropagation.propagatableerrorgen import *\n", + "from pygsti.extras.errorgenpropagation.errorpropagator import *\n", + "from pygsti.circuits import Circuit\n", + "import numpy as np\n", + "import pygsti.processors\n", + "import pygsti\n", + "import pygsti.tools.lindbladtools as _lt\n", + "import scipy\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Introduction to the Propagatable Error Generators Code" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Defining a circuit and error generators" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Currently Error Propgagation works for any model that meets three criteria\n", + "\n", + " 1. The circuit is clifford\n", + " 2. The errors on each gate can be defined at a time t of interest in the small markovian errors basis\n", + " 3. The error error model is defined such that a gate G has some linear combination of error generators following it\n", + "\n", + "We can therefore, start a code by defining a circuit and an error model by simply following the common pyGSTi notation" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "errorModel={\n", + " 'Gxpi2' : {('H','Y'):.01}\n", + "\n", + "}\n", + "c=Circuit(10*[('Gxpi2',0)])" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can take the above definitions and plug them into the errorpropagator function, to get out a list of post-circuit error generators out." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "errors=ErrorPropagator(c,errorModel,BCHOrder=1,BCHLayerwise=False,NonMarkovian=False)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here BCH order determines the to what order the BCH order will be taken to (if applicable). BCHLayerwise will if false, propagatate all errors to the end before taking the BCH expansion, otherwise it will push the errorgens through a layer and combine with the the error generators for that layer by the rules given by the BCHOrder. Non-markovian prevents any simplification or BCH expansions being taken, instead allowing the output to be a list a lists, where the each sublist denotes the errorgenerators that were occuring at time t in the circuit." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Additionally, if you want to describe a gate with multiple associated error definitions you can define it as follows." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('H', ('X',), (0.09999999999999999+0j))]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "MultiGateDict={'Gxpi22' : 'Gxpi2'}\n", + "errorModel={\n", + " 'Gxpi2' : {('H','Y'):.01},\n", + " 'Gxpi22' : {('H','X'):.01}\n", + "\n", + "}\n", + "c=Circuit(10*[('Gxpi2',0),('Gxpi22',0)])\n", + "\n", + "ErrorPropagator(c,errorModel,MultiGateDict=MultiGateDict, MultiGate=True)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the errors are propagated to the process matrix given by the end of circuit error generators is given by" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "expMat=np.zeros([4**len(c.line_labels),4**len(c.line_labels)],dtype=np.complex128)\n", + "for error in errors:\n", + " expMat +=error.toWeightedErrorBasisMatrix()\n", + "processMatrix = scipy.linalg.expm(expMat)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "PyGSTi_EOC", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/pygsti/circuits/circuit.py b/pygsti/circuits/circuit.py index 84d1e0a27..bdb5a88d3 100644 --- a/pygsti/circuits/circuit.py +++ b/pygsti/circuits/circuit.py @@ -3644,6 +3644,59 @@ def _write_q_circuit_tex(self, filename): # TODO f.write("\\end{document}") f.close() + + def convert_to_stim_tableau_layers(self,gate_name_conversions=None): + """ + Converts this circuit to a list of stim tableau layers + + Parameters + ---------- + gate_name_conversions : Dict + A map from pygsti gatenames to standard stim tableaus. If set to None a standard set of gate names is used + + Returns + ------- + A layer by layer list of stim tabluaes + """ + try: + import stim + except ImportError: + raise ImportError("Stim is required for this operation, and it does not appear to be installed.") + if gate_name_conversions is None: + gate_name_conversions = _itgs.standard_gatenames_stim_conversions() + + qubits=len(self.line_labels) + stim_layers=[] + for j in range(self.depth): + layer = self.layer(j) + stim_layer=stim.Tableau(qubits) + for sub_lbl in layer: + temp = gate_name_conversions[sub_lbl.name] + stim_layer.append(temp,sub_lbl.qubits) + stim_layers.append(stim_layer) + return stim_layers + + def convert_to_stim_tableau(self,gate_name_conversions=None): + """ + Converts this circuit to a stim tableu + + Parameters + ---------- + gate_name_conversions : Dict + A map from pygsti gatenames to standard stim tableaus. If set to None a standard set of gate names is used + + Returns + ------- + A single stim tableu representing the entire circuit + """ + layers=self.convert_to_stim_tableau_layers(gate_name_conversions) + tableu=layers.pop(0) + for layer in layers: + tableu=tableu*layer + return tableu + + + def convert_to_cirq(self, qubit_conversion, wait_duration=None, diff --git a/pygsti/propErrorGens/ErrorPropagator.py b/pygsti/extras/errorgenpropagation/errorpropagator.py similarity index 94% rename from pygsti/propErrorGens/ErrorPropagator.py rename to pygsti/extras/errorgenpropagation/errorpropagator.py index 0ea24cdca..97a4f0f98 100644 --- a/pygsti/propErrorGens/ErrorPropagator.py +++ b/pygsti/extras/errorgenpropagation/errorpropagator.py @@ -1,9 +1,10 @@ import stim -from pygsti.propErrorGens.propagatableerrorgen import * -from pygsti.propErrorGens.pyGSTiStimTranslator import * +from pygsti.extras.errorgenpropagation.propagatableerrorgen import * +from pygsti.extras.errorgenpropagation.utilspygstistimtranslator import * from numpy import abs from numpy.linalg import multi_dot from scipy.linalg import expm +from pygsti.tools.internalgates import standard_gatenames_stim_conversions ''' @@ -22,12 +23,11 @@ returns: list of propagatableerrorgens ''' def ErrorPropagator(circ,errorModel,MultiGateDict={},BCHOrder=1,BCHLayerwise=False,NonMarkovian=False,MultiGate=False): - qubits=len(circ.line_labels) - stim_layers=[] - for j in range(circ.depth): - layer = circ.layer(j) - stim_layer=pyGSTiLayer_to_stimLayer(layer,qubits,MultiGateDict,MultiGate) - stim_layers.append(stim_layer) + stim_dict=standard_gatenames_stim_conversions() + if MultiGate: + for key in MultiGateDict: + stim_dict[key]=stim_dict[MultiGateDict[key]] + stim_layers=circ.convert_to_stim_tableau_layers(gate_name_conversions=stim_dict) stim_layers.pop(0) #Immeditielty toss the first layer because it is not important, propagation_layers=[] @@ -40,7 +40,7 @@ def ErrorPropagator(circ,errorModel,MultiGateDict={},BCHOrder=1,BCHLayerwise=Fal else: propagation_layers = stim_layers - errorLayers=buildErrorlayers(circ,errorModel,qubits) + errorLayers=buildErrorlayers(circ,errorModel,len(circ.line_labels)) num_error_layers=len(errorLayers) fully_propagated_layers=[] diff --git a/pygsti/propErrorGens/propagatableerrorgen.py b/pygsti/extras/errorgenpropagation/propagatableerrorgen.py similarity index 98% rename from pygsti/propErrorGens/propagatableerrorgen.py rename to pygsti/extras/errorgenpropagation/propagatableerrorgen.py index 1fa6febe2..7976b0f50 100644 --- a/pygsti/propErrorGens/propagatableerrorgen.py +++ b/pygsti/extras/errorgenpropagation/propagatableerrorgen.py @@ -1,5 +1,5 @@ from pygsti.baseobjs.errorgenlabel import ElementaryErrorgenLabel -from pygsti.propErrorGens.pyGSTiStimTranslator import * +from pygsti.extras.errorgenpropagation.utilspygstistimtranslator import * import stim from numpy import array,kron from pygsti.tools import change_basis @@ -7,6 +7,8 @@ ''' Similar to errorgenlabel but has an errorrate included as well as additional classes ''' +# Create a new pygsti-ish method where we use a modified dictionary and a modified local error generator where the keys are +# stim PauliStrings class propagatableerrorgen(ElementaryErrorgenLabel): ''' Labels an elementary errorgen by a type, pauli and error rate diff --git a/pygsti/propErrorGens/pyGSTiStimTranslator.py b/pygsti/extras/errorgenpropagation/utilspygstistimtranslator.py similarity index 99% rename from pygsti/propErrorGens/pyGSTiStimTranslator.py rename to pygsti/extras/errorgenpropagation/utilspygstistimtranslator.py index 039ada10c..6e33c1e99 100644 --- a/pygsti/propErrorGens/pyGSTiStimTranslator.py +++ b/pygsti/extras/errorgenpropagation/utilspygstistimtranslator.py @@ -39,7 +39,6 @@ def Gate_Translate_Dict_s_2_p(): ''' def pyGSTiLayer_to_stimLayer(player,qubits,MultiGateDict={},MultiGate=False): slayer=stim.Tableau(qubits) - started = False stimDict=Gate_Translate_Dict_p_2_s() for sub_lbl in player: if not MultiGate: diff --git a/pygsti/tools/internalgates.py b/pygsti/tools/internalgates.py index e3664f79c..91ac69567 100644 --- a/pygsti/tools/internalgates.py +++ b/pygsti/tools/internalgates.py @@ -315,7 +315,37 @@ def unitary_to_standard_gatename(unitary): if not callable(U) and not callable(unitary) and U.shape == unitary.shape and _np.allclose(unitary, U): return std_name return None +def standard_gatenames_stim_conversions(): + """ + A dictionary converting the gates with standard names to stim tableus for these gates. Currently is only capable of converting + clifford gates, no capability for T gates + Returns + ------- + A dict mapping string to tableu + """ + try: + import stim + except ImportError: + raise ImportError("Stim is required for this operation, and it does not appear to be installed.") + pyGSTi_to_stim_GateDict={ + 'Gi' : stim.Tableau.from_named_gate('I'), + 'Gxpi' : stim.Tableau.from_named_gate('X'), + 'Gypi' : stim.Tableau.from_named_gate('Y'), + 'Gzpi' : stim.Tableau.from_named_gate('Z'), + 'Gxpi2' : stim.Tableau.from_named_gate('SQRT_X'), + 'Gypi2' : stim.Tableau.from_named_gate('SQRT_Y'), + 'Gzpi2' : stim.Tableau.from_named_gate('SQRT_Z'), + 'Gxmpi2': stim.Tableau.from_named_gate('SQRT_X_DAG'), + 'Gympi2': stim.Tableau.from_named_gate('SQRT_Y_DAG'), + 'Gzmpi2': stim.Tableau.from_named_gate('SQRT_Z_DAG'), + 'Gh' : stim.Tableau.from_named_gate('H'), + 'Gxx' : stim.Tableau.from_named_gate('SQRT_XX'), + 'Gzz' : stim.Tableau.from_named_gate('SQRT_ZZ'), + 'Gcnot' : stim.Tableau.from_named_gate('CNOT'), + 'Gswap' : stim.Tableau.from_named_gate('SWAP') + } + return pyGSTi_to_stim_GateDict def standard_gatenames_cirq_conversions(): """ From 9faef8aad145898a14404f4c795dc23aea2012ac Mon Sep 17 00:00:00 2001 From: ashenmill <156946147+ashenmill@users.noreply.github.com> Date: Wed, 20 Mar 2024 10:30:24 -0600 Subject: [PATCH 03/11] Added Single Error Per Gate non Markovianity --- .../errorgenpropagation/errorpropagator.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pygsti/extras/errorgenpropagation/errorpropagator.py b/pygsti/extras/errorgenpropagation/errorpropagator.py index 97a4f0f98..56788b0d9 100644 --- a/pygsti/extras/errorgenpropagation/errorpropagator.py +++ b/pygsti/extras/errorgenpropagation/errorpropagator.py @@ -213,14 +213,16 @@ def buildErrorlayers(circ,errorDict,qubits): def nm_propagators(corr, Elist): Kms = [] for idm in range(len(Elist)): - Am = Elist[idm].toWeightedErrorBasisMatrix - # This assumes that Elist is in reverse chronological order - partials = [] - for idn in range(idm, len(Elist)): - An = Elist[idn].toWeightedErrorBasisMatrix() - partials += [corr[idm,idn] * Am @ An] - partials[0] = partials[0]/2 - Kms += [sum(partials,0)] + for idmm in range(len(Elist[idm][0])): + Am = Elist[idm][0][idmm].toWeightedErrorBasisMatrix() + # This assumes that Elist is in reverse chronological order + partials = [] + for idn in range(idm, len(Elist)): + for idnn in range(len(Elist[idn][0])): + An = Elist[idn][0][idnn].toWeightedErrorBasisMatrix() + partials += [corr[idm,idn] * Am @ An] + partials[0] = partials[0]/2 + Kms += [sum(partials,0)] return Kms def averaged_evolution(corr, Elist): From 3f5d569b84b57758034579845394e42ef1782f67 Mon Sep 17 00:00:00 2001 From: ashenmill <156946147+ashenmill@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:32:03 -0600 Subject: [PATCH 04/11] Fixed a bug in the non-markovianity code, added tutorial --- .../Propagatable error gens tutorial.ipynb | 35 +++++++++++++++---- .../errorgenpropagation/errorpropagator.py | 27 +++++++------- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/jupyter_notebooks/Examples/Propagatable error gens tutorial.ipynb b/jupyter_notebooks/Examples/Propagatable error gens tutorial.ipynb index 7489126d9..bd8342532 100644 --- a/jupyter_notebooks/Examples/Propagatable error gens tutorial.ipynb +++ b/jupyter_notebooks/Examples/Propagatable error gens tutorial.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -49,7 +49,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -70,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -95,7 +95,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -104,7 +104,7 @@ "[('H', ('X',), (0.09999999999999999+0j))]" ] }, - "execution_count": 4, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -131,7 +131,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -140,6 +140,29 @@ " expMat +=error.toWeightedErrorBasisMatrix()\n", "processMatrix = scipy.linalg.expm(expMat)" ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Non-Markovianity" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you want to use the non markovianity function you need to define an n x n correlation where n is the number of layers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/pygsti/extras/errorgenpropagation/errorpropagator.py b/pygsti/extras/errorgenpropagation/errorpropagator.py index 56788b0d9..9b41ba89f 100644 --- a/pygsti/extras/errorgenpropagation/errorpropagator.py +++ b/pygsti/extras/errorgenpropagation/errorpropagator.py @@ -1,7 +1,8 @@ import stim from pygsti.extras.errorgenpropagation.propagatableerrorgen import * from pygsti.extras.errorgenpropagation.utilspygstistimtranslator import * -from numpy import abs +from numpy import abs,zeros, complex128 + from numpy.linalg import multi_dot from scipy.linalg import expm from pygsti.tools.internalgates import standard_gatenames_stim_conversions @@ -210,21 +211,23 @@ def buildErrorlayers(circ,errorDict,qubits): # There's a factor of a half missing in here. -def nm_propagators(corr, Elist): +def nm_propagators(corr, Elist,qubits): Kms = [] for idm in range(len(Elist)): + Am=zeros([4**qubits,4**qubits],dtype=complex128) for idmm in range(len(Elist[idm][0])): - Am = Elist[idm][0][idmm].toWeightedErrorBasisMatrix() + Am += Elist[idm][0][idmm].toWeightedErrorBasisMatrix() # This assumes that Elist is in reverse chronological order - partials = [] - for idn in range(idm, len(Elist)): - for idnn in range(len(Elist[idn][0])): - An = Elist[idn][0][idnn].toWeightedErrorBasisMatrix() - partials += [corr[idm,idn] * Am @ An] - partials[0] = partials[0]/2 - Kms += [sum(partials,0)] + partials = [] + for idn in range(idm, len(Elist)): + An=zeros([4**qubits,4**qubits],dtype=complex128) + for idnn in range(len(Elist[idn][0])): + An = Elist[idn][0][idnn].toWeightedErrorBasisMatrix() + partials += [corr[idm,idn] * Am @ An] + partials[0] = partials[0]/2 + Kms += [sum(partials,0)] return Kms -def averaged_evolution(corr, Elist): - Kms = nm_propagators(corr, Elist) +def averaged_evolution(corr, Elist,qubits): + Kms = nm_propagators(corr, Elist,qubits) return multi_dot([expm(Km) for Km in Kms]) \ No newline at end of file From 582787d4cfb10343b4e591b8ab159af940d1eb5e Mon Sep 17 00:00:00 2001 From: ashenmill <156946147+ashenmill@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:45:56 -0600 Subject: [PATCH 05/11] added layer defined errors --- pygsti/extras/errorgenpropagation/errorpropagator.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pygsti/extras/errorgenpropagation/errorpropagator.py b/pygsti/extras/errorgenpropagation/errorpropagator.py index 9b41ba89f..d8134540c 100644 --- a/pygsti/extras/errorgenpropagation/errorpropagator.py +++ b/pygsti/extras/errorgenpropagation/errorpropagator.py @@ -23,7 +23,7 @@ MultiGate: lets the code know returns: list of propagatableerrorgens ''' -def ErrorPropagator(circ,errorModel,MultiGateDict={},BCHOrder=1,BCHLayerwise=False,NonMarkovian=False,MultiGate=False): +def ErrorPropagator(circ,errorModel,MultiGateDict={},BCHOrder=1,BCHLayerwise=False,NonMarkovian=False,MultiGate=False,ErrorLayerDef=False): stim_dict=standard_gatenames_stim_conversions() if MultiGate: for key in MultiGateDict: @@ -41,7 +41,10 @@ def ErrorPropagator(circ,errorModel,MultiGateDict={},BCHOrder=1,BCHLayerwise=Fal else: propagation_layers = stim_layers - errorLayers=buildErrorlayers(circ,errorModel,len(circ.line_labels)) + if not ErrorLayerDef: + errorLayers=buildErrorlayers(circ,errorModel,len(circ.line_labels)) + else: + errorLayers=[[errorModel]]*circ.depth num_error_layers=len(errorLayers) fully_propagated_layers=[] @@ -206,6 +209,7 @@ def buildErrorlayers(circ,errorDict,qubits): paulis.append(p2) errorLayer.append(propagatableerrorgen(errType,paulis,gErrorDict[errs])) ErrorGens.append([errorLayer]) + print(ErrorGens) return ErrorGens From b4a1b6633940533f07c827bb1ddee8c3d322776c Mon Sep 17 00:00:00 2001 From: ashenmill <156946147+ashenmill@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:23:01 -0600 Subject: [PATCH 06/11] Started refactor to use dictionaries --- .../Propagatable error gens tutorial.ipynb | 109 ++++++++-- .../errordict_deprecated.py | 10 + .../errorgenpropagation/errorpropagator.py | 4 +- .../errorpropagator_dev.py | 172 +++++++++++++++ .../errorgenpropagation/localstimerrorgen.py | 96 +++++++++ .../propagatableerrorgen.py | 15 ++ .../utilserrorgenpropagation.py | 195 ++++++++++++++++++ .../utilspygstistimtranslator.py | 2 + pygsti/tools/internalgates.py | 2 + 9 files changed, 591 insertions(+), 14 deletions(-) create mode 100644 pygsti/extras/errorgenpropagation/errordict_deprecated.py create mode 100644 pygsti/extras/errorgenpropagation/errorpropagator_dev.py create mode 100644 pygsti/extras/errorgenpropagation/localstimerrorgen.py create mode 100644 pygsti/extras/errorgenpropagation/utilserrorgenpropagation.py diff --git a/jupyter_notebooks/Examples/Propagatable error gens tutorial.ipynb b/jupyter_notebooks/Examples/Propagatable error gens tutorial.ipynb index bd8342532..47f95861c 100644 --- a/jupyter_notebooks/Examples/Propagatable error gens tutorial.ipynb +++ b/jupyter_notebooks/Examples/Propagatable error gens tutorial.ipynb @@ -2,10 +2,21 @@ "cells": [ { "cell_type": "code", - "execution_count": 7, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", "from pygsti.extras.errorgenpropagation.propagatableerrorgen import *\n", "from pygsti.extras.errorgenpropagation.errorpropagator import *\n", "from pygsti.circuits import Circuit\n", @@ -49,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -70,9 +81,26 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[[('H', ('Y',), 0.01)]]]\n", + "[[[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]]]\n", + "[[[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]]]\n", + "[[[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]]]\n", + "[[[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]]]\n", + "[[[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]]]\n", + "[[[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]]]\n", + "[[[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]]]\n", + "[[[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]]]\n", + "[[[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]], [[('H', ('Y',), 0.01)]]]\n" + ] + } + ], "source": [ "errors=ErrorPropagator(c,errorModel,BCHOrder=1,BCHLayerwise=False,NonMarkovian=False)" ] @@ -95,7 +123,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -104,7 +132,7 @@ "[('H', ('X',), (0.09999999999999999+0j))]" ] }, - "execution_count": 10, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -131,7 +159,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -154,15 +182,72 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If you want to use the non markovianity function you need to define an n x n correlation where n is the number of layers." + "If you want to use the non markovianity function you need to define an n x n correlation where n is the number of layers. Currently, we are capable of describing each layer to be governed by some stochastic process, that is correlated to the other layers. To using the code is relatively simple, see the below example" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'White noise dephasing')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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+kB1zZs3j0aNGpEnTx7Onz9vf4SEhBAfH8+aNWvu+xkVK1akUaNG9tcFChSgXLlyDjUtXbqUunXrOnynXLly8cILL3D8+HH27duX5Gdnz54dT09PVq1axT///JPkMeHh4URHR9OlSxeH7+Du7k5QUFCSI+DutHTpUurVq0fdunUdvkPXrl1TfJ47pxG4Na3A9evX+fXXXx2O69SpE3ny5LG/vnU977yGd7Y4Xbt2jfPnz1OvXj0Ah9tVuXPnZtOmTZw+ffqe3zsp96vj9OnT7N69m+7du5MrVy77cU2aNKFKlSpOn0/ECrqlJZKGbDYbwcHBrFmzhoSEBNavX0/BggUpU6YMYAaeiRMnAtiDT1KBp3jx4om25cmT565h4H4OHz7Mrl27KFCgQJL7o6Ki7vsZyanpzz//JCgoKNFxFSpUsO+vXLlyov1eXl6MHj2aV199FX9/f+rVq8djjz1G9+7dKVSokP07ADRr1izJ+nx9fe9Z/91qK1eunMNrZ8/j5uZGqVKlHLaVLVsWIFH/o39fw1uh485reOHCBd5//31mz56d6M/l4sWL9udjxowhLCyMgIAAatWqRatWrejevXuiWpJyvzr+/PNPAPu/t3cqU6aMQ/ASyagUeETSWMOGDfnxxx/ZvXu3vf/OLcHBwbz++uucOnWKdevWUaRIkSR/oO42+sq4S0fY+0lISKBFixa88cYbSe6/9QN9L6ld07/179+fNm3asGjRIn7++WeGDBnCqFGjWLFiBTVq1LB3Lv7222/tIehOHh6p89dbWp4nOdfwySefZMOGDbz++utUr16dXLlykZCQwKOPPurQwfrJJ5+kUaNGLFy4kF9++YWxY8cyevRoFixYQMuWLVNch0hmp8AjksbunI9n/fr19O/f376vVq1aeHl5sWrVKjZt2kSrVq1S9dy3OrT+W+nSpbl8+TIhISGper5/K1GiRJLzvhw4cMC+/15Kly7Nq6++yquvvsrhw4epXr0648eP57vvvrPf4itYsOADfY8SJUrYW2/u9O96nT1PQkICR48edQiNhw4dAnB6NNM///xDREQE77//Pu+++659e1J1AxQuXJiXX36Zl19+maioKGrWrMmIESPuG3ju59af0x9//JFoX1LbRDIi9eERSWO1a9fG29ubmTNncurUKYcWHi8vL2rWrMmkSZO4cuXKA82/cy85c+YkOjo60fYnn3ySjRs38vPPPyfaFx0dzc2bN1Pl/K1atWLz5s1s3LjRvu3KlStMnTqVwMBAKlasmOT7YmNjuXbtmsO20qVL4+PjQ1xcHAChoaH4+voycuRIbty4kegzzp07d9/afvvtNzZv3uzwnpkzZzoc9yDnuXWbEsxWkokTJ5ItWzaaN29+z5r+7VbLy79bWv49ii4+Pt7h9haYAa1IkSL265USRYoUoXLlynzzzTdcvnzZvn316tXs3r07xZ8vkh7UwiOSxjw9PalTpw5r167Fy8uLWrVqOewPDg5m/PjxwINNOHgvtWrVYvLkyQwfPpwyZcpQsGBBmjVrxuuvv84PP/zAY489Zh9KfuXKFXbv3s38+fM5fvw4+fPnT/H5Bw0axPfff0/Lli155ZVXyJs3LzNmzODYsWP873//w80t6f/nOnToEM2bN+fJJ5+kYsWKeHh4sHDhQs6ePUvnzp0Bs+/M5MmT6datGzVr1qRz584UKFCAEydOsGTJEho0aOAQPP7tjTfe4Ntvv+XRRx+lX79+9mHpJUqUYNeuXfbjnD2Pt7c3y5cvJywsjKCgIJYtW8aSJUt466237tpn6m58fX1p3LgxY8aM4caNGxQtWpRffvmFY8eOORx36dIlihUrxhNPPEG1atXIlSsXv/76K1u2bLH/u5VSI0eOpG3btjRo0ICePXvyzz//MHHiRCpXruwQgkQyLCuHiIlkFYMHDzYAIzg4ONG+BQsWGIDh4+Nj3Lx5M9F+IMnhxiVKlDDCwsLsr5Malh4ZGWm0bt3a8PHxMQCHIeqXLl0yBg8ebJQpU8bw9PQ08ufPbwQHBxvjxo0zrl+/fs/vU6JECaN169aJtjdp0iTRMPgjR44YTzzxhJE7d27D29vbqFu3rvHTTz85HPPvYennz583evfubZQvX97ImTOn4efnZwQFBRlz585NdM6VK1caoaGhhp+fn+Ht7W2ULl3a6NGjh/H777/f8zsYhmHs2rXLaNKkieHt7W0ULVrU+OCDD4wvv/wy0XVM7nnCwsKMnDlzGkeOHDEeeeQRI0eOHIa/v78xdOhQ+xD8O79vUsPuAWPo0KH213/99ZfRvn17I3fu3Iafn5/RsWNH4/Tp0w7HxcXFGa+//rpRrVo1w8fHx8iZM6dRrVo147PPPnP47LsNS09OHYZhGLNnzzbKly9veHl5GZUrVzZ++OEHo0OHDkb58uXvc6VFrGczDPVKExFJDT169GD+/PlZqsWjevXqFChQgPDwcKtLEbkn9eEREZH7unHjRqK+XatWrWLnzp0OS5aIZFTqwyMiIvd16tQpQkJCePrppylSpAgHDhxgypQpFCpUiF69elldnsh9KfCIiMh95cmTh1q1avHFF19w7tw5cubMSevWrfnwww/vup6bSEaiPjwiIiLi8tSHR0RERFyeAo+IiIi4vCzXhychIYHTp0/j4+Nz12n3RUREJGMxDINLly5RpEiRu05aei9ZLvCcPn2agIAAq8sQERGRB3Dy5EmKFSvm9PuyXODx8fEBzAvm6+trcTUiIiKSHDExMQQEBNh/x52V5QLPrdtYvr6+CjwiIiKZzIN2R1GnZREREXF5CjwiIiLi8hR4RERExOUp8IiIiIjLU+ARERERl6fAIyIiIi5PgUdERERcngKPiIiIuDwFHhEREXF5CjwiIiLi8hR4RERExOUp8IiIiIjLU+ARERERl6fAIyIiIi5PgUdERERcngKPiIiIuDwFHhEREXF5CjwiIiLi8hR4RERExOUp8IiIiIjLU+ARERERl6fAIyIiIi5PgUdERERcngKPiIiIuDwFHhEREXF5CjwiIiLi8iwNPGvWrKFNmzYUKVIEm83GokWL7vueVatWUbNmTby8vChTpgxff/11mtcpIiIimZulgefKlStUq1aNSZMmJev4Y8eO0bp1ax5++GF27NhB//79ee655/j555/TuFIRERHJzDysPHnLli1p2bJlso+fMmUKJUuWZPz48QBUqFCBdevW8fHHHxMaGppWZYqIiEgml6n68GzcuJGQkBCHbaGhoWzcuNGiikRERCQzsLSFx1mRkZH4+/s7bPP39ycmJoarV6+SPXv2RO+Ji4sjLi7O/jomJibN6xQREZGMJVO18DyIUaNG4efnZ38EBARYXZKIiIiks0wVeAoVKsTZs2cdtp09exZfX98kW3cABg8ezMWLF+2PkydPpkepIiIikoFkqlta9evXZ+nSpQ7bwsPDqV+//l3f4+XlhZeXV1qXJiIiIhmYpS08ly9fZseOHezYsQMwh53v2LGDEydOAGbrTPfu3e3H9+rVi6NHj/LGG29w4MABPvvsM+bOncuAAQOsKF9EREQyCUsDz++//06NGjWoUaMGAAMHDqRGjRq8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7LPPsn37dtq1a0e7du3Ys2dPOlcuIiIimYXNMAzDygKCgoKoU6cOEydOBCAhIYGAgAD69u3LoEGDEh3fqVMnrly5wk8//WTfVq9ePapXr86UKVPue76YmBj8/Py4ePEivr6+qfdFREREJM2k9Pfb0hae69evs3XrVkJCQuzb3NzcCAkJYePGjUm+Z+PGjQ7HA4SGht71eBEREREPK09+/vx54uPj8ff3d9ju7+/PgQMHknxPZGRkksdHRkYmeXxcXBxxcXH21xcvXgTMpCgiIiKZw63f7Qe9MWVp4EkPo0aN4v3330+0PSAgwIJqREREJCUuXbqEn5+f0++zNPDkz58fd3d3zp4967D97NmzFCpUKMn3FCpUyKnjBw8ezMCBA+2vExISuHDhAvny5cNmsyWrzpiYGAICAjh58qT6/aQzXXvr6NpbR9feOrr21rnftTcMg0uXLlGkSJEH+nxLA4+npye1atUiIiKCdu3aAWYgiYiIoE+fPkm+p379+kRERNC/f3/7tvDwcOrXr5/k8V5eXnh5eTlsy5079wPV6+vrq/8ALKJrbx1de+vo2ltH194697r2D9Kyc4vlt7QGDhxIWFgYtWvXpm7dukyYMIErV67Qs2dPALp3707RokUZNWoUAP369aNJkyaMHz+e1q1bM3v2bH7//XemTp1q5dcQERGRDMzywNOpUyfOnTvHu+++S2RkJNWrV2f58uX2jsknTpzAze32YLLg4GBmzZrFO++8w1tvvcVDDz3EokWLqFy5slVfQURERDI4ywMPQJ8+fe56C2vVqlWJtnXs2JGOHTumcVW3eXl5MXTo0ES3xiTt6dpbR9feOrr21tG1t05aX3vLJx4UERERSWuWLy0hIiIiktYUeERERMTlKfCIiIiIy1PgEREREZenwJMMkyZNIjAwEG9vb4KCgti8ebPVJbmcUaNGUadOHXx8fChYsCDt2rXj4MGDDsdcu3aN3r17ky9fPnLlykWHDh0SzbotKfPhhx9is9kcJvbUdU87p06d4umnnyZfvnxkz56dKlWq8Pvvv9v3G4bBu+++S+HChcmePTshISEcPnzYwopdQ3x8PEOGDKFkyZJkz56d0qVL88EHHzis0aRrnzrWrFlDmzZtKFKkCDabjUWLFjnsT851vnDhAl27dsXX15fcuXPz7LPPcvnyZadrUeC5jzlz5jBw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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "x_coherence = []\n", + "Ls = range(2,100,5)\n", + "for L in Ls:\n", + " c=Circuit((L)*[('Gi' ,0)])\n", + " ErrorDict={'Gi' : {('H','Z'): 1}}\n", + " EndErrors = ErrorPropagator(c,ErrorDict,NonMarkovian=True)\n", + "\n", + " corr=np.eye(len(c))*.01\n", + " error = averaged_evolution(corr,EndErrors,1)\n", + "\n", + " x_coherence += [np.real(error[1,1])]\n", + "plt.plot(Ls,x_coherence, color='blue')\n", + "plt.ylim(0,1.1)\n", + "plt.xlabel('Circuit Length')\n", + "plt.ylabel('X Coherence Decay')\n", + "plt.title('White noise dephasing')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[[('H', ('X',), (1+0j))]], [[('H', ('X',), (1+0j))]], [[('H', ('X',), (1+0j))]], [[('H', ('X',), (1+0j))]], [[('H', ('X',), (1+0j))]], [[('H', ('X',), (1+0j))]], [[('H', ('X',), (1+0j))]], [[('H', ('X',), (1+0j))]], [[('H', ('X',), (1+0j))]], [[('H', ('X',), (1+0j))]]]\n" + ] + } + ], + "source": [ + "list=[propagatableerrorgen('H',['X'],1)]\n", + "errors=ErrorPropagator(c,list,NonMarkovian=True,ErrorLayerDef=True)\n", + "print(errors)" + ] } ], "metadata": { diff --git a/pygsti/extras/errorgenpropagation/errordict_deprecated.py b/pygsti/extras/errorgenpropagation/errordict_deprecated.py new file mode 100644 index 000000000..95dadea0b --- /dev/null +++ b/pygsti/extras/errorgenpropagation/errordict_deprecated.py @@ -0,0 +1,10 @@ +from pygsti.extras.errorgenpropagation.propagatableerrorgen import propagatableerrorgen +from numpy import complex128 + +class errordict(dict): + + def __setitem__(self, __key: any, __value: any) -> None: + if __key in self : + super().__setitem__(__key,self[__key]+__value) + else: + super().__setitem__(__key,__value) \ No newline at end of file diff --git a/pygsti/extras/errorgenpropagation/errorpropagator.py b/pygsti/extras/errorgenpropagation/errorpropagator.py index d8134540c..2fe015823 100644 --- a/pygsti/extras/errorgenpropagation/errorpropagator.py +++ b/pygsti/extras/errorgenpropagation/errorpropagator.py @@ -44,7 +44,7 @@ def ErrorPropagator(circ,errorModel,MultiGateDict={},BCHOrder=1,BCHLayerwise=Fal if not ErrorLayerDef: errorLayers=buildErrorlayers(circ,errorModel,len(circ.line_labels)) else: - errorLayers=[[errorModel]]*circ.depth + errorLayers=[[errorModel]]*circ.depth #this doesn't work num_error_layers=len(errorLayers) fully_propagated_layers=[] @@ -209,7 +209,7 @@ def buildErrorlayers(circ,errorDict,qubits): paulis.append(p2) errorLayer.append(propagatableerrorgen(errType,paulis,gErrorDict[errs])) ErrorGens.append([errorLayer]) - print(ErrorGens) + return ErrorGens diff --git a/pygsti/extras/errorgenpropagation/errorpropagator_dev.py b/pygsti/extras/errorgenpropagation/errorpropagator_dev.py new file mode 100644 index 000000000..68916d815 --- /dev/null +++ b/pygsti/extras/errorgenpropagation/errorpropagator_dev.py @@ -0,0 +1,172 @@ +import stim +from localstimerrorgen import * +from numpy import abs,zeros, complex128 +from numpy.linalg import multi_dot +from scipy.linalg import expm +from pygsti.tools.internalgates import standard_gatenames_stim_conversions +from utilserrorgenpropagation import * + + +def ErrorPropagator(circ,errorModel,MultiGateDict=None,BCHOrder=1,BCHLayerwise=False,NonMarkovian=False,MultiGate=False,ErrorLayerDef=False): + if MultiGate and MultiGateDict is None: + MultiGateDict=dict() + stim_dict=standard_gatenames_stim_conversions() + if MultiGate: + for key in MultiGateDict: + stim_dict[key]=stim_dict[MultiGateDict[key]] + stim_layers=circ.convert_to_stim_tableau_layers(gate_name_conversions=stim_dict) + stim_layers.pop(0) #Immediatly toss the first layer because it is not important, + + propagation_layers=[] + if not BCHLayerwise or NonMarkovian: + while len(stim_layers) != 0: + top_layer=stim_layers.pop(0) + for layer in stim_layers: + top_layer = layer*top_layer + propagation_layers.append(top_layer) + else: + propagation_layers = stim_layers + + if not ErrorLayerDef: + errorLayers=buildErrorlayers(circ,errorModel,len(circ.line_labels)) + else: + errorLayers=[[errorModel]]*circ.depth #this doesn't work + + num_error_layers=len(errorLayers) + + fully_propagated_layers=[] + for _ in range(0,num_error_layers-1): + err_layer=errorLayers.pop(0) + layer=propagation_layers.pop(0) + new_error_layer=[] + for err_order in err_layer: + new_error_dict=dict() + for key in err_order: + propagated_error_gen=key.propagate_error_gen_tableau(layer,err_order[key]) + new_error_dict[propagated_error_gen[0]]=propagated_error_gen[1] + new_error_layer.append(new_error_dict) + if BCHLayerwise and not NonMarkovian: + following_layer = errorLayers.pop(0) + new_errors=BCH_Handler(err_layer,following_layer,BCHOrder) + errorLayers.insert(new_errors,0) + else: + fully_propagated_layers.append(new_error_layer) + + fully_propagated_layers.append(errorLayers.pop(0)) + if BCHLayerwise and not NonMarkovian: + final_error=dict() + for order in errorLayers[0]: + for error in order: + if error in final_error: + final_error[error]=final_error[error]+order[error] + else: + final_error[error]=order[error] + return final_error + + elif not BCHLayerwise and not NonMarkovian: + simplified_EOC_errors=dict() + if BCHOrder == 1: + for layer in fully_propagated_layers: + for order in layer: + for error in order: + if error in simplified_EOC_errors: + simplified_EOC_errors[error]=simplified_EOC_errors[error]+order[error] + else: + simplified_EOC_errors[error]=order[error] + + else: + Exception("Higher propagated through Errors are not Implemented Yet") + return simplified_EOC_errors + + else: + return fully_propagated_layers + + + +def buildErrorlayers(circ,errorDict,qubits): + ErrorGens=[] + #For the jth layer of each circuit + for j in range(circ.depth): + l = circ.layer(j) # get the layer + errorLayer=dict() + for _, g in enumerate(l): # for gate in layer l + gErrorDict = errorDict[g.name] #get the errors for the gate + p1=qubits*'I' # make some paulis why? + p2=qubits*'I' + for errs in gErrorDict: #for an error in the accompanying error dictionary + errType=errs[0] + paulis=[] + for ind,el in enumerate(g): #enumerate the gate ind =0 is name ind = 1 is first qubit ind = 2 is second qubit + if ind !=0: #if the gate element of concern is not the name + p1=p1[:el] + errs[1][ind-1] +p1[(el+1):] + + paulis.append(stim.PauliString(p1)) + if errType in "CA": + for ind,el in enumerate(g): + if ind !=0: + p2=p2[:el] + errs[2][ind-1] +p2[(el+1):] + paulis.append(stim.PauliString(p2)) + errorLayer[localstimerrorgen(errType,paulis)]=gErrorDict[errs] + ErrorGens.append([errorLayer]) + return ErrorGens +''' + +Inputs: +_______ +err_layer (list of dictionaries) +following_layer (list of dictionaries) +BCHOrder: + +''' +def BCH_Handler(err_layer,following_layer,BCHOrder): + new_errors=[] + for curr_order in range(0,BCHOrder): + working_order=dict() + #add first order terms into new layer + if curr_order == 0: + for error_key in err_layer[curr_order]: + working_order[error_key]=err_layer[curr_order][error_key] + for error_key in following_layer[curr_order]: + working_order[error_key]=following_layer[curr_order[error_key]] + new_errors.append(working_order) + + elif curr_order ==1: + working_order={} + for error1 in err_layer[curr_order-1]: + for error2 in following_layer[curr_order-1]: + errorlist = commute_errors(error1,error2,BCHweight=1/2*err_layer[error1]*following_layer[error2]) + for error_tuple in errorlist: + working_order[error_tuple[0]]=error_tuple[1] + if len(err_layer)==2: + for error_key in err_layer[1]: + working_order[error_key]=err_layer[1][error_key] + if len(following_layer)==2: + for error_key in following_layer[1]: + working_order[error_key]=following_layer[1][error_key] + new_errors.append(working_order) + + else: + Exception("Higher Orders are not Implemented Yet") + return new_errors + +# There's a factor of a half missing in here. +def nm_propagators(corr, Elist,qubits): + Kms = [] + for idm in range(len(Elist)): + Am=zeros([4**qubits,4**qubits],dtype=complex128) + for key in Elist[idm][0]: + Am += key.toWeightedErrorBasisMatrix() + # This assumes that Elist is in reverse chronological order + partials = [] + for idn in range(idm, len(Elist)): + An=zeros([4**qubits,4**qubits],dtype=complex128) + for key2 in Elist[idn][0]: + An = key2.toWeightedErrorBasisMatrix() + partials += [corr[idm,idn] * Am @ An] + partials[0] = partials[0]/2 + Kms += [sum(partials,0)] + return Kms + +def averaged_evolution(corr, Elist,qubits): + Kms = nm_propagators(corr, Elist,qubits) + return multi_dot([expm(Km) for Km in Kms]) \ No newline at end of file diff --git a/pygsti/extras/errorgenpropagation/localstimerrorgen.py b/pygsti/extras/errorgenpropagation/localstimerrorgen.py new file mode 100644 index 000000000..5d6b57c71 --- /dev/null +++ b/pygsti/extras/errorgenpropagation/localstimerrorgen.py @@ -0,0 +1,96 @@ +from pygsti.baseobjs.errorgenlabel import ElementaryErrorgenLabel +from pygsti.extras.errorgenpropagation.utilspygstistimtranslator import * +import stim +from numpy import array,kron +from pygsti.tools import change_basis +from pygsti.tools.lindbladtools import create_elementary_errorgen + +class localstimerrorgen(ElementaryErrorgenLabel): + + + ''' + Initiates the errorgen object + Inputs: + ______ + errorgen_type: characture can be set to 'H' Hamiltonian, 'S' Stochastic, 'C' Correlated or 'A' active following the conventions + of the taxonomy of small markovian errorgens paper + + basis_element_labels + + Outputs: + Null + ''' + def __init__(self,errorgen_type: str ,basis_element_labels: list): + self.errorgen_type=str(errorgen_type) + self.basis_element_labels=tuple(basis_element_labels) + + ''' + hashes the error gen object + ''' + def __hash__(self): + pauli_hashable=[] + for pauli in self.basis_element_labels: + pauli_hashable.append(str(pauli)) + return hash((self.errorgen_type,tuple(pauli_hashable))) + + def labels_to_strings(self): + strings=[] + for paulistring in self.basis_element_labels: + strings.append(str(paulistring)[1:].replace('_',"I")) + return tuple(strings) + + + ''' + checks and if two error gens have the same type and labels + ''' + def __eq__(self, other): + return (self.errorgen_type == other.errorgen_type + and self.basis_element_labels == other.basis_element_labels) + + ''' + displays the errorgens as strings + ''' + def __str__(self): + return self.errorgen_type + "(" + ",".join(map(str, self.basis_element_labels)) + ")" + + + def __repr__(self): + return str((self.errorgen_type, self.basis_element_labels)) + + ''' + Returns the errorbasis matrix for the associated errorgenerator mulitplied by its error rate + + input: A pygsti defined matrix basis by default can be pauli-product, gellmann 'gm' or then pygsti standard basis 'std' + functions defaults to pauli product if not specified + ''' + def toWeightedErrorBasisMatrix(self,weight=1.0,matrix_basis='pp'): + PauliDict={ + 'I' : array([[1.0,0.0],[0.0,1.0]]), + 'X' : array([[0.0j, 1.0+0.0j], [1.0+0.0j, 0.0j]]), + 'Y' : array([[0.0, -1.0j], [1.0j, 0.0]]), + 'Z' : array([[1.0, 0.0j], [0.0j, -1.0]]) + } + paulis=[] + for paulistring in self.basis_element_labels: + for idx,pauli in enumerate(paulistring): + if idx == 0: + pauliMat = PauliDict[pauli] + else: + pauliMat=kron(pauliMat,PauliDict[pauli]) + paulis.append(pauliMat) + if self.errorgen_type in 'HS': + return weight*change_basis(create_elementary_errorgen(self.errorgen_type,paulis[0]),'std',matrix_basis) + else: + return weight*change_basis(create_elementary_errorgen(self.errorgen_type,paulis[0],paulis[1]),'std',matrix_basis) + + def propagate_error_gen_tableau(self, slayer,weight): + new_basis_labels = [] + weightmod = 1 + for pauli in self.basis_element_labels: + temp = slayer(pauli) + weightmod=weightmod*temp.sign + temp=temp*temp.sign + new_basis_labels.append(temp) + + return (localstimerrorgen(self.errorgen_type,new_basis_labels),weightmod*weight) + diff --git a/pygsti/extras/errorgenpropagation/propagatableerrorgen.py b/pygsti/extras/errorgenpropagation/propagatableerrorgen.py index 7976b0f50..b9e71dbc5 100644 --- a/pygsti/extras/errorgenpropagation/propagatableerrorgen.py +++ b/pygsti/extras/errorgenpropagation/propagatableerrorgen.py @@ -89,6 +89,21 @@ def getP1(self): ''' def getP2(self): return self.basis_element_labels[1] + + def ErrorWeight(self): + def Weight(pauli): + weight=0 + for char in pauli: + if char is 'I': + continue + else: + weight+=1 + return weight + if len(self.basis_element_labels)==1 or Weight(self.basis_element_labels[0]) > Weight(self.basis_element_labels[1]): + return Weight(self.basis_element_labels[0]) + else: + return Weight(self.basis_element_labels[1]) + ''' propagates a propagatableerrorgen object through a clifford layer, returns the created error gen diff --git a/pygsti/extras/errorgenpropagation/utilserrorgenpropagation.py b/pygsti/extras/errorgenpropagation/utilserrorgenpropagation.py new file mode 100644 index 000000000..ca07bdb58 --- /dev/null +++ b/pygsti/extras/errorgenpropagation/utilserrorgenpropagation.py @@ -0,0 +1,195 @@ + +from pygsti.extras.errorgenpropagation.localstimerrorgen import localstimerrorgen +from numpy import conjugate + +''' +Returns the Commutator of two errors +''' +def commute_errors(ErG1,ErG2, weightFlip=1.0, BCHweight=1.0): + def com(P1,P2): + P3=P1*P2-P2*P1 + return (P3.weight,P3*conjugate(P3.weight)) + + def acom(P1,P2): + P3=P1*P2+P2*P1 + return (P3.weight,P3*conjugate(P3.weight)) + + def labelMultiply(P1,P2): + P3=P1*P2 + return (P3.weight,P3*conjugate(P3.weight)) + + errorGens=[] + + wT=weightFlip*BCHweight + + if ErG1.getType()=='H' and ErG2.getType()=='H': + pVec=com(ErG1.basis_element_labels[0] , ErG2.basis_element_labels[0]) + errorGens.append( localstimerrorgen( 'H' , [pVec[1]] , -1j*wT *pVec[0] ) ) + + elif ErG1.getType()=='H' and ErG2.getType()=='S': + pVec=com(ErG2.basis_element_labels[0] , ErG1.basis_element_labels[0]) + errorGens.append( localstimerrorgen( 'C' , [ErG2.basis_element_labels[0] , pVec[1]] , 1j*wT*pVec[0] ) ) + + elif ErG1.getType()=='S' and ErG2.getType()=='H': + pVec=com(ErG2.basis_element_labels[0] , ErG1.basis_element_labels[0]) + errorGens.append( localstimerrorgen( 'C' , [ErG2.basis_element_labels[0] , pVec[1]] , -1j*wT *pVec[0] ) ) + + elif ErG1.getType()=='H' and ErG2.getType()=='C': + pVec1=com(ErG2.basis_element_labels[0] , ErG1.basis_element_labels[0]) + errorGens.append( localstimerrorgen('C' , [pVec1[1], ErG2.basis_element_labels[1]] , 1j*wT*pVec1[0] ) ) + pVec2=com(ErG2.basis_element_labels[1] , ErG1.basis_element_labels[0]) + errorGens.append( localstimerrorgen('C' , [pVec2[1] , ErG2.basis_element_labels[0]] , 1j*wT*pVec2[0] ) ) + + elif ErG1.getType()=='C' and ErG2.getType()=='H': + errorGens = commute_errors(ErG2,ErG1,weightFlip=-1.0,BCHweight=BCHweight) + + elif ErG1.getType()=='H' and ErG2.getType()=='A': + pVec1 = com(ErG1.basis_element_labels[0] , ErG2.basis_element_labels[0]) + errorGens.append( localstimerrorgen('A' , [pVec1[1] , ErG2.basis_element_labels[1]] , -1j*wT*pVec1[0]) ) + pVec2 = com(ErG1.basis_element_labels[0] , ErG2.basis_element_labels[1]) + errorGens.append( localstimerrorgen('A' , [ErG2.basis_element_labels[0], pVec2[1]] , -1j*wT*pVec2[0] ) ) + + elif ErG1.getType()=='A' and ErG2.getType()=='H': + errorGens = commute_errors(ErG2,ErG1,weightFlip=-1.0,BCHweight=BCHweight) + + elif ErG1.getType()=='S' and ErG2.getType()=='S': + errorGens.append( localstimerrorgen('H', ErG1.basis_element_labels[0],0 )) + + elif ErG1.getType()=='S' and ErG2.getType()=='C': + pVec1=labelMultiply(ErG1.basis_element_labels[0] , ErG2.basis_element_labels[0]) + pVec2=labelMultiply(ErG2.basis_element_labels[1] , ErG1.basis_element_labels[0]) + errorGens.append( localstimerrorgen( 'A' , [pVec1[1], pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0] )) + pVec1 = labelMultiply(ErG1.basis_element_labels[0] , ErG2.basis_element_labels[1]) + pVec2 = labelMultiply(ErG2.basis_element_labels[0] , ErG1.basis_element_labels[0]) + errorGens.append( localstimerrorgen( 'A', [pVec1[1], pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0])) + pVec1 =acom(ErG2.basis_element_labels[0], ErG2.basis_element_labels[1]) + pVec2 = labelMultiply(pVec1[1],ErG1.basis_element_labels[0]) + errorGens.append( localstimerrorgen( 'A' ,[pVec2[1], ErG1.basis_element_labels[0]] , -1j*.5*wT*pVec1[0]*pVec2[0])) + pVec1=acom(ErG2.basis_element_labels[0], ErG2.basis_element_labels[1]) + pVec2=labelMultiply(ErG1.basis_element_labels[0],pVec1[1]) + errorGens.append( localstimerrorgen( 'A', [ErG1.basis_element_labels[0] ,pVec2[1]],-1j*.5*wT*pVec1[0]*pVec2[0])) + + elif ErG1.getType() == 'C' and ErG2.getType() == 'S': + errorGens = commute_errors(ErG2,ErG1,weightFlip=-1.0,BCHweight=BCHweight) + + elif ErG1.getType() == 'S' and ErG2.getType() == 'A': + pVec1 =labelMultiply(ErG1.basis_element_labels[0] , ErG2.basis_element_labels[0]) + pVec2=labelMultiply(ErG2.basis_element_labels[1] , ErG1.basis_element_labels[0]) + errorGens.append( localstimerrorgen( 'C', [pVec1[1], pVec2[1]] ,1j*wT*pVec1[0]*pVec2[0] )) + pVec1=labelMultiply(ErG1.basis_element_labels[0] , ErG2.basis_element_labels[1]) + pVec2=labelMultiply(ErG2.basis_element_labels[0] , ErG1.basis_element_labels[0]) + errorGens.append( localstimerrorgen( 'C', [pVec1[1], pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0])) + pVec1 = com(ErG2.basis_element_labels[0] , ErG2.basis_element_labels[1]) + pVec2 = com(ErG1.basis_element_labels[0],pVec1[1]) + errorGens.append( localstimerrorgen( 'A', [ErG1.basis_element_labels[0], pVec2[1]] ,-.5*wT*pVec1[0]*pVec2[0])) + + elif ErG1.getType() == 'A' and ErG1.getType() == 'S': + errorGens = commute_errors(ErG2,ErG1,weightFlip=-1.0,BCHweight=BCHweight) + + elif ErG1.getType() == 'C' and ErG2.getType() == 'C': + A=ErG1.basis_element_labels[0] + B=ErG1.basis_element_labels[1] + P=ErG2.basis_element_labels[0] + Q=ErG2.basis_element_labels[1] + pVec1 = labelMultiply(A,P) + pVec2 =labelMultiply(Q,B) + errorGens.append( localstimerrorgen( 'A', [pVec1[1], pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0] )) + pVec1 = labelMultiply(A,Q) + pVec2 =labelMultiply(P,B) + errorGens.append( localstimerrorgen( 'A' , [pVec1[1] , pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0] )) + pVec1 = labelMultiply(B,P) + pVec2 =labelMultiply(Q,A) + errorGens.append( localstimerrorgen( 'A' , [pVec1[1] , pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0] )) + pVec1 = labelMultiply(B,Q) + pVec2 =labelMultiply(P,A) + errorGens.append( localstimerrorgen( 'A' , [pVec1[1] , pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0])) + pVec1=acom(A,B) + pVec2=com(P,pVec1[1]) + errorGens.append( localstimerrorgen( 'A' , [pVec2[1] , Q ], -.5*1j*wT*pVec1[0]*pVec2[0])) + pVec1=acom(A,B) + pVec2=com(Q,pVec1[1]) + errorGens.append( localstimerrorgen( 'A' , [pVec2[1], P] , -.5*1j*wT*pVec1[0]*pVec2[0])) + pVec1=acom(P,Q) + pVec2=com(pVec1[1],A) + errorGens.append( localstimerrorgen( 'A' , [pVec2[1] , B] , -.5*1j*wT*pVec1[0]*pVec2[0])) + pVec1=acom(P,Q) + pVec2=com(pVec1[1],B) + errorGens.append( localstimerrorgen( 'A' , [pVec2[1] , A ] , -.5*1j*wT*pVec1[0]*pVec2[0])) + pVec1=acom(A,B) + pVec2=acom(P,Q) + pVec3=com(pVec1[1],pVec2[1]) + errorGens.append( localstimerrorgen( 'H', [pVec3[1]] ,.25*1j*wT*pVec1[0]*pVec2[0]*pVec3[0])) + + elif ErG1.getType() == 'C' and ErG2.getType() == 'A': + A=ErG1.basis_element_labels[0] + B=ErG1.basis_element_labels[1] + P=ErG2.basis_element_labels[0] + Q=ErG2.basis_element_labels[1] + pVec1 = labelMultiply(A,P) + pVec2 =labelMultiply(Q,B) + errorGens.append( localstimerrorgen('C' , [pVec1[1],pVec2[1]] , 1j*wT*pVec1[0]*pVec2[0])) + pVec1 = labelMultiply(A,Q) + pVec2 =labelMultiply(P,B) + errorGens.append( localstimerrorgen('C' ,[pVec1[1],pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0])) + pVec1 = labelMultiply(B,P) + pVec2 =labelMultiply(Q,A) + errorGens.append( localstimerrorgen('C' , [pVec1[1],pVec2[1]] , 1j*wT*pVec1[0]*pVec2[0])) + pVec1 = labelMultiply(P,A) + pVec2 =labelMultiply(B,Q) + errorGens.append( localstimerrorgen('C' ,[pVec1[1],pVec2[1]] , -1j*wT*pVec1[0]*pVec2[0])) + pVec1 = com(P,Q) + pVec2 =com(A,pVec1[1]) + errorGens.append( localstimerrorgen('A' , [pVec2[1] , B] , .5*wT*pVec1[0]*pVec2[0] )) + pVec1 = com(P,Q) + pVec2 =com(B,pVec1[1]) + errorGens.append( localstimerrorgen('A' , [pVec2[1], A ], .5*wT*pVec1[0]*pVec2[0] )) + pVec1 = acom(A,B) + pVec2 =com(P,pVec1[1]) + errorGens.append( localstimerrorgen('C', [pVec2[1] , Q ], .5*1j*wT*pVec1[0]*pVec2[0] )) + pVec1 = acom(A,B) + pVec2 =com(Q,pVec1[1]) + errorGens.append( localstimerrorgen('C',[pVec2[1],P ],-.5*1j*wT*pVec1[0]*pVec2[0] )) + pVec1 = com(P,Q) + pVec2 =acom(A,B) + pVec3=com(pVec1[1],pVec2[1]) + errorGens.append( localstimerrorgen('H',[pVec3[1]],-.25*wT*pVec1[0]*pVec2[0]*pVec3[0])) + + elif ErG1.getType() == 'A' and ErG2.getType() == 'C': + errorGens = commute_errors(ErG2,ErG1,weightFlip=-1.0,BCHweight=BCHweight) + + elif ErG1.getType() == 'A' and ErG2.getType() == 'A': + A=ErG1.basis_element_labels[0] + B=ErG1.basis_element_labels[1] + P=ErG2.basis_element_labels[0] + Q=ErG2.basis_element_labels[1] + pVec1=labelMultiply(Q,B) + pVec2=labelMultiply(A,P) + errorGens.append(localstimerrorgen('A',[pVec1[1],pVec2[1]] ,-1j*wT*pVec1[0]*pVec2[0])) + pVec1=labelMultiply(P,A) + pVec2=labelMultiply(B,Q) + errorGens.append(localstimerrorgen('A',[pVec1[1],pVec2[1]],-1j*wT*pVec1[0]*pVec2[0])) + pVec1=labelMultiply(B,P) + pVec2=labelMultiply(Q,A) + errorGens.append(localstimerrorgen('A',[pVec1[1],pVec2[1]],-1j*wT*pVec1[0]*pVec2[0])) + pVec1=labelMultiply(A,Q) + pVec2=labelMultiply(P,B) + errorGens.append(localstimerrorgen('A',[pVec1[1],pVec2[1]],-1j*wT*pVec1[0]*pVec2[0])) + pVec1=com(P,Q) + pVec2=com(B,pVec1[1]) + errorGens.append(localstimerrorgen('C',[pVec2[1],A],.5*wT*pVec1[0]*pVec2[0])) + pVec1=com(P,Q) + pVec2=com(A,pVec1[1]) + errorGens.append(localstimerrorgen('C',[pVec2[1],B] ,-.5*wT*pVec1[0]*pVec2[0])) + pVec1=com(A,B) + pVec2=com(P,pVec1[1]) + errorGens.append(localstimerrorgen('C', [pVec2[1],Q] ,.5*wT*pVec1[0]*pVec2[0])) + pVec1=com(A,B) + pVec2=com(Q,pVec1[1]) + errorGens.append(localstimerrorgen('C', [pVec2[1],P] ,-.5*wT*pVec1[0]*pVec2[0])) + pVec1=com(P,Q) + pVec2=com(A,B) + pVec3=com(pVec1[1],pVec2[1]) + errorGens.append( localstimerrorgen('H',[pVec3[1]] ,.25*wT*pVec1[0]*pVec2[0]*pVec3[0])) + + + return errorGens \ No newline at end of file diff --git a/pygsti/extras/errorgenpropagation/utilspygstistimtranslator.py b/pygsti/extras/errorgenpropagation/utilspygstistimtranslator.py index 6e33c1e99..98d07a87f 100644 --- a/pygsti/extras/errorgenpropagation/utilspygstistimtranslator.py +++ b/pygsti/extras/errorgenpropagation/utilspygstistimtranslator.py @@ -1,4 +1,5 @@ import stim +from numpy import conjugate @@ -61,4 +62,5 @@ def pyGSTiPauli_2_stimPauli(pauli): in this function, if the weight is needed please store paulistring::weight prior to applying this function ''' def stimPauli_2_pyGSTiPauli(pauliString): + pauliString=conjugate(pauliString.sign)*pauliString return str(pauliString)[1:].replace('_',"I") \ No newline at end of file diff --git a/pygsti/tools/internalgates.py b/pygsti/tools/internalgates.py index 91ac69567..8f320b93c 100644 --- a/pygsti/tools/internalgates.py +++ b/pygsti/tools/internalgates.py @@ -320,6 +320,8 @@ def standard_gatenames_stim_conversions(): A dictionary converting the gates with standard names to stim tableus for these gates. Currently is only capable of converting clifford gates, no capability for T gates + TODO: Add all standard clifford gate names in + Returns ------- A dict mapping string to tableu From 7a5d94c4784faa56e0635987c74971e509a10e2c Mon Sep 17 00:00:00 2001 From: ashenmill <156946147+ashenmill@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:22:51 -0600 Subject: [PATCH 07/11] Fixed a bug in the translation code --- pygsti/extras/errorgenpropagation/errorpropagator.py | 2 ++ pygsti/extras/errorgenpropagation/propagatableerrorgen.py | 7 ++++--- .../errorgenpropagation/utilspygstistimtranslator.py | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pygsti/extras/errorgenpropagation/errorpropagator.py b/pygsti/extras/errorgenpropagation/errorpropagator.py index 2fe015823..f58115d3e 100644 --- a/pygsti/extras/errorgenpropagation/errorpropagator.py +++ b/pygsti/extras/errorgenpropagation/errorpropagator.py @@ -46,6 +46,7 @@ def ErrorPropagator(circ,errorModel,MultiGateDict={},BCHOrder=1,BCHLayerwise=Fal else: errorLayers=[[errorModel]]*circ.depth #this doesn't work + num_error_layers=len(errorLayers) fully_propagated_layers=[] for _ in range(0,num_error_layers-1): @@ -54,6 +55,7 @@ def ErrorPropagator(circ,errorModel,MultiGateDict={},BCHOrder=1,BCHLayerwise=Fal for err_order in err_layer: for errorGenerator in err_order: errorGenerator.propagate_error_gen_inplace_tableau(layer) + if BCHLayerwise and not NonMarkovian: following_layer = errorLayers.pop(0) new_errors=BCH_Handler(err_layer,following_layer,BCHOrder) diff --git a/pygsti/extras/errorgenpropagation/propagatableerrorgen.py b/pygsti/extras/errorgenpropagation/propagatableerrorgen.py index b9e71dbc5..3fbaa9e33 100644 --- a/pygsti/extras/errorgenpropagation/propagatableerrorgen.py +++ b/pygsti/extras/errorgenpropagation/propagatableerrorgen.py @@ -50,7 +50,7 @@ def __eq__(self, other): displays the errorgens as strings ''' def __str__(self): - return self.errorgen_type + "(" + ",".join(map(str, self.basis_element_labels)) + ")" + ": " + self.error_rate + return self.errorgen_type + "(" + ",".join(map(str, self.basis_element_labels)) + ")" + ": " + str(self.error_rate) def __repr__(self): @@ -94,7 +94,7 @@ def ErrorWeight(self): def Weight(pauli): weight=0 for char in pauli: - if char is 'I': + if char == 'I': continue else: weight+=1 @@ -133,11 +133,12 @@ def propagate_error_gen_inplace_tableau(self, slayer): temp = slayer(temp) weightmod=weightmod*temp.sign new_basis_labels.append(stimPauli_2_pyGSTiPauli(temp)) - if self.errorgen_type in 'HCA': self.error_rate=self.error_rate*weightmod self.basis_element_labels =tuple(new_basis_labels) + + ''' returns the strings representing the pauli labels in the pygsti representation of paulis as stim PauliStrings ''' diff --git a/pygsti/extras/errorgenpropagation/utilspygstistimtranslator.py b/pygsti/extras/errorgenpropagation/utilspygstistimtranslator.py index 98d07a87f..b6473318f 100644 --- a/pygsti/extras/errorgenpropagation/utilspygstistimtranslator.py +++ b/pygsti/extras/errorgenpropagation/utilspygstistimtranslator.py @@ -62,5 +62,7 @@ def pyGSTiPauli_2_stimPauli(pauli): in this function, if the weight is needed please store paulistring::weight prior to applying this function ''' def stimPauli_2_pyGSTiPauli(pauliString): - pauliString=conjugate(pauliString.sign)*pauliString - return str(pauliString)[1:].replace('_',"I") \ No newline at end of file + n=1 + if pauliString.sign==1j or pauliString.sign==-1j: + n=2 + return str(pauliString)[n:].replace('_',"I") \ No newline at end of file From 486b690b54f4336154b666c632991ddd532cba92 Mon Sep 17 00:00:00 2001 From: ashenmill <156946147+ashenmill@users.noreply.github.com> Date: Mon, 8 Jul 2024 09:57:54 -0600 Subject: [PATCH 08/11] Analytic Propagation Added Analytic Propagation --- .../errorpropagator_dev.py | 92 ++++++++++++++++++- .../errorgenpropagation/localstimerrorgen.py | 37 +++++++- pygsti/tools/internalgates.py | 2 + 3 files changed, 123 insertions(+), 8 deletions(-) diff --git a/pygsti/extras/errorgenpropagation/errorpropagator_dev.py b/pygsti/extras/errorgenpropagation/errorpropagator_dev.py index 68916d815..6a311e20b 100644 --- a/pygsti/extras/errorgenpropagation/errorpropagator_dev.py +++ b/pygsti/extras/errorgenpropagation/errorpropagator_dev.py @@ -1,10 +1,74 @@ import stim -from localstimerrorgen import * +from pygsti.extras.errorgenpropagation.localstimerrorgen import * from numpy import abs,zeros, complex128 from numpy.linalg import multi_dot from scipy.linalg import expm from pygsti.tools.internalgates import standard_gatenames_stim_conversions -from utilserrorgenpropagation import * +from pygsti.extras.errorgenpropagation.utilserrorgenpropagation import * +import copy as _copy + +def ErrorPropagatorAnalytic(circ,errorModel,ErrorLayerDef=False,startingErrors=None): + stim_layers=circ.convert_to_stim_tableau_layers() + + if startingErrors is None: + stim_layers.pop(0) + + propagation_layers=[] + while len(stim_layers)>0: + top_layer=stim_layers.pop(0) + for layer in stim_layers: + top_layer = layer*top_layer + propagation_layers.append(top_layer) + + if not ErrorLayerDef: + errorLayers=buildErrorlayers(circ,errorModel,len(circ.line_labels)) + else: + errorLayers=[[_copy.deepcopy(eg) for eg in errorModel] for i in range(circ.depth)] + + if not startingErrors is None: + errorLayers.insert(0,startingErrors) + + fully_propagated_layers=[] + for (idx,layer) in enumerate(errorLayers): + new_error_dict=dict() + if idx Date: Thu, 18 Jul 2024 15:06:11 -0600 Subject: [PATCH 09/11] ErrorPropagator Object inital implementation Created an initial implementation of the error_gen Object initial implementation. Also merged a few functions from the ml branch for compressed sensing purposes. --- pygsti/circuits/circuit.py | 3 +- .../error_propagator_obj.py | 192 ++++++++++++++++++ .../errorpropagator_dev.py | 10 +- .../errorgenpropagation/localstimerrorgen.py | 3 +- pygsti/tools/internalgates.py | 6 +- 5 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 pygsti/extras/errorgenpropagation/error_propagator_obj.py diff --git a/pygsti/circuits/circuit.py b/pygsti/circuits/circuit.py index 4ea29578b..4cccaadc5 100644 --- a/pygsti/circuits/circuit.py +++ b/pygsti/circuits/circuit.py @@ -3734,7 +3734,8 @@ def convert_to_stim_tableau_layers(self,gate_name_conversions=None): layer = self.layer(j) stim_layer=stim.Tableau(qubits) for sub_lbl in layer: - temp = gate_name_conversions[sub_lbl.name] + temp = gate_name_conversions[sub_lbl.name] + stim_layer.append(temp,sub_lbl.qubits) stim_layers.append(stim_layer) return stim_layers diff --git a/pygsti/extras/errorgenpropagation/error_propagator_obj.py b/pygsti/extras/errorgenpropagation/error_propagator_obj.py new file mode 100644 index 000000000..20471e81d --- /dev/null +++ b/pygsti/extras/errorgenpropagation/error_propagator_obj.py @@ -0,0 +1,192 @@ +import copy as _copy +import numpy as _np +from pygsti.extras.errorgenpropagation.localstimerrorgen import localstimerrorgen +import stim + +class error_propagator: + def __init__(self,circ,potential_errors): + self.circ=circ + self.__prop_dict=self.__build_error_dict(potential_errors) + + def __build_error_dict(self,error_model): + stim_layers=self.circ.convert_to_stim_tableau_layers() + stim_layers.pop(0) + + propagation_layers=[] + while len(stim_layers)>0: + top_layer=stim_layers.pop(0) + for layer in stim_layers: + top_layer = layer*top_layer + propagation_layers.append(top_layer) + + errorLayers=[[_copy.deepcopy(eg) for eg in error_model] for i in range(self.circ.depth)] + + fully_propagated_layers=[] + for (idx,layer) in enumerate(errorLayers): + new_error_dict=dict() + if idx 0, and 'IX' -> 1, and 'ZZ' -> 15. + + ps: str, list, or tuple. + + num_qubits: int + + Returns + int + """ + idx = 0 + p_to_i = {'I': 0, 'X': 1, 'Y': 2, 'Z': 3} + for i in range(num_qubits): + idx += p_to_i[ps[num_qubits - 1 - i]] * 4**i + return idx \ No newline at end of file diff --git a/pygsti/extras/errorgenpropagation/errorpropagator_dev.py b/pygsti/extras/errorgenpropagation/errorpropagator_dev.py index 6a311e20b..69c9c931e 100644 --- a/pygsti/extras/errorgenpropagation/errorpropagator_dev.py +++ b/pygsti/extras/errorgenpropagation/errorpropagator_dev.py @@ -28,12 +28,14 @@ def ErrorPropagatorAnalytic(circ,errorModel,ErrorLayerDef=False,startingErrors=N if not startingErrors is None: errorLayers.insert(0,startingErrors) + fully_propagated_layers=[] for (idx,layer) in enumerate(errorLayers): new_error_dict=dict() if idx Date: Thu, 18 Jul 2024 15:52:29 -0600 Subject: [PATCH 10/11] quick bugfix --- pygsti/extras/errorgenpropagation/error_propagator_obj.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pygsti/extras/errorgenpropagation/error_propagator_obj.py b/pygsti/extras/errorgenpropagation/error_propagator_obj.py index 20471e81d..273058228 100644 --- a/pygsti/extras/errorgenpropagation/error_propagator_obj.py +++ b/pygsti/extras/errorgenpropagation/error_propagator_obj.py @@ -96,7 +96,7 @@ def Organize_error_layers(self,Gate_Err_Dict): for sub_lbl in layer: existant_errors=[] - p1='I'*len(self.circ.qubits) + p1='I'*len(self.circ.line_labels) for errs in Gate_Err_Dict[sub_lbl.name]: #for an error in the accompanying error dictionary errType=errs[0] paulis=[] @@ -107,9 +107,9 @@ def Organize_error_layers(self,Gate_Err_Dict): paulis.append(stim.PauliString(p1)) existant_errors.append(localstimerrorgen(errType,paulis)) - - for key in self.__prop_dict[layer_no]: - if not self.__prop_dict[layer_no][key][0] in existant_errors: + key_list=list(self.__prop_dict[layer_no].keys()) + for key in key_list: + if not key in existant_errors: self.__prop_dict[layer_no].pop(key,None) From 5599e658755f2d0e5d140b835819c94066fcd32b Mon Sep 17 00:00:00 2001 From: ashenmill <156946147+ashenmill@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:34:45 -0700 Subject: [PATCH 11/11] improved the code for error propagation --- .../error_propagator_obj.py | 153 +++++++++++++++--- .../errorgenpropagation/localstimerrorgen.py | 3 +- 2 files changed, 135 insertions(+), 21 deletions(-) diff --git a/pygsti/extras/errorgenpropagation/error_propagator_obj.py b/pygsti/extras/errorgenpropagation/error_propagator_obj.py index 273058228..77ab5bdf1 100644 --- a/pygsti/extras/errorgenpropagation/error_propagator_obj.py +++ b/pygsti/extras/errorgenpropagation/error_propagator_obj.py @@ -4,34 +4,124 @@ import stim class error_propagator: - def __init__(self,circ,potential_errors): + def __init__(self,circ,potential_errors,label_layers=False,labels=None,gate_lbled=False): self.circ=circ - self.__prop_dict=self.__build_error_dict(potential_errors) + if not gate_lbled: + self.__prop_dict=self.__build_error_dict(potential_errors,label_layers,labels) + else: + self.__prop_dict=self.__build_gate_labeled_dict(potential_errors) - def __build_error_dict(self,error_model): + def __build_gate_labeled_dict(self,errors): + #converts pygsti circuit to stim tableau stim_layers=self.circ.convert_to_stim_tableau_layers() stim_layers.pop(0) + # list to hold the propagation propagation_layers=[] + #go through the list of sub circuits until there are none left while len(stim_layers)>0: + #get first stim layer top_layer=stim_layers.pop(0) + #find stim tableu until the end for layer in stim_layers: top_layer = layer*top_layer propagation_layers.append(top_layer) + #create a bunch of error layers including every layer in our model + errorLayers=[] + for layer in self.circ: + error_layer=[] + for gate_lbl in layer: + for errs in errors[tuple(gate_lbl)]: + errType=errs[0] + paulis=[] + p1='I'*len(self.circ.line_labels) + + + qbt1=gate_lbl[1] + if gate_lbl[0] =='Gcphase': + qbt2=gate_lbl[2] + if len(errs[1]) != 2: + sub_pauli=errs[1].split(':')[0] + qbt1=int(errs[1].split(':')[1].split(',')[0]) + if len(errs[1])==6: + qbt2=int(errs[1].split(':')[1].split(',')[1]) + else: + qbt2=None + else: + sub_pauli=errs[1] + p1=p1[:qbt1]+sub_pauli[0]+p1[(qbt1+1):] + if qbt2 is not None: + p1=p1[:qbt2]+sub_pauli[1]+p1[(qbt2+1):] + + + paulis.append(stim.PauliString(p1)) + error_layer.append(localstimerrorgen(errType,paulis,label=tuple(gate_lbl))) + errorLayers.append(error_layer) + fully_propagated_layers=[] + #get a subcircuit starting at layer idx + for (idx,layer) in enumerate(errorLayers): + #create a dictionary + new_error_dict=dict() + if idx 0: + #get first stim layer + top_layer=stim_layers.pop(0) + #find stim tableu until the end + for layer in stim_layers: + top_layer = layer*top_layer + propagation_layers.append(top_layer) + + #create a bunch of error layers including every layer in our model errorLayers=[[_copy.deepcopy(eg) for eg in error_model] for i in range(self.circ.depth)] fully_propagated_layers=[] + #get a subcircuit starting at layer idx for (idx,layer) in enumerate(errorLayers): + #create a dictionary new_error_dict=dict() if idx