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)
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))
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)
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)
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
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
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
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)
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)