# Lesson 14 — Translating electrons into qubits

## The same chemistry, a different language

Our nitrogen molecule has not changed. It is still neutral, singlet N₂ at 1.85 Å. The original cc-pVDZ model had 28 spatial molecular orbitals. The active-space workflow studied earlier produced a six-electron, six-spatial-orbital model, with three active alpha and three active beta electrons. That reduced chemical model, not the complete 28-orbital problem, will now be translated into qubit operators. The separate notebook at 1.270025 Å remains a different calculation.

A **fermion-to-qubit mapping** specifies how an electronic state and every operator acting on it are represented by qubits. It does not select a better basis, recover omitted correlation, or solve the Schrödinger equation. With an exact, compatible mapping, the same model has the same predictions in different coordinates. Numerical screening during mapping is a separate approximation.

For the ordinary mappings in this lesson, six spatial orbitals mean twelve spin-orbital modes and twelve occupation-register qubits. There are still six active electrons. Neither number includes scratch or phase-estimation registers. Had we mapped the full 28-spatial-orbital model without reduction, the occupation register would instead have 56 qubits.

This distinction becomes concrete in the repository’s [nitrogen mapping tutorial](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/docs/source/_static/examples/python/tutorial_map_n2_to_qubits.py): it constructs a Hamiltonian from the selected orbitals, explicitly creates a `MajoranaMapping`, maps the active operator, retains the core-energy scalar separately, and validates in the appropriate electron-number sector. We will unpack that sequence rather than treat mapping as a mysterious conversion command. No tutorial, mapper, or test is being executed in this lesson.

## From coordinates to occupations

In **first quantization**, one describes a fixed number of electrons using their coordinates and spin variables. Antisymmetry is a condition on the many-electron wavefunction: exchanging two electrons changes its sign. A determinant is one way to enforce it.

In **second quantization**, one first chooses an ordered list of one-electron modes, such as spin orbitals, and describes which modes are occupied. The underlying physics has not been quantized twice. The language has changed from named particle coordinates to occupation numbers and operations that change them.

With \(M\) spin-orbital modes, an occupation basis state is

\[
|f_0,f_1,\ldots,f_{M-1}\rangle,
\qquad f_j\in\{0,1\}.
\]

Here \(f_j\) says whether mode \(j\) contains an electron. The exclusion principle permits at most one electron in a particular spin orbital. Two electrons of opposite spin may occupy the same spatial orbital because they occupy different modes.

The space containing all these occupation states is **Fock space**. It contains sectors with zero, one, two, and more electrons. This is not the SCF Fock matrix, which was an effective one-electron operator. The similar names refer to different objects.

A twelve-mode occupation register has \(2^{12}=4096\) basis states. The sector with three alpha electrons in six alpha modes and three beta electrons in six beta modes has only

\[
\binom{6}{3}\binom{6}{3}=400
\]

states. These are illustrative combinatorial counts for the recorded active-space size, not a newly computed molecular result. An untapered qubit Hamiltonian ordinarily acts on all 4096 states. The mapping does not automatically remove the other electron sectors, and fixing alpha/beta populations does not by itself enforce a particular total-spin eigenvalue.

The ordering is part of the definition. We use

\[
|f_0,\ldots,f_{M-1}\rangle
=(a_0^\dagger)^{f_0}\cdots(a_{M-1}^\dagger)^{f_{M-1}}|\mathrm{vac}\rangle.
\]

The vacuum \(|\mathrm{vac}\rangle\) has no occupied modes. The dagger denotes an adjoint. The operator \(a_j^\dagger\) creates occupation in mode \(j\), and \(a_j\) removes it. These are mathematical building blocks; a number-conserving molecular Hamiltonian does not manufacture net electrons.

Define the preceding-occupation count \(P_j=\sum_{k<j}f_k\). Then

\[
a_j|f\rangle=(-1)^{P_j}f_j
|f_0,\ldots,0_j,\ldots\rangle,
\]

\[
a_j^\dagger|f\rangle=(-1)^{P_j}(1-f_j)
|f_0,\ldots,1_j,\ldots\rangle.
\]

The factors \(f_j\) and \(1-f_j\) enforce empty/occupied restrictions. The sign encodes fermionic exchange. It is not an optional cosmetic phase: different paths between determinants interfere, so inconsistent signs change matrix elements and energies.

Consider the hand-worked four-mode state \(|1,0,1,0\rangle\), written in increasing mode order, not as a software bitstring. Removing the electron from mode 2 crosses one preceding occupied mode, giving \(-|1,0,0,0\rangle\). Creating in mode 3 crosses two and gives \(+|1,0,1,1\rangle\). Creating in already occupied mode 2 gives zero.

