# 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 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 Base Problem class."""
from __future__ import annotations
from typing import Callable
import numpy as np
from qiskit_algorithms import EigensolverResult, MinimumEigensolverResult
from qiskit.quantum_info.analysis.z2_symmetries import Z2Symmetries
from qiskit_nature.second_q.mappers import QubitMapper, TaperedQubitMapper
from qiskit_nature.second_q.operators import SparseLabelOp
from qiskit_nature.second_q.hamiltonians import Hamiltonian
from .eigenstate_result import EigenstateResult
from .properties_container import PropertiesContainer
[docs]class BaseProblem:
"""The base representation of a second-quantization problem.
If none of the specific subclasses of this class fit your use case, you can instantiate this
class itself with your custom :class:`.Hamiltonian` instance and pass it into one of the
available algorithms.
The following attributes can be read and updated once the ``BaseProblem`` object has been
constructed.
Attributes:
properties (PropertiesContainer): a container for additional observable operator factories.
"""
def __init__(self, hamiltonian: Hamiltonian) -> None:
"""
Args:
driver: A driver encoding the molecule information.
transformers: A list of transformations to be applied to the driver result.
main_property_name: A main property name for the problem
"""
self._hamiltonian = hamiltonian
self.properties = PropertiesContainer()
@property
def hamiltonian(self) -> Hamiltonian:
"""Returns the hamiltonian wrapped by this problem."""
return self._hamiltonian
[docs] def second_q_ops(self) -> tuple[SparseLabelOp, dict[str, SparseLabelOp]]:
"""Returns the second quantized operators associated with this problem.
Returns:
A tuple, with the first object being the main operator and the second being a dictionary
of auxiliary operators.
"""
main_op = self.hamiltonian.second_q_op()
aux_ops: dict[str, SparseLabelOp] = {}
for prop in self.properties:
aux_ops.update(prop.second_q_ops())
return main_op, aux_ops
def _symmetry_sector_locator(
self,
z2_symmetries: Z2Symmetries,
mapper: QubitMapper,
) -> list[int] | None:
# pylint: disable=unused-argument
"""Given the detected Z2Symmetries, it can determine the correct sector of the tapered
operators so the correct one can be returned
Args:
z2_symmetries: the z2 symmetries object.
mapper: the ``QubitMapper`` used for the operator conversion that symmetries are to be
determined for.
Returns:
the sector of the tapered operators with the problem solution
"""
return None
[docs] def get_tapered_mapper(self, mapper: QubitMapper) -> TaperedQubitMapper:
"""Builds a ``TaperedQubitMapper`` from one of the mappers.
This simplifies the identification of the Pauli operator symmetries and of the symmetry sector
in which lies the solution of the problem.
Args:
mapper: ``QubitMapper`` object implementing the mapping of second quantized operators to
Pauli operators.
Raises:
ValueError: If the mapper is a ``TaperedQubitMapper``.
Returns:
A ``TaperedQubitMapper`` with pre-built symmetry specifications.
"""
if isinstance(mapper, TaperedQubitMapper):
raise ValueError(
"TaperedQubitMapper instance cannot be built from another "
"TaperedQubitMapper. If you want to update your TaperedQubitMapper "
"instance please build a new one starting from the standard mappers."
)
qubit_op, _ = self.second_q_ops()
mapped_op = mapper.map(qubit_op)
z2_symmetries = Z2Symmetries.find_z2_symmetries(mapped_op)
# pylint: disable=assignment-from-none
# Known issue for abstract class methods https://github.com/PyCQA/pylint/issues/2559
tapering_values = self._symmetry_sector_locator(z2_symmetries, mapper)
z2_symmetries.tapering_values = tapering_values
return TaperedQubitMapper(mapper, z2_symmetries)
[docs] def interpret(
self,
raw_result: EigenstateResult | EigensolverResult | MinimumEigensolverResult,
) -> EigenstateResult:
"""Interprets an EigenstateResult in the context of this problem.
Args:
raw_result: an eigenstate result object.
Returns:
An interpreted `EigenstateResult` in the form of a subclass of it. The actual type
depends on the problem that implements this method.
"""
return EigenstateResult.from_result(raw_result)
[docs] def get_default_filter_criterion(
self,
) -> Callable[[list | np.ndarray, float, list[float] | None], bool] | None:
"""Returns a default filter criterion method to filter the eigenvalues computed by the
eigen solver. For more information see also
:meth:`~qiskit_algorithms.NumPyEigensolver.filter_criterion`.
In the fermionic case the default filter ensures that the number of particles is being
preserved.
"""
return None