Fermions (Jordan-Wigner mapping)¶
A fermion (an electron, say) obeys the Pauli exclusion principle -- two of them can
never occupy the same state -- which shows up mathematically as anticommutation:
swapping two fermionic operators flips a sign, a*b = -b*a, unlike ordinary qubit
operators, which mostly commute. The Jordan-Wigner mapping is the standard recipe for
building qubit operators that reproduce this anticommuting behavior exactly, by
attaching a string of Z gates that tracks the "which fermions come before this one"
bookkeeping. This module builds two different flavors of that mapping: Majorana
operators (this page's main guide) and, for a specific Hubbard-model use case, ordinary
fermion creation/annihilation operators (Step 4).
Step 1. A Majorana operator, and what makes it special¶
majorana_pauli_terms(mode_index, n_qubits) returns one Majorana fermion operator as
a (coeff, {qubit: pauli}) term -- the same format
pauli_hamiltonian_to_matrix and
pauli_sum_expectation accept. mode_index runs from 1 to
2*n_qubits (two Majorana modes share every qubit); mode 1 on a 2-qubit register is
just X on qubit 0. Every Majorana operator is Hermitian and squares to the identity
(chi_i^2 = I) -- it behaves like a real, physical observable, not an abstract
placeholder.
Step 2. Anticommutation, checked on a real state¶
qasm = 'OPENQASM 2.0; include "qelib1.inc"; qreg q[2]; h q[0]; cx q[0],q[1];'
circuit = de.QASMParser().parse(qasm)
sim = de.DenseSVSimulator(2)
sim.run_circuit_jit(circuit.to_tuples())
sv = sim.get_statevector()
from dense_evolution.physics.observables import pauli_sum_matvec
chi1 = de.majorana_pauli_terms(1, 2)
chi3 = de.majorana_pauli_terms(3, 2)
def apply(term, v):
coeff, pauli = term
return coeff * pauli_sum_matvec(v, [(1.0, pauli)], n_qubits=2)
anticommutator = apply(chi1, apply(chi3, sv)) + apply(chi3, apply(chi1, sv))
anticommutator
sv is the same Bell state built on the Simulator page. chi1 lives
entirely on qubit 0 (mode 1); chi3 is mode 1 of the second Majorana pair, which
lands on qubit 1 but carries a leading Z on qubit 0 -- the Jordan-Wigner string. That
Z is exactly what makes chi1 and chi3 anticommute even though they act on
different qubits: applying both operators in either order to the Bell state gives the
exact zero vector, {chi1, chi3}|psi> = 0 for every |psi>, the defining property of
two independent Majorana modes.
Step 3. Combining two separate registers: the Klein factor¶
total_parity_operator multiplies every Majorana in a register together, which
collapses to the register's total-parity operator -- Z on every qubit in that
register (all 4 modes of a 2-qubit register, above, give Z0 Z1). It anticommutes with
every individual Majorana in that same register, the same way chi1/chi3 did in
Step 2. That property is the tool needed when two independently Jordan-Wigner-mapped
registers (e.g. the two sides of a wormhole-teleportation construction) have to be
combined into one joint fermionic algebra: two Majoranas from different registers act
on disjoint qubits with no shared Z-string, so they naively commute -- wrong for a
genuine cross-register fermion. Dressing one register's operators with its own
total_parity_operator first restores the correct anticommutation across the join.
Step 4. A different mapping, for the Hubbard model¶
The Hubbard model (electrons hopping between sites, paying an energy penalty U for
two electrons sharing a site) needs ordinary creation/annihilation operators, not
Majoranas -- hubbard_hamiltonian_pauli_terms uses the other standard Jordan-Wigner
convention for that (c_q = sigma+_q * Z-string). n_sites=2, periodic=False is the
smallest non-trivial case: 2 lattice sites, 4 qubits (spin-up and spin-down per site).
qasm4 = 'OPENQASM 2.0; include "qelib1.inc"; qreg q[4]; x q[0]; x q[2];'
circuit4 = de.QASMParser().parse(qasm4)
energy_fn, n_params = de.circuit_to_energy_fn(circuit4, n_qubits=4)
h_op = de.PauliSumOperator(terms, n_qubits=4)
theta = []
energy, sv = energy_fn(theta, h_op)
energy
x q[0]; x q[2]; prepares both spin-up and spin-down electrons on site 0 -- one
doubly-occupied site, no electron anywhere else. No hopping is possible from a state
this localized (t never contributes), so the energy is exactly the interaction
penalty U=2.0 for that one double occupancy -- PauliSumOperator
applies terms directly to the statevector, the same differentiable path
circuit_to_energy_fn uses for jax.grad-based VQE, without ever
building a dense Hamiltonian matrix.
Step 5. Beyond 1D: a 2D lattice¶
hubbard_hamiltonian_pauli_terms's hopping construction only ever needs a Jordan-Wigner
Z-string between two given qubit indices -- it never assumed a 1D ring. square_lattice_edges(lx,
ly, periodic) builds the site-index pairs for an lx by ly square lattice instead (site
index = y*lx + x); passing them via the edges parameter swaps out the default ring
for any lattice this function can describe. Above, a 2x2 open plaquette (4 sites, no
diagonal bond) gives the 4 nearest-neighbor pairs shown.
4 bonds, 2 spins, 2 Pauli terms (XX and YY) per bond-spin gives 4*2*2=16 hopping
terms, plus 4*4=16 onsite-interaction terms (4 sites) -- 32 total, PauliSumOperator-
ready exactly like Step 4's 1D Hamiltonian. periodic and periodic_y wrap the x- and
y-directions independently, so periodic=True, periodic_y=False gives a "cylinder" --
periodic around one direction, open along the other, the finite-width-ladder geometry
Arovas, Bandyopadhyay & Zhu's Hubbard-model review (cited below) uses for its cylinder
DMRG results.
Details¶
Indexing convention: mode_index is 1-indexed (1 to 2*n_qubits), matching the
physics literature's chi_1, chi_2, ... convention -- mode_index=0 raises
ValueError. Qubit assignment follows this package's usual most-significant-bit
convention throughout.
Building a Hamiltonian from Majoranas: combine several majorana_pauli_terms
results with multiply_pauli_terms (e.g. a 4-Majorana product
chi_i*chi_j*chi_k*chi_l for a Sachdev-Ye-Kitaev-style term) and pass the resulting
terms list to pauli_hamiltonian_to_matrix or
PauliSumOperator.
Provenance: majorana_pauli_terms/total_parity_operator were promoted from a
real traversable-wormhole-inspired quantum teleportation reproduction (Gao-Jafferis-Wall
theory, arXiv:2604.10090) -- see
Dense-Evolution-Discovery
for that experiment. hubbard_hamiltonian_pauli_terms was promoted from Dense-Evolution-Discovery
Experiment 39 (the "Hubbard square", Arovas, Bandyopadhyay & Zhu, "The Hubbard Model",
Annual Review of Condensed Matter Physics 2022, arXiv:2103.12097), which checked the
paper's own Table 2 perturbative ground-state formula against exact diagonalization and
its predicted x^2-y^2 (B1g/d-wave) ground-state symmetry against a real pairing-correlation
sign pattern -- see
Dense-Evolution-Discovery's hubbard_square_arovas.py
for the full writeup, including the independent brute-force check that the periodic
wraparound bond (the one place a naive Jordan-Wigner implementation could plausibly
need an extra parity correction) needs none.
fermions ¶
Majorana-fermion -> qubit (Jordan-Wigner) mapping.
Standard convention, one qubit per two Majorana modes: chi_{2j-1} = (prod_{k<j} Z_k) X_j chi_{2j} = (prod_{k<j} Z_k) Y_j mode_index is 1-indexed (chi_1 .. chi_{2n_qubits}). Each chi_i is Hermitian and satisfies chi_i^2 = I by this normalization; the anticommutation relation {chi_a, chi_b} = 2delta_ab*I holds exactly (verified in tests/unit/test_fermions.py against the actual matrices, not assumed from the textbook formula alone).
Originated in research/wormhole_syk.py (a traversable-wormhole-inspired quantum teleportation reproduction, arXiv:2604.10090) -- promoted here because Jordan-Wigner fermion mapping is a generic building block, not specific to that one experiment, and nothing like it existed anywhere in this package before (dashboard_core/hamiltonians.py only has PennyLane's molecule-specific Hartree-Fock Jordan-Wigner, not a general Majorana map).
Combine the returned Pauli term with dense_evolution.pauli_hamiltonian_to_matrix to build any Majorana-operator Hamiltonian as a dense matrix, e.g. a sparse SYK model: H = sum_{ijkl} J_ijkl * chi_ichi_jchi_k*chi_l.
total_parity_operator (the "Klein factor" for a set of Majorana modes) was promoted alongside majorana_pauli_terms from Dense-Evolution-Discovery's wormhole_magic_entropy.py (2026-08-29): a second, independently-Jordan- Wigner-mapped fermionic register (e.g. the "R" side of a two-copy/thermofield- double construction) puts its Majoranas on disjoint qubits, so cross-register Majorana products COMMUTE by construction instead of anticommuting -- the well-known "Klein factor" problem from bosonization / fermionic-entanglement literature (e.g. Fidkowski-Kitaev). Multiplying one register's operators by its own total_parity_operator before combining them with the other register's restores the correct anticommutation; see that function's docstring for the algebraic proof.
hubbard_hamiltonian_pauli_terms uses the OTHER standard Jordan-Wigner convention -- ordinary spin-orbital creation/annihilation operators (c_q = sigma+_q * Z-string, not Majoranas) -- promoted from Dense-Evolution-Discovery's hubbard_square_arovas.py (2026-09-01), which reproduces Arovas, Bandyopadhyay & Zhu, "The Hubbard Model" (Annual Review of Condensed Matter Physics 2022, arXiv:2103.12097). Nothing like it existed anywhere in this package before: dashboard_core/hamiltonians.py only has PennyLane's molecule-specific Hartree-Fock Jordan-Wigner (routed through PennyLane's own internal mapping, not this module's Pauli-term machinery), not a general lattice-fermion-model builder.
majorana_pauli_terms ¶
Jordan-Wigner term for one Majorana mode.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode_index
|
int
|
1-indexed Majorana mode, 1 <= mode_index <= 2*n_qubits. |
required |
n_qubits
|
int
|
Number of qubits the fermionic system is mapped onto (n_majorana_modes = 2*n_qubits). |
required |
Returns:
| Type | Description |
|---|---|
(float, dict)
|
A (coeff, pauli_dict) term -- coeff is always 1.0, pauli_dict is {qubit: 'X'|'Y'|'Z'} -- ready for dense_evolution.pauli_hamiltonian_to_matrix. |
Source code in dense_evolution/physics/fermions.py
total_parity_operator ¶
Total fermion-parity ("Klein factor") operator for a set of Majorana
modes: i^(N/2) times the ORDERED product of majorana_pauli_terms(m,
n_qubits) for every m in mode_indices (N = len(mode_indices)),
computed via multiply_pauli_terms (exact symbolic Pauli algebra, no
numerical approximation).
The i^(N/2) PHASE CORRECTION is not optional decoration -- found necessary by testing, not assumed from the general anticommutation argument alone: for N mutually anticommuting HERMITIAN operators, the raw ordered product Pi = chi_1chi_2...chi_N satisfies Pi^dagger = chi_N...chi_1 = (-1)(N(N-1)/2) * Pi (reversing N anticommuting factors takes N(N-1)/2 transpositions, each contributing -1) -- so Pi itself is Hermitian only when N(N-1)/2 is even (N=4, 8, 12, ... i.e. N%4==0), and ANTI-Hermitian when N(N-1)/2 is odd (N=2, 6, 10, ... i.e. N%4==2). This was caught by a test at N=6 (n_qubits=3) that the original N=8-only manual check never exercised. Multiplying by i^(N/2) fixes this for every even N: verified numerically for N=2,4,6,8,10 that i*(N/2) * Pi is exactly Hermitian AND squares to exactly the identity in every case (see tests/unit/test_fermions.py).
Why this fixes cross-register anticommutation: an even number of mutually anticommuting, squares-to-identity operators, correctly phase-normalized to be Hermitian and square to I (as above), still anticommutes with each individual factor -- multiplying an overall scalar phase never changes an operator's (anti)commutation relations with OTHER operators. Concretely: if psi_L (from THIS register) and psi_R (from an independently Jordan-Wigner-mapped SECOND register, e.g. dense_evolution.physics.fermions.majorana_pauli_terms called again with its own qubit offset) act on disjoint qubits, they commute by construction -- but P_L = total_parity_operator(all_of_this_ register's_modes, n_qubits) anticommutes with every psi_L, so replacing psi_R with multiply_pauli_terms([P_L, psi_R]) (P_L applied first) makes it anticommute with psi_L instead, while leaving {psi_R, psi_R'} (two operators from the SAME second register) unchanged, since P_L^2 = I factors out trivially: {P_Lpsi_R, P_Lpsi_R'} = P_L^2 * {psi_R, psi_R'}.
len(mode_indices) must be even -- an odd-length parity operator would anticommute with an EVEN number of factors' worth of sign flips, i.e. NOT anticommute with its own members, defeating the purpose (raises ValueError rather than silently returning something that doesn't have the intended algebraic property).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode_indices
|
iterable of int
|
1-indexed Majorana modes (same indexing as majorana_pauli_terms), typically ALL of one register's modes (range(1, n_majorana+1)) to get that register's total parity, though any even-length subset is algebraically valid. |
required |
n_qubits
|
int
|
Same meaning as majorana_pauli_terms's n_qubits. |
required |
Returns:
| Type | Description |
|---|---|
(complex, dict)
|
A (coeff, pauli_dict) term, same shape as majorana_pauli_terms's return -- ready for pauli_hamiltonian_to_matrix / pauli_expectation / another multiply_pauli_terms call. |
Examples:
>>> total_parity_operator([1, 2], n_qubits=1) # i^1 * chi_1*chi_2 = i*(i*Z) = -Z
((-1+0j), {0: 'Z'})
Source code in dense_evolution/physics/fermions.py
square_lattice_edges ¶
Nearest-neighbor site-index pairs (i, j) on an Lx by Ly square lattice, site index = y*lx + x (row-major). One x-bond and one y-bond generated per site; a periodic direction wraps via i -> (i+1) % L, the same convention as the 1D ring below -- an L=2 periodic direction generates the same physical bond twice (once from each end), matching that same known behavior already present in the 1D n_sites=2 case, not a new quirk introduced here.
periodic sets the x-direction boundary; periodic_y sets the
y-direction independently (defaults to the same value as periodic
when not given, so a single periodic=True/False still means "both
directions" for the common torus/open cases). Pass periodic=True,
periodic_y=False for a "cylinder" -- periodic around one direction,
open (a finite "leg" ladder) along the other -- the same geometry
Arovas, Bandyopadhyay & Zhu's Hubbard-model review (arXiv:2103.12097,
already cited below) discusses for W-leg cylinder DMRG studies
("periodic boundary conditions have been enforced around the
cylinder" while the leg direction stays open/finite), not an
invented boundary condition.
With ly=1 this reduces exactly to the 1D ring hubbard_hamiltonian_pauli_terms builds internally (verified in tests/unit/test_fermions.py), so passing edges=square_lattice_edges( n_sites, 1, periodic) to hubbard_hamiltonian_pauli_terms reproduces its own default 1D behavior; ly>1 is the actual 2D generalization, cross-checked there against an independent brute-force fermionic construction on a real 2x2 lattice, both fully open and periodic-in-x-only -- kept small since the hopping loop keys each term only on that edge's own qubit indices, so nothing about the check depends on lattice size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lx
|
int
|
Lattice extent along x and y. |
required |
ly
|
int
|
Lattice extent along x and y. |
required |
periodic
|
bool
|
Wrap the x direction. Also used for y when |
True
|
periodic_y
|
bool
|
Wrap the y direction independently of |
None
|
Returns:
| Type | Description |
|---|---|
list of (int, int)
|
Site-index pairs, ready for hubbard_hamiltonian_pauli_terms's
|
Source code in dense_evolution/physics/fermions.py
hubbard_hamiltonian_pauli_terms ¶
Jordan-Wigner mapping of the Hubbard Hamiltonian
H = -t * sum_
Pass edges explicitly (a list of (i, j) site-index pairs, 0 <= i,
j < n_sites) to describe a different lattice -- e.g. a 2D square
lattice via square_lattice_edges(lx, ly, periodic) with n_sites=
lx*ly -- and periodic is then ignored (the wraparound choice is
already baked into the edges you pass). The hopping/Jordan-Wigner
machinery below only ever uses the qubit-index Z-string between two
given sites (see the wraparound-bond note below), so it was already
lattice-agnostic; only the default 1D edge list was ring-specific.
The wraparound bond needed a self-test before being trusted: some Jordan-Wigner conventions need an extra fermion-parity correction for a periodic-boundary term written as a short Pauli string. This function instead always uses the full-length Jordan-Wigner string between the two mapped qubit indices (c_i^dagger c_j = sigma+_i * (Z-string between i and j) * sigma-_j, i<j), which is the exact fermionic identity for ANY pair of modes regardless of whether they are lattice-adjacent -- so no extra correction is needed here, and this was verified directly against an independent brute-force fermionic operator construction (not just argued from the formula): max diff 0.00e+00 (machine-exact) at n_sites=2,3,4, both periodic and open (Dense-Evolution-Discovery's hubbard_square_arovas.py), and at a 2D lattice via square_lattice_edges, both open and periodic-in-x-only (see tests).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_sites
|
int
|
Number of lattice sites (n_qubits = 2*n_sites). |
required |
t
|
float
|
Hopping amplitude. |
required |
U
|
float
|
On-site interaction strength. |
required |
periodic
|
bool
|
Include the wraparound bond (site n_sites-1 to site 0). With
n_sites=4, this is the "Hubbard square" studied in Arovas,
Bandyopadhyay & Zhu, "The Hubbard Model" (Annual Review of
Condensed Matter Physics 2022, arXiv:2103.12097) -- Table 2 (p.6)
gives a closed-form small-U/t perturbative ground-state energy
for this exact model, verified directly against exact
diagonalization in the Discovery experiment above, and identifies
this ground state's orbital symmetry as x^2-y^2 (i.e. B1g/d-wave),
checkable via the sign pattern of pairing correlations
|
True
|
edges
|
list of (int, int)
|
Explicit nearest-neighbor site pairs, overriding the default 1D ring. See square_lattice_edges for a 2D square-lattice builder. |
None
|
Returns:
| Type | Description |
|---|---|
list of (float, dict)
|
Pauli terms in the same (coeff, {qubit: 'X'|'Y'|'Z'}) form majorana_pauli_terms returns -- ready for dense_evolution.pauli_hamiltonian_to_matrix or pauli_sum_expectation. |
Source code in dense_evolution/physics/fermions.py
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | |
See also: entropy and trotter, the other two
modules promoted alongside majorana_pauli_terms/total_parity_operator from the same
wormhole-teleportation reproduction.