# 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:

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 += 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):

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