Source code for qiskit_nature.second_q.mappers.mode_based_mapper

# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2023, 2025.
#
# 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.

"""Mode Based Mapper."""

from __future__ import annotations

from typing import Union
from abc import abstractmethod

import numpy as np
from qiskit.quantum_info.operators import Pauli, PauliList, SparsePauliOp

from qiskit_nature import QiskitNatureError
from qiskit_nature.second_q.operators import SparseLabelOp
from qiskit_nature.second_q.mappers.qubit_mapper import QubitMapper

# Types that can be data for a SparsePauliOp
PauliType = Union[PauliList, SparsePauliOp, Pauli, list, str]


[docs] class ModeBasedMapper(QubitMapper): """Mapper from ``SparseLabelOp`` to a qubit operator using a Pauli table.""" def _map_single( self, second_q_op: SparseLabelOp, *, register_length: int | None = None ) -> SparsePauliOp: return self.mode_based_mapping(second_q_op, register_length=register_length)
[docs] @abstractmethod def pauli_table(self, register_length: int) -> list[tuple[PauliType, PauliType]]: r"""Generates a Pauli-lookup table mapping from modes to Pauli operators or pairs of Pauli operators. This table is a list of tuples :math:`(P, Q)` of two Pauli operators, corresponding to the real part :math:`P` and imaginary part :math:`Q` for the respective mode index. These Pauli operators are used to construct the creation and annihilation operators :math:`(P \pm i Q)/2`. The generated table is processed by :meth:`.sparse_pauli_operators`. Args: register_length: the register length for which to generate the table. Returns: A list of tuples of two Pauli string operators. """
[docs] def sparse_pauli_operators( self, register_length: int ) -> tuple[list[SparsePauliOp], list[SparsePauliOp]]: # pylint: disable=unused-argument """Generates the :class:`.SparsePauliOp` terms. This uses :meth:`.pauli_table` to construct a list of operators used to translate the second-quantization symbols into qubit operators. Args: register_length: the register length for which to generate the operators. Returns: Two lists stored in a tuple, consisting of the creation and annihilation operators, applied on the individual modes. """ times_creation_op = [] times_annihilation_op = [] for paulis in self.pauli_table(register_length): real_part = SparsePauliOp(paulis[0], coeffs=[0.5]) imag_part = SparsePauliOp(paulis[1], coeffs=[0.5j]) # The creation operator is given by 0.5*(X - 1j*Y) creation_op = real_part - imag_part times_creation_op.append(creation_op) # The annihilation operator is given by 0.5*(X + 1j*Y) annihilation_op = real_part + imag_part times_annihilation_op.append(annihilation_op) return (times_creation_op, times_annihilation_op)
[docs] def mode_based_mapping( self, second_q_op: SparseLabelOp, register_length: int | None = None, ) -> SparsePauliOp: # pylint: disable=unused-argument """Utility method to map a ``SparseLabelOp`` to a qubit operator using a pauli table. Args: second_q_op: the `SparseLabelOp` to be mapped. register_length: when provided, this will be used to overwrite the ``register_length`` attribute of the operator being mapped. This is possible because the ``register_length`` is considered a lower bound. Returns: The qubit operator corresponding to the problem-Hamiltonian in the qubit space. Raises: QiskitNatureError: If number length of pauli table does not match the number of operator modes, or if the operator has unexpected label content """ if register_length is None: register_length = second_q_op.register_length times_creation_op, times_annihilation_op = self.sparse_pauli_operators(register_length) mapped_string_length = times_creation_op[0].num_qubits # make sure ret_op_list is not empty by including a zero op ret_op_list = [SparsePauliOp("I" * mapped_string_length, coeffs=[0])] for terms, coeff in second_q_op.terms(): # 1. Initialize an operator list with the identity scaled by the `coeff` ret_op = SparsePauliOp("I" * mapped_string_length, coeffs=np.array([coeff])) # Go through the label and replace the fermion operators by their qubit-equivalent, then # save the respective Pauli string in the pauli_str list. for term in terms: char = term[0] position = int(term[1]) if char in ("+", ""): # "" for MajoranaOp, creator = annihilator ret_op = ret_op.compose(times_creation_op[position], front=True).simplify() elif char == "-": ret_op = ret_op.compose(times_annihilation_op[position], front=True).simplify() # catch any disallowed labels else: raise QiskitNatureError( f"FermionicOp label included '{char}'. Allowed characters: I, N, E, +, -" ) ret_op_list.append(ret_op) sparse_op = SparsePauliOp.sum(ret_op_list).simplify() return sparse_op