Exemple #1
0
def sk_hamiltonian(n, p=3, use_Z2_symmetry=False, use_degenerate=True, verbose=False):
    w = []

    if use_Z2_symmetry:
        c = np.zeros([2**(n-1), 1])
    else:
        c = np.zeros([2**n, 1])

    z = np.expand_dims(np.diagonal(qubit.Z), axis=0).T

    def my_eye(n):
        return np.ones((np.asarray(2) ** n, 1))
    weights = [-1, 1]
    if n%2 == 1 and use_Z2_symmetry:
        raise Exception('n odd hamiltonians are not Z2 symmetric')
    for i in itertools.combinations(range(n), p):
        op = []
        i = sorted(i)
        for j in range(len(i)):
            if use_Z2_symmetry:
                if j == 0:
                    if i[j] != 0:
                        op.append(my_eye(i[j]-1))
                        op.append(z)
                elif j != len(i)-1:
                    op.append(my_eye(i[j]-i[j-1]-1))
                    op.append(z)
                else:
                    op.append(my_eye(i[j] - i[j - 1] - 1))
                    op.append(z)
                    op.append(my_eye(n-i[j]-1))
            else:
                if j == 0:
                    op.append(my_eye(i[j]))
                    op.append(z)
                elif j != len(i) - 1:
                    op.append(my_eye(i[j] - i[j - 1] - 1))
                    op.append(z)
                else:
                    op.append(my_eye(i[j] - i[j - 1] - 1))
                    op.append(z)
                    op.append(my_eye(n - i[j] - 1))
        if use_Z2_symmetry:
            weight = weights[np.random.randint(2)]
            w.append(weight)
            c = c - weight * (tools.tensor_product(op))
        else:
            weight = weights[np.random.randint(2)]
            w.append(weight)
            c = c - weight * (tools.tensor_product(op))
    if use_degenerate:
        return c
    else:
        # Check that the result is not degenerate
        if len(ground_states(c)) == 1:
            return c
        else:
            if verbose:
                print('degeneracy', len(ground_states(c)))
            return sk_hamiltonian(n, p=p, use_Z2_symmetry=use_Z2_symmetry, use_degenerate=use_degenerate, verbose=verbose)
Exemple #2
0
def rotation(state: State,
             apply_to: Union[int, list],
             angle: float,
             op,
             is_involutary=False,
             is_idempotent=False):
    """
    Apply a single qubit rotation :math:`e^{-i \\alpha A}` to the input ``codes``.

    :param apply_to:
    :param is_idempotent:
    :param is_involutary:
    :param state: input wavefunction or density matrix
    :type state: np.ndarray
    :param angle: The angle :math:`\\alpha`` to rotate by.
    :type angle: float
    :param op: Operator to act with.
    :type op: np.ndarray
    """
    if isinstance(apply_to, int):
        apply_to = [apply_to]
    if not isinstance(op, list):
        op = [op]
    # Type handling: to determine if Pauli, check if a list of strings
    pauli = False
    if isinstance(op, list):
        if all(isinstance(elem, str) for elem in op):
            pauli = True
        else:
            op = tensor_product(op)
    if pauli:
        # Construct operator to use
        temp = []
        for i in range(len(op)):
            if op[i] == 'X':
                temp.append(X)
            elif op[i] == 'Y':
                temp.append(Y)
            elif op[i] == 'Z':
                temp.append(Z)
        temp = tensor_product(temp)
        temp = np.cos(angle) * np.identity(
            temp.shape[0]) - temp * 1j * np.sin(angle)
        return multiply(state, apply_to, temp)
    else:
        if is_involutary:
            op = np.cos(angle) * np.identity(
                op.shape[0]) - op * 1j * np.sin(angle)
            return multiply(state, apply_to, op)
        elif is_idempotent:
            op = (np.exp(-1j * angle) - 1) * op + np.identity(op.shape[0])
            return multiply(state, apply_to, op)
        else:
            return multiply(state, apply_to, expm(-1j * angle * op))
