fermion_jordan_wigner

fermion_jordan_wigner(op, num_qubits)

Map a FermionOperator to a SparseObservable under the Jordan-Wigner transformation. [1]

Fermionic mode \(j\) is mapped to qubit \(j\) of the resulting SparseObservable (i.e. the identity is used on any qubit outside the operator’s support). This follows Qiskit’s little-endian qubit ordering, where the qubit index in a Pauli label such as X_2 Z_1 Z_0 is the mode index.

Parameters:
  • op – the fermionic operator to map.

  • num_qubits – the number of qubits for the resulting qubit operator. This must be strictly greater than the largest mode index in op (any additional qubits are padded with the identity).

Returns:

The mapped qubit operator. The result is not guaranteed to be fully simplified; call simplify() to combine any remaining duplicate terms. Duplicates are merged as the result is assembled, to bound the memory required, so the exact number of terms returned may vary with the number of threads used. This does not affect the operator that the result represents.

Raises:

ValueError – if num_qubits is too small to hold the operator’s support, i.e. if it is not larger than the largest mode index acted upon by op.

Memory usage

This mapping is parallelized for speed, and that choice costs memory. Each worker thread accumulates into an observable of its own, and the terms are handed to whichever thread is free rather than partitioned by which Pauli strings they produce, so every thread ends up holding roughly a full copy of the mapped operator. Peak memory therefore grows with the number of threads: expect on the order of the mapped operator’s size times the thread count, plus the input operator.

If memory matters more than wall-clock time, there are two things to reach for:

  • Reduce the thread count, using rayon’s RAYON_NUM_THREADS environment variable. Peak memory falls roughly in proportion, and the mapping takes correspondingly longer.

  • Map the operator in batches and combine the results yourself, which bounds the peak by the batch size at the cost of repeating the merging work:

    import itertools
    
    terms = op.iter_terms()
    total = None
    while batch := list(itertools.islice(terms, 100_000)):
        partial = fermion_jordan_wigner(FermionOperator.from_terms(batch), num_qubits)
        partial = partial.simplify()
        total = partial if total is None else (total + partial).simplify()
    

    Note that the terms are streamed rather than materialized: list(op.iter_terms()) on a large operator costs more than the mapping itself.

Definition

The Jordan-Wigner transformation maps fermionic creation and annihilation operators to spin (or in this case, qubit) operators:

\[a^\dagger_j \rightarrow \bigotimes_{k\lt j} \sigma^Z_k \otimes \sigma^-_j ~~\text{and}~~ a_j \rightarrow \bigotimes_{k\lt j} \sigma^Z_k \otimes \sigma^+_j \, ,\]

where \(a^\dagger_j\) (\(a_j\)) is the fermionic creation (annihilation) operator acting on the \(j\)-th spin-less fermionic mode, \(\sigma^P\) with \(P \in \{X,Y,Z\}\) are the spin-\(\frac{1}{2}\) Pauli operators and \(\sigma^\pm = (\sigma^X \pm \mathrm{i} \sigma^Y) / 2\).

This mapping preserves the fermionic anti-commutation relations by introducing a chain of \(\sigma^Z\) operators on all qubits preceding the acted-upon index \(j\).

Usage

Since a FermionOperator does not determine a fixed number of modes which it acts upon, one can specify the number of qubits to map onto when calling this function.

>>> from qiskit_fermions.mappers.library import fermion_jordan_wigner
>>> from qiskit_fermions.operators import FermionOperator
>>> fop = FermionOperator.from_dict(
...     {
...         (): 2.0,
...         ((True, 0), (False, 0)): 0.1,
...         ((True, 1), (False, 2), (True, 2), (False, 1)): -1.0j,
...     }
... )
>>> qop = fermion_jordan_wigner(fop, 4)
>>> qop.simplify()
<SparseObservable with 5 terms on 4 qubits: (2.05-0.25j)() + (-0.05+0j)(Z_0) + (0+0.25j)(Z_1) + (0+0.25j)(Z_2 Z_1) + (0-0.25j)(Z_2)>