The two-mode state \(|1,1\rangle\) makes the algebra especially visible: \(a_1a_0|1,1\rangle=|0,0\rangle\), whereas \(a_0a_1|1,1\rangle=-|0,0\rangle\). Operators act rightmost first. Their sum vanishes. In general the **canonical anticommutation relations** are

\[
\{a_p,a_q\}=0,\qquad
\{a_p^\dagger,a_q^\dagger\}=0,\qquad
\{a_p,a_q^\dagger\}=\delta_{pq}I.
\]

The braces mean \(\{A,B\}=AB+BA\); \(\delta_{pq}\) is one when the indices agree and zero otherwise. A correct mapping must preserve these relations, or their appropriate even-operator relations inside an encoded subspace.

## Writing the operator without losing a factor or an index

Let \(K\) denote the active spatial-orbital count. Let \(p,q,r,s\) run over local active indices from zero to \(K-1\), and \(\sigma,\tau\) denote alpha or beta spin. For the real, spin-conserving molecular integral paths considered here, write

\[
H_{\mathrm{model}}=E_{\mathrm{core}}I+H_{\mathrm{active}},
\]

\[
H_{\mathrm{active}}
=\sum_{pq,\sigma}h^\sigma_{pq}a^\dagger_{p\sigma}a_{q\sigma}
+\frac12\sum_{pqrs,\sigma\tau}g^{\sigma\tau}_{pqrs}
 a^\dagger_{p\sigma}a^\dagger_{r\tau}a_{s\tau}a_{q\sigma}.
\]

Here \(h\) contains the one-electron terms, including the effect of frozen inactive electrons when applicable. The raw, **not antisymmetrized**, Coulomb integral is in chemist order:

\[
g^{\sigma\tau}_{pqrs}=(p_\sigma q_\sigma|r_\tau s_\tau).
\]

The first pair belongs to electron coordinate 1 and the second to coordinate 2. The factor one-half accounts for the two ways of ordering the electron pair in this unrestricted sum. Formulas using antisymmetrized integrals often have a factor one-quarter and different index labels. They are not interchangeable array conventions.

The real restricted implementation shares spatial orbitals and integral blocks between spins. The unrestricted implementation keeps distinct alpha, beta, and mixed blocks. The mixed `aabb` tensor has the alpha pair on the left and beta pair on the right; exchanging whole pairs constructs the opposite-spin ordering. This matches the current producer/mapper paths, not every older explanatory comment in the repository.

Local active index 0 means the first entry of the selected active list. In our refined model it can correspond to full molecular-orbital index 4. Feeding the full index 4 where a local index 0 is required describes the wrong matrix element.

The native mapper uses excitation operators \(E^\sigma_{pq}=a^\dagger_{p\sigma}a_{q\sigma}\) and the identity

\[
a^\dagger_{p\sigma}a^\dagger_{r\tau}a_{s\tau}a_{q\sigma}
=E^\sigma_{pq}E^\tau_{rs}-\delta_{\sigma\tau}\delta_{qr}E^\sigma_{ps}.
\]

That subtraction explains an otherwise surprising implementation detail: the restricted engine folds a two-electron contraction into its effective one-body matrix before mapping. It is algebraic bookkeeping, not an additional chemical approximation. See the [native mapping engine](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/cpp/src/qdk/chemistry/data/majorana_map_engine.cpp).

Most importantly, the ordinary `qubit_mapper/qdk` path deliberately maps **only** \(H_{\mathrm{active}}\). Its Hamiltonian-taking C++ entry point sets the supplied scalar to zero. Nuclear repulsion and frozen-inactive contributions remain in `hamiltonian.get_core_energy()`. ECP effects, if present in a different model, have already entered its chosen integrals/constants; mapping does not add another universal ECP correction. Our nitrogen example is all-electron before freezing the selected inactive orbitals.

There may nevertheless be an identity Pauli term in the output: mapping occupation operators generates one. That identity coefficient is not a substitute for the omitted core scalar. The [core-energy test](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/tests/test_qdk_qubit_mapper.py) uses one-body integrals that produce an identity coefficient of 1 and a separately supplied core energy of 5; it asserts that the mapped identity coefficient remains 1. Lower-level array-based C++ overloads can accept a nonzero scalar, which explains why their tests look different.

## Pauli arithmetic: the target language

A **Pauli word** is a product of \(I,X,Y,Z\), with at most one factor on each qubit. Operators on different qubits commute because they act on different tensor factors. Operators on the same qubit do not: \(XY=iZ\), but \(YX=-iZ\); \(X^2=Y^2=Z^2=I\).

