UCC

class UCC(variant, t1, t2, *, antisymmetric=False, atol=1e-08)

Bases: FermionicGate

Implements the unitary coupled cluster (UCC) ansatz.

A unitary coupled cluster operator has the form

\[e^{T - T^\dagger}\]

where \(T = T_1 + T_2\) is the cluster operator built from the single and double fermionic excitations, parameterized by the \(t_1\) and \(t_2\) amplitudes. Since \(T - T^\dagger\) is anti-Hermitian, its exponential is unitary.

This gate supports three spin variants (see UCC.Variant), selected explicitly by the variant argument and validated against the shapes of the supplied amplitudes (mirroring ffsim’s UCCSDOpRestrictedReal and UCCSDOpUnrestrictedReal):

  • restricted – a single spin-summed amplitude pair. The cluster operator is

    \[\begin{align} T_1 &= \sum_{ia} t_{ia}\left( a^\dagger_{a\alpha} a_{i\alpha} + a^\dagger_{a\beta} a_{i\beta}\right), \\ T_2 &= \sum_{ijab} t_{ijab}\left[ \frac12\left( a^\dagger_{a\alpha} a^\dagger_{b\alpha} a_{j\alpha} a_{i\alpha} + a^\dagger_{a\beta} a^\dagger_{b\beta} a_{j\beta} a_{i\beta}\right) + a^\dagger_{a\alpha} a^\dagger_{b\beta} a_{j\beta} a_{i\alpha}\right], \end{align}\]

    with t1 of shape (nocc, nvrt) and t2 of shape (nocc, nocc, nvrt, nvrt). Acts on 2 * norb block-spin modes.

  • unrestricted – independent per-spin amplitudes. The cluster operator is

    \[\begin{align} T_1 &= \sum_{ia} t^{(\alpha)}_{ia} a^\dagger_{a\alpha} a_{i\alpha} + \sum_{IA} t^{(\beta)}_{IA} a^\dagger_{A\beta} a_{I\beta}, \\ T_2 &= \frac14 \sum_{ijab} t^{(\alpha\alpha)}_{ijab} a^\dagger_{a\alpha} a^\dagger_{b\alpha} a_{j\alpha} a_{i\alpha} + \frac14 \sum_{IJAB} t^{(\beta\beta)}_{IJAB} a^\dagger_{A\beta} a^\dagger_{B\beta} a_{J\beta} a_{I\beta} + \sum_{iJaB} t^{(\alpha\beta)}_{iJaB} a^\dagger_{a\alpha} a^\dagger_{B\beta} a_{J\beta} a_{i\alpha}, \end{align}\]

    with t1 a pair (t1a, t1b) and t2 a triple (t2aa, t2ab, t2bb). Acts on 2 * norb block-spin modes. Note that the occupied/virtual split is resolved per spin sector, so the two sectors may have different numbers of occupied orbitals.

  • spinless – a single register of norb spinless modes,

    \[T_1 = \sum_{ia} t_{ia}\, a^\dagger_a a_i, \qquad T_2 = \frac14 \sum_{ijab} t_{ijab}\, a^\dagger_a a^\dagger_b a_j a_i,\]

    with the same amplitude shapes as the "restricted" variant. Acts on norb modes.

In every variant the occupied orbitals are ordered before the virtual ones, so orbital \(i < n_\text{occ}\) is occupied and orbital \(n_\text{occ} + a\) is virtual.

Note

Unlike UCJ, this ansatz carries no final orbital rotation: its \(t_1\) amplitudes already provide the single excitations, so a trailing rotation would be redundant freedom. Append an OrbitalRotation explicitly if you want one.

Note

By default only the symmetry the cluster operator actually enforces is imposed on the same-spin \(t_2\) blocks. The stricter antisymmetry of the standard coupled-cluster convention is available opt-in via the antisymmetric flag (see antisymmetric), which both validates supplied amplitudes and shrinks the parameter vector accordingly. It is not supported for the "restricted" variant, whose single \(t_2\) also carries the cross-spin amplitudes.

Note

Because the individual excitation terms of \(T - T^\dagger\) do not commute, the circuit definition() this gate produces is a first-order product formula (Trotter) approximation of the exponential, not an exact decomposition – the usual situation for UCC ansatz circuits. The state-vector simulation path (_apply_unitary_placed_()), by contrast, applies the exponential exactly via scipy’s expm_multiply. Consequently the simulated gate and its synthesized circuit agree only up to the Trotter error; use a higher-order product formula during transpilation to tighten it.

Caution

This is an early development prototype. Beware of changes to its interface without warning during the pre-release development of this package.

Initializing an instance of this gate can be done with the arguments listed below.

