State preparation
State preparation is an input to the library’s algorithms, not an
algorithm in itself: every preparation here is (or can be packaged as) a
CUDA-Q kernel taking the qubit register as its only argument
(qubits: cudaq.qview), which makes it directly injectable into the
algorithm factories via their state_prep= parameter — the block-encoding
factories (Block encodings), the Trotter primitive (Trotter (product-formula simulation)),
and qubitization / QSVT (Qubitization and QSVT) all accept it. See
State preparation injection below for the shared contract.
Everything is exposed through the cudaq_algorithms.stateprep namespace:
unitary-coupled-cluster (UCC) ansatz kernels, operator pools and Pauli-list
converters, Hartree-Fock reference determinants, fixed-parameter UCC
products, and Givens-rotation Slater determinants — all pure Python,
requiring only the cudaq package.
The runnable examples are hartree_fock_ucc.py (fixed-parameter UCCSD
vs. dense matrix exponentials) and givens_slater_determinant.py (Slater
determinants) on the state-preparation examples page.
Ansatz kernels and operator pools
The uccsd (unitary coupled cluster with singles and doubles), uccgsd
(generalized singles and doubles), upccgsd (paired generalized doubles),
and ceo (coupled exchange operator) device kernels are @cudaq.kernel
functions, composable from user kernels:
import cudaq
from cudaq_algorithms import stateprep
@cudaq.kernel
def ansatz(thetas: list[float], num_electrons: int, spin: int):
qubits = cudaq.qvector(num_qubits)
for i in range(num_electrons):
x(qubits[i]) # Hartree-Fock reference
stateprep.uccsd(qubits, thetas, num_electrons, spin)
uccsd computes its excitation indices inline; its parameter order is
fixed by get_uccsd_excitations (singles alpha, singles beta, mixed
doubles, alpha doubles, beta doubles), and thetas must hold
get_num_uccsd_parameters(num_qubits, num_electrons, spin) entries.
uccgsd, upccgsd, and ceo instead consume grouped Pauli data — one
variational parameter per group — produced on the host:
words, coeffs = stateprep.get_uccgsd_pauli_lists(num_qubits)
stateprep.uccgsd(qubits, thetas, words, coeffs) # inside a kernel
stateprep.get_upccgsd_pauli_lists(num_qubits, only_doubles=False)
stateprep.get_ceo_pauli_lists(num_orbitals) # spatial orbitals!
The pools themselves are available as cudaq.SpinOperator lists for
ADAPT-style workflows or custom conversion:
stateprep.make_uccsd_operator_pool(num_qubits, num_electrons, spin=0)
stateprep.make_uccgsd_operator_pool(num_qubits, only_singles=False,
only_doubles=False)
stateprep.make_upccgsd_operator_pool(num_qubits, only_doubles=False)
stateprep.make_ceo_operator_pool(num_orbitals) # spatial orbitals!
Device kernels have no error channel, so they perform no input validation — the host helpers are where the contracts are enforced.
Hartree-Fock references and fixed-parameter UCC
The reference determinant those ansatz kernels are applied to, plus a non-variational UCC product on top of it (amplitudes fixed by, e.g., a classical pre-optimization — no optimizer in the loop):
# Device kernels, composable from user kernels:
stateprep.hartree_fock(qubits, num_electrons) # closed shell
stateprep.hartree_fock_occupation(qubits, occupied) # explicit/open shell
stateprep.fixed_parameter_ucc(qubits, thetas, words, coeffs)
# Host helpers:
occ = stateprep.make_hartree_fock_occupation(num_qubits, num_electrons,
spin=2)
stateprep.validate_hartree_fock_occupation(num_qubits, occ)
pool = stateprep.make_uccsd_operator_pool(num_qubits, num_electrons)
words, coeffs = stateprep.get_fixed_parameter_ucc_pauli_lists(
pool, num_qubits) # works for ANY operator pool
stateprep.validate_fixed_parameter_ucc(num_qubits, thetas, words, coeffs)
make_hartree_fock_occupation builds the occupied spin-orbital list:
contiguous {0, ..., num_electrons - 1} for closed shell, and for
spin > 0 the interleaved alpha (even) / beta (odd) layout of
get_uccsd_excitations — e.g. 4 electrons at spin 2 occupy
{0, 1, 2, 4}, not {0, 1, 2, 3} — so the reference lines up with a
UCCSD pool built at the same spin.
get_fixed_parameter_ucc_pauli_lists converts any operator pool to the
grouped form the kernel takes, dropping terms with
|coefficient| <= coefficient_tolerance and rejecting complex
coefficients (exp_pauli angles are real).
The hartree_fock_ucc_kernel factory
The packaged form: a (qubits: cudaq.qview) kernel preparing the
reference determinant and applying the fixed-amplitude UCC product —
directly injectable as a state_prep kernel.
prep = stateprep.hartree_fock_ucc_kernel(
num_qubits, parameters, words, coeffs,
num_electrons=num_electrons, spin=0) # canonical reference
prep = stateprep.hartree_fock_ucc_kernel(
num_qubits, parameters, words, coeffs,
occupied_orbitals=[0, 1, 2, 4]) # explicit reference
Provide exactly one of num_electrons (with optional spin) or
occupied_orbitals. The returned kernel expects a num_qubits-wide
register in |0...0>. Validation runs at factory time; the grouped data
is flattened into per-rotation angles before capture (nested lists
marshal as kernel arguments but cannot be closure-captured), and
empty occupation/rotation lists select a kernel shape with no empty
captures (an empty list cannot cross the kernel boundary).
Resource estimation
stateprep.estimate_hartree_fock_resources(num_qubits, num_electrons,
spin=0)
stateprep.estimate_hartree_fock_occupation_resources(num_qubits, occ)
# -> HartreeFockResourceEstimate(num_qubits, num_electrons, num_x_gates)
stateprep.estimate_fixed_parameter_ucc_resources(num_qubits, words)
# -> FixedParameterUccResourceEstimate(num_qubits, num_excitations,
# num_pauli_rotations, max_pauli_rotations_per_excitation)
Like TrotterResourceEstimate, these are frozen dataclasses counting
logical operations before transpilation, not hardware gate counts.
Givens-rotation Slater determinants
Prepares the Slater determinant of an orthonormal occupied-orbital
matrix Q (num_spin_orbitals x num_electrons) on the Jordan-Wigner /
little-endian layout: the amplitude of basis state |S> with occupied
set S is det(Q[S, :]), up to a global phase. The construction is
that of Jiang et al., Phys. Rev. Applied 9, 044036 (2018)
(arXiv:1711.05395, Sec. III), with
the strictly adjacent, linear-depth rotation scheduling of Kivlichan et
al., Phys. Rev. Lett. 120, 110501 (2018)
(arXiv:1711.04789).
schedule = stateprep.make_givens_rotation_schedule(orbital_coefficients)
schedule.num_spin_orbitals, schedule.num_electrons, schedule.is_complex
schedule.rotations # adjacent rotations, application order
schedule.final_phases # one per electron (complex only)
prep = stateprep.slater_determinant_kernel(schedule) # (qubits: qview)
stateprep.estimate_givens_resources(schedule) # GivensResourceEstimate
make_givens_rotation_schedule reduces Q to the computational-basis
determinant with adjacent (nearest-neighbor) Givens row rotations on the
host; the kernels apply the inverse rotations in reverse order to the
determinant |1...10...0>. Real and complex matrices dispatch
automatically (a complex dtype routes complex even when every value is
real); complex schedules carry a relative phase per rotation plus one
final phase per electron. validate_givens_rotation_schedule guards
hand-built schedules; built schedules always pass.
slater_determinant_kernel(schedule) is the packaged, injectable form.
The module-level device kernels remain composable from user kernels,
with get_givens_rotation_indices / ..._angles / ..._phases
supplying the flattened arrays that cross the kernel boundary:
stateprep.slater_determinant(qubits, indices, angles, num_electrons)
stateprep.complex_slater_determinant(qubits, indices, angles, phases,
final_phases, num_electrons)
stateprep.givens_rotation(qubits, theta, first, second) # adjacent
stateprep.phase_givens_rotation(qubits, theta, phase, first, second)
estimate_givens_resources returns a GivensResourceEstimate
(num_givens_rotations, num_exp_pauli_calls, num_phase_rotations,
two_qubit_gate_count_proxy, depth_proxy) — decomposition-independent
proxies, not transpiled gate counts.
State preparation injection
Every kernel factory in the package family — PauliLCU.encode_kernel,
Walk.kernel (and its adjoint/controlled variants), QSVT.kernel, and
Trotter.kernel — takes an optional state_prep kernel with signature
(qubits: cudaq.qview). The returned circuit is then zero-argument:
the system register is allocated in |0...0>, state_prep runs on it,
and the operation follows — directly sampleable and fully synthesizable,
with the statevector abstracted away from the caller (a simulator still uses one internally).
Any (qubits: cudaq.qview) preparation qualifies, including the
factory outputs of this package — hartree_fock_ucc_kernel(...) and
slater_determinant_kernel(...) — and the raw kernels wrapped by the
caller:
import cudaq
from cudaq_algorithms import PauliLCU, Trotter, stateprep
pool = stateprep.make_uccsd_operator_pool(4, 2)
words, coeffs = stateprep.get_fixed_parameter_ucc_pauli_lists(pool, 4)
prep = stateprep.hartree_fock_ucc_kernel(4, [0.11, -0.04, 0.28], words,
coeffs, num_electrons=2)
hamiltonian = {"ZIII": 0.5, "IZII": 0.5, "XXII": 0.25}
# Trotter time evolution from the UCC state:
kernel = Trotter(hamiltonian).kernel(time=0.8, steps=4, state_prep=prep)
counts = cudaq.sample(kernel)
# The same prep drives the block-encoding/walk/QSVT factories:
enc = PauliLCU(hamiltonian)
kernel = enc.encode_kernel(state_prep=prep)
Contract (shared with the other primitives): state_prep acts only on
the system register it is handed, which arrives in |0...0> with the
factory’s system width (num_qubits for Trotter, num_system for the
encodings) — not verifiable at factory time, so a mismatched prep fails
at launch. The stateprep factories bake their width in at construction
(num_qubits for hartree_fock_ucc_kernel, num_spin_orbitals for
slater_determinant_kernel), so build them to match.
tests/python/test_state_prep_injection.py exercises the contract
end-to-end.
Package conventions
Qubit layout: interleaved spin orbitals — alpha spin orbitals on even qubits, beta on odd (spatial orbital
imaps to qubits2i/2i + 1). All pools, excitation enumerations, and Hartree-Fock occupations share it.`spin` semantics:
spinis the difference between alpha and beta electron counts (twice the total S_z), son_beta = (num_electrons - spin) // 2andn_alpha = num_electrons - n_beta.spin == 0selects the closed-shell forms;spin > 0requires an evennum_qubitsand produces the interleaved open-shell occupations and excitations.Error types: two error cases raise
RuntimeError(odd qubit count and odd-electrons-at-spin-0 inget_uccsd_excitations); every guard added in the pure implementation raisesValueError.CEO orbital counts: the CEO helpers (
make_ceo_operator_pool,get_ceo_pauli_lists) takenum_orbitalsin spatial orbitals — the pool acts on2 * num_orbitalsqubits. Everything else in the package counts qubits (spin orbitals).
See Conventions and Reference for the library-wide qubit-ordering, Pauli-word, and integral-tensor conventions these build on.