For example,

\[
(X_0Z_1)(Y_0X_1)
=(iZ_0)(iY_1)=-Z_0Y_1.
\]

This illustrative multiplication shows why merely XOR-ing letter labels is insufficient: phases must also be accumulated. The repository’s native [Pauli implementation](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/cpp/src/qdk/chemistry/data/pauli_operator.cpp) merges sparse words by qubit index and implements these cyclic and anticyclic signs explicitly.

A qubit operator has the form

\[
H_q=\sum_t c_tP_t,
\]

where \(t\) labels terms, \(P_t\) is a Pauli word, and \(c_t\) its coefficient. The individual Pauli words are Hermitian. After identical words have been combined, their coefficients must be real for a Hermitian Hamiltonian. Complex intermediate coefficients are normal during multiplication; surviving imaginary coefficients cannot simply be discarded on the assumption that all input is valid chemistry.

Two repository objects serve different purposes. `PauliOperator.X(0)` constructs a symbolic Pauli expression. Products and sums build expression trees; `.distribute()` expands products of sums, `.simplify()` performs Pauli arithmetic and combines terms, and `.prune_threshold(epsilon)` removes small terms. This is useful for deriving or constructing small operators. `QubitOperator`, in contrast, wraps an operator **representation container**. Its Pauli-decomposition form holds dense string labels and a coefficient array, plus encoding, ordering, grouping, and optional tapering metadata. A different sum-of-squares container does not automatically expose a Pauli list.

There is an important explicit text-convention boundary. `PauliOperator.to_canonical_terms(4)` puts qubit 0 on the **left**. `QubitOperator` labels put qubit 0 on the **right**. The expression \(X_0Z_2\) is therefore `XIZI` in the first interface and `IZIX` in the second. The [model-Hamiltonian conversion helper](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/utils/model_hamiltonians.py) actually reverses canonical strings when crossing that boundary. Explicit index positions are more reliable than the overloaded word “little-endian.”

This instructional excerpt follows that implementation; it is not executed here:

```python
from qdk_chemistry.data import PauliOperator, QubitOperator
import numpy as np

expr = PauliOperator.X(0) * PauliOperator.Z(2)
terms = expr.simplify().to_canonical_terms(4)
operator = QubitOperator(
    pauli_strings=[label[::-1] for _, label in terms],
    coefficients=np.array([complex(coefficient) for coefficient, _ in terms]),
)
```

This arbitrary spin operator has no claimed fermionic encoding. Attaching the text `jordan-wigner` would not prove it was derived from a particular fermionic Hamiltonian.

The current [Pauli container](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/data/qubit_operator/containers/pauli_decomposition.py) validates matching term counts, nonempty equal-length labels, and valid Pauli characters. Addition concatenates terms rather than simplifying them; `equiv` combines duplicates for comparison. `is_hermitian` checks each stored coefficient, so duplicate imaginary terms that cancel should be combined before relying on that check. `get_real_coefficients` explicitly discards imaginary parts. Its property named `schatten_norm` computes the sum of absolute **stored coefficients**; it is not the general matrix Schatten trace norm. Duplicates can therefore affect that reported quantity.

The [wrapper](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/data/qubit_operator/__init__.py) preserves a historical `qubit_hamiltonian` wire-format identifier even though the current class is `QubitOperator`. Serialization should preserve the actual container and encoding metadata, not just an informal printed list.

## Jordan–Wigner: making the fermionic sign visible

In Jordan–Wigner encoding, qubit \(j\) stores occupation \(f_j\). A local lowering operator is \((X+iY)/2\), and a local raising operator is \((X-iY)/2\). Neither alone knows how many lower-index modes are occupied. The missing factor is a string of Z operators:

\[
a_j=\left(\prod_{k<j}Z_k\right)\frac{X_j+iY_j}{2},
\qquad
a_j^\dagger=\left(\prod_{k<j}Z_k\right)\frac{X_j-iY_j}{2}.
\]

Each preceding occupied qubit contributes a minus sign because \(Z|1\rangle=-|1\rangle\). Thus the Z string implements precisely \((-1)^{P_j}\). Empty products, such as for mode 0, mean identity.

Multiplying the raising and lowering expressions gives the number operator

\[
n_j=a_j^\dagger a_j=\frac{I-Z_j}{2}.
\]

This returns zero on an empty mode and one on an occupied mode. The Z prefixes cancel. For a real hopping term between \(p<q\), the result is

\[
a_p^\dagger a_q+a_q^\dagger a_p
=\frac12\left(X_pZ_{p+1}\cdots Z_{q-1}X_q
+Y_pZ_{p+1}\cdots Z_{q-1}Y_q\right).
\]

