Source code for qiskit_nature.second_q.transformers.basis_transformer
# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2022, 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 http://www.apache.org/licenses/LICENSE-2.0.
#
# 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 basis transformer."""
from __future__ import annotations
import logging
from typing import cast
import numpy as np
from qiskit_nature.exceptions import QiskitNatureError
from qiskit_nature.second_q.hamiltonians import ElectronicEnergy, Hamiltonian
from qiskit_nature.second_q.operators import ElectronicIntegrals, PolynomialTensor, Tensor
from qiskit_nature.second_q.problems import BaseProblem, ElectronicBasis, ElectronicStructureProblem
from qiskit_nature.second_q.properties import (
AngularMomentum,
ElectronicDensity,
ElectronicDipoleMoment,
Magnetization,
ParticleNumber,
)
from .base_transformer import BaseTransformer
LOGGER = logging.getLogger(__name__)
[docs]class BasisTransformer(BaseTransformer):
"""A transformer to map from one basis to another.
Since problems have a basis associated with them (e.g. the
:class:`qiskit_nature.second_q.problems.ElectronicBasis` in the case of the
:class:`qiskit_nature.second_q.problems.ElectronicStructureProblem`), this transformer can be
used to map from one :attr:`initial_basis` to another :attr:`final_basis`.
For example, this is how you can create an AO-to-MO transformer for an
:class:`qiskit_nature.second_q.problems.ElectronicStructureProblem`:
.. code-block:: python
# assuming you have the transformation coefficients from somewhere
ao2mo_coeff, ao2mo_coeff_b = ...
from qiskit_nature.second_q.operators import ElectronicIntegrals
from qiskit_nature.second_q.problems import ElectronicBasis
from qiskit_nature.second_q.transformers import BasisTransformer
transformer = BasisTransformer(
ElectronicBasis.AO,
ElectronicBasis.MO,
ElectronicIntegrals.from_raw_integrals(ao2mo_coeff, h1_b=ao2mo_coeff_b),
)
problem_MO = transformer.transform(problem_AO)
Attributes:
initial_basis: the initial basis from which to map away from.
final_basis: the final basis into which to map into.
coefficients: the coefficients which transform from the initial to the final basis.
"""
# TODO: figure out how we can make its interface non-electronic specific
def __init__(
self,
initial_basis: ElectronicBasis,
final_basis: ElectronicBasis,
coefficients: PolynomialTensor | ElectronicIntegrals,
) -> None:
"""
Args:
initial_basis: the initial basis from which to map away from.
final_basis: the final basis into which to map into.
coefficients: the coefficients which transform from the initial to the final basis.
"""
self.initial_basis = initial_basis
self.final_basis = final_basis
self.coefficients = coefficients
[docs] def invert(self) -> BasisTransformer:
"""Invert the transformer to do the reversed transformation.
Returns:
A new ``BasisTransformer`` mapping from :attr:`final_basis` to :attr:`initial_basis`.
"""
return BasisTransformer(
self.final_basis,
self.initial_basis,
self.coefficients.__class__.apply( # type: ignore[arg-type]
np.transpose, self.coefficients, validate=False
),
)
[docs] def transform(self, problem: BaseProblem) -> BaseProblem:
if isinstance(problem, ElectronicStructureProblem):
return self._transform_electronic_structure_problem(problem)
else:
raise NotImplementedError(
f"The problem of type, {type(problem)}, is not supported by this transformer."
)
def _transform_electronic_structure_problem(
self, problem: ElectronicStructureProblem
) -> ElectronicStructureProblem:
"""Transforms an :class:`qiskit_nature.second_q.problems.ElectronicStructureProblem`.
.. note::
This function will log warnings, when encountering unsupported/unknown properties in the
:class:`qiskit_nature.second_q.problems.ElectronicPropertiesContainer`.
Args:
problem: the ``ElectronicStructureProblem`` to transform.
Raises:
QiskitNatureError: if the basis of the supplied `ElectronicStructureProblem` does not
match the :attr:`initial_basis`.
Returns:
The transformed ``ElectronicStructureProblem``.
"""
if problem.basis != self.initial_basis:
raise QiskitNatureError(
f"The problems' basis, {problem.basis}, does not match the initial basis of this "
f"transformer, {self.initial_basis}."
)
new_problem = ElectronicStructureProblem(
cast(ElectronicEnergy, self.transform_hamiltonian(problem.hamiltonian))
)
new_problem.basis = self.final_basis
new_problem.molecule = problem.molecule
new_problem.reference_energy = problem.reference_energy
new_problem.num_particles = problem.num_particles
new_problem.num_spatial_orbitals = problem.num_spatial_orbitals
for prop in problem.properties:
if isinstance(prop, ElectronicDipoleMoment):
new_problem.properties.electronic_dipole_moment = (
self._transform_electronic_dipole_moment(prop)
)
elif isinstance(prop, ElectronicDensity):
new_problem.properties.electronic_density = self.transform_electronic_integrals(
prop
)
elif isinstance(prop, AngularMomentum):
if prop.overlap is None:
# nothing needs to be changed
new_problem.properties.add(prop)
continue
if isinstance(self.coefficients, ElectronicIntegrals):
coeff_alpha = self.coefficients.alpha["+-"]
coeff_beta: Tensor
if self.coefficients.beta.is_empty():
coeff_beta = coeff_alpha
else:
coeff_beta = self.coefficients.beta["+-"]
new_problem.properties.angular_momentum = AngularMomentum(
prop.num_spatial_orbitals,
coeff_alpha.transpose() @ prop.overlap @ coeff_beta,
)
elif isinstance(prop, (Magnetization, ParticleNumber)):
new_problem.properties.add(prop)
else:
LOGGER.warning("Encountered an unsupported property of type '%s'.", type(prop))
return new_problem
[docs] def transform_electronic_integrals(self, integrals: ElectronicIntegrals) -> ElectronicIntegrals:
"""Transforms an :class:`qiskit_nature.second_q.operators.ElectronicIntegrals` instance.
Args:
integrals: the ``ElectronicIntegrals`` to transform.
Raises:
QiskitNatureError: when using this method on a ``BasisTransformer`` that does not store
its :attr:`coefficients` as ``ElectronicIntegrals``, too.
Returns:
The transformed ``ElectronicIntegrals``.
"""
if not isinstance(self.coefficients, ElectronicIntegrals):
raise QiskitNatureError(
"You cannot transform ElectronicIntegrals with a BasisTransformer containing "
f"coefficients of type, {type(self.coefficients)}, rather than ElectronicIntegrals."
)
prsq = "prsq"
iklj = "iklj"
two_body_aa = integrals.alpha.get("++--", None)
if two_body_aa is not None:
prsq = "".join(two_body_aa._reverse_label_template(prsq))
iklj = "".join(two_body_aa._reverse_label_template(iklj))
einsum_map = {
"jk,ji,kl->il": ("+-",) * 4,
f"{prsq},pi,qj,rk,sl->{iklj}": ("++--", *("+-",) * 4, "++--"),
}
transformed_integrals = ElectronicIntegrals.einsum(
einsum_map, integrals, *(self.coefficients,) * 4
)
if not self.coefficients.beta.is_empty() and transformed_integrals.beta_alpha.is_empty():
transformed_integrals.beta_alpha = PolynomialTensor.einsum(
{f"{prsq},pi,qj,rk,sl->{iklj}": ("++--", *("+-",) * 4, "++--")},
integrals.alpha if integrals.beta_alpha.is_empty() else integrals.beta_alpha,
*(self.coefficients.beta,) * 2,
*(self.coefficients.alpha,) * 2,
)
return transformed_integrals
[docs] def transform_hamiltonian(self, hamiltonian: Hamiltonian) -> Hamiltonian:
if isinstance(hamiltonian, ElectronicEnergy):
integrals = hamiltonian.electronic_integrals
hamiltonian.electronic_integrals = self.transform_electronic_integrals(integrals)
return hamiltonian
else:
raise NotImplementedError(
f"The hamiltonian of type, {type(hamiltonian)}, is not supported by this "
"transformer."
)
def _transform_electronic_dipole_moment(
self, dipole_moment: ElectronicDipoleMoment
) -> ElectronicDipoleMoment:
"""Transforms an :class:`qiskit_nature.second_q.properties.ElectronicDipoleMoment`.
Args:
dipole_moment: the ``ElectronicDipoleMoment`` to transform.
Returns:
The transformed ``ElectronicDipoleMoment``.
"""
new_dipole_moment = ElectronicDipoleMoment(
self.transform_electronic_integrals(dipole_moment.x_dipole),
self.transform_electronic_integrals(dipole_moment.y_dipole),
self.transform_electronic_integrals(dipole_moment.z_dipole),
)
new_dipole_moment.reverse_dipole_sign = dipole_moment.reverse_dipole_sign
new_dipole_moment.nuclear_dipole_moment = dipole_moment.nuclear_dipole_moment
return new_dipole_moment