Source code for qdk_chemistry.plugins.qiskit.qubit_mapper

"""Qiskit-based qubit mappers to map electronic structure Hamiltonians to qubit operators.

This module provides a QiskitQubitMapper class to convert Hamiltonians to QubitOperators
using different mapping strategies ("jordan-wigner", "bravyi-kitaev", and "parity").
"""
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from __future__ import annotations

from typing import TYPE_CHECKING, ClassVar

from qiskit_nature.second_q.hamiltonians import ElectronicEnergy
from qiskit_nature.second_q.mappers import (
    BravyiKitaevMapper,
    JordanWignerMapper,
    ParityMapper,
)

from qdk_chemistry.algorithms.qubit_mapper import QubitMapper, QubitMapperSettings
from qdk_chemistry.data import Hamiltonian, QubitOperator
from qdk_chemistry.data._spin_channels import spin_channel_indices
from qdk_chemistry.data.enums.fermion_mode_order import FermionModeOrder
from qdk_chemistry.data.symmetry import axes
from qdk_chemistry.utils import Logger

if TYPE_CHECKING:
    from qdk_chemistry.data import MajoranaMapping

__all__ = ["QiskitQubitMapper", "QiskitQubitMapperSettings"]

_SUPPORTED_ENCODINGS: dict[str, type] = {
    "jordan-wigner": JordanWignerMapper,
    "bravyi-kitaev": BravyiKitaevMapper,
    "parity": ParityMapper,
}


[docs] class QiskitQubitMapperSettings(QubitMapperSettings): """Settings configuration for a QiskitQubitMapper."""
[docs] def __init__(self): """Initialize QiskitQubitMapperSettings.""" Logger.trace_entering() super().__init__()
[docs] class QiskitQubitMapper(QubitMapper): """Map an electronic structure Hamiltonian to a QubitOperator using Qiskit. This is a **third-party** backend: it reads ``mapping.base_encoding`` to select the corresponding Qiskit Nature mapper class and **ignores** ``mapping.table``. The qubit operator is built from scratch using Qiskit Nature's own fermion-to-qubit pipeline. .. warning:: Because this backend chooses its transform by encoding *name* rather than from the Pauli table, it relies on the mapping's ``base_encoding`` string being consistent with its table. This is guaranteed for factory-produced mappings (``MajoranaMapping.jordan_wigner()``, ``.bravyi_kitaev()``, etc.) and is verified by cross-backend eigenvalue tests in the test suite. Manually built mappings with mismatched names will produce silently incorrect results. Tapering-based encodings (e.g. parity two-qubit reduction) are supported — each backend handles tapering in its own ``_run_impl()`` via the ``QubitMapper._taper_result()`` helper. Both restricted (RHF) and unrestricted (UHF) Hamiltonians are supported. For unrestricted systems, separate alpha and beta one-body and two-body integrals are forwarded to Qiskit Nature's ``ElectronicEnergy``. Supported base encodings: - ``"jordan-wigner"`` → :class:`qiskit_nature.second_q.mappers.JordanWignerMapper` - ``"bravyi-kitaev"`` → :class:`qiskit_nature.second_q.mappers.BravyiKitaevMapper` - ``"parity"`` → :class:`qiskit_nature.second_q.mappers.ParityMapper` Examples: >>> from qdk_chemistry.algorithms import create >>> from qdk_chemistry.data import MajoranaMapping >>> mapper = create("qubit_mapper", "qiskit") >>> mapping = MajoranaMapping.jordan_wigner(num_modes=n_spin_orbitals) >>> qh = mapper.run(hamiltonian, mapping) """ QubitMappers: ClassVar = _SUPPORTED_ENCODINGS
[docs] def __init__(self): """Initialize QiskitQubitMapper.""" Logger.trace_entering() super().__init__() self._settings = QiskitQubitMapperSettings()
def _run_impl( self, hamiltonian: Hamiltonian, mapping: MajoranaMapping, ) -> QubitOperator: """Build a qubit operator via Qiskit Nature. Reads ``mapping.base_encoding`` to select a Qiskit Nature mapper class. ``mapping.table`` is **not used** — the qubit operator is built entirely by Qiskit's own pipeline. If *mapping* carries tapering metadata, the base encoding is extracted first, mapped, and tapering is applied to the result via :meth:`~QubitMapper._taper_result`. Args: hamiltonian: The fermionic Hamiltonian (restricted or unrestricted). mapping: Encoding selector — only ``base_encoding`` is read. Returns: QubitOperator: An instance of the QubitOperator. Raises: NotImplementedError: If ``mapping.base_encoding`` is not a supported Qiskit encoding. """ Logger.trace_entering() # --- Select transform by encoding name (see QubitMapper class docstring) --- encoding_name = mapping.base_encoding if encoding_name not in _SUPPORTED_ENCODINGS: raise NotImplementedError( f"Qiskit plugin does not support base encoding {encoding_name!r}. " f"Supported encodings: {sorted(_SUPPORTED_ENCODINGS.keys())}." ) h1_a, h1_b = hamiltonian.get_one_body_integrals() h2_aa, h2_ab, h2_bb = hamiltonian.get_two_body_integrals() num_orbs = len(spin_channel_indices(hamiltonian.get_orbitals().active_indices(), axes.alpha())) is_restricted = hamiltonian.get_orbitals().is_restricted() if is_restricted: electronic_hamiltonian = ElectronicEnergy.from_raw_integrals( h1_a=h1_a, h2_aa=h2_aa.reshape(num_orbs, num_orbs, num_orbs, num_orbs) ) else: # h2_ab is eri_aabb in chemist notation: (aa|bb). # Qiskit's h2_ba parameter expects (bb|aa) = eri_aabb transposed. # By Coulomb symmetry (pq|rs)=(rs|pq), this is eri_aabb[r,s,p,q]. h2_ab_4d = h2_ab.reshape(num_orbs, num_orbs, num_orbs, num_orbs) electronic_hamiltonian = ElectronicEnergy.from_raw_integrals( h1_a=h1_a, h2_aa=h2_aa.reshape(num_orbs, num_orbs, num_orbs, num_orbs), h1_b=h1_b, h2_bb=h2_bb.reshape(num_orbs, num_orbs, num_orbs, num_orbs), h2_ba=h2_ab_4d.transpose(2, 3, 0, 1), ) fermionic_op = electronic_hamiltonian.second_q_op() qubit_mapper = _SUPPORTED_ENCODINGS[encoding_name]() qubit_op = qubit_mapper.map(fermionic_op) qh = QubitOperator( pauli_strings=qubit_op.paulis.to_labels(), coefficients=qubit_op.coeffs, encoding=encoding_name, fermion_mode_order=FermionModeOrder.BLOCKED, ) return self._taper_result(qh, mapping)
[docs] def name(self) -> str: """Return the algorithm name ``qiskit``.""" Logger.trace_entering() return "qiskit"