“Hopping” here means moving occupation between modes; molecular orbitals need not be localized sites. The interior parity string remains when the modes are nonadjacent. Its length illustrates the tradeoff: direct occupation storage is simple, but some mapped operators reach across many qubits.

A hand-worked one-spatial-orbital model ties everything together. Let its one-body integral be \(\epsilon\), its Coulomb integral \(U\), and its two spin modes be alpha and beta. Then

\[
H_{\mathrm{active}}=\epsilon(n_\alpha+n_\beta)+U n_\alpha n_\beta,
\]

\[
H_q=\left(\epsilon+\frac U4\right)I
-\left(\frac\epsilon2+\frac U4\right)(Z_\alpha+Z_\beta)
+\frac U4 Z_\alpha Z_\beta.
\]

The half in the original two-electron sum has not halved the final opposite-spin interaction: both spin orderings contribute. With illustrative values \(\epsilon=-1\), \(U=0.4\), and \(E_{\mathrm{core}}=0.1\), the Pauli coefficients are \(-0.9,0.4,0.4,0.1\). Empty, singly occupied, and doubly occupied active states have energies 0, −1, and −1.6 before adding the core scalar. The doubly occupied total is −1.5. These numbers are toy arithmetic, not nitrogen energies or executed outputs.

## Why the API calls the encoding a MajoranaMapping

Define two Hermitian operators for each fermionic mode:

\[
\gamma_{2j}=a_j+a_j^\dagger,
\qquad
\gamma_{2j+1}=-i(a_j-a_j^\dagger).
\]

They satisfy \(\{\gamma_j,\gamma_k\}=2\delta_{jk}I\), and

\[
a_j=\frac{\gamma_{2j}+i\gamma_{2j+1}}2,
\qquad
a_j^\dagger=\frac{\gamma_{2j}-i\gamma_{2j+1}}2.
\]

These are **Majorana operators** as a mathematical decomposition of ordinary fermionic operators. Their use does not imply that the eventual processor uses physical Majorana devices.

For Jordan–Wigner, the even Majorana is the preceding Z string followed by X, and the odd Majorana is the same string followed by Y. The two-mode factory table, in rightmost-qubit-zero labels, is exactly `IX`, `IY`, `XZ`, `YZ`. The [factory implementation](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/cpp/src/qdk/chemistry/data/majorana_mapping_factories.cpp) constructs those sparse words directly, and [Python Majorana tests](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/tests/test_majorana_mapping.py) assert that reference table and its anticommutation algebra.

A **bilinear** contains two Majoranas. The common primitive is \(B_{jk}=i\gamma_j\gamma_k\), for distinct indices. It is Hermitian for a valid Majorana representation. In particular, \(B_{2j,2j+1}=-Z_j\) for Jordan–Wigner, so \(n_j=(I+B_{2j,2j+1})/2\).

The [MajoranaMapping data class](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/cpp/src/qdk/chemistry/data/majorana_mapping.cpp) precomputes all upper-triangle bilinears. `mapping.majorana(k)` returns a sparse word when individual Majoranas are available. `mapping.bilinear(j,k)` returns a complex coefficient and sparse word; reversing its arguments changes the sign. Equal indices are deliberately rejected by that bilinear accessor. Products of bilinears suffice for this even, number-conserving Hamiltonian, allowing the engine to support some encodings that do not expose individual Majoranas.

## The native mapping method, step by step

The algorithm family is `qubit_mapper`; its default implementation is `qdk`. The separate `MajoranaMapping` argument is required. There is no need to mutate an encoding-name setting on the mapper. Its two numerical settings are `threshold=1e-12` and `integral_threshold=1e-12`, both doubles. See the [Python adapter](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/algorithms/qubit_mapper/qdk_qubit_mapper.py) and [factory contract](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/algorithms/qubit_mapper/qubit_mapper.py).

First, the adapter gets \(K\) from the active one-body matrix and requires `mapping.num_modes == 2*K`. It determines the restricted/unrestricted strategy from the orbitals. It maps the untapered base if the supplied mapping carries tapering metadata, then handles reduction afterward.

Second, the native engine expands excitation operators through Majorana bilinears. Restricted input permits spin summation and real eightfold Coulomb symmetry; unrestricted input keeps distinct spin channels and combines opposite-spin contributions using whole-pair exchange. This is not a general complex spin–orbit or spin-flipping Hamiltonian interface: the present integral entry points consume real arrays and these spin-conserving structures.

