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.operators.second_quantization.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 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(JordanWignerMapper().map(op), expected) with self.subTest("test -"): op = FermionicOp("-", display_format="dense") expected = PauliSumOp.from_list([("X", 0.5), ("Y", 0.5j)]) self.assertEqual(JordanWignerMapper().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(JordanWignerMapper().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(JordanWignerMapper().map(op), expected) with self.subTest("test I"): op = FermionicOp("I", display_format="dense") expected = PauliSumOp.from_list([("I", 1)]) self.assertEqual(JordanWignerMapper().map(op), expected)
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_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_div(self): """Test __truediv__""" fer_op = FermionicOp([("+N", 3), ("E-", 1)], display_format="dense") / 3 targ = FermionicOp([("+N", 1.0), ("E-", 1 / 3)], display_format="dense") self.assertFermionEqual(fer_op, targ)
def test_sub(self): """Test __sub__""" fer_op = 3 * FermionicOp("++", display_format="dense") - 2 * FermionicOp( "--", display_format="dense") targ = FermionicOp([("++", 3), ("--", -2)], display_format="dense") self.assertFermionEqual(fer_op, targ)
def test_matmul(self, label1, label2): """Test matrix multiplication""" fer_op = FermionicOp(label1, display_format="dense") @ FermionicOp( label2, display_format="dense") mapping = { "II": "I", "I+": "+", "I-": "-", "IN": "N", "IE": "E", "+I": "+", "++": 0, "+-": "N", "+N": 0, "+E": "+", "-I": "-", "-+": "E", "--": 0, "-N": "-", "-E": 0, "NI": "N", "N+": "+", "N-": 0, "NN": "N", "NE": 0, "EI": "E", "E+": 0, "E-": "-", "EN": 0, "EE": "E", } result = mapping[label1 + label2] targ = FermionicOp([(result, 1)] if result != 0 else [("I", 0)], display_format="dense") self.assertFermionEqual(fer_op, targ)
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)) op -= op.adjoint() # we need to account for an additional imaginary phase in the exponent (see also # `PauliTrotterEvolution.convert`) op *= 1j operators.append(op) return operators
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)], register_length=13) desired = [ ("II-IIIIIII+II", 1 + 2j), ("IIIIIIIIIIII-", 56), ] self.assertListEqual(actual.to_list(), desired)
def test_add(self): """Test __add__""" fer_op = 3 * FermionicOp("+N") + FermionicOp("E-") targ = FermionicOp([("+N", 3), ("E-", 1)]) self.assertFermionEqual(fer_op, targ) fer_op = sum(FermionicOp(label) for label in ["NIII", "INII", "IINI", "IIIN"]) targ = FermionicOp([("NIII", 1), ("INII", 1), ("IINI", 1), ("IIIN", 1)]) self.assertFermionEqual(fer_op, targ)
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 _create_base_op_from_labels(coeff, length: int, coeffs_with_ops) -> FermionicOp: label = ["I"] * length base_op = coeff * FermionicOp("".join(label)) for i, op in coeffs_with_ops: label_i = label.copy() label_i[i] = op base_op @= FermionicOp("".join(label_i)) return base_op
def test_init_multiterm(self): """Test __init__ with multi terms""" with self.subTest("Test 1"): labels = [("N", 2), ("-", 3.14)] self.assertListEqual(FermionicOp(labels).to_list(), labels) with self.subTest("Test 2"): labels = [("+-", 1), ("-+", -1)] op = FermionicOp([("+_0 -_1", 1.0), ("-_0 +_1", -1.0)], register_length=2) self.assertListEqual(op.to_list(), labels)
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_adjoint(self): """Test adjoint method and dagger property""" with self.subTest("adjoint"): fer_op = ~FermionicOp([("+N", 3), ("N-", 1), ("--", 2 + 4j)]) targ = FermionicOp([("-N", 3), ("N+", 1), ("++", (-2 + 4j))]) self.assertFermionEqual(fer_op, targ) with self.subTest("dagger"): fer_op = FermionicOp([("+-", 1), ("II", 2j)]).dagger targ = FermionicOp([("-+", -1), ("II", -2j)]) 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""" actual = FermionicOp( [([("-", 2), ("+", 10)], 1 + 2j), ([("-", 12)], 56)], register_length=13, display_format="dense", ) desired = [ ("II-IIIIIII+II", 1 + 2j), ("IIIIIIIIIIII-", 56), ] self.assertListEqual(actual.to_list(), desired)
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", "properties/second_quantization/electronic/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_matmul_multi(self): """Test matrix multiplication""" with self.subTest("single matmul"): fer_op = FermionicOp("+-") @ FermionicOp("-I") targ = FermionicOp([("N-", -1)]) self.assertFermionEqual(fer_op, targ) with self.subTest("multi matmul"): fer_op = FermionicOp("+-") @ FermionicOp("-I") fer_op = (FermionicOp("+N") + FermionicOp("E-")) @ ( FermionicOp("II") + FermionicOp("-+") ) targ = FermionicOp([("+N", 1), ("N+", 1), ("E-", 1), ("-E", -1)]) self.assertFermionEqual(fer_op, targ)
def _convert_operator(ferm_op: FermionicOp, edge_list: np.ndarray) -> SparsePauliOp: """Convert a fermionic operator together with qubit-connectivity graph to a Pauli operator. This is the heart of the implementation of BKSF mapping. The connectivity graph must be computed before this method is called. The returned Pauli operator must be sorted and simplified. Args: ferm_op: The fermionic operator to convert. edge_list: The qubit-connectivity graph expressed as an edge list. Returns: An un-simplified Pauli operator representing `ferm_op`. Raises: ValueError: if the type of interaction of any term is unknown. """ sparse_pauli = None for term in ferm_op.to_list(): term_type, facs = _analyze_term(_operator_string(term)) if facs[0][1] == "-": # keep only one of h.c. pair continue ## Following only filters h.c. of some number-excitation op if facs[0][0] == facs[1][0]: # first op is number op, which is it's own h.c. if len(facs) > 2 and facs[2][1] == "-": # So, look at next op to skip h.c. continue if term_type == TermType.NUMBER: # a^\dagger_p a_p p = facs[0][0] # pylint: disable=invalid-name h1_pq = _operator_coefficient(term) sparse_pauli = _add_sparse_pauli(sparse_pauli, _number_operator(edge_list, p, h1_pq)) continue if term_type == TermType.EXCITATION: (p, q) = [facs[i][0] for i in range(2)] # p < q always # pylint: disable=invalid-name h1_pq = _operator_coefficient(term) sparse_pauli = _add_sparse_pauli( sparse_pauli, _excitation_operator(edge_list, p, q, h1_pq) ) else: facs_reordered, phase = _to_physicist_index_order(facs) h2_pqrs = phase * _operator_coefficient(term) (p, q, r, s) = [facs_reordered[i][0] for i in range(4)] # pylint: disable=invalid-name if term_type == TermType.DOUBLE_EXCITATION: sparse_pauli = _add_sparse_pauli( sparse_pauli, _double_excitation(edge_list, p, q, r, s, h2_pqrs) ) elif term_type == TermType.COULOMB_EXCHANGE: sparse_pauli = _add_sparse_pauli( sparse_pauli, _coulomb_exchange(edge_list, p, q, s, h2_pqrs) ) elif term_type == TermType.NUMBER_EXCITATION: # Note that h2_pqrs is not divided by 2 here, as in the aqua code sparse_pauli = _add_sparse_pauli( sparse_pauli, _number_excitation(edge_list, p, q, r, s, h2_pqrs) ) else: raise ValueError("Unknown interaction: ", term_type) return sparse_pauli
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]))) qubit_op: PauliSumOp = qubit_converter.convert_match(fer_op) 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)]) commuting = qubit_op.primitive.table.commutes_with_all( symmetry_op.primitive.table) anticommuting = qubit_op.primitive.table.anticommutes_with_all( symmetry_op.primitive.table) if commuting != anticommuting: # only one of them is True if commuting: commutativities.append(True) elif anticommuting: commutativities.append(False) else: raise QiskitNatureError( "Symmetry {} is nor commute neither anti-commute " "to exciting operator.".format(symmetry.to_label())) return qubit_op, commutativities
def __init__(self, num_spin_orbitals: int, num_particles: Tuple[int, int], qubit_converter: QubitConverter) -> None: """ Args: num_spin_orbitals: The number of spin orbitals, has a min. value of 1. num_particles: The number of particles as a tuple storing the number of alpha- and beta-spin electrons in the first and second number, respectively. qubit_converter: a QubitConverter instance. """ # get the bitstring encoding the Hartree Fock state bitstr = hartree_fock_bitstring(num_spin_orbitals, num_particles) # encode the bitstring as a `FermionicOp` label = ['+' if bit else 'I' for bit in bitstr] bitstr_op = FermionicOp(''.join(label)) # map the `FermionicOp` to a qubit operator qubit_op: PauliSumOp = qubit_converter.convert_match(bitstr_op) # construct the circuit qr = QuantumRegister(qubit_op.num_qubits, 'q') super().__init__(qr, name='HF') # Add gates in the right positions: we are only interested in the `X` gates because we want # to create particles (0 -> 1) where the initial state introduced a creation (`+`) operator. for i, bit in enumerate(qubit_op.primitive.table.X[0]): if bit: self.x(i)
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_hermiticity(self): """test is_hermitian""" with self.subTest("operator hermitian"): # deliberately define test operator with duplicate terms in case .adjoint() simplifies terms fer_op = (1j * FermionicOp("+-NE", display_format="dense") + 1j * FermionicOp("+-NE", display_format="dense") + 1j * FermionicOp("-+NE", display_format="dense") + 1j * FermionicOp("-+NE", display_format="dense") + FermionicOp("+-EN", display_format="dense") - FermionicOp("-+EN", display_format="dense")) self.assertTrue(fer_op.is_hermitian()) with self.subTest("operator not hermitian"): fer_op = (1j * FermionicOp("+-NE", display_format="dense") + 1j * FermionicOp("+-NE", display_format="dense") - 1j * FermionicOp("-+NE", display_format="dense") - 1j * FermionicOp("-+NE", display_format="dense")) self.assertFalse(fer_op.is_hermitian())
def _create_base_op(self, indices: tuple[int, ...], coeff: complex, length: int) -> FermionicOp: """Creates a single base operator for the given coefficient. Args: indices: the indices of the current integral. coeff: the current integral value. length: the register length of the created operator. Returns: The base operator. """ base_op = FermionicOp(("I_0", coeff), register_length=length, display_format="sparse") for i, op in self._calc_coeffs_with_ops(indices): base_op @= FermionicOp(f"{op}_{i}", display_format="sparse") return base_op
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_to_matrix(self): """Test to_matrix""" with self.subTest("identity operator matrix"): mat = FermionicOp.one(2).to_matrix(sparse=False) targ = np.eye(4) self.assertTrue(np.allclose(mat, targ)) with self.subTest("number operator matrix"): mat = FermionicOp("IN").to_matrix(sparse=False) targ = np.array([[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 0], [0, 0, 0, 1]]) self.assertTrue(np.allclose(mat, targ)) with self.subTest("emptiness operator matrix"): mat = FermionicOp("IE").to_matrix(sparse=False) targ = np.array([[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]]) self.assertTrue(np.allclose(mat, targ)) with self.subTest("raising operator matrix"): mat = FermionicOp("I+").to_matrix(sparse=False) targ = np.array([[0, 0, 0, 0], [1, 0, 0, 0], [0, 0, 0, 0], [0, 0, -1, 0]]) self.assertTrue(np.allclose(mat, targ)) with self.subTest("lowering operator matrix"): mat = FermionicOp("I-").to_matrix(sparse=False) targ = np.array([[0, 1, 0, 0], [0, 0, 0, 0], [0, 0, 0, -1], [0, 0, 0, 0]]) self.assertTrue(np.allclose(mat, targ)) with self.subTest("nontrivial sparse matrix"): mat = FermionicOp([("ENI+", 3j), ("-N+-", -2)]).to_matrix() targ = csc_matrix(([-3j, 3j, -2], ([5, 7, 6], [4, 6, 13])), shape=(16, 16)) self.assertTrue((mat != targ).nnz == 0) with self.subTest("Test Hydrogen spectrum"): h2_labels = [ ("+_0 -_1 +_2 -_3", (0.18093120148374142)), ("+_0 -_1 -_2 +_3", (-0.18093120148374134)), ("-_0 +_1 +_2 -_3", (-0.18093120148374134)), ("-_0 +_1 -_2 +_3", (0.18093120148374128)), ("+_3 -_3", (-0.4718960038869427)), ("+_2 -_2", (-1.2563391028292563)), ("+_2 -_2 +_3 -_3", (0.48365053378098793)), ("+_1 -_1", (-0.4718960038869427)), ("+_1 -_1 +_3 -_3", (0.6985737398458793)), ("+_1 -_1 +_2 -_2", (0.6645817352647293)), ("+_0 -_0", (-1.2563391028292563)), ("+_0 -_0 +_3 -_3", (0.6645817352647293)), ("+_0 -_0 +_2 -_2", (0.6757101625347564)), ("+_0 -_0 +_1 -_1", (0.48365053378098793)), ] h2_matrix = FermionicOp(h2_labels, register_length=4).to_matrix() evals, evecs = eigs(h2_matrix) self.assertTrue(np.isclose(np.min(evals), -1.8572750)) # make sure the ground state has support only in the 2-particle subspace groundstate = evecs[:, np.argmin(evals)] for idx in np.where(~np.isclose(groundstate, 0))[0]: binary = f"{idx:0{4}b}" self.assertEqual(binary.count("1"), 2)
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_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))