Exemple #3
0
 def run(self, param, initial_state=None):
     if self.code.logical_code and initial_state is None:
         initial_state = State(tensor_product([self.code.logical_basis[1]] *
                                              self.N),
                               code=self.code)
     elif initial_state is None:
         if isinstance(self.cost_hamiltonian, HamiltonianMIS):
             initial_state = State(np.zeros(
                 (self.cost_hamiltonian.hamiltonian.shape[0], 1)),
                                   code=self.code)
             initial_state[-1, -1] = 1
         else:
             initial_state = State(
                 np.ones((self.cost_hamiltonian.hamiltonian.shape[0], 1)) /
                 np.sqrt(self.cost_hamiltonian.hamiltonian.shape[0]),
                 code=self.code)
     if not (self.noise_model is None or self.noise_model == 'monte_carlo'):
         # Initial s should be a density matrix
         initial_state = State(outer_product(initial_state, initial_state),
                               code=self.code)
     s = initial_state
     for j in range(self.depth):
         s = self.hamiltonian[j].evolve(s, param[j])
         if self.noise_model is not None:
             if self.noise[j] is not None:
                 s = self.noise[j].evolve(s, param[j])
     # Return the expected value of the cost function
     # Note that the codes's defined expectation function won't work here due to the shape of C
     return self.cost_hamiltonian.cost_function(s)
Exemple #4
0
def multiply(state: State, apply_to: Union[int, list], op):
    """
    Apply a multi-qubit operator on several qubits (indexed in apply_to) of the input codes.
    :param state: input wavefunction or density matrix
    :type state: np.ndarray
    :param apply_to: zero-based indices of qudit locations to apply the operator
    :type apply_to: list of int
    :param op: Operator to act with.
    :type op: np.ndarray (2-dimensional)
    """
    # Type handling
    if isinstance(apply_to, int):
        apply_to = [apply_to]
    if not isinstance(op, list):
        op = [op]
    pauli = False
    if isinstance(op, list):
        if all(isinstance(elem, str) for elem in op):
            pauli = True
        else:
            op = tensor_product(op)
    if not state.is_ket:
        if pauli:
            out = state.copy()
            for i in range(len(apply_to)):
                ind = d**apply_to[i]
                out = out.reshape(
                    (-1, d, d**(state.number_physical_qudits - 1), d, ind),
                    order='F')
                if op[i] == 'X':  # Sigma_X
                    out = np.flip(out, axis=(1, 3))
                elif op[i] == 'Y':  # Sigma_Y
                    out = np.flip(out, axis=(1, 3))
                    out[:, d - 1, :, 0, :] = -out[:, d - 1, :, 0, :]
                    out[:, 0, :, d - 1, :] = -out[:, 0, :, d - 1, :]
                elif op[i] == 'Z':  # Sigma_Z
                    out[:, d - 1, :, 0, :] = -out[:, d - 1, :, 0, :]
                    out[:, 0, :, d - 1, :] = -out[:, 0, :, d - 1, :]

            out = out.reshape(state.shape, order='F')
            return State(out,
                         is_ket=state.is_ket,
                         IS_subspace=state.IS_subspace,
                         code=state.code)
        else:
            # Note that the conjugate transpose it taken automatically in right_multiply
            return right_multiply(left_multiply(state, apply_to, op), apply_to,
                                  op)
    else:
        return left_multiply(state, apply_to, op)
Exemple #5
0
def IS_projector(graph, code):
    """Returns a projector (represented as a column vector or matrix) into the space of independent sets for
    general codes."""
    n = graph.n
    # Check if U is diagonal
    if tools.is_diagonal(code.U):
        U = np.diag(code.U)
        proj = np.ones(code.d**n)
        for i, j in graph.edges:
            if i > j:
                # Requires i < j
                temp = i
                i = j
                j = temp
            temp = tools.tensor_product([
                np.ones(code.d**i), U,
                np.ones(code.d**(j - i - 1)), U,
                np.ones(code.d**(n - j - 1))
            ])
            proj = proj * (np.ones(code.d**n) - temp)
        return np.array([proj]).T
    else:
        # TODO: make this output a sparse matrix
        proj = np.identity(code.d**n)
        for i, j in graph.edges:
            if i > j:
                # Requires i < j
                temp = i
                i = j
                j = temp
            temp = tools.tensor_product([
                tools.identity(i, d=code.d), code.U,
                tools.identity(j - i - 1, d=code.d), code.U,
                tools.identity(n - j - 1, d=code.d)
            ])
            proj = proj @ (np.identity(code.d**n) - temp)
        return np.array([np.diagonal(proj)]).T
