def test_sum(self, num_qubits, num_ops): """Test sum method for {num_qubits} qubits with {num_ops} operators.""" ops = [self.random_spp_op(num_qubits, 2 ** num_qubits) for _ in range(num_ops)] sum_op = SparsePauliOp.sum(ops) value = Operator(sum_op) target_operator = sum((Operator(op) for op in ops[1:]), Operator(ops[0])) self.assertEqual(value, target_operator) target_spp_op = sum((op for op in ops[1:]), ops[0]) self.assertEqual(sum_op, target_spp_op) np.testing.assert_array_equal(sum_op.paulis.phase, np.zeros(sum_op.size))
def test_sum_error(self): """Test sum method with invalid cases.""" with self.assertRaises(QiskitError): SparsePauliOp.sum([]) with self.assertRaises(QiskitError): ops = [self.random_spp_op(num_qubits, 2 ** num_qubits) for num_qubits in [1, 2]] SparsePauliOp.sum(ops) with self.assertRaises(QiskitError): SparsePauliOp.sum([1, 2])
def mode_based_mapping( second_q_op: SecondQuantizedOp, pauli_table: List[Tuple[Pauli, Pauli]]) -> PauliSumOp: """Utility method to map a `SecondQuantizedOp` to a `PauliSumOp` using a pauli table. Args: second_q_op: the `SecondQuantizedOp` to be mapped. pauli_table: a table of paulis built according to the modes of the operator Returns: The `PauliSumOp` corresponding to the problem-Hamiltonian in the qubit space. Raises: QiskitNatureError: If number length of pauli table does not match the number of operator modes, or if the operator has unexpected label content """ nmodes = len(pauli_table) if nmodes != second_q_op.register_length: raise QiskitNatureError( f"Pauli table len {nmodes} does not match" f"operator register length {second_q_op.register_length}") # 0. Some utilities times_creation_op = [] times_annihilation_op = [] times_occupation_number_op = [] times_emptiness_number_op = [] for paulis in pauli_table: real_part = SparsePauliOp(paulis[0], coeffs=[0.5]) imag_part = SparsePauliOp(paulis[1], coeffs=[0.5j]) # The creation operator is given by 0.5*(X - 1j*Y) creation_op = real_part - imag_part times_creation_op.append(creation_op) # The annihilation operator is given by 0.5*(X + 1j*Y) annihilation_op = real_part + imag_part times_annihilation_op.append(annihilation_op) # The occupation number operator N is given by `+-`. times_occupation_number_op.append( creation_op.compose(annihilation_op, front=True).simplify()) # The `emptiness number` operator E is given by `-+` = (I - N). times_emptiness_number_op.append( annihilation_op.compose(creation_op, front=True).simplify()) # make sure ret_op_list is not empty by including a zero op ret_op_list = [SparsePauliOp("I" * nmodes, coeffs=[0])] # TODO to_list() is not an attribute of SecondQuantizedOp. Change the former to have this or # change the signature above to take FermionicOp? label_coeff_list = (second_q_op.to_list( display_format="dense") if isinstance(second_q_op, FermionicOp) else second_q_op.to_list()) for label, coeff in label_coeff_list: # 1. Initialize an operator list with the identity scaled by the `self.coeff` ret_op = SparsePauliOp("I" * nmodes, coeffs=[coeff]) # Go through the label and replace the fermion operators by their qubit-equivalent, then # save the respective Pauli string in the pauli_str list. for position, char in enumerate(label): if char == "+": ret_op = ret_op.compose(times_creation_op[position], front=True) elif char == "-": ret_op = ret_op.compose(times_annihilation_op[position], front=True) elif char == "N": ret_op = ret_op.compose( times_occupation_number_op[position], front=True) elif char == "E": ret_op = ret_op.compose( times_emptiness_number_op[position], front=True) elif char == "I": continue # catch any disallowed labels else: raise QiskitNatureError( f"FermionicOp label included '{char}'. Allowed characters: I, N, E, +, -" ) ret_op_list.append(ret_op) return PauliSumOp(SparsePauliOp.sum(ret_op_list).simplify())