Third, it multiplies Pauli words using **binary symplectic storage**. This simply means storing two bit masks: X sets an X bit, Z sets a Z bit, Y sets both, and I sets neither. XOR combines the masks, while a separate phase calculation tracks powers of \(i\). A hash table accumulates all contributions with the same Pauli word. Intermediate complex values are retained.

Fourth, `threshold` is applied to the **accumulated final coefficient**, not to each fractional contribution to that coefficient. The engine retains magnitudes greater than or equal to this threshold. This matters because many individually small terms can add constructively, and larger ones can cancel. `integral_threshold` is different: it screens input integrals, or the contracted effective one-body entries on the restricted path, before their contributions are accumulated. Late Pauli screening does not undo earlier integral screening. The equality convention also differs between individual input loops; do not build a scientific error specification around the exact threshold boundary.

Finally, sparse words are rendered using the mapping’s full qubit width and returned with complex coefficient storage, `encoding=base_mapping.base_encoding`, and `fermion_mode_order=BLOCKED`. A name is descriptive metadata for this native engine: the actual Pauli/bilinear data determines the transformation. Ordinary `.run` locks algorithm settings as explained in Lesson 3; configure thresholds before the call.

The implementation supports up to 1024 qubit positions through its packed-word dispatch and rejects zero-qubit maps. This is an implementation capacity, emphatically not a claim that constructing a dense 512-spatial-orbital molecular operator is cheap. A generic two-electron tensor still has quartic-size work, and the accumulated Pauli sum can itself be large. Precomputing all bilinears adds quadratic-in-mode-count table overhead, with additional word-length storage. If positive screening removes every term, the current wrapper reaches a Pauli container that rejects an empty term list; a completely screened operator is not automatically padded to a zero operator.

Container choice affects preprocessing memory. Canonical input supplies dense two-electron arrays. Cholesky input is contracted one pair-index row at a time without reconstructing the whole four-index tensor. Generic factorized input falls through to dense getters on this ordinary Pauli route. Sparse input has a restricted-only fast path that canonicalizes stored entries under real eightfold Coulomb symmetry. Unlike the sparse container’s literal getter semantics, this **consumer** supplies symmetry partners. Conflicting values at distinct symmetry-related positions are not averaged into a repaired tensor: the canonical position, or a deterministic selected partner, wins. Use physically consistent sparse data rather than exploit that selection rule. Negative/out-of-range sparse indices are rejected.

The [factorized-storage mapper tests](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/tests/test_qdk_qubit_mapper_factorized.py) compare Cholesky and sparse paths with appropriate dense references. Those are source-defined checks, not tests run here. Storage efficiency does not eliminate the cost of the resulting operator or guarantee equivalent behavior for arbitrary nonsymmetric arrays.

For error accounting, a removed Pauli contribution \(\Delta H=\sum_t d_tP_t\) obeys \(\|\Delta H\|\leq\sum_t|d_t|\), where the norm is the operator norm and every Pauli word has norm one. Thus many small dropped coefficients can matter collectively. This bound addresses that truncation only; it is not an automatic chemical-accuracy guarantee covering active-space choice, integral factorization, or the wrong symmetry sector.

## Parity and Bravyi–Kitaev: changing what a qubit stores

Jordan–Wigner stores occupations directly. **Parity encoding** instead stores cumulative parity:

\[
b_j=f_0\oplus f_1\oplus\cdots\oplus f_j.
\]

Here \(\oplus\) is addition modulo two: two ones add to zero. The inverse is \(f_0=b_0\), \(f_j=b_{j-1}\oplus b_j\). No information has been lost. Counting ones in the encoded bitstring now does not count electrons.

For a hand-worked example, the increasing-mode occupation vector \(f=(1,0,1,0)\) becomes \(b=(1,1,0,0)\). Its displayed rightmost-qubit-zero bitstring is `0011`, whereas the Jordan–Wigner string was `0101`. Both describe the same two occupied modes under different decoding rules.

CNOT gates can compute these cumulative XORs reversibly. For two modes the parity mapping is obtained by conjugating the Jordan–Wigner operators by CNOT from qubit 0 to qubit 1. The source test checks the resulting Majorana labels `XX`, `XY`, `XZ`, `YI`. For more modes, the factory builds the corresponding X tails explicitly. Parity makes some global occupation parity information local, but changing an occupation can require updating many stored parity bits; it is not uniformly more local than Jordan–Wigner.

**Bravyi–Kitaev**, abbreviated BK, balances occupation and parity information using a tree. A **Fenwick tree** stores parity over selected ranges so that both queries and updates touch logarithmically many ranges rather than every preceding or following mode. The native factory computes parity, update, and remainder sets and uses them to choose Z and X factors in each Majorana image.

