def numpy_vs_lazy(): min_qubits = 8 max_qubits = 14 had = Sparse.ColMatrix(2) had[0, 0] = 1 / np.sqrt(2) had[0, 1] = 1 / np.sqrt(2) had[1, 0] = 1 / np.sqrt(2) had[1, 1] = -1 / np.sqrt(2) np_had = np.array([[1, 1], [1, -1]]) / np.sqrt(2) times = np.zeros((2, max_qubits - min_qubits)) memoryusage = np.zeros((2, max_qubits - min_qubits)) for i in range(min_qubits, max_qubits): test_vector = np.ones(2**i) / np.sqrt(2**i) #test_vector[0] = 1 t1 = time.time() for qubit in range(i): gate = Sparse.Gate(2**i, had, [qubit]) test_vector = gate.apply(test_vector) t2 = time.time() memoryusage[0, i - min_qubits] = sys.getsizeof(gate) #print(test_vector) test_vector = np.ones(2**i) / np.sqrt(2**i) #test_vector[0] = 1 t3 = time.time() gate = np.array([1]) for qubit in range(i): gate = np.kron(gate, np_had) test_vector = gate.dot(test_vector) #print(test_vector) t4 = time.time() times[0, i - min_qubits] = t2 - t1 times[1, i - min_qubits] = t4 - t3 memoryusage[1, i - min_qubits] = sys.getsizeof(gate) print( 'Time taken to superimpose all elements of an array using hadamard gates' ) print(times) print(memoryusage) fig1 = plt.figure() ax1 = fig1.add_axes([0.1, 0.1, 0.8, 0.8]) ax1.plot([i for i in range(min_qubits, max_qubits)], times[0], label='Lazy Implementation') ax1.plot([i for i in range(min_qubits, max_qubits)], times[1], label='Numpy Implementation') fig1.suptitle( "Runtime of Applying a Hadamard Gate to Every Qubit in a Register", fontsize='15') plt.xlabel("Number of Qubits", fontsize='14') plt.ylabel("Runtime (s)", fontsize='14') plt.yscale('log') plt.xlim((min_qubits, max_qubits - 1)) ax1.legend() plt.show()
def __cP(self, gate_info): mat = Sparse.ColMatrix(4) mat[0, 0] = 1 mat[1, 1] = 1 mat[2, 2] = 1 mat[3, 3] = np.exp(1j * gate_info[-1]) gate = Sparse.Gate(self.size, mat, list(gate_info)[:-1]) self.register.Statevec = gate.apply(self.register.Statevec.Elements)
def __cZ(self, gate_info): """ Creates and applies the matrix representing the controlled z operation on two qubits. :param gate_info: (tuple(int, int)) The qubits the controlled z flip applies to. """ gate = Sparse.Gate(self.size, self.large_gates['cz'], list(gate_info)) self.register.Statevec = gate.apply(self.register.Statevec.Elements)
def __cNot(self, gate_info): """ Creates and applies the matrix representing the controlled x operation on 2 qubits. :param gate_info: (tuple(int, int, int)) First int is the control qubit, last int is the controlled qubit. """ gate = Sparse.Gate(self.size, self.large_gates['cn'], list(gate_info)) self.register.Statevec = gate.apply(self.register.Statevec.Elements)
def __Swap(self, gate_info): """ Creates and applies the matrix representing the swap operation between two qubits. :param gate_info: (tuple(int, int)) The two gates to be swapped. """ gate = Sparse.Gate(self.size, self.large_gates['swap'], list(gate_info)) self.register.Statevec = gate.apply(self.register.Statevec.Elements)
def __Rt(self, theta, pos): """ Creates and applies the matrix representing the rotation gate on one qubit. :param theta: (float) Rotation angle. :param pos: (int) position of the qubit. """ mat = np.array([[1, 0], [0, np.exp(1j * theta)]]) gate = Sparse.Gate(self.size, Sparse.toColMat(mat), [pos]) self.register.Statevec = gate.apply(self.register.Statevec.Elements)
def __custom(self, gate_info): """ Creates and applies a custom, user defined matrix to the system :param gate_info: (tuple(int, int, str)) The ints represent the range of qubits the gate applies to, the string is the name of the gate. """ bits = [ i for i in range(min(list(gate_info)[:-1]), max(gate_info[:-1]) + 1) ] gate = Sparse.Gate(self.size, self.customgates[gate_info[-1]], bits) self.register.Statevec = gate.apply(self.register.Statevec.Elements)
def __NCP(self, gate_info): """ Creates and applies the matrix representing the n controlled phase operation on n qubits. :param gate_info: (tuple(int,..., int, float)) The qubits the rotation applies to, Float for the rotation angle. """ length = max(gate_info) - min(gate_info) + 1 ncp = Sparse.ColMatrix(2**(length)) for i in range(2**(length)): ncp[i, i] = 1 ncp[2**length - 1, 2**length - 1] = np.exp(gate_info[-1]) gate = Sparse.Gate(self.size, ncp, list(gate_info)[:-1]) self.register.Statevec = gate.apply(self.register.Statevec.Elements)
def __NCZ(self, gate_info): """ Creates and applies the matrix representing the n controlled z operation on n qubits. :param gate_info: (tuple(int,..., int)) The qubits the z flip applies to. """ length = max(gate_info) - min(gate_info) + 1 ncz = Sparse.ColMatrix(2**(length)) for i in range(2**(length)): ncz[i, i] = 1 ncz[2**length - 1, 2**length - 1] = -1 gate = Sparse.Gate(self.size, ncz, list(gate_info)) self.register.Statevec = gate.apply(self.register.Statevec.Elements)
def simulate(self): """ Simulates the quantum circuit by iterating through all of the gates and applyig them one by one. Slower for smaller circuit, but better for larger ones, especially with dense matrices. :return self.register: (QuantumRegister) The quantum register representing the system :return self.measurements: (2xn list of lists) Any measurements taken during the simulation """ self.gates = np.array(self.gates, dtype=object).T for i, row in enumerate(self.gates): for j, g in enumerate(row): if type(g) == tuple: self.__addBigGate(g, j) elif g == 's' or g == 'i': continue else: gate = Sparse.Gate(self.size, Sparse.toColMat(self.singlegates[g]), [j]) self.register.Statevec = gate.apply(self.register.Statevec) if i in self.measurements[0]: self.measurements[1].append(self.register.Statevec.Elements) return self.register, self.measurements
def tensorTest(): """ Checks the runtime of computing the Tensorproduct in three different ways. First using our own Lazy Matrix Implementation, second using Numpy Tensor Product of Gates and third using Numpy Tensor Product of individual qubits. """ # Change this depending on memory on computer used to run this. max_qubits = 14 qbits = np.arange(2, max_qubits + 1, 1) times = np.zeros((3, len(qbits))) had = Sparse.ColMatrix(2) had[0, 0] = 1 / np.sqrt(2) had[0, 1] = 1 / np.sqrt(2) had[1, 0] = 1 / np.sqrt(2) had[1, 1] = -1 / np.sqrt(2) for q in qbits: print( f"\nChecking the time it takes for each of the two implementations\nto do a tensor product of {q} items:" ) qbit_zero = Qubit(1, 0) reg = [] for i in range(q): reg.append(qbit_zero) state = Tensor(reg) state = state.to_state() test_vector = np.copy(state.vector) t1 = time.time() for i in range(q): gate = Sparse.Gate(2**q, had, [i]) test_vector = gate.apply( test_vector) # apply the hadamards to register? t2 = time.time() print(f"\nResult 1 :\n {test_vector} ") print(f"Time taken : {t2-t1} ") times[0, q - 2] = t2 - t1 t1 = time.time() reg = [] h_gate = Gate("Hadamard") for i in range(q): reg.append(h_gate) register = Tensor(reg) big_gate = register.to_gate( "Biggie") # basically what creates the Memory error state.apply_gate(big_gate) t2 = time.time() print(f"\nResult 2 :\n {state.vector.T} ") print(f"Time taken : {t2-t1} ") times[1, q - 2] = t2 - t1 t1 = time.time() qbit_zero = Qubit(1, 0) reg = [] h_gate = Gate("Hadamard") qbit_zero.apply_gate(h_gate) for i in range(q): reg.append( qbit_zero ) # doing it this way is much faster and gives the same result register = Tensor(reg) state = register.to_state() t2 = time.time() print(f"\nResult 3 :\n {state.vector.T} ") print(f"Time taken : {t2-t1} ") times[2, q - 2] = t2 - t1 plt.plot(qbits, times[0], label='Lazy') plt.plot(qbits, times[1], label='Numpy (Tensor Product of Gates)') plt.plot(qbits, times[2], label='Numpy (Tensor Product of Qubits)') plt.title("Runtime of Tensor product over Number of Qubits in the system") plt.xlabel("Number of Qubits") plt.ylabel("Runtime (s)") plt.yscale("log") plt.xticks(qbits) plt.legend() plt.show()