def ghz_state(n, noisy=False, kraus=None): """ This creates an n qubit ghz state :param n: The number of qubits in the state :param string: The string for the initial state of the density matrix e.g '000' produces a state where all the three qubits are in the ground state while '111' produces a state where all the qubits are the excited state :param ar: This is a list of numbers that specifies the various control and target position e.g ghz_state(4, '0000', [1,2,1,3,1,4]) creates two control operations with first qubit being the control and the second qubit being the target and the second operation has first being the control with the third qubit being the target third operation has the first qubit being the control and the fourth qubit being the target :param noisy: If true decoherence is added between gate applications :param kraus: This will be a 3 dimensional array of kraus matrices :return: returns the state of the qubit after the controlled x operations. This should be a ghz state. """ string = '0'*n q = qubit.Qubit(string, n) h_gate = op.superkron(g.h(), np.eye(pow(2, n-1))) q.state = np.dot(h_gate, np.dot(q.state, h_gate)) if noisy is False: for i in range(1, n+1): controlgate = g.c_u(g.x(), n, i, i + 1) q.state = np.dot(controlgate, np.dot(q.state, op.ctranspose(controlgate))) else: for i in range(1, n+1): controlgate = g.c_u(g.x(), n, i, i + 1) q.q_decohere(kraus, n) q.state = np.dot(controlgate, np.dot(q.state, op.ctranspose(controlgate))) return q
def clusterstate(n, noisy=False, kraus=None): """ This creates a cluster state :param n: This is the number of qubits in the system :param string: This is the string that determines the state of the initial system. e.g '0000' produces 4 qubit state being all in the ground state while '01010' produces a five qubits with qubits being in ground and excited states alternately :param ar: This is a list of numbers that specifies the various control and target position e.g clusterstate(4, '0000', [1,3,2,4]) creates two control operations with first qubit being the control and the third qubit being the target and the second operation has second being the control with the fourth qubit being the target. :param noisy: If true decoherence is added between gate applications :param kraus: This will be a 3 dimensional array of kraus matrices :return: returns the state of the qubit after the controlled z operations. This should be a cluster state """ string = '0'*n q = qubit.Qubit(string, n) q.state = np.dot(g.multi_hadamard(n), np.dot(q.state, g.multi_hadamard(n))) if noisy is False: for i in range(1, n+1): controlgate = g.c_u(g.z(), n, i, i+1) q.state = np.dot(controlgate, np.dot(q.state, op.ctranspose(controlgate))) else: for i in range(1, n+1): controlgate = g.c_u(g.z(), n, i, i+1) q.q_decohere(kraus, n) q.state = np.dot(controlgate, np.dot(q.state, op.ctranspose(controlgate))) return q
def __init__(self, string, n, no_measurment_qubits=1, left_justified=False): """ :param string: This should be a string of 1's and 0's where 1 is the |1> and 0 is |0> :param n: The number of qubits in the system :param no_measurment_qubits: The number of qubits that will be measured :return: Returns the Description of class variables state: This is the density matrix of the system classical_states: This is a dictionary of computational basis states that should have has its keys labels of computational basis states and values the corresponding probabilities no_measurement_qubits: The number of qubits you intend to measure classical_states_history: Has a key values all possible measurement syndromes and keeps a record of their probabilities single_kraus: These are the kraus operators used for a single qubit. They are used to create kraus operators for the n qubit system partial_bath: This is aa boolean variable. If true some qubits the last m qubits on the left will not decohere no_non_ideal_qubits: These are the number of qubits, k that will decohere. m+k must equal to n the number of qubits in the system hamiltonian: The hamiltonian of the system T: The total evolution time of the system state: The state of the quantum system for n qubits """ self.oper_dict = {'0': g.b1(), '1': g.b4(), '2': g.id()} self.state = op.superkron(self.oper_dict, val=1, string=string) self.n = n self.no_measurement_qubits = no_measurment_qubits self.measurement_string = op.createlabel(self.no_measurement_qubits, 2) for i in range(len(self.measurement_string)): if left_justified: self.measurement_string[i] = self.measurement_string[i].ljust(self.n, '2') else: self.measurement_string[i] = self.measurement_string[i].rjust(self.n, '2') # self.projectors = {i.replace('2', ''): op.superkron(self.oper_dict, val=1, string=i) for i in # self.measurement_string} self.classical_states = {i.replace('2', ''): 0 for i in self.measurement_string} self.classical_states_history = {i.replace('2', ''): [] for i in self.measurement_string} self.hamiltonian = None self.dt = None self.U = None self.xlabel = '' self.ylabel = '' self.T = None self.kraus_operators = [] self.expect_values = {} self.yield_kraus = None self.single_kraus = None self.partial_bath= False self.no_non_ideal_qubits = 0
def pta_ad(n, t, t1): """ Produces the kraus matrices for the pta channel :param n: number of qubit :param t: time step for evolution :param t1: relaxation time :return: returns a 3 dimensinal array of pta kraus matrices """ gamma = 1 - np.exp(-t / t1) px = py = gamma / 4.0 pz = 1.0 / 2.0 - py - np.sqrt(1 - gamma) / 2 pi = 1 - (px + py + pz) A = np.zeros((pow(4, n), pow(2, n), pow(2, n)), dtype=complex) # 3 dimensional array to store kraus matrices ptaOperators = { '0': np.sqrt(pi) * g.id(), '1': np.sqrt(px) * g.x(), '2': np.sqrt(py) * g.y(), '3': np.sqrt(pz) * g.z() } # get labels labels = op.createlabel(n, 4) for i in range(len(labels)): temp = 1 for digit in labels[i]: temp = np.kron(temp, ptaOperators[digit]) A[i] = temp return A
def kraus_pta(n, t, t1, t2): """ Produces the kraus matrices for the pta channel :param n: number of qubit :param t: time step for evolution :param t1: relaxation time :param t2: dephasing time :return: returns a 3 dimensinal array of pta kraus matrices """ gamma = 1 - np.exp(-t / t1) t_phi = Decimal(1 / t2) - Decimal(1 / (2 * t1)) # lambda1 = np.exp(-t/t1)*(1-np.exp(-2*(t/t_phi))) px = py = gamma / 4.0 pz = 1.0 / 2.0 - py - np.exp(-t / (2 * t1)) * np.exp(-(t / t_phi)) / 2 pi = 1 - (px + py + pz) print('px: ', py, 'pz: ', pz, 'pi: ', pi) A = np.zeros((pow(4, n), pow(2, n), pow(2, n)), dtype=complex) # 3 dimensional array to store kraus matrices ptaOperators = { '0': np.sqrt(pi) * g.id(), '1': np.sqrt(px) * g.x(), '2': np.sqrt(py) * g.y(), '3': np.sqrt(pz) * g.z() } # get labels labels = op.createlabel(n, 4) for i in range(len(labels)): temp = 1 for digit in labels[i]: temp = np.kron(temp, ptaOperators[digit]) A[i] = temp return A
def get_projectors(self, measure_strings): """ :return: """ if isinstance(measure_strings, list): for i in measure_strings: yield op.superkron(self.oper_dict, val=1, string=i)
def pauli_group(n, full=False, normalize=True): """ :param n: number of qubits :param full: If true returns the full pauli group for n qubits including group elements that differ by center of the group :param normalize: If true returns pauli group elements so that group are normalized :return: Returns a dictionary of unitary representation of the single qubit pauli group """ if normalize: pauli_matrix = { 'I': id() / sqrt(2), 'X': x() / sqrt(2), 'Y': y() / sqrt(2), 'Z': z() / sqrt(2) } else: pauli_matrix = {'I': id(), 'X': x(), 'Y': y(), 'Z': z()} center = {'i': 1j, '-i': -1j, '1': 1, '-1': -1} pauli_labels = [''.join(i) for i in product('IXYZ', repeat=n)] qubit_group = {} pauli_dict = {} if full is False: for pl in pauli_labels: pauli_dict[pl] = op.superkron(pauli_matrix, val=1, string=pl) else: for i in center: for p in pauli_dict: qubit_group[str(i) + str(p)] = dot(center[i], pauli_dict[p]) return pauli_dict
def circuit_unitary(self): """ :return: Returns the unitary for the circuit """ out = np.eye(2**self.n, dtype='complex128') for i in self.bucket: out = np.dot(out, self.step_operator(i)) out = op.ctranspose(out) return out
def apply_kraus(K, rho_s, n, non_ideal_qubits=0, partial_bath=False): """ :param K: List of kraus operators :param rho: density matrix of system :param n: Number of qubits :return: Return evolved density matrix """ out = np.zeros((pow(2, n), pow(2, n)), dtype=complex) if partial_bath: ideal_qubits_oper = {'0': g.id()} id_oper = op.superkron(ideal_qubits_oper, val=1, string='0' * (n - non_ideal_qubits)) for kraus in K: new_kraus = op.superkron(kraus, id_oper) out += np.dot(new_kraus, np.dot(rho_s, op.ctranspose(new_kraus))) return out else: for kraus in K: out += np.dot(kraus, np.dot(rho_s, op.ctranspose(kraus))) return out
def bakersmap(n): """ :param n: The number of qubits :return: """ q = qft(n) q_1 = qft(n / 2) out = op.ctranspose(q) * kron(id(), q_1) return out
def c_u(u, n, i, j): """ This creates a controlled unitary operation on n qubits :param u: Unitary matrix :param n: The number of qubits to be used :param i: the position of control qubit for the controlled operation :param j: the position of target qubit for the controlled operation :return: the controlled operation """ term_1 = {"0": id(), "1": e_ij((2, 2), 1, 1)} term_2 = {"0": id(), "2": u, "1": e_ij((2, 2), 2, 2)} # What happens when the control qubit is in the zero state label_1 = op.generatetensorstring(n, i) cu_1 = op.superkron(term_1, val=1, string=label_1) # What happens when the control bit is in the one state label_2 = op.controlgatestring(n, ('1', i), ('2', j)) cu_2 = op.superkron(term_2, val=1, string=label_2) return cu_1 + cu_2
def measure_basis(self, pauli_string='', undo_basis=False): """ :param pauli_string: Basis in which to make the measurement :param undo_basis: Boolean variable :return: """ measurement_operator = 1 if pauli_string != '' and undo_basis is False: for char in pauli_string: if char == 'X': measurement_operator = op.superkron(measurement_operator, g.r_y(-np.pi / 2)) elif char == 'Y': measurement_operator = op.superkron(measurement_operator, g.r_x(np.pi / 2)) else: measurement_operator = op.superkron(measurement_operator, g.id()) self.operator(measurement_operator) elif pauli_string != '' and undo_basis is True: for char in pauli_string: if char == 'X': measurement_operator = op.superkron(measurement_operator, g.r_y(np.pi / 2)) elif char == 'Y': measurement_operator = op.superkron(measurement_operator, g.r_x(-np.pi / 2)) else: measurement_operator = op.superkron(measurement_operator, g.id()) self.operator(measurement_operator) else: pass
def stabilizer(stabilizer, n, ancilla_label): """ Stabilizer must contain only X and Z operators :param stabilizer: The stabilizer you want to measure :param ancilla_label: The ancilla_label qubit used to measure the stabilizer :param n: The number of data qubits in the circuit :return: A unitary that represents a quantum circuit that is used to measure a specific stabilizer, using CNOT and Hadamard gates """ # The numbering of qubits starts from 1 rather than 0 stabilizer_dict = {} oper_dict = {'0': id(), '1': h()} unitary = identity(pow(2, n)) for counter, s in enumerate(stabilizer, 1): if s == 'I' or s == 'i': continue else: stabilizer_dict[counter] = s for s in stabilizer_dict: if stabilizer_dict[s] == 'Z' or stabilizer_dict[s] == 'z': unitary = dot(unitary, c_u(x(), n, s, ancilla_label)) elif stabilizer_dict[s] == 'X' or stabilizer_dict[s] == 'x': string = op.generatehamiltoniantring(n, '1', onestring=True, pos=s - 1, pad='0') unitary = dot(unitary, op.superkron(oper_dict, val=1, string=string)) unitary = dot(unitary, c_u(x(), n, s, ancilla_label)) unitary = dot(unitary, op.superkron(oper_dict, val=1, string=string)) return unitary
def time_step_evolve(self, basis=''): """ :param h: Hamiltonian by which to evolve the system :param dt: time step to evolve by could be small or large :param basis: Basis of measurement :return: returns the state of qubit after evolution """ self.U = expm(-1j * self.hamiltonian * self.dt) self.state = np.dot(self.U, np.dot(self.state, op.ctranspose(self.U))) for state, projector in zip(self.classical_states, self.get_projectors(self.measurement_string)): # Possibly change basis of measurement before calculating probability history. self.measure_basis(pauli_string=basis) self.classical_states_history[state].append(np.trace(np.dot(self.state, projector)).real) self.measure_basis(pauli_string=basis, undo_basis=True)
def kraus_generator(n, non_ideal_qubits=0, classical_error=False, partialbath=False, opers=[]): """ :param n: :param classical_error: :param opers: :param prob_error: :return: """ num_operators = len(opers) kraus_operators = {str(i): opers[i] for i in range(len(opers))} if classical_error is False and partialbath is False: labels = op.createlabel(n, num_operators) for i in range(len(labels)): kraus = 1 for digit in labels[i]: kraus = np.kron(kraus, kraus_operators[digit]) yield kraus elif classical_error is False and partialbath: labels = op.createlabel(non_ideal_qubits, num_operators) for i in range(len(labels)): kraus = 1 for digit in labels[i]: kraus = np.kron(kraus, kraus_operators[digit]) yield kraus
def serial_decohere(K, rho_s, n_s): """ :param K: List of kraus operators :param rho: density matrix of system :param n: Number of qubits :return: Return evolved density matrix """ K = list(K) out = np.zeros((pow(2, n_s), pow(2, n_s)), dtype=complex) try: assert type(K) == list for i in range(len(K)): out += np.dot(K[i], np.dot(rho_s, op.ctranspose(K[i]))) except: raise TypeError('The input K must be a list of numpy arrays') return out
def q_Hamiltonian(ham, n, s): """ :param ham: hamiltonian by which the qubits will evolve by :param s : must be a string of ones e.g 010 represents id (tensor)ham(tensor)id while 10 represents ham(tensor) id :param n : The length of the string. This determines how many zeros there will be :return: """ label = op.generatehamiltoniantring(n, s) a = zeros((pow(2, n), pow(2, n)), dtype=complex) terms = {"0": id(), "1": ham} for qubit in range(len(label)): tmp = 1 for digit in label[qubit]: tmp = kron(tmp, terms[digit]) a += tmp return a
def step_operator(self, step): """ :param step: The step in the circuit :return: Returns a the operator for the particular step in the circuit """ op_list = [] if isinstance(step, str): for s in self.bucket[step]: op_list.append(self.gate_list[s]) operators = list(map(eval, op_list)) for i in range(len(operators)): if self.check_signature(operators[i]) is not []: arg = self.arg_bucket[step][operators[i].__name__] operators[i] = operators[i](*arg) else: operators[i] = operators[i]() o = op.superkron(*operators) return o
def generic_kraus(n, classical_error=False, opers=[], prob_error=[], generator=False): """ This functions takes as an input generic operators and outputs a corresponding list of kraus operators for those n qubits. It also can do a classical simulation of noise by throwing down errors classically :param n: The number of qubits experiencing the noise :param classical_error: If True the user must supply list of probabilities for list in oper :param opers: The list of kraus operators that appear in the sum :param prob_error: The list of probabilities for each operator in oper. Entries must add to 1. First operator is returned with probability in first position of prob_error. :param generator: Function becomes a generator in order to save memeory when a large number of qubits is being used :return: """ num_operators = len(opers) if classical_error is False: A = np.zeros((pow(num_operators, n), pow(2, n), pow(2, n)), dtype=complex) # 3 dimensional array to store kraus matrices kraus_operators = {str(i): opers[i] for i in range(len(opers))} labels = op.createlabel(n, num_operators) for i in range(len(labels)): temp=1 for digit in labels[i]: temp = np.kron(temp, kraus_operators[digit]) A[i] = temp return A if classical_error: # Returns a 3 dimensional array which stores only one kraus matrix A = np.zeros((pow(num_operators, 0), pow(2, n), pow(2, n)), dtype=complex) # 3 dimensional array to store kraus matrices if len(prob_error) != 0 or len(prob_error) != len(opers): if math.isclose(sum(prob_error), 1, rel_tol=1e-4): operators = {i: prob_error[i]*100 for i in range(len(prob_error))} lea_object = pmf(operators) picked_operator = lea_object.random() A[0] = opers[picked_operator] return A else: raise Exception('Probabilities must add to 1') else: raise Exception('prob_error list is empty or does not equal oper list')
def noise_op(self, same_bath=True, relax=True, dephase=False, create=False): noise_op_string_list = op.generatehamiltoniantring(self.n, '1') for qubit, string in enumerate(noise_op_string_list): for char in string: self.c_op[str(qubit)].append(self.annihilate[char]) self.dephase_op[str(qubit)].append(self.dephase[char]) self.create_op[str(qubit)].append(self.create[char]) for q, q1, q2 in zip(self.c_op, self.dephase_op, self.create_op): if relax: self.c_op[q] = tensor(*self.c_op[q]) if dephase: self.dephase_op[q1] = tensor(*self.dephase_op[q1]) if create: self.create_op[q2] = tensor(*self.create_op[q2]) self.collap.append(self.create_op[q2]) if same_bath: if relax: for qs in self.c_op: self.c_op[qs] = np.sqrt(1 / self.t1[0]) * self.c_op[qs] self.collap.append(self.c_op[qs]) if dephase: for qs in self.dephase_op: self.dephase_op[qs] = np.sqrt(1 / (2 * self.t2[0])) * self.dephase_op[qs] self.collap.append(self.dephase_op[qs]) if create: for qs in self.dephase_op: self.create_op[qs] = np.sqrt(1 / (self.t3[0])) * self.create_op[qs] self.collap.append(self.create_op[qs]) if same_bath is False: if relax: for t1_list, qs in enumerate(self.c_op): self.c_op[qs] = np.sqrt(1 / self.t1[t1_list]) * self.c_op[qs] self.collap.append(self.c_op[qs]) if dephase: for t2_list, qs in enumerate(self.dephase_op): self.dephase_op[qs] = np.sqrt(1 / (2 * self.t2[t2_list])) * self.dephase_op[qs] self.collap.append(self.dephase_op[qs]) if create: for t3_list, qs in enumerate(self.create_op): self.create_op[qs] = np.sqrt(1 / self.t3[t3_list]) * self.create_op[qs] self.collap.append(self.create_op[qs])
def kraus_exact(n, t, t1, t2, markovian=False, alpha=None): """ Produces the kraus matrices for the exact evolution of amplitude damping with dephasing channel :param n: number of qubit :param t: time step for evolution :param t1: relaxation time :param t2: dephasing time must be smaller than t1 :param markovian: If true the kraus matrices are for non markovian evolution and t2 takes the role of t_phi :param alpha: The power of 1/f^{alpha} flux noise :return: a 3 dimensinal array of kraus matrices with amplitude damping and dephasing """ A = np.zeros((pow(3, n), pow(2, n), pow(2, n)), dtype=complex) # 3 dimensional array to store kraus matrices gamma = 1 - np.exp(-t / t1) if markovian: t_phi = 1 / t2 - 1 / (2 * t1) lambda1 = np.exp(-t / t1) * (1 - np.exp(-2 * (t / t_phi))) print('We are markovian') else: print('We are non markovian') t_phi = t2 lambda1 = np.exp(-t / t1) * (1 - np.exp(-2 * (t / t_phi) ** (1 + alpha))) krausOperators = { "0": np.array([[1, 0], [0, np.sqrt(1 - gamma - lambda1)]]), "1": np.array([[0, np.sqrt(gamma)], [0, 0]]), "2": np.array([[0, 0], [0, np.sqrt(lambda1)]]), } labels = op.createlabel(n, 3) for i in range(len(labels)): temp = 1 for digit in labels[i]: temp = np.kron(temp, krausOperators[digit]) A[i] = temp return A
def kraus_ad(n, t, t1): """ Produces the kraus matrices for the amplitude damping channel :param n: number of qubit :param t: time step for evolution :param t1: relaxation time :return: returns a 3 dimensinal array of amplitude damping kraus matrices """ A = np.zeros((pow(2, n), pow(2, n), pow(2, n)), dtype=complex) # 3 dimensional array to store kraus matrices gamma = 1 - np.exp(-t / t1) adOperators = { "0": np.array([[1, 0], [0, np.sqrt(1 - gamma)]]), "1": np.array([[0, np.sqrt(gamma)], [0, 0]]) } labels = op.createlabel(n, 2) for i in range(len(labels)): temp = 1 for digit in labels[i]: temp = np.kron(temp, adOperators[digit]) A[i] = temp return A
init = '111' oper_dict = {'0': g.z()} E = [np.array([[1, 0], [0, np.cos(np.pi/10)]]), np.array([[0, np.sin(np.pi/10)], [0, 0]])] # k = ne.generic_kraus(1, classical_error=False, opers=E) # generator_kraus = ne.kraus_generator(1, classical_error=False, opers=E) # m = [op.superkron(k[i]) for i in range(len(k))] q2 = Qubit(init, 3, no_measurment_qubits=3) q2.single_kraus = E q2.dt = 0.01 q2.T = 1 q2.partial_bath = True q2.no_non_ideal_qubits = 2 # k = ne.kraus_ad(q2.n, 0.001, 10 ** (-2)) # k = ne.kraus_exact(q2.n, q2.dt, 10**(-6), 10**(-6), markovian=True) # q2.kraus_operators = m # q2.yield_kraus = generator_kraus q2.xlabel = 'time' q2.ylabel = 'Probability' q2.hamiltonian = 0.5*op.superkron(oper_dict, val=1, string='000') q2.evolve(basis='ZZZ') # print('Returned state : ', q2.measure(return_state=True)) # print('Probability distribution :', q2.classical_states) # q2.expectation_values({'X':op.superkron(g.z(), g.z(), g.z(), g.z(), g.z()), 'Y':op.superkron(g.y(), g.y(), g.y(), g.y(), g.y())}) q2.graph('111') q2.graph('011') q2.graph('001') q2.graph('000') # print(q2.expect_values)
out = np.zeros((pow(2, n), pow(2, n)), dtype=complex) if partial_bath: ideal_qubits_oper = {'0': g.id()} id_oper = op.superkron(ideal_qubits_oper, val=1, string='0' * (n - non_ideal_qubits)) for kraus in K: new_kraus = op.superkron(kraus, id_oper) out += np.dot(new_kraus, np.dot(rho_s, op.ctranspose(new_kraus))) return out else: for kraus in K: out += np.dot(kraus, np.dot(rho_s, op.ctranspose(kraus))) return out if __name__ == '__main__': state = op.superkron(g.b4(), g.b4()) state_1 = op.superkron(g.b4(), g.b4()) E = [np.array([[1, 0], [0, np.cos(np.pi / 5)]]), np.array([[0, np.sin(np.pi / 5)], [0, 0]])] kraus = generic_kraus(2, classical_error=False, opers=E) m = [op.superkron(kraus[i]) for i in range(len(kraus))] for i in range(4): print(i) generator_kraus = kraus_generator(2, non_ideal_qubits=1, partialbath=True, classical_error=False, opers=E) state = apply_kraus(generator_kraus, state, 2, non_ideal_qubits=1, partial_bath=True) print('New Method:', state) for i in range(4): state_1 = serial_decohere(m, state_1, 2) print('Old Method: ', state_1)
def apply_op(self, op): self.psi = op * self.psi self.rho = op * self.rho * op.dag()
def operator(self, o): """ :param o: The operator you want applied to the qubit :return: Returns the transformed density matrix after the operation """ self.state = np.dot(o, np.dot(self.state, op.ctranspose(o)))
out = np.dot(out, self.step_operator(i)) out = op.ctranspose(out) return out def apply_unitary(self): """ Applies unitary to the quantum state """ U = self.circuit_unitary() self.qubits.operator(U) if __name__ == '__main__': # This should create a 3 qubit GHZ circuit xxx = op.superkron(g.x(), g.x(), g.x()) c = Circuit('000', measurement_qubits=3) c.add_step('h,id,id', arg_list=[[], [], []]) c.add_step('c_u', arg_list=[[g.x(), 3, 1, 2]]) c.add_step('c_u', arg_list=[[g.x(), 3, 2, 3]]) ghz = c.qubits.state # print('GHZ state : ', ghz) # This should be a Toffoli gate circuit d = Circuit('110', measurement_qubits=3) d.add_step('x,id,id', arg_list=[[], [], []]) d.add_step('id,id,r_y', arg_list=[[], [], [g.pi / 4]]) d.add_step('c_u', arg_list=[[g.x(), 3, 2, 3]]) d.add_step('id,id,r_y', arg_list=[[], [], [g.pi / 4]]) d.add_step('id,id,x', arg_list=[[], [], []])