Parameters:
  • variant (UCC.Variant | str) – the spin variant, a UCC.Variant (or its string value "restricted", "unrestricted", or "spinless"). Determines the number of modes this gate acts on (see the class docstring) and the expected amplitude shapes.

  • t1 (np.ndarray | tuple[np.ndarray, np.ndarray]) – the \(t_1\) (singles) amplitudes. For the "restricted" and "spinless" variants, a single array of shape (nocc, nvrt). For the "unrestricted" variant, a pair (t1a, t1b).

  • t2 (np.ndarray | tuple[np.ndarray, np.ndarray, np.ndarray]) – the \(t_2\) (doubles) amplitudes. For the "restricted" and "spinless" variants, a single array of shape (nocc, nocc, nvrt, nvrt). For the "unrestricted" variant, a triple (t2aa, t2ab, t2bb).

  • antisymmetric (bool) – whether the same-spin \(t_2\) blocks obey the separate occupied and virtual antisymmetry (the standard coupled-cluster convention, see antisymmetric). When True the supplied blocks are validated against it and the parameter vector is restricted to the corresponding subspace. Not supported for the "restricted" variant.

  • atol (float) – the absolute tolerance for the antisymmetric validation.

Raises:

ValueError – if variant is not recognized, if the amplitude shapes are inconsistent with each other or with variant, if antisymmetric is requested for the "restricted" variant, or if antisymmetric is requested but a same-spin \(t_2\) block violates that antisymmetry.

Attributes

antisymmetric

Whether the same-spin \(t_2\) blocks obey the separate occupied/virtual antisymmetry.

The cluster operator only ever sees the part of a same-spin \(t_2\) block that is symmetric under the simultaneous exchange \(t_2[i,j,a,b] = t_2[j,i,b,a]\), because the underlying excitation \(a^\dagger_a a^\dagger_b a_j a_i\) is invariant under relabeling the pairs \((i,a) \leftrightarrow (j,b)\). That weaker symmetry is therefore always imposed.

The standard coupled-cluster convention additionally makes the block antisymmetric in each index pair separately, \(t_2[i,j,a,b] = -t_2[j,i,a,b] = -t_2[i,j,b,a]\), which is a strict subspace of the above. Setting this flag opts into that convention: the supplied amplitudes are validated against it, and num_parameters() / from_parameters() / to_parameters() switch to the smaller parameter basis that spans exactly this subspace.

t1: ndarray | tuple[ndarray, ...]

The \(t_1\) (singles) amplitudes.

t2: ndarray | tuple[ndarray, ...]

The \(t_2\) (doubles) amplitudes.

norb

The number of spatial orbitals (or spinless modes, for the spinless variant).

Methods

cluster_operator()

Returns the anti-Hermitian cluster generator \(T - T^\dagger\).

The generator is expressed in the block-spin mode convention (mode p is alpha orbital p, mode norb + p is beta orbital p) for the spinful variants, and directly on the norb modes for the spinless variant. Occupied orbitals are ordered before virtual ones.

Being anti-Hermitian, this generator relates to the ansatz unitary by \(e^{T - T^\dagger} = e^{-i H}\) with the Hermitian \(H = i (T - T^\dagger)\). That \(H\) is what Evolution consumes, since it requires a Hermitian operator to produce a unitary, and it is how _build_definition() expresses the ansatz.

The returned operator carries groups that pair every excitation with its Hermitian conjugate. That grouping is load-bearing: Evolution decomposes group-by-group, so each group becomes one factor \(e^{-i H_k}\) of the product formula, and multiplying by \(i\) leaves every group individually Hermitian – hence every factor a genuine unitary. Splitting term-by-term instead would not be (see the comment below).

Returns:

The cluster generator \(T - T^\dagger\) as a FermionOperator.

Return type:

FermionOperator

classmethod from_parameters(params, norb, nocc, variant, *, antisymmetric=False)

Constructs a UCC ansatz from a real-valued parameter vector.

With antisymmetric=False (the default) the parameter ordering matches ffsim’s UCCSDOpRestrictedReal / UCCSDOpUnrestrictedReal convention, so a vector produced by ffsim’s own to_parameters round-trips through this method.

With antisymmetric=True the same-spin \(t_2\) blocks are instead built from the smaller basis spanning the standard coupled-cluster antisymmetric subspace (see antisymmetric), so the expected vector length differs and ffsim’s vectors no longer apply.

Parameters:
  • params (ndarray) – the real-valued parameter vector.

  • norb (int) – the number of spatial orbitals (or spinless modes, for the spinless variant).

  • nocc (int | tuple[int, int]) – the number of occupied orbitals. For the "unrestricted" variant a pair (nocc_a, nocc_b); otherwise a single integer.

  • variant (Variant | str) – the spin variant, a UCC.Variant (or its string value "restricted", "unrestricted", or "spinless").

  • antisymmetric (bool) – whether to build the same-spin \(t_2\) blocks in the antisymmetric subspace (see antisymmetric). The resulting amplitudes then satisfy that antisymmetry by construction. Not supported for the "restricted" variant.

Returns:

The constructed UCC gate.

Raises:

