Source code for ferrmion.interop.qiskit_mapper

"""Defines the interface to Qiskit-Nature."""

import logging
from itertools import product

from qiskit.quantum_info import SparsePauliOp
from qiskit_nature.second_q.mappers.fermionic_mapper import FermionicMapper
from qiskit_nature.second_q.operators import FermionicOp

from ferrmion import FermionQubitEncoding
from ferrmion.utils import symplectic_product, symplectic_to_sparse

logger = logging.getLogger(__name__)


[docs] class QiskitAdapter(FermionicMapper): """Wrapper class enabling the use of ferrmion in qiskit. In qiskit_nature, encodings are handled by a general `QubitMapper` class, and a `FermionMapper` for Fermion to Qubit encodings. These classes have an abstract method `_map_single` which transforms a single qiskit operator into a `qiskit.SparsePauliOp`. NOTE: You must have installed the optional dependencies with `pip install ferrmion[qiskit]` for this functionality to be available. Example: >>> from ferrmion.encode import JordanWigner >>> from ferrmion.interop.qiskit import QiskitAdapter >>> from qiskit_nature.second_q.operators import FermionOp >>> fop = FermionicOp( >>> { >>> "+_0 -_0": 1.0, >>> "+_1 -_1": -1.0, >>> }, >>> num_spin_orbitals=2, >>> ) >>> mapper = QiskitAdapter(JordanWigner(2)) >>> mapper.map(fop) ) """ def __init__(self, encoding: FermionQubitEncoding) -> None: """Initialise QiskitAdapter. Args: encoding (FermionQubitEncoding): A valid ferrmion encoding. """ self.encoding = encoding super().__init__() def _map_single( self, second_q_op: FermionicOp, *, register_length: int | None = None ) -> SparsePauliOp: """Function required to adapt ferrmion encodings to qiskit_nature. Allows the use of a ferrmion.FermionQubitEncoding to encode qiskit_nature. Args: second_q_op (qiskit.FermionicOp): A fermionic Operator. register_length (int): Number of qubits to use for operator (typically equals the number of modes of encoding). """ if register_length is None: register_length = second_q_op.register_length ipowers, symplectics = self.encoding._build_symplectic_matrix() term_ops = [] for term, term_coeff in second_q_op.terms(): majoranas = [] term_ipowers = [] logger.debug(f"{term=}, {term_coeff=}") for signature, index in term: logger.debug(f"{signature=}, {index=}") match signature: case "+": term_ipowers.append([0, 3]) case "-": term_ipowers.append([0, 1]) case _: raise ValueError("Term signature should be + or -") majoranas.append([2 * int(index), 2 * int(index) + 1]) sparse_list = [] for iterm, comb in zip(product(*term_ipowers), product(*majoranas)): logger.debug(f"{comb=}") ipower = sum(ipowers[m] for m in comb) + sum(it for it in iterm) left = symplectics[comb[0]] logger.debug(f"{comb[0]=},{ipower=}, {left=}") for m_index in comb[1:]: right = symplectics[m_index] iprod, left = symplectic_product(left, right) ipower += iprod logger.debug(f"{m_index=}, {ipower=}, {left=}") sparse = symplectic_to_sparse(left, ipower) logger.debug(f"{sparse=}") coeff = term_coeff coeff *= 0.5 ** len(comb) coeff *= sparse[2] sparse_list.append((sparse[0], list(sparse[1]), coeff)) logger.debug(f"{sparse_list=}") term_ops.append( SparsePauliOp.from_sparse_list( sparse_list, num_qubits=register_length ).simplify() ) logger.debug(f"{term_ops=}") sparse_op: SparsePauliOp = term_ops[0] for term in term_ops[1:]: sparse_op += term return sparse_op.simplify()