The UCC Family¶
The following unitary coupled cluster (UCC) ansatzes are available in InQuanto:
These are discussed in further detail in the following subsections.
Unitary Coupled Cluster¶
This ansatz corresponds to a variant of the (non-unitary) coupled cluster method [45], in which the operator acting on a reference state becomes unitary [46]
Here, the excitation operator \(\hat{T}\) has the same form as in coupled cluster
theory. Under the exponent the excitation operator self-adjoint is subtracted, rendering the exponent
anti-Hermitian, and hence the operator \(\hat{U}_{\text{UCC}}\) is unitary. The unitarity of
\(\hat{U}_{\text{UCC}}\) makes its implementation on a quantum circuit more natural. By parameterizing the
excitations, this ansatz is suitable for variational algorithms in which the
parameters \(\boldsymbol{\theta} = \{\theta_1, \theta_2, ..., \theta_\lambda, ...\}\) are to be
optimized such that the total energy is minimized according to the variational principle. The
reference wavefunction is often the Hartree-Fock ground state, but excited configurations are also
possible, as long as \(\hat{U}_{\text{UCC}}\) is applied to a single configuration.
Truncating excitations to singles and doubles leads to the
(FermionSpaceAnsatzUCCSD
) ansatz. Doubles-only
(FermionSpaceAnsatzUCCD
) ansatz is also available.
Consider the following example for a system of 4 spin orbitals and 2 electrons (applicable to the H2 molecule in minimal basis):
from inquanto.states import FermionState
from inquanto.spaces import FermionSpace
from inquanto.mappings import QubitMappingJordanWigner
from inquanto.ansatzes import FermionSpaceAnsatzUCCD, FermionSpaceAnsatzUCCSD
space = FermionSpace(4)
state = FermionState([1, 1, 0, 0])
jw_map = QubitMappingJordanWigner()
ansatz_uccsd = FermionSpaceAnsatzUCCSD(
fermion_space=space, fermion_state=state, qubit_mapping=jw_map
)
ansatz_uccd = FermionSpaceAnsatzUCCD(
fermion_space=space, fermion_state=state, qubit_mapping=jw_map
)
print("UCCSD parameters:", ansatz_uccsd.state_symbols)
print("\nUCCD parameters:", ansatz_uccd.state_symbols)
report_uccsd = ansatz_uccsd.generate_report()
report_uccd = ansatz_uccd.generate_report()
print("\nNumber of parameters: {} (UCCSD), {} (UCCD)".format(
report_uccsd['n_parameters'],
report_uccd['n_parameters'])
)
print("Number of qubits: {} (UCCSD), {} (UCCD)".format(
report_uccsd['n_qubits'],
report_uccd['n_qubits'])
)
print("Ansatz circuit depth: {} (UCCSD), {} (UCCD)".format(
report_uccsd['ansatz_circuit_depth'],
report_uccd['ansatz_circuit_depth'])
)
uccsd_circuit = ansatz_uccsd.get_circuit(
ansatz_uccsd.state_symbols.construct_random()
)
uccd_circuit = ansatz_uccd.get_circuit(
ansatz_uccd.state_symbols.construct_random()
)
UCCSD parameters: [d0, s0, s1]
UCCD parameters: [d0]
Number of parameters: 3 (UCCSD), 1 (UCCD)
Number of qubits: 4 (UCCSD), 4 (UCCD)
Ansatz circuit depth: 58 (UCCSD), 29 (UCCD)
In the above script, we have built UCCSD and UCCD ansatzes objects using the fermionic orbital
space (FermionSpace
) and occupation
state (FermionState
) objects, as well as our
choice of fermion-to-qubit mapping
(QubitMappingJordanWigner
). Symbols corresponding to the
ansatz parameters can be accessed by the state_symbols
attribute, which returns an
InQuanto SymbolSet
object that stores the SymPy symbols.
For more information on how to access and manipulate symbolic parameters in InQuanto please see the
corresponding section.
Useful information about the ansatz circuit such as number of qubits, number of parameters, and
circuit depth is available from the dict
returned by the generate_report()
method
of each ansatz object. A
pytket Circuit object can be generated
using the get_circuit()
method (which needs numeric values for the parameters, and we
provide random values here using the construct_random()
method).
For this system, we see three parameters for UCCSD: 2 single (alpha-beta single excitations are not allowed in order to preserve spin symmetry), and 1 double excitation. Exclusion of the singles in the UCCD ansatz leads to only one excitation – the double. This is consistent with minimal basis H2. As we can see, fewer excitations in UCCD leads to equally less parameters and a reduced circuit depth compared to the UCCSD ansatz.
InQuanto generates the excitation operators for UCC using the
FermionOperatorList
class. To construct a UCC ansatz object, anti-hermitian UCC excitations must be
transformed to qubit operators via a provided fermion to qubit mapping
method and exponentiated. To facilitate its implementation on a quantum circuit, the UCC ansatz is
expressed in a trotterized form (facilitated by the FermionOperatorList
internal structure), i.e. approximated as
which corresponds to the first order Trotter decomposition. In general this is an approximation,
which leads to (usually small) variations of the energy that depend on the order of terms in the
product \(\prod_\lambda e^{\theta_\lambda (\hat{T}_\lambda - \hat{T}^\dagger_\lambda)}\). For
variational algorithms, it is expected that this error will be absorbed by the variational
procedure; however, this must be considered – particularly when using non-variational algorithms.
All the UCC-family ansatzes in InQuanto use first-order trotterization. To implement higher-order
approximations, one needs to use their base FermionSpaceStateExp
class described in
the corresponding section.
Generalized Unitary Coupled Cluster¶
The UCC ansatz discussed in the previous section refers to set of coupled cluster operators with a well defined separation between occupied and unoccupied (virtual) orbital spaces, such that all excitations are transitions between occupied and virtual spin orbitals. Lee et al. [27] proposed variations of UCC in which occupied-to-occupied and virtual-to-virtual excitations are also included. The direct extension of UCC(S)D ansatzes to include these generalized transitions is referred to as UCCG(S)D. Here
where \(p,q,r,s\) run over both occupied and virtual spin orbital spaces. Note there is a
one-to-one mapping between the set of parameters (labelled by generic indexes for cluster operators)
and the set of excitation coefficients for singles and doubles (labelled by orbitals involved in the
transition), i.e. \(\boldsymbol{\theta} = \{\theta_1, \theta_2, ..., \theta_\lambda, ...\} \mapsto \{t_{p}^{q}, ..., t_{p,q}^{r,s}, ...\}\).
The UCCG(S)D ansatzes are instantiated in the same way as UCC(S)D (see the code snippet in
Unitary Coupled Cluster), by simply replacing FermionSpaceAnsatzUCCSD
(FermionSpaceAnsatzUCCD
) with FermionSpaceAnsatzUCCGSD
(FermionSpaceAnsatzUCCGD
) for singles+doubles (doubles only).
A more compact form of generalized UCC has also been proposed in which the double excitations are restricted to pair-doubles, i.e. transitions of a pair of electrons between the two spatial orbitals (but spatial orbital indexes still span the general range as above)
where \({\alpha, \beta}\) label spins, \({p, q}\) labels spatial orbitals, and the singles are
as in \(T^{(1\text{G})}\). This ansatz, referred to as \(k\)-UpCCGSD
[27] (FermionSpaceAnsatzkUpCCGSD
),
is expressed as a product of \(k\) factors of cluster operators, each one with an independent set
of parameters
The following snippet demonstrates the initialization of the UCCGSD and \(k\)-UpCCGSD ansatzes:
from inquanto.states import FermionState
from inquanto.spaces import FermionSpace
from inquanto.mappings import QubitMappingJordanWigner
from inquanto.ansatzes import FermionSpaceAnsatzkUpCCGSD, FermionSpaceAnsatzUCCGSD
space = FermionSpace(4)
state = FermionState([1, 1, 0, 0])
jw_map = QubitMappingJordanWigner()
ansatz_uccgsd = FermionSpaceAnsatzUCCGSD(
fermion_space=space, fermion_state=state, qubit_mapping=jw_map
)
ansatz_kupccsd = FermionSpaceAnsatzkUpCCGSD(
fermion_space=space, fermion_state=state, k_input=2, qubit_mapping=jw_map
)
print("UCCGSD parameters:", ansatz_uccgsd.state_symbols)
print("\nkUpCCGSD parameters:", ansatz_kupccsd.state_symbols)
UCCGSD parameters: [gd0, gs0, gs1]
kUpCCGSD parameters: [gd0k0, gd0k1, gs0k0, gs0k1, gs1k0, gs1k1]
In this case, there are 2 duplicates for the set of singles and doubles of \(k\)-UpCCGSD, as the input value of \(k\) is set to 2.