# fermion_jordan_wigner

### fermion_jordan_wigner(op, num_qubits)

Map a [`FermionOperator`](qiskit_fermions.operators.FermionOperator.html.md#qiskit_fermions.operators.FermionOperator) to a [`SparseObservable`](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.quantum_info.SparseObservable) under the
Jordan-Wigner transformation. <sup>[1](#id2)</sup>

Fermionic mode $j$ is mapped to qubit $j$ of the resulting
[`SparseObservable`](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.quantum_info.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()`](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.quantum_info.SparseObservable#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**](https://docs.python.org/3/builtins/exceptions.html#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`.
  * [**OverflowError**](https://docs.python.org/3/builtins/exceptions.html#OverflowError) – if `num_qubits` is negative or does not fit in 32 bits.

## 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:
  ```python
  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`](qiskit_fermions.operators.FermionOperator.html.md#qiskit_fermions.operators.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.

```pycon
>>> 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)>
```

* <a id='id2'>**[1]**</a> P. Jordan and E. Wigner, Über das Paulische Äquivalenzverbot, Zeitschrift für Physik 47, No. 9. (1928), pp. 631–651, [doi:10.1007/BF01331938](https://link.springer.com/article/10.1007/BF01331938).
