Ansatz#

A quantum circuit comprising parameterized gates (e.g. \(RX(\theta)\), \(RY(\theta)\) and \(RZ(\theta)\)), represents a unitary transformation \(U(\theta)\) that transforms some initial quantum state into a parametrized ansatz state \(|\psi(\theta)\rangle\).

Examples of some ansatzes available in Qibochem are described in the subsections below.

Hardware Efficient Ansatz#

Qibochem provides a hardware efficient ansatz that simply consists of a layer of single-qubit rotation gates followed by a layer of two-qubit gates that entangle the qubits. For the H2 case discussed in previous sections, a possible hardware efficient circuit ansatz can be constructed as such:

../_images/qibochem_doc_ansatz_hardware-efficient.svg
from qibochem.ansatz import he_circuit

nqubits = 4
nlayers = 1

circuit = he_circuit(nqubits, nlayers)
print(circuit.draw())
q0: ─RY─RZ─o─────Z─
q1: ─RY─RZ─Z─o───|─
q2: ─RY─RZ───Z─o─|─
q3: ─RY─RZ─────Z─o─

The energy of the state generated from the hardware efficient ansatz for the fermionic two-body Hamiltonian can then be estimated, using state vectors or samples.

The following example demonstrates how the energy of the H2 molecule is affected with respect to the rotational parameters:

import numpy as np

from qibochem.driver import Molecule
from qibochem.measurement.expectation import expectation
from qibochem.ansatz import he_circuit

mol = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.74804))])
mol.run_pyscf()
hamiltonian = mol.hamiltonian()

# Define and build the HEA
nlayers = 1
nqubits = mol.nso
ntheta = 2 * nqubits * nlayers
circuit = he_circuit(nqubits, nlayers)

print("Energy expectation values for thetas: ")
print("-----------------------------")
print("| theta | Electronic energy |")
print("|---------------------------|")
thetas = [-0.2, 0.0, 0.2]
for theta in thetas:
    params = np.full(ntheta, theta)
    circuit.set_parameters(params)
    electronic_energy = expectation(circuit, hamiltonian)
    print(f"| {theta:5.1f} | {electronic_energy:^18.12f}|")
print("-----------------------------")
converged SCF energy = -1.11628373627429

Energy expectation values for thetas:
-----------------------------
| theta | Electronic energy |
|---------------------------|
|  -0.2 |   0.673325849299  |
|   0.0 |   0.707418334474  |
|   0.2 |   0.673325849299  |
-----------------------------

Unitary Coupled Cluster Ansatz#

The Unitary Coupled Cluster (UCC) ansatz [1] [2] [3] is a variant of the popular gold standard Coupled Cluster ansatz [4] of quantum chemistry. The UCC wave function is a parameterized unitary transformation of a reference wave function \(\psi_{\mathrm{ref}}\), of which a common choice is the Hartree-Fock wave function.

\[\begin{align*} |\psi_{\mathrm{UCC}}\rangle &= U(\theta)|\psi_{\mathrm{ref}}\rangle \\ &= e^{\hat{T}(\theta) - \hat{T}^\dagger(\theta)}|\psi_{\mathrm{ref}}\rangle \end{align*}\]

Similar to the process for the molecular Hamiltonian, the fermionic excitation operators \(\hat{T}\) and \(\hat{T}^\dagger\) are mapped using e.g. Jordan-Wigner mapping into Pauli operators. This is typically followed by a Suzuki-Trotter decomposition of the exponentials of these Pauli operators, which allows the UCC ansatz to be implemented on quantum computers. [5]

An example of how to build a UCC doubles circuit ansatz for the \(H_2\) molecule is given as:

from qibochem.driver.molecule import Molecule
from qibochem.ansatz import hf_circuit, ucc_circuit

mol = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.74804))])
mol.run_pyscf()
hamiltonian = mol.hamiltonian()

# Set parameters for the rest of the experiment
n_qubits = mol.nso
n_electrons = mol.nelec

# Build UCCD circuit
circuit = hf_circuit(n_qubits, n_electrons) # Start with HF circuit
circuit += ucc_circuit(n_qubits, [0, 1, 2, 3]) # Then add the double excitation circuit ansatz