ValueError – if variant is not recognized, if antisymmetric is requested for the "restricted" variant, or if len(params) does not match num_parameters() for the given settings.

Return type:

Self

classmethod from_t_amplitudes(t2, *, t1=None, variant='restricted', antisymmetric=False, atol=1e-08)

Constructs a UCC ansatz from coupled-cluster \(t_2\) (and optional \(t_1\)) amplitudes.

This is a convenience constructor mirroring UCJ.from_t_amplitudes(). Unlike the (L)UCJ ansatz – which factorizes the amplitudes into diagonal Coulomb layers – the UCC ansatz uses the amplitudes directly as its parameters, so this simply defaults an omitted t1 to zeros of the shape implied by t2, giving a doubles-only (UCCD) ansatz.

Parameters:
  • t2 (ndarray | tuple[ndarray, ndarray, ndarray]) – the \(t_2\) amplitudes. For the "restricted" and "spinless" variants, a single array of shape (nocc, nocc, nvrt, nvrt). For the "unrestricted" variant, a triple (t2aa, t2ab, t2bb).

  • t1 (ndarray | tuple[ndarray, ndarray] | None) – the optional \(t_1\) amplitudes. For "unrestricted", a pair (t1a, t1b); otherwise a single array of shape (nocc, nvrt). Defaults to zeros.

  • variant (Variant | str) – the spin variant to build, a UCC.Variant (or its string value "restricted", "unrestricted", or "spinless").

  • antisymmetric (bool) – whether to assert the standard coupled-cluster antisymmetry of the same-spin \(t_2\) blocks (see antisymmetric). Amplitudes from a genuine coupled-cluster calculation satisfy it, so this is a cheap way to confirm they survived whatever preprocessing produced them. Not supported for the "restricted" variant.

  • atol (float) – the absolute tolerance for the antisymmetric validation.

Returns:

The constructed UCC gate.

Raises:

ValueError – if variant is not recognized, if the amplitude shapes are inconsistent with each other or with variant, if antisymmetric is requested for the "restricted" variant, or if antisymmetric is requested but a same-spin \(t_2\) block violates that antisymmetry.

Return type:

Self

classmethod num_parameters(norb, nocc, variant, *, antisymmetric=False)

Returns the number of parameters of a UCC ansatz with the given settings.

Parameters:
  • norb (int) – the number of spatial orbitals (or spinless modes, for the spinless variant).

  • nocc (int | tuple[int, int]) – the number of occupied orbitals. For the "unrestricted" variant a pair (nocc_a, nocc_b) giving the per-spin occupations; otherwise a single integer.

  • variant (Variant | str) – the spin variant, a UCC.Variant (or its string value "restricted", "unrestricted", or "spinless").

  • antisymmetric (bool) – whether the same-spin \(t_2\) blocks are restricted to the standard coupled-cluster antisymmetric subspace (see antisymmetric), which needs strictly fewer parameters. Not supported for the "restricted" variant.

Returns:

The number of parameters.

Raises:

ValueError – if variant is not recognized, if nocc is a pair for a variant other than UCC.Variant.UNRESTRICTED (or an integer for that variant), or if antisymmetric is requested for the "restricted" variant.

Return type:

int

to_parameters()

Converts this UCC ansatz to a real-valued parameter vector.

The inverse of from_parameters(), using the same ordering and the same basis this gate’s antisymmetric flag selects – so from_parameters(gate.to_parameters(), ...) round-trips as long as the flag is passed consistently.

Note

Only the independent amplitude entries implied by the variant’s symmetries (and by antisymmetric) are written out; see num_parameters(). Amplitudes violating those symmetries – or carrying a non-negligible imaginary part – are therefore not recoverable from the parameter vector.

Note

The round-trip is two-sided and holds at any parameter scale: the amplitudes are this ansatz’s parameters directly, so both directions are a plain re-indexing.

Returns:

The real-valued parameter vector.

Return type:

ndarray

Protocol Methods

_apply_unitary_placed_(vec, norb, nelec, copy, freg_indices)

Applies the ansatz after placing its modes onto the vector’s global modes.

This builds the gate’s definition (the cluster-operator evolution) and applies it to vec, with the definition circuit placed onto the global modes freg_indices. Because the definition’s single Evolution carries the whole cluster generator, its own _apply_unitary_placed_ exponentiates it exactly (via scipy’s expm_multiply) – so this path incurs no Trotter error, unlike the synthesized circuit. See _define() for the exact gate sequence.

Parameters:
  • vec (ndarray) – the state vector to act on.

  • norb (int) – the number of spatial orbitals of the global state vector.

  • nelec (int | tuple[int, int]) – either a single integer for a spinless system, or a pair of integers storing the numbers of spin alpha and spin beta fermions.

  • copy (bool) – whether to copy the vector before operating on it.

  • freg_indices (list[int]) – the absolute (global) mode indices that this gate’s local modes map onto.

Returns:

The transformed vector.

Return type:

ndarray