Exemple #6
0
def sk_p3_instance():
    n = 18
    use_Z2_symmetry = False
    w = [1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1]

    if use_Z2_symmetry:
        c = np.zeros([2**(n-1), 1])
    else:
        c = np.zeros([2**n, 1])

    z = np.expand_dims(np.diagonal(qubit.Z), axis=0).T

    def my_eye(n):
        return np.ones((np.asarray(2) ** n, 1))
    k = 0
    if n%2 == 1 and use_Z2_symmetry:
        raise Exception('n odd hamiltonians are not Z2 symmetric')
    for i in itertools.combinations(range(n), 3):
        op = []
        i = sorted(i)
        for j in range(len(i)):
            if use_Z2_symmetry:
                if j == 0:
                    if i[j] != 0:
                        op.append(my_eye(i[j]-1))
                        op.append(z)
                elif j != len(i)-1:
                    op.append(my_eye(i[j]-i[j-1]-1))
                    op.append(z)
                else:
                    op.append(my_eye(i[j] - i[j - 1] - 1))
                    op.append(z)
                    op.append(my_eye(n-i[j]-1))
            else:
                if j == 0:
                    op.append(my_eye(i[j]))
                    op.append(z)
                elif j != len(i) - 1:
                    op.append(my_eye(i[j] - i[j - 1] - 1))
                    op.append(z)
                else:
                    op.append(my_eye(i[j] - i[j - 1] - 1))
                    op.append(z)
                    op.append(my_eye(n - i[j] - 1))

        weight = w[k]
        c = c - weight * (tools.tensor_product(op))
        k += 1
    return c