print(circuit.draw())
q0:     ─X──H─────X─RZ─X─────H──RX─────X─RZ─X─────RX─RX─────X─RZ─X─────RX─H─── ...
q1:     ─X──H───X─o────o─X───H──RX───X─o────o─X───RX─H────X─o────o─X───H──RX── ...
q2:     ─RX───X─o────────o─X─RX─RX─X─o────────o─X─RX─H──X─o────────o─X─H──H──X ...
q3:     ─H────o────────────o─H──H──o────────────o─H──H──o────────────o─H──H──o ...

q0: ... ───X─RZ─X─────H──RX─────X─RZ─X─────RX─H──────X─RZ─X─────H──H──────X─RZ ...
q1: ... ─X─o────o─X───RX─H────X─o────o─X───H──RX───X─o────o─X───RX─H────X─o─── ...
q2: ... ─o────────o─X─H──RX─X─o────────o─X─RX─RX─X─o────────o─X─RX─H──X─o───── ...
q3: ... ────────────o─H──RX─o────────────o─RX─RX─o────────────o─RX─RX─o─────── ...

q0: ... ─X─────H──RX─────X─RZ─X─────RX─
q1: ... ─o─X───H──RX───X─o────o─X───RX─
q2: ... ───o─X─H──H──X─o────────o─X─H──
q3: ... ─────o─RX─RX─o────────────o─RX─

Basis rotation ansatz#

The starting points for contemporary quantum chemistry methods are often those based on the mean field approximation within a (finite) molecular orbital basis, i.e. the Hartree-Fock method. The electronic energy is calculated as the mean value of the electronic Hamiltonian \(\hat{H}_{\mathrm{elec}}\) acting on a normalized single Slater determinant function \(\psi\) [6]

\[\begin{align*} E[\psi] &= \langle \psi | \hat{H}_{\mathrm{elec}} |\psi \rangle \\ &= \sum_i^{N_f} \langle \phi_i |\hat{h}|\phi_i \rangle + \frac{1}{2} \sum_{i,j}^{N_f} \langle \phi_i\phi_j||\phi_i\phi_j \rangle \end{align*}\]

The orthonormal molecular orbitals \(\phi\) are optimized by a direct minimization of the energy functional with respect to parameters \(\kappa\) that parameterize the unitary rotations of the orbital basis. Qibochem’s implementation uses the QR decomposition of the unitary matrix as employed by Clements et al., [7] which results in a rectangular gate layout of Givens rotation gates that yield linear CNOT gate depth when decomposed.

import numpy as np
from qibochem.driver.molecule import Molecule
from qibochem.ansatz import basis_rotation, ucc
from qibo import Circuit, gates, models

def basis_rotation_circuit(mol, parameters=0.0):

    nqubits = mol.nso
    occ = range(0, mol.nelec)
    vir = range(mol.nelec, mol.nso)

    U, kappa = basis_rotation.unitary(occ, vir, parameters=parameters)
    gate_angles, final_U = basis_rotation.givens_qr_decompose(U)
    gate_layout = basis_rotation.basis_rotation_layout(nqubits)
    gate_list, ordered_angles = basis_rotation.basis_rotation_gates(gate_layout, gate_angles, kappa)

    circuit = Circuit(nqubits)
    for _i in range(mol.nelec):
        circuit.add(gates.X(_i))
    circuit.add(gate_list)

    return circuit, gate_angles

h3p = Molecule([('H', (0.0000,  0.0000, 0.0000)),
                ('H', (0.0000,  0.0000, 0.8000)),
                ('H', (0.0000,  0.0000, 1.6000))],
                charge=1, multiplicity=1)
h3p.run_pyscf(max_scf_cycles=1)

e_init = h3p.e_hf
h3p_sym_ham = h3p.hamiltonian("sym", h3p.oei, h3p.tei, 0.0, "jw")

hf_circuit, qubit_parameters = basis_rotation_circuit(h3p, parameters=0.1)

print(hf_circuit.draw())

vqe = models.VQE(hf_circuit, h3p_sym_ham)
res = vqe.minimize(qubit_parameters)

print('energy of initial guess: ', e_init)
print('energy after vqe       : ', res[0])
q0: ─X─G─────────G─────────G─────────
q1: ─X─G─────G───G─────G───G─────G───
q2: ─────G───G─────G───G─────G───G───
q3: ─────G─────G───G─────G───G─────G─
q4: ───────G───G─────G───G─────G───G─
q5: ───────G─────────G─────────G─────
basis rotation: using uniform value of 0.1 for each parameter value
energy of initial guess:  -1.1977713400022736
energy after vqe       :  -1.2024564133305427

References