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 i maps to qubits 2i / 2i + 1). All pools, excitation enumerations, and Hartree-Fock occupations share it.

  • `spin` semantics: spin is the difference between alpha and beta electron counts (twice the total S_z), so n_beta = (num_electrons - spin) // 2 and n_alpha = num_electrons - n_beta. spin == 0 selects the closed-shell forms; spin > 0 requires an even num_qubits and produces the interleaved open-shell occupations and excitations.

  • Error types: two error cases raise RuntimeError (odd qubit count and odd-electrons-at-spin-0 in get_uccsd_excitations); every guard added in the pure implementation raises ValueError.

  • CEO orbital counts: the CEO helpers (make_ceo_operator_pool, get_ceo_pauli_lists) take num_orbitals in spatial orbitals — the pool acts on 2 * num_orbitals qubits. 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.