Exemple #7
0
    def variational_grad(self, param, initial_state=None):
        """Calculate the objective function F and its gradient exactly
            Input:
                param = parameters of QAOA

            Output: (F, Fgrad)
               F = <HamC> for minimization
               Fgrad = gradient of F with respect to param
        """
        # TODO: make this work for continuous noise models
        if self.noise_model == 'continuous':
            raise NotImplementedError(
                'Variational gradient does not currently support continuous noise model'
            )
        param = np.asarray(param)
        # Preallocate space for storing copies of wavefunction - necessary for efficient computation of analytic
        # gradient
        if self.code.logical_code and initial_state is None:
            if isinstance(self.cost_hamiltonian, HamiltonianMIS):
                initial_state = State(tensor_product(
                    [self.code.logical_basis[1]] * self.N),
                                      code=self.code)
        elif initial_state is None:
            if isinstance(self.cost_hamiltonian, HamiltonianMIS):
                initial_state = State(np.zeros(
                    (self.cost_hamiltonian.hamiltonian.shape[0], 1)),
                                      code=self.code)
                initial_state[-1, -1] = 1
            else:
                initial_state = State(
                    np.ones((self.cost_hamiltonian.hamiltonian.shape[0], 1)) /
                    np.sqrt(self.cost_hamiltonian.hamiltonian.shape[0]),
                    code=self.code)
        if not (self.noise_model is None or self.noise_model == 'monte_carlo'):
            # Initial s should be a density matrix
            initial_state = State(outer_product(initial_state, initial_state),
                                  code=self.code)
        psi = initial_state
        if initial_state.is_ket:
            memo = np.zeros([psi.shape[0], 2 * self.depth + 2],
                            dtype=np.complex128)
            memo[:, 0] = np.squeeze(psi.T)
            tester = psi.copy()
        else:
            memo = np.zeros([psi.shape[0], psi.shape[0], self.depth + 1],
                            dtype=np.complex128)
            memo[..., 0] = np.squeeze(outer_product(psi, psi))
            tester = State(outer_product(psi, psi), code=self.code)
        # Evolving forward
        for j in range(self.depth):
            if initial_state.is_ket:
                tester = self.hamiltonian[j].evolve(tester, param[j])
                memo[:, j + 1] = np.squeeze(tester.T)
            else:
                self.hamiltonian[j].evolve(tester, param[j])
                # Goes through memo, evolves every density matrix in it, and adds one more in the j*m+i+1 position
                # corresponding to H_i*p
                s0_prenoise = memo[..., 0]
                for k in range(j + 1):
                    s = State(memo[..., k], code=self.code)
                    s = self.hamiltonian[j].evolve(s, param[j])
                    if k == 0:
                        s0_prenoise = s.copy()
                    if self.noise_model is not None:
                        if not (self.noise[j] is None):
                            s = self.noise[j].evolve(s, param[j])
                    memo[..., k] = s.copy()
                s0_prenoise = self.hamiltonian[j].left_multiply(s0_prenoise)
                if self.noise_model is not None:
                    if not (self.noise[j] is None):
                        s0_prenoise = self.noise[j].evolve(
                            s0_prenoise, param[j])
                memo[..., j + 1] = s0_prenoise.copy()

        # Multiply by cost_hamiltonian
        if initial_state.is_ket:
            memo[:, self.depth +
                 1] = self.cost_hamiltonian.hamiltonian @ memo[:, self.depth]

            s = State(np.array([memo[:, self.depth + 1]]).T, code=self.code)
        else:
            for k in range(self.depth + 1):
                s = memo[..., k]
                s = State(self.cost_hamiltonian.hamiltonian * s,
                          code=self.code)
                memo[..., k] = s

        # Evolving backwards, if ket:
        if initial_state.is_ket:
            for k in range(self.depth):
                s = self.hamiltonian[self.depth - k - 1].evolve(
                    s, -1 * param[self.depth - k - 1])
                memo[:, self.depth + k + 2] = np.squeeze(s.T)

        # Evaluating objective function
        if initial_state.is_ket:
            F = np.real(np.vdot(memo[:, self.depth], memo[:, self.depth + 1]))
        else:
            F = np.real(np.trace(memo[..., 0]))

        # Evaluating gradient analytically
        Fgrad = np.zeros(self.depth)
        for r in range(self.depth):
            if initial_state.is_ket:
                s = State(np.array([memo[:, 2 * self.depth + 1 - r]]).T,
                          code=self.code)
                s = self.hamiltonian[r].left_multiply(s)
                Fgrad[r] = -2 * np.imag(np.vdot(memo[:, r], np.squeeze(s.T)))
            else:
                Fgrad[r] = 2 * np.imag(np.trace(memo[..., r + 1]))
        return F, Fgrad
