Source code for stabilizer_project.stabilizer

"Contains the classes and function to manipulate stabilizer and graph states"
import numpy as np
from qiskit import QuantumCircuit, ClassicalRegister
from qiskit.quantum_info import StabilizerState

[docs]class Stabilizer: ''' This is a class that encodes the stabilizer state in terms of its stabilizers. If no input is given, it will initialize a bell state. If only the n is given, it will initialize n qubits in the 0 state :param n: Number of qubits :type n: int, Optional :param stabs: The stabilizers, either in a string or a list, in the format 'XX,-YY' or '[XX,-YY]' (case sensitive). Optional :type stabs: list or string, optional :param edgelist: A list of edges for a graph state. Optional :type edgelist: List :cvar size: The number of qubits, initial value: n :cvar __stabs: The stabilizers of the state, initial value: stabs (note, this is a dunder attribute, can't be directly called outside the class. There's a method to do that instead) :cvar tab: The tableau of the state :cvar signvector: The signvector of the state :cvar gauss: A nxn Gaussian matrix (used for empty_column calculations) ''' def __init__(self, n = None, stabs = None, edgelist = None): """Constructor method """ if edgelist is None: if n is None and stabs is None: n = 2 stabs = 'XX,ZZ' elif n is not None and stabs is None: stabs = [] for i in range(n): str = '' for j in range(n): if i==j: str = str+'Z' else: str = str+'I' stabs.append(str) self.size = n try: self.__stab = stabs.split(',') except: self.__stab = stabs list = self.tableau() self.tab = list[0] self.signvector = list[1] while not self.square(): print("Invalid input, number of qubits not equal to number of stabilizers") n = int(input("Number of Qubits ")) stabs = input("Stabilizers ") self.size = n try: self.__stab = stabs.split(',') except: self.__stab = stabs list = self.tableau() self.tab = list[0] self.signvector = list[1] while self.empty_column(): print("Invalid input, free qubit (all stabilizers for some qubit is the identity)") n = int(input("Number of Qubits ")) stabs = input("Stabilizers ") self.size = n try: self.__stab = stabs.split(',') except: self.__stab = stabs list = self.tableau() self.tab = list[0] self.signvector = list[1] while not self.commuter(): print("Invalid Inputs, Stabilizers do not commute") n = int(input("Number of Qubits ")) stabs = input("Stabilizers ") self.size = n try: self.__stab = stabs.split(',') except: self.__stab = stabs list = self.tableau() self.tab = list[0] self.signvector = list[1] while not self.linear_independence(): print("Invalid Inputs, Stabilizers are not independant") n = int(input("Number of Qubits ")) stabs = input("Stabilizers ") self.size = n try: self.__stab = stabs.split(',') except: self.__stab = stabs list = self.tableau() self.tab = list[0] self.signvector = list[1] else: self.graph_state() def square(self): toggler = True for i in range(len(self.__stab)): str = self.__stab[i] str = str.lstrip('-') if len(str)!=len(self.__stab): return False return toggler
[docs] def commuter(self): """ Tests whether the stabilizers commute with each other :return: Whether or not they commute :rtype: boolean """ for i in range(self.size): toggler=0 for j in range(i+1,self.size): for k in range(self.size): if self.tab[i,k]==self.tab[j,k] and self.tab[i,k+self.size]==self.tab[j,k+self.size]: toggler = toggler elif (self.tab[i,k+self.size]==0 and self.tab[i,k]==0) or (self.tab[j,k+self.size]==0 and self.tab[j,k]==0): toggler = toggler else: toggler = toggler+1 if toggler%2 != 0: return False return True
[docs] def num_qubits(self): """ Returns the size of the stabilizer (the number of qubits) :return: The size of the stabilizer :rtype: int """ return self.size
[docs] def graph_state(self,edgelist = [[0,1],[1,2],[2,3],[3,4],[4,0]]): """ Generates a graph state based on inputed edgelist :param edgelist: The list of connections, defaults to [[0,1],[1,2],[2,3],[3,4],[4,0]] :type edgelist: Nested list """ num=0 for i in range(len(edgelist)): for j in range(len(edgelist[i])): if edgelist[i][j]>num: num = edgelist[i][j] self.size = num+1 tab = np.zeros(2*self.size*self.size) tab = tab.reshape(self.size,2*self.size) for i in range(self.size): tab[i,i]=1 for i in range(len(edgelist)): q1 = edgelist[i][0] q2 = edgelist[i][1] tab[q1,q2+self.size]=1 tab[q2,q1+self.size]=1 sign = np.zeros(self.size) self.tab = tab self.signvector = sign
[docs] def tableau(self): """ Converts the stabilizers to a tableau and signvector :return: A list contained the tableau and the signvector :rtype: list """ tab = np.zeros(2*self.size*self.size) tab = tab.reshape(self.size,2*self.size) sign = np.zeros(self.size) for i in range(len(self.__stab)): if self.__stab[i][0]=='-': sign[i]=1 self.__stab[i]=self.__stab[i][1:] for j in range(len(self.__stab[i])): if self.__stab[i][j]=='I': pass elif self.__stab[i][j]=='X': tab[i,j]=1 elif self.__stab[i][j]=='Z': tab[i,j+self.size]=1 elif self.__stab[i][j]=='Y': tab[i,j]=1 tab[i,j+self.size]=1 else: print('Invalid Stabilizer') return [tab,sign]
[docs] def stabilizers(self): """ Returns a list of the stabilizers of the state, as per the tableau :return: A list of operations to take a standard state to the given stabilizer state :rtype: list """ self.__stab = [] for i in range(self.size): str = "" if self.signvector[i]==1: str = str+"-" for j in range(self.size): if self.tab[i,j]==0 and self.tab[i,j+self.size]==0: str = str+"I" elif self.tab[i,j]==1 and self.tab[i,j+self.size]==0: str = str+"X" if self.tab[i,j]==0 and self.tab[i,j+self.size]==1: str = str+"Z" if self.tab[i,j]==1 and self.tab[i,j+self.size]==1: str = str+"Y" self.__stab.append(str) return self.__stab
[docs] def new_stab(self,size=None,newstabs=None): """ Resets the stabilizer and new tableau associated with it :param size: The size of the new state :type size: int (optional) :param newstabs: The new stabilizers :type newstabs: string or list """ if size is None and newstabs is None: size = 2 newstabs = 'XX,ZZ' if size is not None and newstabs is None: newstabs = [] for i in range(size): str = '' for j in range(size): if i==j: str = str+'Z' else: str = str+'I' newstabs.append(str) self.size = size try: self.__stab = newstabs.split(',') except: self.__stab = newstabs list = self.tableau() self.tab = list[0] self.signvector = list[1] while not self.square(): print("Invalid input, number of qubits not equal to number of stabilizers") n = int(input("Number of Qubits ")) stabs = input("Stabilizers ") self.size = n try: self.__stab = stabs.split(',') except: self.__stab = stabs list = self.tableau() self.tab = list[0] self.signvector = list[1] while self.empty_column(): print("Invalid input, free qubit (all stabilizers for some qubit is the identity)") n = int(input("Number of Qubits ")) stabs = input("Stabilizers ") self.size = n try: self.__stab = stabs.split(',') except: self.__stab = stabs list = self.tableau() self.tab = list[0] self.signvector = list[1] while not self.commuter(): print("Invalid Inputs, Stabilizers do not commute") n = int(input("Number of Qubits ")) stabs = input("Stabilizers ") self.size = n try: self.__stab = stabs.split(',') except: self.__stab = stabs list = self.tableau() self.tab = list[0] self.signvector = list[1] while not self.linear_independence(): print("Invalid Inputs, Stabilizers are not independant") n = int(input("Number of Qubits ")) stabs = input("Stabilizers ") self.size = n try: self.__stab = stabs.split(',') except: self.__stab = stabs list = self.tableau() self.tab = list[0] self.signvector = list[1]
[docs] def clifford(self,type,q1,q2=None): """ Applies a clifford gate to the stabilizer :param type: The clifford gate to be operated, 'H', 'X', 'Y', 'Z', 'CNOT', 'CZ', or 'S' :type type: string :param q1: The qubit to operate on, or the control qubit for entangling gates :type q1: int :param q2: The qubit to target, defaults to None :type q2: int """ if type.lower() == 'h': for i in range(self.size): alpha = self.tab[i,q1] beta = self.tab[i,q1+self.size] self.tab[i,q1]=beta self.tab[i,q1+self.size]=alpha self.signvector[i] = (self.signvector[i]+(alpha*beta))%2 elif type.lower() == 'cnot': if q2 == None: print('Recall method and specify second qubit') else: for i in range(self.size): self.tab[i,q2] = (self.tab[i,q1]+self.tab[i,q2])%2 self.tab[i,self.size+q1] = (self.tab[i,q1+self.size]+self.tab[i,q2+self.size])%2 if self.tab[i,q1]==1 and self.tab[i,q2+self.size]==1: if self.tab[i,q2]==self.tab[i,self.size+q1]: self.signvector[i]=(self.signvector[i]+1)%2 elif type.lower() == 'z': for i in range(self.size): if self.tab[i,q1]==1: self.signvector[i]=(self.signvector[i]+1)%2 elif type.lower() == 'x': for i in range(self.size): if self.tab[i,q1+self.size]==1: self.signvector[i]=(self.signvector[i]+1)%2 elif type.lower() == 'y': for i in range(self.size): if (self.tab[i,q1]+self.tab[i,q1+self.size])==1: self.signvector[i]=(self.signvector[i]+1)%2 elif type.lower() == 's': for i in range(self.size): if self.tab[i,q1]==1: self.signvector[i]=(self.signvector[i]+self.tab[i,q1+self.size])%2 self.tab[i,q1+self.size] = (self.tab[i,q1+self.size]+1)%2 elif type.lower() == 'cz': if q2 == None: print('Recall method and specify second qubit') else: self.clifford('h',q2) self.clifford('cnot',q1,q2) self.clifford('h', q2) else: print("Something went wrong, make sure you inputted a valid type. Valid types are 'H' for Hadamard, 'S' for the phase gate, 'CNOT' for the Control Not, 'CZ' for the Control Z.")
[docs] def report(self): """ Prints the tableau and the signvector """ print(self.tab) print(self.signvector)
[docs] def gaussian(self): """ Generates an array that contains information about where stabilizers are known """ self.gauss = np.zeros(self.size*self.size) self.gauss = self.gauss.reshape(self.size,self.size) for i in range(self.size): for j in range(self.size): if self.tab[i,j]==1 or self.tab[i,j+self.size]==1: self.gauss[i,j]=1
[docs] def empty_column(self): """ Tests whether there are any empty stabilizers (free qubits) :return: Whether there is an empty column or not :rtype: boolean """ self.gaussian() zed = self.gauss.sum(axis=0) empty = False for i in range(self.size): if zed[i]==0: empty = True return empty
[docs] def linear_independence(self): """ Checks if the generators are linearly independent """ rank = np.linalg.matrix_rank(self.tab) rank = int(rank) if rank == self.size: return True else: return False
[docs] def row_add(self,row1,row2): """ Multiplies two stabilizers in the tableau together, specifying a new stabilizer, and puts them into the second row """ stabs=self.stabilizers() stab1 = stabs[row1] stab2 = stabs[row2] stab1 = stab1.lstrip('-') stab2 = stab2.lstrip('-') phase = np.ones(self.size, dtype=complex) for i in range(self.size): if stab1[i]=='Z' and stab2[i]=="X": phase[i]=np.complex(1j)*phase[i] elif stab1[i]=='X' and stab2[i]=="Z": phase[i]=np.complex(-1j)*phase[i] elif stab1[i]=='Y' and stab2[i]=="Z": phase[i]=np.complex(1j)*phase[i] elif stab1[i]=='Z' and stab2[i]=="Y": phase[i]=np.complex(-1j)*phase[i] elif stab1[i]=='X' and stab2[i]=="Y": phase[i]=np.complex(1j)*phase[i] elif stab1[i]=='Y' and stab2[i]=="X": phase[i]=np.complex(-1j)*phase[i] toggler = 1 for i in range(self.size): toggler = toggler*phase[i] toggler = (1-1*np.real(toggler))/2 self.signvector[row2]=(self.signvector[row1]+self.signvector[row2]+toggler)%2 for i in range(2*self.size): self.tab[row2,i]=(self.tab[row2,i]+self.tab[row1,i])%2
[docs] def circuit_builder(self): """ Uses reverse operations to build the stabilizer state :return: A Qiskit circuit that makes the stabilizer :rtype: QuantumCircuit """ reference = np.copy(self.tab) sign = np.copy(self.signvector) rev_operations = [] broken = False for i in range(self.size): if self.tab[i,i]==0: if self.tab[i,i+self.size]==1: rev_operations.append(['H',i]) self.clifford('H',i) if self.tab[i,i]==0: for j in range(i+1,self.size): if self.tab[j,i]==1: self.tab[[i,j]]=self.tab[[j,i]] self.signvector[[i,j]]=self.signvector[[j,i]] break if self.tab[i,i]==0: for j in range(i+1,self.size): if self.tab[j,i+self.size]==1: self.tab[[i,j]]=self.tab[[j,i]] self.signvector[[i,j]]=self.signvector[[j,i]] rev_operations.append(['H',i]) self.clifford('H',i) break if self.tab[i,i]==0: for j in range(i): if self.tab[j,i+self.size]==1: self.row_add(j,i) rev_operations.append(['H',i]) self.clifford('H',i) break if self.tab[i,i]==0: broken = True break elif self.tab[i,i]==1: for j in range(self.size): if self.tab[i,j]==1 and j!=i: rev_operations.append(["CNOT",i,j]) self.clifford("CNOT",i,j) if broken: self.tab = np.copy(reference) self.signvector = np.copy(sign) print("Something went wrong in the building procedure. Check your stabilizers and maybe reformat them and try again") return None for i in range(self.size): if self.tab[i,i+self.size]==1: rev_operations.append(["S",i]) self.clifford("S",i) for i in range(self.size): for j in range(self.size): if self.tab[i,j+self.size]==1: rev_operations.append(["CZ",i,j]) self.clifford("CZ",i,j) for i in range(self.size): self.clifford('H',i) rev_operations.append(['H',i]) for i in range(self.size): if self.signvector[i]==1: rev_operations.append(['X',i]) self.clifford('X',i) self.tab = np.copy(reference) self.signvector = np.copy(sign) rev_operations.reverse() circuit = QuantumCircuit(self.size) for i in range(len(rev_operations)): if rev_operations[i][0]=='H': circuit.h(rev_operations[i][1]) elif rev_operations[i][0]=='S': circuit.s(rev_operations[i][1]) circuit.z(rev_operations[i][1]) elif rev_operations[i][0]=='X': circuit.x(rev_operations[i][1]) elif rev_operations[i][0]=='Y': circuit.y(rev_operations[i][1]) elif rev_operations[i][0]=='Z': circuit.z(rev_operations[i][1]) elif rev_operations[i][0]=='CNOT': circuit.cnot(rev_operations[i][1],rev_operations[i][2]) elif rev_operations[i][0]=='CZ': circuit.cz(rev_operations[i][1],rev_operations[i][2]) return circuit
[docs] def draw_circuit(self, style = 'mpl', save = None): """ Draws a circuit that can generate the given stabilizer state (requires matplotlib and pylatexenc package) :param style: The type of output, 'mpl' for matplotlib, 'text' for ASCII drawing, 'latex_source' for raw latex output :type style: String, optional. Defaults to 'mpl' :param save: If you want to save the file to something (optional) :type save: String """ if style == 'mpl': try: import matplotlib import matplotlib.pyplot as plt try: circ = self.circuit_builder() circ.draw(output = style, filename = save) plt.show() except: print("pylatexenc likely not installed") except: print("matplotlib package not installed") elif style == 'text': circ = self.circuit_builder() circ.draw(output = style, filename = save) elif style == 'latex_source': circ = self.circuit_builder() circ.draw(output = style, filename = save)
[docs] def qiskit_stabilizers(self): """ Asks Qiskit to return the stabilizers :return: A qiskit stabilizer state representation :rtype: StabilizerState (qiskit) """ circ = self.circuit_builder() stab = StabilizerState(circ) return stab
[docs] def stabilizer_measurement(self): """ A circuit t-o measure the associated stabilizers of this state :return: A qiskit circuit for measureing stabilizer :rtype: QuantumCircuit """ qs = QuantumCircuit(2*self.size) bits = [] for i in range(self.size): bits.append(i) reg = ClassicalRegister(self.size) qs.add_register(reg) stabs = self.stabilizers() for i in range(self.size): qs.h(self.size+i) for i in range(self.size): stabs[i]=stabs[i].lstrip('-') for j in range(self.size): if stabs[i][j]=='X': qs.cx(self.size+i,j) elif stabs[i][j]=='Z': qs.cz(self.size+i,j) elif stabs[i][j]=='Y': qs.cy(self.size+i,j) for i in range(self.size): qs.h(self.size+i) for i in range(self.size): if self.signvector[i]==1: qs.x(self.size+i) for i in range(self.size): qs.measure(i+self.size,i) return qs
[docs] def build_and_measure(self): """ A circuit to implement the circuit and then to measure the associated stabilizers. :return: A qiskit circuit for measureing stabilizer :rtype: QuantumCircuit """ circ = self.circuit_builder() qs = QuantumCircuit(2*self.size) bits = [] for i in range(self.size): bits.append(i) qs = qs.compose(circ,bits) reg = ClassicalRegister(self.size) qs.add_register(reg) qs.barrier() stabs = self.stabilizers() for i in range(self.size): qs.h(self.size+i) for i in range(self.size): stabs[i]=stabs[i].lstrip('-') for j in range(self.size): if stabs[i][j]=='X': qs.cx(self.size+i,j) elif stabs[i][j]=='Z': qs.cz(self.size+i,j) elif stabs[i][j]=='Y': qs.cy(self.size+i,j) for i in range(self.size): qs.h(self.size+i) for i in range(self.size): if self.signvector[i]==1: qs.x(self.size+i) for i in range(self.size): qs.measure(i+self.size,i) return qs
[docs] def swap(self, row1,row2): """ Swaps two rows in the stabilizer :param r1: The first row :type type: int :param r2: The second row :type q1: int """ self.tab[[row1, row2]] = self.tab[[row2, row1]] self.signvector[[row1, row2]] = self.signvector[[row2, row1]]
[docs] def flip(self): """ Flips the tableau over """ self.tab = np.flip(self.tab,axis=0) self.signvector = np.flip(self.signvector,axis=0)
[docs]def grapher(edgelist): """ Function that can graph a graph state provided an edgelist Parameters ---------- edgelist : list A list that denotes all the connections inp a graph state. The edgelist is a nested list, with each inner list containing two elements, the numbers of the qubits that are connected. Returns ------- circuit : QuantumCircuit A Qiskit quantum circuit that encodes the circuit that generates that graph """ num=0 for i in range(len(edgelist)): for j in range(len(edgelist[i])): if edgelist[i][j]>num: num = edgelist[i][j] circuit = QuantumCircuit(num+1, num+1) for i in range(num+1): circuit.h(i) for i in range(len(edgelist)): circuit.cz(edgelist[i][0],edgelist[i][1]) stab = StabilizerState(circuit) print(stab) return circuit
if __name__ == "__main__": # Do something if this file is invoked on its own grapher([[0,1],[1,2],[2,3],[3,4],[4,5],[5,0]])