def set_relationship(self, relationships, qubit0, qubit1, fraction=1, update=True): ''' Rotates the given pair of qubits towards the given target expectation values. Args: target_state: Target expectation values. qubit0, qubit1: Qubits on which the operation is applied fraction: fraction of the rotation toward the target state to apply. update: whether to update the tomography after the rotation is added to the circuit. ''' zero = 0.001 def inner(vec1, vec2): inner = 0 for j in range(len(vec1)): inner += conj(vec1[j]) * vec2[j] return inner def normalize(vec): renorm = sqrt(inner(vec, vec)) if abs((renorm * conj(renorm))) > zero: return np.copy(vec) / renorm else: return [nan for amp in vec] def random_vector(ortho_vecs=[]): vec = np.array([2 * random() - 1 for _ in range(4)], dtype='complex') vec[0] = abs(vec[0]) for ortho_vec in ortho_vecs: vec -= inner(ortho_vec, vec) * ortho_vec return normalize(vec) def get_rho(qubit0, qubit1): if type(self.backend) == ExpectationValue: rel = self.get_relationship(qubit0, qubit1) b0 = self.get_bloch(qubit0) b1 = self.get_bloch(qubit1) rho = np.identity(4, dtype='complex128') for pauli in ['X', 'Y', 'Z']: rho += b0[pauli] * matrices[pauli + 'I'] rho += b1[pauli] * matrices['I' + pauli] for pauli in [ 'XX', 'XY', 'XZ', 'YX', 'YY', 'YZ', 'ZX', 'ZY', 'ZZ' ]: rho += rel[pauli] * matrices[pauli] return rho / 4 else: (q0, q1) = sorted([qubit0, qubit1]) return self.tomography.fit(output='density_matrix', pairs_list=[(q0, q1)])[q0, q1] raw_vals, raw_vecs = la.eigh(get_rho(qubit0, qubit1)) vals = sorted([(val, k) for k, val in enumerate(raw_vals)], reverse=True) vecs = [[raw_vecs[j][k] for j in range(4)] for (val, k) in vals] Pup = np.identity(4, dtype='complex') for (pauli, sign) in relationships.items(): Pup = dot(Pup, (matrices['II'] + sign * matrices[pauli]) / 2) Pdown = (matrices['II'] - Pup) new_vecs = [[nan for _ in range(4)] for _ in range(4)] valid = [False for _ in range(4)] # the first new vector comes from projecting the first eigenvector vec = np.copy(vecs[0]) while not valid[0]: new_vecs[0] = normalize(dot(Pup, vec)) valid[0] = True not in [isnan(new_vecs[0][j]) for j in range(4)] # if that doesn't work, a random vector is projected instead vec = random_vector() # the second is found by similarly projecting the second eigenvector # and then finding the component orthogonal to new_vecs[0] vec = dot(Pup, vecs[1]) while not valid[1]: new_vecs[1] = vec - inner(new_vecs[0], vec) * new_vecs[0] new_vecs[1] = normalize(new_vecs[1]) valid[1] = True not in [isnan(new_vecs[1][j]) for j in range(4)] # if that doesn't work, start with a random one instead vec = random_vector() # the third is the projection of the third eigenvector to the subpace orthogonal to the first two vec = np.copy(vecs[2]) for j in range(2): vec -= inner(new_vecs[j], vec) * new_vecs[j] while not valid[2]: new_vecs[2] = normalize(vec) valid[2] = True not in [isnan(new_vecs[2][j]) for j in range(4)] # if that doesn't work, use a random vector orthogonal to the first two vec = random_vector(ortho_vecs=[new_vecs[0], new_vecs[1]]) # the last is just orthogonal to the rest vec = normalize(dot(Pdown, vecs[3])) while not valid[3]: new_vecs[3] = random_vector( ortho_vecs=[new_vecs[0], new_vecs[1], new_vecs[2]]) valid[3] = True not in [isnan(new_vecs[3][j]) for j in range(4)] # a unitary is then constructed to rotate the old basis into the new U = [[0 for _ in range(4)] for _ in range(4)] for j in range(4): U += outer(new_vecs[j], conj(vecs[j])) if fraction != 1: U = pwr(U, fraction) try: circuit = two_qubit_cnot_decompose(U) gate = circuit.to_instruction() done = True except Exception as e: print(e) gate = None if gate: self.qc.append(gate, [qubit0, qubit1]) if update: self.update_tomography() return gate
def set_bloch(self, target_expect, qubit, fraction=1, update=True): ''' Rotates the given qubit towards the given target state. Args: target_state: Expectation values of the target state. qubit: Qubit on which the operation is applied fraction: fraction of the rotation toward the target state to apply. update: whether to update the tomography after the rotation is added to the circuit. ''' def basis_change(pole, basis, qubit, dagger=False): ''' Returns the circuit required to change from the Z basis to the eigenbasis of a particular Pauli. The opposite is done when `dagger=True`. ''' if pole == '+' and dagger == True: self.qc.x(qubit) if basis == 'X': self.qc.h(qubit) elif basis == 'Y': if dagger: self.qc.rx(-pi / 2, qubit) else: self.qc.rx(pi / 2, qubit) if pole == '+' and dagger == False: self.qc.x(qubit) def normalize(expect): ''' Returns the given expectation values after normalization. ''' R = sqrt(expect['X']**2 + expect['Y']**2 + expect['Z']**2) return {pauli: expect[pauli] / R for pauli in expect} def get_basis(expect): ''' Get the eigenbasis of the density matrix for a the given expectation values. ''' normalized_expect = normalize(expect) theta = arccos(normalized_expect['Z']) phi = arctan2(normalized_expect['Y'], normalized_expect['X']) state0 = [cos(theta / 2), exp(1j * phi) * sin(theta / 2)] state1 = [conj(state0[1]), -conj(state0[0])] return [state0, state1] # add in missing zeros for pauli in ['X', 'Y', 'Z']: if pauli not in target_expect: target_expect[pauli] = 0 # determine the unitary which rotates as close to the target state as possible current_basis = get_basis(self.get_bloch(qubit)) target_basis = get_basis(target_expect) U = array([[0 for _ in range(2)] for _ in range(2)], dtype=complex) for i in range(2): for j in range(2): for k in range(2): U[j][k] += target_basis[i][j] * conj(current_basis[i][k]) # get the unitary for the desired fraction of the rotation if fraction != 1: U = pwr(U, fraction) # apply the corresponding gate the, phi, lam = OneQubitEulerDecomposer().angles(U) self.qc.u3(the, phi, lam, qubit) if update: self.update_tomography()