Exemple #8
0
def left_multiply(state: State, apply_to: Union[int, list], op):
    """
    Apply a multi-qubit operator on several qubits (indexed in apply_to) of the input codes.
    :param state: input wavefunction or density matrix
    :type state: np.ndarray
    :param apply_to: zero-based indices of qudit locations to apply the operator
    :type apply_to: list of int
    :param op: Operator to act with.
    :type op: np.ndarray (2-dimensional)
    """
    # Handle typing
    if isinstance(apply_to, int):
        apply_to = [apply_to]
    pauli = False
    if isinstance(op, list):
        if all(isinstance(elem, str) for elem in op):
            pauli = True
        else:
            op = tensor_product(op)
    n_op = len(apply_to)
    if not pauli:
        if is_sorted(apply_to):
            # Generate all shapes for left multiplication
            preshape = d * np.ones((2, n_op), dtype=int)
            preshape[1,
                     0] = int(state.dimension / (d**(1 + apply_to[n_op - 1])))
            if n_op > 1:
                preshape[1, 1:] = np.flip(d**np.diff(apply_to)) / 2

            shape1 = np.zeros(2 * n_op + 1, dtype=int)
            shape2 = np.zeros(2 * n_op + 1, dtype=int)
            order1 = np.zeros(2 * n_op + 1, dtype=int)
            order2 = np.zeros(2 * n_op + 1, dtype=int)

            shape1[:-1] = np.flip(preshape, axis=0).reshape((2 * n_op),
                                                            order='F')
            shape1[-1] = -1
            shape2[:-1] = preshape.reshape((-1), order='C')
            shape2[-1] = -1

            preorder = np.arange(2 * n_op)
            order1[:-1] = np.flip(preorder.reshape((-1, 2), order='C'),
                                  axis=1).reshape((-1), order='F')
            order2[:-1] = np.flip(preorder.reshape((2, -1), order='C'),
                                  axis=0).reshape((-1), order='F')
            order1[-1] = 2 * n_op
            order2[-1] = 2 * n_op

            # Now left multiply
            out = state.reshape(shape1, order='F').transpose(order1)
            out = np.dot(op, out.reshape((d**n_op, -1), order='F'))
            out = out.reshape(shape2, order='F').transpose(order2)
            out = out.reshape(state.shape, order='F')
            return State(out,
                         is_ket=state.is_ket,
                         IS_subspace=state.IS_subspace,
                         code=state.code)
        else:
            # Need to reshape the operator given
            apply_to = np.asarray(apply_to, dtype=int)
            new_shape = d * np.ones(2 * n_op, dtype=int)
            permut = np.argsort(apply_to)
            transpose_ord = np.zeros(2 * n_op, dtype=int)

            transpose_ord[:n_op] = (n_op - 1) * np.ones(
                n_op, dtype=int) - np.flip(permut, axis=0)
            transpose_ord[n_op:] = (2 * n_op - 1) * np.ones(
                n_op, dtype=int) - np.flip(permut, axis=0)

            sorted_op = np.reshape(np.transpose(np.reshape(op,
                                                           new_shape,
                                                           order='F'),
                                                axes=transpose_ord),
                                   (d**n_op, d**n_op),
                                   order='F')
            sorted_apply_to = apply_to[permut]

            return left_multiply(state, sorted_apply_to, sorted_op)
    else:
        # op should be a list of Pauli operators
        out = state.copy()
        for i in range(len(apply_to)):
            ind = d**apply_to[i]
            if state.is_ket:
                # Note index start from the right (sN,...,s3,s2,s1)
                out = out.reshape((-1, d, ind), order='F')
                if op[i] == 'X':  # Sigma_X
                    out = np.flip(out, 1)
                elif op[i] == 'Y':  # Sigma_Y
                    out = np.flip(out, 1)
                    out[:, 0, :] = -1j * out[:, 0, :]
                    out[:, d - 1, :] = 1j * out[:, d - 1, :]
                elif op[i] == 'Z':  # Sigma_Z
                    out[:, d - 1, :] = -out[:, d - 1, :]

                out = out.reshape(state.shape, order='F')
            else:
                out = out.reshape(
                    (-1, d, d**(state.number_physical_qudits - 1), d, ind),
                    order='F')
                if op[i] == 'X':  # Sigma_X
                    out = np.flip(out, 1)
                elif op[i] == 'Y':  # Sigma_Y
                    out = np.flip(out, axis=1)
                    out[:, 0, :, :, :] = -1j * out[:, 0, :, :, :]
                    out[:, d - 1, :, :, :] = 1j * out[:, d - 1, :, :, :]
                elif op[i] == 'Z':  # Sigma_Z
                    out[:, d - 1, :, :, :] = -out[:, d - 1, :, :, :]

                out = out.reshape(state.shape, order='F')
        return State(out,
                     is_ket=state.is_ket,
                     IS_subspace=state.IS_subspace,
                     code=state.code)
