Quantum Selected Configuration Interaction
QSCI is released under a custom source available license. This prohibits commercial use. The full license can be found here.
In this tutorial we will introduce Quantum Selected Configuration Interaction (QSCI) from the quri-parts-qsci package in the context of early fault tolerant quantum computing (EFTQC)
Overview
QSCI is a quantum algorithm that produces approximate ground states of some quantum Hamiltonian. This is done by sampling a trial wave-function that is believed to be an approximate solution to the problem and then reconstructing the Hamiltonian in the space spanned by the most significant computational basis states sampled.
- QSCI is sometimes considered an EFTQC algorithm because it benefits from using deep ansatze
- The accuracy of QSCI comes from the matrix diagonalization which is performed on classical hardware
In this notebook, we will use qsci to solve a small quantum chemistry problem. First we use coupled cluster (CC) theory to obtain an approximate solution, which we encode with the unitary coupled cluster singles and doubles (UCCSD) ansatz. Finally we will estimate the fidelity of the final state obtained by real devices. The flow and major take-aways from this notebook will be
- Using
pyscfto define an small molecule and obtain its qubit Hamiltonian as well as the CC ground state - Importing the
TrotterUCCSDansatz fromquri_parts.openfermion.ansatzand populating its variational parameters using the CC solution - By using the
TrotterUCCSDansatz state, useqscifromquri_parts_qscito obtain the QSCI ground state and ground state energy - Using QURI Parts' transpiler library to transpile the resulting circuits
- Estimating the fidelity using QURI VM
To check out the source-code we use for qsci please have a look at our open source repository
Prerequisites
Although this notebook is pretty self-contained, users will benefit from first studying
Setup and methodology testing
We start with a simple problem to test our setup and methodology. This is more like a sanity check than a real test, but we want to
- Define a molecule
- Verify that CC solution works
- Verify that the trotterUCCSD solution (almost) replicates the CC energy
- Verify that QSCI improves on the CC energy
We first define the molecule below using pyscf and then using the openfermion submodule of quri_parts we convert it from a fermionic Hamiltonian to one that can operate on qubits. A lot of steps, such as applying the Jordan-Wigner transformation are performed for us automatically. If you are interested in learning about them in more detail, please see the QURI Parts documentation on the fermion-to-qubit mapping and molecular Hamiltonians.
import numpy as np
from pyscf import gto, scf, cc
from quri_parts.core.operator import get_sparse_matrix
from quri_parts.pyscf.mol import get_spin_mo_integrals_from_mole
from quri_parts.openfermion.mol import get_qubit_mapped_hamiltonian
from quri_parts.openfermion.ansatz import TrotterUCCSD
mole = gto.M(atom="H 0 0 0; H 0 0 1")
mf = scf.RHF(mole).run(verbose=0)
hamiltonian, mapping = get_qubit_mapped_hamiltonian(
*get_spin_mo_integrals_from_mole(mole, mf.mo_coeff)
)
vals, vecs = np.linalg.eigh(get_sparse_matrix(hamiltonian).toarray())
casci_gs_e = np.min(vals)
ccsd = cc.CCSD(mf).run(verbose=0)
print("The exact ground state energy is:", casci_gs_e)
print("The coupled cluster singles and doubles energy is:", ccsd.e_tot)
The exact ground state energy is: -1.1011503302326187
The coupled cluster singles and doubles energy is: -1.1011503302444787
The coupled cluster energy is almost identical to the exact ground state energy. This is not surprising for a small problem like , but in general we expect there to be some error in the CCSD result.
TrotterUCCSD
In QURI Parts the TrotterUCCSD ansatz is available. It is a quantum circuit version of the CCSD state which is generated by applying an excitation operator to a reference state (usually the Hartree-Fock state). The singles and doubles cluster operators
are used to generate the excitation operator
in CCSD. The RHS is an approximation because may include higher order terms representing triple or higher excitations, which are not accounted for in CCSD. The ground state energy in coupled cluster theory is then
The excitation operator generates a set of states from a set of excited reference states, which must be orthogonal. This condition taken together with the above equation results in a set of non-linear algebraic equations, the coupled cluster equations, which are solved in order to obtain tensors and so as to minimize the ground state energy.
The Unitary coupled cluster (UCC) ansatz constructs unitary operators by taking a modified approach using anti-Hermitian cluster operators, constructed from the cluster operators from CC theory. Generically, the UCC energy is then obtained as
and may again be truncated to include only singles and doubles if needed. The excitation operator can be generated through a Trotter decomposition, which can be realized as a quantum circuit ansatz. That is the TrotterUCCSD ansatz, which we will use in the following.
First we need a function that can convert the one-body and two-body cluster operators into parameters for the TrotterUCCSD ansatz.
from typing import Sequence
import numpy.typing as npt
def ccsd_param_to_circuit_param(
uccsd: TrotterUCCSD,
n_electrons: int,
t1: npt.NDArray[np.complex128],
t2: npt.NDArray[np.complex128],
) -> Sequence[float]:
in_param_list = uccsd.param_mapping.in_params
param_list = []
for param in in_param_list:
name_split = param.name.split("_")
if name_split[0] == "s":
_, i_str, j_str = name_split
i, j = int(i_str), int(j_str) - n_electrons // 2
param_list.append(t1[i, j])
if name_split[0] == "d":
_, i_str, j_str, a_str, b_str = name_split
i, j, b, a = (
int(i_str),
int(j_str),
int(b_str) - n_electrons // 2,
int(a_str) - n_electrons // 2,
)
param_list.append(t2[i, j, a, b])
return param_list
We specify the numer of trotter steps and other metaparameters in the trotter UCCSD ansatz below
TROTTER_STEPS = 1
USE_SINGLES = True
REDUCE_PARAMETER = True
Then the following code will generate the ansatz prepared with parameters obtained using coupled cluster theory
uccsd = TrotterUCCSD(
mole.nao * 2,
mole.nelectron,
trotter_number=TROTTER_STEPS,
use_singles=USE_SINGLES,
singlet_excitation=REDUCE_PARAMETER,
)
param = ccsd_param_to_circuit_param(uccsd, mole.nelectron, ccsd.t1, ccsd.t2)
Let's check the energy obtained by the Trotterized UCCSD
from quri_parts.qulacs.estimator import (
create_qulacs_vector_concurrent_parametric_estimator,
)
from quri_parts.core.state import quantum_state, apply_circuit
hf_state = quantum_state(mole.nao * 2, bits=2**mole.nelectron - 1)
estimator = create_qulacs_vector_concurrent_parametric_estimator()
state = apply_circuit(uccsd, hf_state)
estimate = estimator(hamiltonian, state, [param])
print("The coupled cluster singles and doubles energy is:", ccsd.e_tot)
print(
"The Trotter unitary coupled cluster singles and doubles energy is:",
estimate[0].value.real,
)
The coupled cluster singles and doubles energy is: -1.1011503302444787
The Trotter unitary coupled cluster singles and doubles energy is: -1.10114644059093
Here we see that the Trotter UCCSD energy is very close to the CCSD energy.
Noise
Now it's time to look at how actual quantum devices will behave. For this we introduce noise.
We will use QURI VM for this. QURI VM allows us to define abstract virtual machines that can run QURI Parts circuits. We can specify device type with error rates as well as architecture details such as error correction.
The VM itself contains a noisy estimator based on Qulacs for the purposes of emulation. It can be called using the estimate method.
Now, let's define a partially error corrected STAR device. This is an error corrected device so we will set the error rate, but for shallow circuits the effective error rate should be very modest.
from quri_vm import VM
from quri_parts.backend.devices import star_device
from quri_parts.backend.units import TimeUnit, TimeValue
ERROR_RATE = 1e-4
star_vm = VM.from_device_prop(
star_device.generate_device_property(
qubit_count=4,
code_distance=7,
qec_cycle=TimeValue(1, TimeUnit.MICROSECOND),
physical_error_rate=ERROR_RATE
)
)
Let's try to get an estimate of the UCCSD energy using the above VM
hf_state = quantum_state(mole.nao * 2, bits=2**mole.nelectron - 1)
state = apply_circuit(uccsd, hf_state)
bound_state = state.bind_parameters(param)
estimate = star_vm.estimate(hamiltonian, bound_state)
print("The coupled cluster singles and doubles energy is:", ccsd.e_tot)
print(
"The noisy Trotter unitary coupled cluster singles and doubles energy is:",
estimate.value.real,
)
The coupled cluster singles and doubles energy is: -1.1011503302444787
The noisy Trotter unitary coupled cluster singles and doubles energy is: -1.100903247039786
The error is clearly high compared to previously, but it's not too bad
QSCI
We then run qsci. To run it we have to specify the number of shots, the Hamiltonian, approximate ground states to draw samples from and the number of states to pick out. QSCI is especially sensitive to the number of states picked out as this both determines the accuracy of the end result as well as the complexity of the classical diagonalization. has 2 electrons and in the STO-3g atomic orbital basis therefore 4 molecular orbitals, which each are represented by a qubit.
Since the logical value of each bit represents its occupancy, the number of states of interest is limited to those with a total occupation of . For this reason, even though there are 16 basis states available in the quantum register, only 6 of them are relevant. Let us see how far we can get with only 4 of those 6 states.
BASIS_STATES = 4
TOTAL_SHOTS = 50000