For four modes, the standard BK occupation encoding is

\[
b_0=f_0,\quad b_1=f_0\oplus f_1,\quad b_2=f_2,\quad
b_3=f_0\oplus f_1\oplus f_2\oplus f_3.
\]

Its inverse is \(f_0=b_0\), \(f_1=b_0\oplus b_1\), \(f_2=b_2\), and \(f_3=b_1\oplus b_2\oplus b_3\). Our illustrative \(f=(1,0,1,0)\) therefore becomes \(b=(1,1,1,0)\), displayed as `0111`. Three encoded ones still describe two electrons. These inverse relations agree with the four-mode number-operator Z products exercised in the [mapper tests](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/tests/test_qdk_qubit_mapper.py) and the exact labels checked by the [Qiskit interoperability tests](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/tests/test_interop_qiskit_qubit_mapper.py).

The native `bravyi_kitaev` factory constructs on the next-power-of-two tree and removes out-of-range indices for non-power-of-two mode counts. `bravyi_kitaev_tree` uses a recursively balanced tree over the actual mode count. The two variants can differ, particularly away from powers of two; their names must not be interchanged merely because both contain “BK.”

Neither ordinary parity nor ordinary BK reduces the untapered qubit count below the mode count. They change Pauli support and state encoding. Shorter words can reduce some circuit costs, but term structure, hardware connectivity, state preparation, and the simulation algorithm still matter. Under an exact Clifford change of basis, Pauli words are permuted up to signs, so even their combined coefficient absolute-sum need not improve when their lengths do.

A **Clifford unitary** maps every Pauli word to another Pauli word, possibly with a sign, under conjugation. H, S, and CNOT generate familiar examples. If \(C\) converts encoding A to encoding B, the consistent transformations are

\[
|\psi_B\rangle=C|\psi_A\rangle,
\qquad H_B=CH_AC^\dagger.
\]

Changing only the operator or only its label is not that transformation. More general custom choices can also alter determinant-relative phases, not just the binary occupation labels.

## What mappings are actually available

The native factories at this revision include more than the older JW/BK-only overviews suggest:

| Python construction | What is supplied |
|---|---|
| `MajoranaMapping.jordan_wigner(M)` | Occupation encoding, with M qubits |
| `MajoranaMapping.parity(M)` | Cumulative-parity encoding, with M qubits |
| `MajoranaMapping.bravyi_kitaev(M)` | Standard Fenwick-style BK table |
| `MajoranaMapping.bravyi_kitaev_tree(M)` | Balanced-tree BK table |
| `MajoranaMapping.parity(M, symmetries)` | Parity table plus two-qubit-reduction metadata |
| `MajoranaMapping.symmetry_conserving_bravyi_kitaev(M, symmetries)` | BK-tree table plus two-qubit-reduction metadata |
| `MajoranaMapping.verstraete_cirac(lattice)` | Graph-oriented bilinear encoding with auxiliary qubits and stabilizers |

The Python symmetry argument is a `Symmetries` object carrying alpha/beta counts; it is not the two positional integer arguments used by the corresponding C++ overload. The [bindings](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/pybind11/data/majorana_mapping.cpp) define that boundary. Reduction factories require appropriate even mode counts and valid populations. Their table and `num_qubits` still describe the **pre-taper** basis; the mapper later reduces the operator. How the selected symmetry sector makes that legitimate belongs to Lesson 15, not to an assumption that BK alone “saves two qubits.”

Verstraete–Cirac is a distinct graph-oriented tradeoff, not a free molecular compression option. Its concrete factory requires at least three lattice sites, identifies graph edges nonadjacent in the chosen site sequence, introduces auxiliary fermionic modes, and dresses relevant bilinears with auxiliary stabilizers. A **stabilizer** here is an operator whose chosen eigenvalue specifies the physical encoded subspace. The factory provides bilinears, not an individual-Majorana table, and can use more qubits than physical modes. The native engine adds auxiliary-sector penalty terms when these stabilizers are present. Its full enlarged-space spectrum is therefore not simply the original molecular spectrum with fewer letters. Preparing the intended subspace and tracking those terms are additional obligations; ordinary nitrogen mapping does not need this route.

There is also `qubit_mapper/sum_of_squares`, a representation-specific method rather than another general encoding. Its current implementation requires a `FactorizedHamiltonianContainer`, an untapered mapping labelled Jordan–Wigner, and the matching doubled mode count. It returns structured factors instead of an ordinary Pauli decomposition. Unlike the ordinary Pauli mapper, its metadata energy shift includes the core constant. The [implementation](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/algorithms/qubit_mapper/sum_of_squares.py) also has a different `threshold` meaning: it screens small one-body eigenvalues in that construction. We reserve its operator and circuit derivation for the later sum-of-squares lesson; thresholds and energy offsets must not be transferred between these two methods by name alone.

