"""Fermion-to-qubit mappings.
``encode_operator`` delegates to the qarpx C++ transform kernels
(:mod:`qarp.operators.functions`); ``encode_state`` converts an
occupation-number vector (abab list, index ``i`` = spin orbital ``i`` =
qubit ``i``, LSB — see :mod:`qarp.operators.onv`) into the mapped
computational-basis bitstring, in the same qubit-indexed LSB order.
These stay *classes* (unlike the function-first fermionic surface around
them) because blocks dispatch polymorphically on ``mapping=`` parameters
(``MappedONVStateBlock``, ``UCCBlock``, ``PauliBlockFactory``) — the
encode_operator/encode_state pair travels as one object. ``Parity`` and
``BravyiKitaev`` carry constructor state because their transforms of an
*operator* depend on the total register size, which the operator alone
cannot fix (JW never needs it). ``Parity`` requires it; ``BravyiKitaev``
defaults to inferring it — per operator when given one, and once across
the whole list when given several, so every member of a list is mapped on
the same register.
"""
from abc import ABC, abstractmethod
from typing import Optional, Union
import numpy as np
from ._fermion_operator import FermionOperator
from ._qubit_operator import QubitOperator
from .functions import bravyi_kitaev, count_qubits, jordan_wigner, parity_transform
from .onv import Onv
[docs]
class Mapping(ABC):
"""Abstract base class for fermion-to-qubit mappings."""
[docs]
@abstractmethod
def encode_operator(
self, op: Union[list[FermionOperator], FermionOperator]
) -> Union[list[QubitOperator], QubitOperator]:
"""Map the provided FermionOperator(s) to QubitOperator(s)."""
[docs]
@abstractmethod
def encode_state(self, onv: Onv) -> list[int]:
"""Map an occupation-number vector to the mapped qubit basis state."""
[docs]
class JordanWigner(Mapping):
"""A class for mapping FermionOperators to qubit operators using the Jordan-Wigner mapping."""
[docs]
def encode_operator(self, op: Union[list[FermionOperator], FermionOperator]):
"""Perform a Jordan-Wigner mapping of the provided operator(s).
Args:
op: A list of, or single, FermionOperator object.
Returns:
The result of the mapping as a list of, or single, QubitOperator.
"""
qop = None
if isinstance(op, FermionOperator):
qop = jordan_wigner(op)
if isinstance(op, list):
qop = [jordan_wigner(i) for i in op]
if qop is None or not (isinstance(op, FermionOperator) or isinstance(op, list)):
raise RuntimeError("Unrecognised type passed to .encode_operator().")
return qop
[docs]
def encode_state(self, onv: Onv) -> list[int]:
"""Perform a state mapping of a provided onv to an initial bitrepresentation.
Args:
onv: An occupation-number vector (abab list).
Returns:
A list of 0s and 1s representing the initial qubit state.
"""
# A copy: callers store the result and must not alias the input.
return list(onv)
def _beta(onv: Onv):
"""Computes the beta matrix associated to transform from occupation number basis to Bravyi-Kitaev basis.
Args:
onv: An occupation-number vector.
Returns:
beta: transformation matrix as a numpy array
"""
nqubits = round(
np.ceil(np.log2(len(onv)))
) # ceil because we need to add ancilla qubits to store all orbitals
iden = np.eye(2)
matrix = np.array([[1]])
for x in range(nqubits):
matrix = np.kron(iden, matrix)
matrix[-1, : 2**x] = 1
return matrix[: len(onv), : len(onv)]
[docs]
class BravyiKitaev(Mapping):
"""A class for mapping FermionOperators to qubit operators using the Bravyi-Kitaev mapping.
The BK transform of an operator depends on the register width: the update,
parity and remainder sets of orbital ``j`` change with ``n_qubits``. A
list is therefore mapped on **one** width — ``n_qubits`` when given, else
the largest ``count_qubits`` over the list — so its members compose
(e.g. UCC generators against a Hamiltonian). A single operator with
``n_qubits=None`` keeps the per-operator inference.
Args:
n_qubits: register width for every ``encode_operator`` call, or
``None`` to infer it (per call, as described above).
"""
def __init__(self, n_qubits: Optional[int] = None):
self.n_qubits = n_qubits
[docs]
def encode_operator(self, op: Union[list[FermionOperator], FermionOperator]):
"""Perform a Bravyi-Kitaev mapping of the provided operator(s).
Args:
op: A list of, or single, FermionOperator object.
Returns:
The result of the mapping as a list of, or single, QubitOperator.
"""
if isinstance(op, FermionOperator):
return bravyi_kitaev(op, self.n_qubits)
if isinstance(op, list):
if not op:
return []
width = self.n_qubits
if width is None:
width = max(count_qubits(f) for f in op)
return bravyi_kitaev(op, width)
raise RuntimeError("Unrecognised type passed to .encode_operator().")
[docs]
def encode_state(self, onv: Onv) -> list[int]:
"""Perform a state mapping of a provided onv to the Bravyi-Kitaev basis.
Args:
onv: An occupation-number vector (abab list).
Returns:
A list of 0s and 1s representing the initial qubit state in the Bravyi-Kitaev basis.
"""
beta = _beta(onv)
onvBK = np.mod(beta @ onv, 2)
return [int(i) for i in onvBK]
[docs]
class Parity(Mapping):
"""A class for mapping FermionOperators to qubit operators using the parity mapping.
Args:
n_qubits: number of qubits in the qubit register.
"""
def __init__(self, n_qubits: int):
self.n_qubits = n_qubits
[docs]
def encode_operator(self, op: Union[list[FermionOperator], FermionOperator]):
"""Perform a parity mapping of the provided operator(s).
The transform runs in the qarpx C++ kernel (a verbatim port of the
previous hand-rolled Python implementation, term order included).
Args:
op: A list of, or single, FermionOperator object.
Returns:
The result of the mapping as a list of, or single, QubitOperator.
"""
if isinstance(op, (FermionOperator, list)):
return parity_transform(op, self.n_qubits)
raise RuntimeError("Unrecognised type passed to .encode_operator().")
[docs]
def encode_state(self, onv: Onv) -> list[int]:
"""Perform a state mapping of a provided onv to the parity basis.
Args:
onv: An occupation-number vector (abab list).
Returns:
A list of 0s and 1s representing the initial qubit state in the parity basis.
"""
ones = np.ones((len(onv), len(onv)))
pi = np.tril(ones)
onvP = np.mod(pi @ onv, 2)
return [int(i) for i in onvP]