def partial_trace(self, qubits: Qubits) -> 'QubitVector':
        """
        Return the partial trace over some subset of qubits"""
        N = self.qubit_nb
        R = self.rank
        if R == 1:
            raise ValueError('Cannot take trace of vector')

        new_qubits: List[Qubit] = list(self.qubits)
        for q in qubits:
            new_qubits.remove(q)

        if not new_qubits:
            raise ValueError('Cannot remove all qubits with partial_trace.')

        indices = [self.qubits.index(qubit) for qubit in qubits]
        subscripts = list(EINSUM_SUBSCRIPTS)[0:N*R]
        for idx in indices:
            for r in range(1, R):
                subscripts[r * N + idx] = subscripts[idx]
        subscript_str = ''.join(subscripts)

        # Only numpy's einsum works with repeated subscripts
        tensor = self.asarray()
        tensor = np.einsum(subscript_str, tensor)

        return QubitVector(tensor, new_qubits)
Return the partial trace over some subset of qubits