## Custom tables: flexible does not mean automatically valid

`MajoranaMapping.from_table(table, name=...)` accepts two sparse Pauli words per fermionic mode. Each word is a sorted list of `(qubit_index, operator_code)` pairs, with X=1, Y=2, Z=3 and absent factors interpreted as identity. `from_bilinears(num_modes, bilinears, name=...)` instead accepts coefficient/word pairs for every \(j<k\), ordered row by row through the upper triangle. For \(M\) modes that is \((2M)(2M-1)/2\) bilinears. The number of encoded qubits is inferred from the highest referenced qubit, not forced to equal the mode count.

The actual constructor guards include nonempty/even table size, positive mode count for the bilinear form, and the required number of bilinear entries. Accessors check ranges; asking for individual Majoranas of a bilinear-only map fails. **The constructor does not perform a general proof of canonical anticommutation or a complete validation/normalization of arbitrary sparse-word algebra.** Sorted, well-formed words and a mathematically valid encoding remain the author’s responsibility. A custom name is not evidence of correctness.

For Majorana-atomic tables, there is a compact independent algebra check. Represent words by binary masks \((x,z)\). Two words anticommute when

\[
x\cdot z^{\prime}+z\cdot x^{\prime}=1\pmod 2.
\]

The dot products count overlapping mask bits; this is the symplectic parity test. Distinct Majoranas must anticommute and each must square to identity. The repository tests explicitly multiply factory words and check those relations. Bilinear-only encodings instead need their appropriate product relations and any physical-subspace constraints; checking that the vector has the right length cannot establish them. The common engine’s name-independent dispatch is valuable precisely because it lets a validated custom representation reuse the chemical operator machinery.

## Optional libraries and the order of operations

The `openfermion` and `qiskit` mapper variants are optional plugins, not native implementations hidden behind aliases. They use **`mapping.base_encoding` as a string selector and ignore the supplied table**. A manually built table with a misleading standard name can therefore produce a different result from the native engine without a mismatch error. Use factory-generated mappings when comparing backends.

The [OpenFermion plugin](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/plugins/openfermion/qubit_mapper.py) supports base names `jordan-wigner`, `bravyi-kitaev`, and `bravyi-kitaev-tree`; it does not expose a parity branch here. Its [conversion code](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/plugins/openfermion/conversion.py) constructs an interleaved spin-orbital interaction tensor with the appropriate integral permutation and one-half factor, reindexes those fermionic tensors into blocked order, then calls the external transform. It subtracts the core scalar that OpenFermion initially included. Native and plugin outputs thus follow the same ordinary active-energy convention. Compression and tolerances inside the external library are an additional boundary, not these plugins’ own `threshold` settings.

The [Qiskit Nature plugin](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/plugins/qiskit/qubit_mapper.py) supports `jordan-wigner`, `bravyi-kitaev`, and `parity`. It builds an `ElectronicEnergy`, obtains its second-quantized operator, and invokes the selected external mapper. For unrestricted input it transposes the mixed `aabb` tensor to the `bbaa` arrangement expected by Qiskit’s `h2_ba` argument. It does not add the QDK core scalar. Neither plugin has a general arbitrary-Majorana-table implementation. Their dense getter conversions can also materialize tensors that the native Cholesky mapper avoids.

One optional-plugin pattern is therefore `create("qubit_mapper", "qiskit").run(active_hamiltonian, mapping)` with a supported factory mapping and the plugin actually installed. This is a source-backed calling pattern, not a claim that the dependency is available in this environment. The [OpenFermion tests](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/tests/test_interop_openfermion_qubit_mapper.py) and Qiskit tests contain conditional registration and coefficient-comparison checks. A stale test description claiming BK-tree has no native equivalent does not override the current native factory.

A further practical limit is that external conversion can infer width from surviving nonidentity terms. In particular, the OpenFermion converter removes zero terms and derives width from the highest retained qubit index; zero and identity-only operators are not robust full-width placeholders through that path. Always verify expected width rather than assume metadata carries unused modes through every library.

## Keeping the state and the Hamiltonian in the same encoding

Three concepts must remain separate: **mode order**, **binary encoding**, and **string display order**. Native molecular mapping uses blocked modes, alpha first and beta second. Interleaved mode order alternates alpha and beta. Parity/BK say how the ordered occupations become encoded bits. Rightmost-qubit-zero says how those bits or Pauli letters are printed. The [FermionModeOrder enum](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/data/enums/fermion_mode_order.py) describes the first distinction only.

