Source code for

# This code is part of a Qiskit project.
# (C) Copyright IBM 2021, 2023.
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""The ElectronicDipoleMoment property."""

from __future__ import annotations

from typing import Mapping, MutableMapping, Optional, Tuple, cast

import qiskit_nature  # pylint: disable=unused-import
from qiskit_nature.second_q.operators import ElectronicIntegrals, FermionicOp

# A dipole moment, when present as X, Y and Z components will normally have float values for all the
# components. However when using Z2Symmetries, if the dipole component operator does not commute
# with the symmetry then no evaluation is done and None will be used as the 'value' indicating no
# measurement of the observable took place
DipoleTuple = Tuple[Optional[float], Optional[float], Optional[float]]

[docs]class ElectronicDipoleMoment: r"""The ElectronicDipoleMoment property. This Property implements the operator which evaluates the **electronic** dipole moment, based on the electronic integrals: .. math:: \vec{d}_{pq} = \int \phi_{p} \vec{r} \phi_{q} , where the integral is over the all space. The operator can then be expressed as: .. math:: \hat{d} = \sum_{p, q} d^x_{pq} a^\dagger_p a_q , where :math:`x` can be any of the three Cartesian axes (:math:`\{x, y, z\}`). Just like in the :class:`qiskit_nature.second_q.hamiltonians.ElectronicEnergy`, the nuclear contribution is stored **separately** in the :attr:`nuclear_dipole_moment` attribute and will **not** be included into the generated operator. It is however possible, to manually add this constant shift to the electronic dipole components as a constant term in the internal :class:`qiskit_nature.second_q.operators.ElectronicIntegrals` instances. Assuming you have obtained an ``ElectronicDipoleMoment`` instance (for example from one of the classical code drivers), you can add the nuclear component to be included in the qubit operator like so: .. code-block:: python from qiskit_nature.second_q.operators import PolynomialTensor # you have obtained your dipole moment property and store it in this variable dipole: ElectronicDipoleMoment nuclear_dip = dipole.nuclear_dipole_moment dipole.x_dipole.alpha += PolynomialTensor({"": nuclear_dip[0]}) dipole.y_dipole.alpha += PolynomialTensor({"": nuclear_dip[1]}) dipole.z_dipole.alpha += PolynomialTensor({"": nuclear_dip[2]}) The following attributes can be set via the initializer but can also be read and updated once the ``ElectronicDipoleMoment`` object has been constructed. Attributes: x_dipole (ElectronicIntegrals): the ``ElectronicIntegrals`` for the :math:`x`-axis component. y_dipole (ElectronicIntegrals): the ``ElectronicIntegrals`` for the :math:`y`-axis component. z_dipole (ElectronicIntegrals): the ``ElectronicIntegrals`` for the :math:`z`-axis component. constants (MutableMapping[str, DipoleTuple]): a mapping of constant dipole offsets, not mapped to the qubit operator. Each entry must be a tuple of length three (for the three Cartesian axes). reverse_dipole_sign: whether or not to reverse the sign of the computed electronic dipole moment when adding it to the :attr:`nuclear_dipole_moment` to obtain the total. """ def __init__( self, x_dipole: ElectronicIntegrals, y_dipole: ElectronicIntegrals, z_dipole: ElectronicIntegrals, *, constants: MutableMapping[str, DipoleTuple] = None, reverse_dipole_sign: bool = False, ) -> None: """ Args: x_dipole: the :class:`qiskit_nature.second_q.operators.ElectronicIntegrals` for the :math:`x`-axis component. y_dipole: the :class:`qiskit_nature.second_q.operators.ElectronicIntegrals` for the :math:`y`-axis component. z_dipole: the :class:`qiskit_nature.second_q.operators.ElectronicIntegrals` for the :math:`z`-axis component. constants: a mapping of constant dipole offsets, not mapped to the qubit operator. Each entry must be a tuple of length three (for the three Cartesian axes). reverse_dipole_sign: whether or not to reverse the dipole sign. """ self.x_dipole = x_dipole self.y_dipole = y_dipole self.z_dipole = z_dipole self.constants = constants if constants is not None else {} self.reverse_dipole_sign = reverse_dipole_sign @property def nuclear_dipole_moment(self) -> DipoleTuple | None: """The nuclear dipole moment. See :attr:`qiskit_nature.second_q.hamiltonians.ElectronicEnergy.nuclear_repulsion_energy` for an example on how to add the constant terms as offsets to the internal :class:`qiskit_nature.second_q.operators.ElectronicIntegrals`. """ return self.constants.get("nuclear_dipole_moment", None) @nuclear_dipole_moment.setter def nuclear_dipole_moment(self, d_nuc: DipoleTuple) -> None: self.constants["nuclear_dipole_moment"] = d_nuc
[docs] def second_q_ops(self) -> Mapping[str, FermionicOp]: """Returns the second quantized dipole moment operators. Returns: A mapping of strings to `FermionicOp` objects. """ ops = {} ops["XDipole"] = FermionicOp.from_polynomial_tensor(self.x_dipole.second_q_coeffs()) ops["YDipole"] = FermionicOp.from_polynomial_tensor(self.y_dipole.second_q_coeffs()) ops["ZDipole"] = FermionicOp.from_polynomial_tensor(self.z_dipole.second_q_coeffs()) return ops
[docs] def interpret( self, result: "qiskit_nature.second_q.problems.EigenstateResult" # type: ignore[name-defined] ) -> None: """Interprets an :class:`qiskit_nature.second_q.problems.EigenstateResult`. In particular, this extracts the evaluated electronic dipole moment values from the auxiliary operator eigenvalues and this adds the constant dipole shifts stored in this property to the result object. Args: result: the result to add meaning to. """ result.nuclear_dipole_moment = self.nuclear_dipole_moment result.reverse_dipole_sign = self.reverse_dipole_sign result.computed_dipole_moment = [] result.extracted_transformer_dipoles = [] if result.aux_operators_evaluated is None: return for aux_op_eigenvalues in result.aux_operators_evaluated: if not isinstance(aux_op_eigenvalues, dict): continue dipole_moment = [None] * 3 for idx, axis in enumerate("XYZ"): moment = aux_op_eigenvalues.get(f"{axis}Dipole", None) if moment is not None: dipole_moment[idx] = moment.real result.computed_dipole_moment.append(cast(DipoleTuple, tuple(dipole_moment))) result.extracted_transformer_dipoles.append( { name: constant for name, constant in self.constants.items() if name != "nuclear_dipole_moment" } )