def to_second_q_op(self) -> FermionicOp: """Creates the operator representing the Hamiltonian defined by these electronic integrals. This method uses ``to_spin`` internally to map the electronic integrals into the spin orbital basis. Returns: The :class:`~qiskit_nature.second_q.operators.FermionicOp` given by these electronic integrals. """ spin_matrix = self.to_spin() register_length = len(spin_matrix) if not np.any(spin_matrix): return FermionicOp.zero(register_length) spin_matrix_iter = spin_matrix.flat # NOTE: we need to access `.coords` before `.next()` is called for the first time! coords = spin_matrix_iter.coords op_data = [] for coeff in spin_matrix_iter: if coeff: op_data.append((self._calc_coeffs_with_ops(coords), coeff)) coords = spin_matrix_iter.coords return FermionicOp(op_data, register_length=register_length, display_format="sparse")
def _build_fermionic_excitation_ops( self, excitations: Sequence) -> list[FermionicOp]: """Builds all possible excitation operators with the given number of excitations for the specified number of particles distributed in the number of orbitals. Args: excitations: the list of excitations. Returns: The list of excitation operators in the second quantized formalism. """ operators = [] for exc in excitations: label = ["I"] * self.num_spin_orbitals for occ in exc[0]: label[occ] = "+" for unocc in exc[1]: label[unocc] = "-" op = FermionicOp("".join(label), display_format="dense") op -= op.adjoint() # we need to account for an additional imaginary phase in the exponent (see also # `PauliTrotterEvolution.convert`) op *= 1j # type: ignore operators.append(op) return operators
def test_init_empty_str(self, pre_processing): """Test __init__ with empty string""" actual = FermionicOp(pre_processing(""), register_length=3, display_format="dense") desired = FermionicOp("III", display_format="dense") self.assertFermionEqual(actual, desired)
def test_mapping_for_single_op(self): """Test for single register operator.""" with self.subTest("test +"): op = FermionicOp("+", display_format="dense") expected = PauliSumOp.from_list([("X", 0.5), ("Y", -0.5j)]) self.assertEqual(ParityMapper().map(op), expected) with self.subTest("test -"): op = FermionicOp("-", display_format="dense") expected = PauliSumOp.from_list([("X", 0.5), ("Y", 0.5j)]) self.assertEqual(ParityMapper().map(op), expected) with self.subTest("test N"): op = FermionicOp("N", display_format="dense") expected = PauliSumOp.from_list([("I", 0.5), ("Z", -0.5)]) self.assertEqual(ParityMapper().map(op), expected) with self.subTest("test E"): op = FermionicOp("E", display_format="dense") expected = PauliSumOp.from_list([("I", 0.5), ("Z", 0.5)]) self.assertEqual(ParityMapper().map(op), expected) with self.subTest("test I"): op = FermionicOp("I", display_format="dense") expected = PauliSumOp.from_list([("I", 1)]) self.assertEqual(ParityMapper().map(op), expected)
def test_init_sparse_label(self, labels, pre_processing): """Test __init__ with sparse label""" dense_label, sparse_label = labels fer_op = FermionicOp(pre_processing(sparse_label), register_length=len(dense_label), display_format="sparse") targ = FermionicOp(dense_label, display_format="sparse") self.assertFermionEqual(fer_op, targ)
def test_init_multiple_digits(self): """Test __init__ for sparse label with multiple digits""" actual = FermionicOp([("-_2 +_10", 1 + 2j), ("-_12", 56 + 0j)], register_length=13, display_format="dense") desired = [ ("II-IIIIIII+II", 1 + 2j), ("IIIIIIIIIIII-", 56), ] self.assertListEqual(actual.to_list(), desired)
def test_terms(self): """Test terms""" op = FermionicOp([("+", 1.0), ("N", 2.0), ("-+", 3.0)], display_format="dense") expected = { ((("+", 0), ), 1.0), ((("+", 0), ("-", 0)), 2.0), ((("-", 0), ("+", 1)), 3.0), } self.assertEqual(set(op.terms()), expected)
def test_second_q_ops(self): """Test second_q_ops.""" op = self.prop.second_q_ops()["AngularMomentum"] with open( self.get_resource_path("angular_momentum_op.json", "second_q/properties/resources"), "r", encoding="utf8", ) as file: expected = json.load(file) expected_op = FermionicOp(expected).simplify() self.assertSetEqual(frozenset(op.to_list("dense")), frozenset(expected_op.to_list("dense")))
def test_equiv(self): """test equiv""" op1 = FermionicOp("+_0 -_1") + FermionicOp("+_1 -_0") op2 = FermionicOp("+_0 -_1") op3 = FermionicOp("+_0 -_1") + (1 + 1e-7) * FermionicOp("+_1 -_0") self.assertFalse(op1.equiv(op2)) self.assertFalse(op1.equiv(op3)) self.assertTrue(op1.equiv(op3, atol=1e-6)) with self.assertRaisesRegex(TypeError, "type"): op1.equiv("a")
def test_mul(self): """Test __mul__, and __rmul__""" with self.subTest("rightmul"): fer_op = FermionicOp("+-", display_format="dense") * 2 targ = FermionicOp([("+-", 2)], display_format="dense") self.assertFermionEqual(fer_op, targ) with self.subTest("left mul"): fer_op = (2 + 1j) * FermionicOp([("+N", 3), ("E-", 1)], display_format="dense") targ = FermionicOp([("+N", (6 + 3j)), ("E-", (2 + 1j))], display_format="dense") self.assertFermionEqual(fer_op, targ)
def test_init_multiterm(self): """Test __init__ with multi terms""" with self.subTest("Test 1"): labels = [("N", 2), ("-", 3.14)] self.assertListEqual( FermionicOp(labels, display_format="dense").to_list(), labels) with self.subTest("Test 2"): labels = [("+-", 1), ("-+", -1)] op = FermionicOp([("+_0 -_1", 1.0), ("-_0 +_1", -1.0)], register_length=2, display_format="dense") self.assertListEqual(op.to_list(), labels)
def test_add(self): """Test __add__""" fer_op = 3 * FermionicOp("+N", display_format="dense") + FermionicOp( "E-", display_format="dense") targ = FermionicOp([("+N", 3), ("E-", 1)], display_format="dense") self.assertFermionEqual(fer_op, targ) fer_op = sum( FermionicOp(label, display_format="dense") for label in ["NIII", "INII", "IINI", "IIIN"]) targ = FermionicOp([("NIII", 1), ("INII", 1), ("IINI", 1), ("IIIN", 1)], display_format="dense") self.assertFermionEqual(fer_op, targ)
def test_adjoint(self): """Test adjoint method""" with self.subTest("adjoint"): fer_op = ~FermionicOp([("+N", 3), ("N-", 1), ("--", 2 + 4j)], display_format="dense") targ = FermionicOp([("-N", 3), ("N+", 1), ("++", (-2 + 4j))], display_format="dense") self.assertFermionEqual(fer_op, targ) with self.subTest("adjoint 2"): fer_op = FermionicOp([("+-", 1), ("II", 2j)], display_format="dense").adjoint() targ = FermionicOp([("-+", -1), ("II", -2j)], display_format="dense") self.assertFermionEqual(fer_op, targ)
def test_init_from_tuple_label(self): """Test __init__ for tuple""" desired = [((("-", 2), ("+", 10)), (1 + 2j)), ((("-", 12), ), 56)] # tuple actual = FermionicOp( [((("-", 2), ("+", 10)), 1 + 2j), ((("-", 12), ), 56)], register_length=13, display_format="dense", ) self.assertEqual(actual._data, desired) # list actual = FermionicOp( [([("-", 2), ("+", 10)], 1 + 2j), ([("-", 12)], 56)], register_length=13, display_format="dense", ) self.assertListEqual(actual._data, desired)
def test_matmul_multi(self): """Test matrix multiplication""" with self.subTest("single matmul"): fer_op = FermionicOp("+-", display_format="dense") @ FermionicOp( "-I", display_format="dense") targ = FermionicOp([("N-", -1)], display_format="dense") self.assertFermionEqual(fer_op, targ) with self.subTest("multi matmul"): fer_op = FermionicOp("+-", display_format="dense") @ FermionicOp( "-I", display_format="dense") fer_op = (FermionicOp("+N", display_format="dense") + FermionicOp("E-", display_format="dense")) @ ( FermionicOp("II", display_format="dense") + FermionicOp("-+", display_format="dense")) targ = FermionicOp([("+N", 1), ("N+", 1), ("E-", 1), ("-E", -1)], display_format="dense") self.assertFermionEqual(fer_op, targ)
def map(self, second_q_op: FermionicOp) -> PauliSumOp: if not isinstance(second_q_op, FermionicOp): raise TypeError("Type ", type(second_q_op), " not supported.") if second_q_op.display_format == "sparse": second_q_op = FermionicOp(second_q_op._to_dense_label_data(), display_format="dense") edge_list = _bksf_edge_list_fermionic_op(second_q_op) sparse_pauli = _convert_operator(second_q_op, edge_list) ## Simplify and sort the result sparse_pauli = sparse_pauli.simplify() indices = sparse_pauli.paulis.argsort() table = sparse_pauli.paulis[indices] coeffs = sparse_pauli.coeffs[indices] sorted_sparse_pauli = SparsePauliOp(table, coeffs) return PauliSumOp(sorted_sparse_pauli)
def test_two_qubit_reduction(self): """Test mapping to qubit operator with two qubit reduction""" mapper = ParityMapper() qubit_conv = QubitConverter(mapper, two_qubit_reduction=True) with self.subTest( "Two qubit reduction ignored as no num particles given"): qubit_op = qubit_conv.convert(self.h2_op) self.assertEqual(qubit_op, TestQubitConverter.REF_H2_PARITY) self.assertIsNone(qubit_conv.num_particles) with self.subTest("Two qubit reduction, num particles given"): qubit_op = qubit_conv.convert(self.h2_op, self.num_particles) self.assertEqual(qubit_op, TestQubitConverter.REF_H2_PARITY_2Q_REDUCED) self.assertEqual(qubit_conv.num_particles, self.num_particles) with self.subTest("convert_match()"): qubit_op = qubit_conv.convert_match(self.h2_op) self.assertEqual(qubit_op, TestQubitConverter.REF_H2_PARITY_2Q_REDUCED) self.assertEqual(qubit_conv.num_particles, self.num_particles) with self.subTest("State is reset (Num particles lost)"): qubit_op = qubit_conv.convert(self.h2_op) self.assertEqual(qubit_op, TestQubitConverter.REF_H2_PARITY) self.assertIsNone(qubit_conv.num_particles) with self.subTest("Num particles given again"): qubit_op = qubit_conv.convert(self.h2_op, self.num_particles) self.assertEqual(qubit_op, TestQubitConverter.REF_H2_PARITY_2Q_REDUCED) with self.subTest("Set for no two qubit reduction"): qubit_conv.two_qubit_reduction = False self.assertFalse(qubit_conv.two_qubit_reduction) qubit_op = qubit_conv.convert(self.h2_op) self.assertEqual(qubit_op, TestQubitConverter.REF_H2_PARITY) # Regression test against https://github.com/Qiskit/qiskit-nature/issues/271 with self.subTest( "Two qubit reduction skipped when operator too small"): qubit_conv.two_qubit_reduction = True small_op = FermionicOp([("N_0", 1.0), ("E_1", 1.0)], register_length=2, display_format="sparse") expected_op = 1.0 * (I ^ I) - 0.5 * (I ^ Z) + 0.5 * (Z ^ Z) with contextlib.redirect_stderr(io.StringIO()) as out: qubit_op = qubit_conv.convert(small_op, num_particles=self.num_particles) self.assertEqual(qubit_op, expected_op) self.assertTrue(out.getvalue().strip().startswith( "The original qubit operator only contains 2 qubits! " "Skipping the requested two-qubit reduction!"))
def test_fermionic_op(self): """Test conversion to FermionicOp.""" hermitian_part = np.array([[1, 2j], [-2j, 3]]) antisymmetric_part = np.array([[0, 4j], [-4j, 0]]) constant = 5.0 quad_ham = QuadraticHamiltonian(hermitian_part, antisymmetric_part, constant) fermionic_op = quad_ham.to_fermionic_op() expected_terms = [ ("NI", 1.0), ("IN", 3.0), ("+-", 2j), ("-+", 2j), ("++", 4j), ("--", 4j), ("II", 5.0), ] expected_op = FermionicOp(expected_terms) matrix = fermionic_op.to_matrix(sparse=False) expected_matrix = expected_op.to_matrix(sparse=False) np.testing.assert_allclose(matrix, expected_matrix)
def test_label_display_mode(self, label, pre_processing): """test label_display_mode""" fer_op = FermionicOp(pre_processing(label), display_format="dense") fer_op.display_format = "sparse" self.assertListEqual(fer_op.to_list(), str2list(label)) fer_op.display_format = "dense" self.assertNotEqual(fer_op.to_list(), str2list(label))
def second_q_ops(self) -> dict[str, FermionicOp]: """Returns the second quantized magnetization operator. Returns: A `dict` of `SecondQuantizedOp` objects. """ op = FermionicOp( [ (f"N_{o}", 0.5 if o < self._num_spin_orbitals // 2 else -0.5) for o in range(self._num_spin_orbitals) ], register_length=self._num_spin_orbitals, display_format="sparse", ) return {self.name: op}
def second_q_ops(self, display_format: str = "sparse") -> FermionicOp: """Return the Hamiltonian of the Fermi-Hubbard model in terms of `FermionicOp`. Args: display_format: If sparse, the label is represented sparsely during output. If dense, the label is represented densely during output. Defaults to "dense". Returns: FermionicOp: The Hamiltonian of the Fermi-Hubbard model. """ kinetic_ham = [] interaction_ham = [] weighted_edge_list = self._lattice.weighted_edge_list register_length = 2 * self._lattice.num_nodes # kinetic terms for spin in range(2): for node_a, node_b, weight in weighted_edge_list: if node_a == node_b: index = 2 * node_a + spin kinetic_ham.append((f"N_{index}", weight)) else: if node_a < node_b: index_left = 2 * node_a + spin index_right = 2 * node_b + spin hopping_parameter = weight elif node_a > node_b: index_left = 2 * node_b + spin index_right = 2 * node_a + spin hopping_parameter = np.conjugate(weight) kinetic_ham.append( (f"+_{index_left} -_{index_right}", hopping_parameter)) kinetic_ham.append((f"-_{index_left} +_{index_right}", -np.conjugate(hopping_parameter))) # on-site interaction terms for node in self._lattice.node_indexes: index_up = 2 * node index_down = 2 * node + 1 interaction_ham.append( (f"N_{index_up} N_{index_down}", self._onsite_interaction)) ham = kinetic_ham + interaction_ham return FermionicOp(ham, register_length=register_length, display_format=display_format)
def to_fermionic_op(self) -> FermionicOp: """Convert to FermionicOp.""" terms: list[tuple[list[tuple[str, int]], complex]] = [([], self.constant)] for i in range(self._num_modes): terms.append(([("+", i), ("-", i)], self.hermitian_part[i, i])) for j in range(i + 1, self._num_modes): terms.append(([("+", i), ("-", j)], self.hermitian_part[i, j])) terms.append(([("+", j), ("-", i)], self.hermitian_part[j, i])) terms.append(([("+", i), ("+", j)], self.antisymmetric_part[i, j])) terms.append( ([("-", j), ("-", i)], self.antisymmetric_part[i, j].conjugate())) # TODO remove display_format="sparse" once it's no longer needed to suppress warning return FermionicOp(terms, register_length=self._num_modes, display_format="sparse")
def _get_adjacency_matrix(fer_op: FermionicOp) -> np.ndarray: """Return an adjacency matrix specifying the edges in the BKSF graph for the operator `fer_op`. The graph is undirected, so we choose to return the edges in the upper triangle. (There are no self edges.) The lower triangle entries are all `False`. Args: fer_op: The Fermionic operator. Returns: numpy.ndarray(dtype=bool): edge_matrix the adjacency matrix. """ n_modes = fer_op.register_length edge_matrix = np.zeros((n_modes, n_modes), dtype=bool) for term in fer_op.to_list(): if _operator_coefficient(term) != 0: _add_edges_for_term(edge_matrix, _operator_string(term)) return edge_matrix
def test_pow(self): """Test __pow__""" with self.subTest("square trivial"): fer_op = FermionicOp([("+N", 3), ("E-", 1)], display_format="dense")**2 targ = FermionicOp([("II", 0)], display_format="dense") self.assertFermionEqual(fer_op, targ) with self.subTest("square nontrivial"): fer_op = FermionicOp([("+N", 3), ("N-", 1)], display_format="dense")**2 targ = FermionicOp([("+-", -3)], display_format="dense") self.assertFermionEqual(fer_op, targ) with self.subTest("3rd power"): fer_op = (3 * FermionicOp("IIII", display_format="dense"))**3 targ = FermionicOp([("IIII", 27)], display_format="dense") self.assertFermionEqual(fer_op, targ) with self.subTest("0th power"): fer_op = FermionicOp([("+N", 3), ("E-", 1)], display_format="dense")**0 targ = FermionicOp([("II", 1)], display_format="dense") self.assertFermionEqual(fer_op, targ)
def _build_single_hopping_operator( excitation: Tuple[Tuple[int, ...], Tuple[int, ...]], num_spin_orbitals: int, qubit_converter: QubitConverter, ) -> Tuple[PauliSumOp, List[bool]]: label = ["I"] * num_spin_orbitals for occ in excitation[0]: label[occ] = "+" for unocc in excitation[1]: label[unocc] = "-" fer_op = FermionicOp(("".join(label), 4.0 ** len(excitation[0])), display_format="sparse") qubit_op: PauliSumOp = qubit_converter.convert_only(fer_op, qubit_converter.num_particles) z2_symmetries = qubit_converter.z2symmetries commutativities = [] if not z2_symmetries.is_empty(): for symmetry in z2_symmetries.symmetries: symmetry_op = PauliSumOp.from_list([(symmetry.to_label(), 1.0)]) paulis = qubit_op.primitive.paulis len_paulis = len(paulis) commuting = len(paulis.commutes_with_all(symmetry_op.primitive.paulis)) == len_paulis anticommuting = ( len(paulis.anticommutes_with_all(symmetry_op.primitive.paulis)) == len_paulis ) if commuting != anticommuting: # only one of them is True if commuting: commutativities.append(True) elif anticommuting: commutativities.append(False) else: raise QiskitNatureError( f"Symmetry {symmetry.to_label()} neither commutes nor anti-commutes " "with excitation operator." ) return qubit_op, commutativities
def test_init_invalid_label(self, label, register_length): """Test __init__ with invalid label""" with self.assertRaises(ValueError): FermionicOp(label, register_length=register_length, display_format="dense")
def test_init(self, label, pre_processing): """Test __init__""" fer_op = FermionicOp(pre_processing(label), display_format="dense") self.assertListEqual(fer_op.to_list(), [(label, 1)]) self.assertFermionEqual(eval(repr(fer_op)), fer_op) # pylint: disable=eval-used
def test_induced_norm(self): """Test induced norm.""" op = 3 * FermionicOp("+") + 4j * FermionicOp("-") self.assertAlmostEqual(op.induced_norm(), 7.0) self.assertAlmostEqual(op.induced_norm(2), 5.0)
def assertFermionEqual(self, first: FermionicOp, second: FermionicOp): """Fail if two FermionicOps are different. Note that this equality check is approximated since the true equality check is costly. """ self.assertSetEqual(frozenset(first.to_list()), frozenset(second.to_list(first.display_format)))
def test_normal_ordered(self): """test normal_ordered method""" with self.subTest("Test for creation operator"): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning) orig = FermionicOp("+") fer_op = orig.normal_ordered() targ = FermionicOp("+_0", display_format="sparse") self.assertFermionEqual(fer_op, targ) self.assertEqual(orig.display_format, "sparse") with self.subTest("Test for annihilation operator"): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning) orig = FermionicOp("-") fer_op = orig.normal_ordered() targ = FermionicOp("-_0", display_format="sparse") self.assertFermionEqual(fer_op, targ) self.assertEqual(orig.display_format, "sparse") with self.subTest("Test for number operator"): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning) orig = FermionicOp("N") fer_op = orig.normal_ordered() targ = FermionicOp("+_0 -_0", display_format="sparse") self.assertFermionEqual(fer_op, targ) self.assertEqual(orig.display_format, "sparse") with self.subTest("Test for empty operator"): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning) orig = FermionicOp("E") fer_op = orig.normal_ordered() targ = FermionicOp([("", 1), ("+_0 -_0", -1)], display_format="sparse") self.assertFermionEqual(fer_op, targ) self.assertEqual(orig.display_format, "sparse") with self.subTest("Test for multiple operators 1"): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning) orig = FermionicOp("-+") fer_op = orig.normal_ordered() targ = FermionicOp([("-_0 +_1", 1)], display_format="sparse") self.assertFermionEqual(fer_op, targ) self.assertEqual(orig.display_format, "sparse") with self.subTest("Test for multiple operators 2"): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning) orig = 3 * FermionicOp("E+-") fer_op = orig.normal_ordered() targ = FermionicOp([("+_1 -_2", 3), ("+_0 -_0 +_1 -_2", -3)], display_format="sparse") self.assertFermionEqual(fer_op, targ) self.assertEqual(orig.display_format, "sparse") with self.subTest("Test normal ordering simplifies"): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning) orig = FermionicOp([("-_1 +_2", 3), ("+_2 -_1", -3)], display_format="sparse") fer_op = orig.normal_ordered() expected = [((("-", 1), ("+", 2)), 6)] self.assertEqual(fer_op._data, expected)