`QubitOperator.to_interleaved(K)` currently permutes Pauli-string positions and marks the result interleaved. It requires width `2*K`. It is a **wire permutation** of an already mapped operator, not a general decode/reorder/re-encode transformation and not a fermionic-sign correction. It does not recompute BK trees or prove compatibility with a freshly remapped interleaved fermionic problem. Reordering canonical fermionic creation operators can introduce occupation-dependent signs; arbitrary wire swaps are not automatically that reordering. A consistent wire permutation can be used if the state and every consumer are transformed accordingly, but the OpenFermion plugin instead reorders fermionic tensors before mapping when establishing its blocked convention.

Current ordinary `state_prep` implementations prepare occupation-basis states and label their circuits `jordan-wigner`. The [base scattering helper](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/algorithms/state_preparation/state_preparation.py) constructs a dense index by adding each determinant occupation bit at its corresponding power-of-two position. It does not accept a `MajoranaMapping` and silently convert to BK. Particular state-preparation methods also have coefficient-type and size restrictions, which we will examine in Lesson 16. Internal “binary encoding” compression in a sparse-state synthesis algorithm is not, by its name alone, Bravyi–Kitaev fermionic encoding; that construction expands back to its declared output basis.

The [encoding_validation module](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/data/encoding_validation.py) is now deprecated. Its retained `validate_encoding_compatibility` facade emits a warning, requires both encoding strings, and compares those strings. It does **not** compare mode order, active-orbital identity, width, custom tables, determinant phases, or full tapering provenance. It is not a mathematical compatibility proof, nor a promise that every consumer automatically calls it. The explicit mapping workflow improves clarity, but cannot make those obligations disappear.

For our continuing nitrogen story, the dependable baseline is therefore the selected active orbitals, a blocked Jordan–Wigner Hamiltonian, and a compatible occupation-basis state preparation. A different encoding is entirely legitimate, but the state must be transformed too. Relabelling a circuit as BK leaves every gate unchanged and does not perform the required conversion.

## Following the nitrogen trace and knowing what it validates

The tutorial’s core mapping excerpt is short because the preceding choices carry its meaning:

```python
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import MajoranaMapping

# active_hamiltonian was constructed from the selected, gauge-fixed orbitals.
n_spatial = active_hamiltonian.get_one_body_integrals()[0].shape[0]
mapping = MajoranaMapping.jordan_wigner(num_modes=2 * n_spatial)
mapper = create("qubit_mapper", "qdk", threshold=1e-10, integral_threshold=1e-14)
qubit_hamiltonian = mapper.run(active_hamiltonian, mapping)
core_energy = active_hamiltonian.get_core_energy()
```

Those are the tutorial’s actual screening choices, not the defaults. This excerpt is instructional only. Its output is an **operator**, not an executed circuit, an eigenstate, or an energy measurement.

The tutorial subsequently validates the transformation by forming the sparse Pauli matrix, selecting only indices whose low six occupation bits contain three alpha electrons and high six bits contain three beta electrons, and diagonalizing that 400-state sector. It adds `core_energy` to the active eigenvalue and compares with the same selected-space CASCI energy. This tests representation consistency for the chosen model; it does not validate that choosing six orbitals was chemically sufficient.

That sector mask is correct for blocked Jordan–Wigner occupations. Applying the same count-of-ones mask to parity or BK bits would choose a different space. Those encodings require decoding occupations or projecting with appropriately transformed number operators. Similarly, the full-register lowest eigenvalue need not be the physical six-electron state.

`QubitOperator.to_matrix` uses a [complex-preserving Pauli matrix utility](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/utils/pauli_matrix.py). Dense matrices cost \(4^q\) entries for \(q\) qubits; its sparse construction also enumerates \(2^q\) register states and has explicit index-capacity limits. This is a small-system check, not a scalable way to validate every large target. It is distinct from the classical qubit-solver wrappers discussed earlier, which can discard imaginary parts and do not enforce the physical electron sector. We have inspected the tutorial and tests, not run either validation route here.

We now have the central translation contract: preserve the chemical model and its constants, define an ordered fermionic space, preserve its signs in the operator algebra, attach an honest encoding description, and prepare states in that same description. **Qubit mapping** performs this operator translation. **Circuit mapping** will synthesize gates for a chosen operation derived from the operator. **Tapering** will reduce a symmetry sector under additional conditions. None is a synonym for the others.

The next lesson uses those distinctions to remove qubits only where a known symmetry makes the reduction legitimate. We will not throw away an apparently unused bit merely because its printed value happens to be zero in one determinant.