UCCSD (native excitation circuits)¶
Circuits, not chemistry: this module builds gate sequences for the single/double fermionic excitations a UCCSD ansatz needs. Pairing this with an actual molecular Hamiltonian is VQE's own job.
A UCCSD ansatz applies one unitary per excitation out of a Hartree-Fock
reference: exp(theta * (a_p^dagger a_q - a_q^dagger a_p)) for a single
excitation, and the four-index equivalent for a double. This module builds
those unitaries as real, exact gate sequences directly against the
Jordan-Wigner mapping — no PennyLane dependency, no Trotter error.
Step 1. Which excitations exist for a given molecule¶
from dense_evolution.circuits.uccsd import find_excitations
singles, doubles = find_excitations(electrons=2, n_qubits=4)
singles, doubles
find_excitations is pure combinatorics, no quantum computation: with 2
electrons filling qubits 0 and 1 (occupied) and qubits 2, 3 empty
(virtual), a single excitation moves one electron from an occupied to a
virtual orbital of the same spin; a double moves two at once. This matches
qml.qchem.excitations(electrons, n_qubits) exactly, without needing
PennyLane installed just to find out which excitations exist.
Step 2. A single excitation, exact for any orbital distance¶
import dense_evolution as de
from dense_evolution.circuits.uccsd import single_excitation_ops
p, q, theta = 0, 2, 0.4173
ops = single_excitation_ops(p, q, theta)
sim = de.DenseSVSimulator(4)
sim.sv[0] = 0
sim.sv[int('0011', 2)] = 1.0
sim.run_circuit(ops)
sim.get_statevector()
single_excitation_ops(p, q, theta) builds exp(theta * (a_p^dagger a_q -
a_q^dagger a_p)) as CNOT(p,q), a chain of CNOTs folding every
in-between qubit's Jordan-Wigner parity onto p, one CRY(2*theta), then
undoing the fold — exact for any p < q, not just adjacent orbitals.
Starting from the Hartree-Fock reference |0011> (qubits 0,1 occupied), it
rotates into a superposition with |0110> — the p=0 -> q=2 excited
configuration — matching scipy.linalg.expm of the exact fermionic
generator to machine precision (~1e-16), not a Trotter approximation.
Step 3. A double excitation, exact when both pairs are adjacent¶
from dense_evolution.circuits.uccsd import double_excitation_ops
p, q, r, s, theta = 0, 1, 2, 3, 0.4173
ops = double_excitation_ops(p, q, r, s, theta, ancilla1=4, ancilla2=5)
sim = de.DenseSVSimulator(6)
sim.sv[0] = 0
sim.sv[int('110000', 2)] = 1.0
sim.run_circuit(ops)
sim.get_statevector()
double_excitation_ops needs two spare ancilla qubits (ancilla1,
ancilla2, must be |0> on entry) whenever the occupied pair (p, q)
and the virtual pair (r, s) are each adjacent — exactly the case
find_excitations produces for a minimal active space like this one.
The ancillas come back to |0> on exit (verified above: no leakage
outside the logical |110000>/|001100> pair), so the same two ancillas
can be reused across every double excitation in an ansatz.
Details¶
Where this came from¶
UCCSD's excitation generators were previously only reachable through
PennyLane's own FermionicSingleExcitation/FermionicDoubleExcitation
decomposition. This module is a direct derivation against
dense_evolution.physics.fermions.majorana_pauli_terms (the package's own
already-verified Jordan-Wigner mapping) instead — every circuit identity
was checked against scipy.linalg.expm of the exact generator matrix,
independently reconstructed from raw ladder operators, before being
written here (see tests/unit/test_uccsd.py).
When the double-excitation closed form doesn't apply¶
For an active space where the occupied pair or the virtual pair is
itself non-adjacent (3+ occupied or 3+ virtual orbitals, e.g. LiH/BeH2),
the closed-form Z-string folding used for single excitations does not
carry over: the naive fold leaves genuine leakage, not just an
unsimplified circuit. double_excitation_ops falls back automatically to
per-term exponentiation of the 8-term Pauli decomposition (via
dense_evolution.circuits.trotter.pauli_rotation_ops) whenever no
ancillas are given, or the pairs aren't both adjacent.
That fallback is exact too, not a Trotter approximation: verified
exhaustively across every computational basis state and several
non-adjacent p<q<r<s choices, to floating-point precision (~1e-15) at
every theta tested — the 8 terms don't commute as general operators, but
the generator only ever couples two basis states for any fixed setting of
the untouched qubits, and within that 2-dimensional subspace the per-term
exponentials compose without error. Only the gate count differs from the
closed form, never correctness.
Gate count¶
The single-excitation closed form costs 2*(q-p) + 2 CX gates (plus the
CRY's own 2 CX). The double-excitation closed form uses two Toffolis
(each itself decomposed into CX + single-qubit gates via
QuantumTranspiler.decompose_toffoli) around one CRY; the per-term
fallback costs one Pauli-rotation gadget per surviving term (up to 8) of
the double-excitation generator, via pauli_rotation_ops.
uccsd ¶
Native (PennyLane-free) UCCSD excitation circuits.
UCCSD's single/double fermionic excitation generators (a_p^dagger a_q - h.c., a_p^dagger a_q^dagger a_r a_s - h.c.) were previously only reachable through PennyLane's own FermionicSingleExcitation/ FermionicDoubleExcitation decomposition (see dashboard/core/vqe.py's _uccsd_tape_to_qasm) -- not because a native circuit is theoretically impossible, but because deriving the exact (non-Trotterized) circuit identity requires real derivation, not just wiring PennyLane through.
This module IS that derivation, done directly against dense_evolution.physics.fermions.majorana_pauli_terms (already-verified Jordan-Wigner mapping) rather than against PennyLane's internals, and every circuit identity below was verified against scipy.linalg.expm of the exact generator matrix (independently reconstructed from raw Jordan-Wigner ladder operators, not from PennyLane) before being written here -- see tests/unit/test_uccsd.py.
Single excitation (any orbital distance): exact closed form, always. G_pq = a_p^dagger a_q - a_q^dagger a_p decomposes (via a_p^dagger=(chi_X(p)-ichi_Y(p))/2) into exactly 2 Pauli strings that share the Jordan-Wigner Z-string between p and q and differ only by an X<->Y swap at p and q. This is the standard "Givens rotation" generator (G^2 = 2I): exact circuit is CNOT(p,q), fold each Z-string qubit's parity onto p via CNOT(k,p), CRY(2theta, control=q, target=p), then undo. Verified for adjacent and long-Z-string pairs alike (any p<q).
Double excitation: exact closed form when the occupied pair (p,q) is adjacent (q=p+1) AND the virtual pair (r,s) is adjacent (s=r+1) -- the gap between q and r (HOMO-LUMO gap) can be any length, no restriction. This is the case dashboard/core/vqe.py's UCCSD ansatz always produces for a 2-occupied/2-virtual active space (e.g. H2 in a minimal basis), and generally whenever qml.qchem.excitations pairs consecutive occupied orbitals with consecutive virtual orbitals. G_pqrs (8 Pauli terms) reduces to a clean 2-level coupling between |p=0,q=1,r=1,s=1,...> and its bit-flip, realized via a CNOT fan (mirroring the p,q,r,s pattern onto q) plus a triple-controlled RY (Toffoli-mediated, 2 ancilla qubits, always returned to |0>).
For a double excitation where the occupied pair or the virtual pair is itself non-adjacent (occurs for active spaces with 3+ occupied or 3+ virtual orbitals, e.g. LiH/BeH2), the same closed-form Z-string folding used for single excitations does NOT carry over cleanly (verified directly: the naive fold leaves genuine leakage, not just an unsimplified-but-correct circuit) -- deriving a comparably compact closed form for that case is unresolved, so double_excitation_ops falls back to per-term exponentiation of the 8 Pauli terms via dense_evolution.circuits.trotter.pauli_rotation_ops for that specific case, applied sequentially in a fixed term order.
That fallback turned out to be exact too, not a Trotter approximation: verified exhaustively (every computational basis state, several p<q<r<s choices including non-adjacent pairs, theta up to pi) against scipy.linalg.expm of the exact generator matrix, to floating-point precision (~1e-15) at every theta tested, not just small theta the way a genuine first-order Trotter error would show. The 8 terms don't commute as operators on the full Hilbert space, but the generator only ever couples two basis states for any fixed setting of the qubits it doesn't act on (same structure as the closed-form case), and within that 2-dimensional invariant subspace the per-term exponentials apparently compose without error -- not derived from first principles here, just confirmed by the exhaustive check above. Only the GATE COUNT differs from the closed form, not correctness.
find_excitations ¶
Enumerate spin-conserving single/double excitation index tuples for a Hartree-Fock reference with the given electron count -- pure classical combinatorics (occupied = orbitals [0, electrons), virtual = the rest, even index = spin-up, odd = spin-down under the standard Jordan-Wigner interleaved spin-orbital ordering), no quantum computation involved. Verified to reproduce qml.qchem.excitations(electrons, n_qubits) exactly across several (electrons, n_qubits) pairs -- see tests/unit/test_uccsd.py -- kept here so finding which excitations exist doesn't need PennyLane installed any more than building their circuits does.
Returns (singles, doubles): singles is a list of [p, q] (p occupied, q virtual, same spin); doubles is a list of [p, q, r, s] with p<q both occupied, r<s both virtual, total spin conserved.
Source code in dense_evolution/circuits/uccsd.py
single_excitation_ops ¶
Exact circuit for exp(theta * (a_p^dagger a_q - a_q^dagger a_p)), the UCCS single-excitation unitary, for ANY p < q (any Jordan-Wigner Z-string length in between) -- no Trotter error, ever.
Gate count: 2(1 + (q-p-1)) CX + 1 CRY (itself 2 CX + 2 RY), so 2(q-p) + 2 CX total.
Source code in dense_evolution/circuits/uccsd.py
double_excitation_ops ¶
double_excitation_ops(
p: int,
q: int,
r: int,
s: int,
theta: float,
ancilla1: int = None,
ancilla2: int = None,
) -> List[Tuple]
Circuit for exp(theta * (a_p^dagger a_q^dagger a_r a_s - h.c.)), the UCCD double-excitation unitary, for p < q < r < s.
Exact closed form (no Trotter error) when ancilla1/ancilla2 are both given AND the occupied pair is adjacent (q == p+1) AND the virtual pair is adjacent (s == r+1) -- the gap between q and r can be any length. This is what qml.qchem.excitations always produces for a 2-occupied/2-virtual active space, and sometimes for larger ones.
Falls back to per-term exponentiation of the 8-term Pauli decomposition whenever no ancillas are given, or the pairs aren't adjacent -- verified exact (not an approximation), just more gates than the closed form; see the module docstring for why the closed form doesn't generalize to a non-adjacent occupied or virtual pair yet.
ancilla1, ancilla2 : two qubits that MUST be |0> on entry and are guaranteed returned to |0> on exit -- only touched by the closed-form path (never referenced by the per-term fallback), so the same two ancillas can be reused across every double excitation in an ansatz. Omit both (leave as None) to force the ancilla-free per-term path unconditionally -- e.g. when the caller's qubit register has no spare qubits to offer.