Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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()