Exemple #9
0
def right_multiply(state: State, apply_to: Union[int, list], op):
    """
    Apply a multi-qubit operator on several qubits (indexed in apply_to) of the input codes.
    :param state: input wavefunction or density matrix
    :type state: np.ndarray
    :param apply_to: zero-based indices of qudit locations to apply the operator
    :type apply_to: list of int
    :param op: Operator to act with.
    :type op: np.ndarray (2-dimensional)
    """
    # Handle types
    if isinstance(apply_to, int):
        apply_to = [apply_to]
    if not isinstance(op, list):
        op = [op]
    pauli = False
    if isinstance(op, list):
        if all(isinstance(elem, str) for elem in op):
            pauli = True
        else:
            op = tensor_product(op)
    if state.is_ket:
        print(
            'Warning: right multiply functionality currently applies the operator and daggers the s.'
        )
    n_op = len(apply_to)
    if not pauli:
        if is_sorted(apply_to):
            # generate necessary shapes
            preshape = d * np.ones((2, n_op), dtype=int)
            preshape[0,
                     0] = int(state.dimension / (d**(1 + apply_to[n_op - 1])))
            if n_op > 1:
                preshape[0, 1:] = np.flip(d**np.diff(apply_to)) / 2

            shape3 = np.zeros(2 * n_op + 2, dtype=int)
            shape3[0] = state.dimension
            shape3[1:-1] = np.reshape(preshape, (2 * n_op), order='F')
            shape3[-1] = -1

            shape4 = np.zeros(2 * n_op + 2, dtype=int)
            shape4[0] = state.dimension
            shape4[1:n_op + 1] = preshape[0]
            shape4[n_op + 1] = -1
            shape4[n_op + 2:] = preshape[1]

            order3 = np.zeros(2 * n_op + 2, dtype=int)
            order3[0] = 0
            order3[1:n_op + 2] = 2 * np.arange(n_op + 1) + np.ones(n_op + 1)
            order3[n_op + 2:] = 2 * np.arange(1, n_op + 1)

            order4 = np.zeros(2 * n_op + 2, dtype=int)
            order4[0] = 0
            order4[1] = 1
            order4[2:] = np.flip(np.arange(2, 2 * n_op + 2).reshape((2, -1),
                                                                    order='C'),
                                 axis=0).reshape((-1), order='F')

            # right multiply
            out = state.reshape(shape3, order='F').transpose(order3)
            out = np.dot(out.reshape((-1, d**n_op), order='F'), op.conj().T)
            out = out.reshape(shape4, order='F').transpose(order4)
            out = out.reshape(state.shape, order='F')
            return State(out,
                         is_ket=state.is_ket,
                         IS_subspace=state.IS_subspace,
                         code=state.code)
        else:
            new_shape = 2 * np.ones(2 * n_op, dtype=int)
            permut = np.argsort(apply_to)
            transpose_ord = np.zeros(2 * n_op, dtype=int)

            transpose_ord[:n_op] = (n_op - 1) * np.ones(
                n_op, dtype=int) - np.flip(permut, axis=0)
            transpose_ord[n_op:] = (2 * n_op - 1) * np.ones(
                n_op, dtype=int) - np.flip(permut, axis=0)

            sorted_op = np.reshape(np.transpose(np.reshape(op,
                                                           new_shape,
                                                           order='F'),
                                                axes=transpose_ord),
                                   (d**n_op, d**n_op),
                                   order='F')
            sorted_apply_to = apply_to[permut]

            return right_multiply(state, sorted_apply_to, sorted_op)
    else:
        out = state.copy()
        for i in range(len(apply_to)):
            ind = d**apply_to[i]
            if state.is_ket:
                # Note index start from the right (sN,...,s3,s2,s1)
                out = out.reshape((-1, d, ind), order='F')
                if op[i] == 'X':  # Sigma_X
                    out = np.flip(out, 1)
                elif op[i] == 'Y':  # Sigma_Y
                    out = np.flip(out, 1)
                    out[:, 0, :] = -1j * out[:, 0, :]
                    out[:, d - 1, :] = 1j * out[:, d - 1, :]
                elif op[i] == 'Z':  # Sigma_Z
                    out[:, d - 1, :] = -out[:, d - 1, :]

                out = out.reshape(state.shape, order='F')
                out = out.conj().T
            else:
                out = out.reshape(
                    (-1, d, d**(state.number_physical_qudits - 1), d, ind),
                    order='F')
                if op[i] == 'X':  # Sigma_X
                    out = np.flip(out, axis=3)
                elif op[i] == 'Y':  # Sigma_Y
                    out = np.flip(out, axis=3)
                    out[:, :, :, 0, ] = 1j * out[:, :, :, 0, :]
                    out[:, :, :, d - 1, ] = -1j * out[:, :, :, d - 1, :]
                elif op[i] == 'Z':  # Sigma_Z
                    out[:, :, :, d - 1, :] = -out[:, :, :, d - 1, :]

                out = out.reshape(state.shape, order='F')
        return State(out,
                     is_ket=state.is_ket,
                     IS_subspace=state.IS_subspace,
                     code=state.code)