Variational Quantum Deflation AlgorithmVQD
The Variational Quantum Deflation (VQD) algorithm is a variational minimization algorithm that sequentially finds excited states by minimizing an objective function (shown below) which penalizes overlapping states over several VQE experiments. [7] Using the orthogonality of eigenvectors of a hermitian matrix, we constrain the state of interest to be orthogonal to the previously found states.
In the above equation, \(\{\theta_i\}\) are the parameters of the known states, and \(\{\theta_k\}\) are the variational parameters of each excited state determined during the k-th VQD iteration. The \(\lambda_i\) parameter is the weight of the penalty corresponding to the overlap of the ith known state with the k-th excited state.
Before running a VQD algorithm, one must first run a VQE experiment to establish the electronic ground state such that the first excited state found during VQD can be constrained to be orthogonal to the ground state.
An example of how to run AlgorithmVQD
is shown below. Following the same steps as before, an initial VQE is run
to obtain the ground state (and re-using the space, state, qubit mapping, and hamiltonian of the previous VQE example).
from inquanto.express import get_system
from inquanto.ansatzes import FermionSpaceAnsatzkUpCCGSD
from inquanto.computables import ExpectationValue
from inquanto.mappings import QubitMappingJordanWigner
from inquanto.minimizers import MinimizerScipy
from inquanto.algorithms import AlgorithmVQE
from pytket.extensions.qiskit import AerStateBackend
from inquanto.protocols import SparseStatevectorProtocol
fermion_hamiltonian, fermion_fock_space, fermion_state = get_system("h2_sto3g.h5")
mapping = QubitMappingJordanWigner()
qubit_hamiltonian = mapping.operator_map(fermion_hamiltonian)
ansatz = FermionSpaceAnsatzkUpCCGSD(fermion_fock_space, fermion_state, k_input=2)
expectation_value = ExpectationValue(ansatz, qubit_hamiltonian)
minimizer = MinimizerScipy(method="L-BFGS-B")
vqe = (
AlgorithmVQE(
minimizer,
expectation_value,
initial_parameters=ansatz.state_symbols.construct_zeros(),
)
.build(
protocol_objective=SparseStatevectorProtocol(AerStateBackend())
)
.run()
)
# TIMER BLOCK-0 BEGINS AT 2024-07-19 14:21:22.616191
# TIMER BLOCK-0 ENDS - DURATION (s): 3.1075028 [0:00:03.107503]
We then insist that any excited state optimized during the VQD algorithm is orthogonal to the VQE ground state.
First we must create a deflationary ansatz (which defines the space in which we expand the excited states).
In this example we simply use the same ansatz as we used in the VQE experiment, and modify the symbols such that the
protocols
can distinguish between the wave functions for the ground and excited states. Similarly to the VQE
example, we also use the ExpectationValue
class for the energy of our trial state.
ansatz_2 = ansatz.subs("{}_2") #Generate a copy of the ansatz with new symbols.
expectation_value = ExpectationValue(ansatz_2, qubit_hamiltonian)
Now we are left with calculating the weight and the overlap, shown in the second term of Eq. (3).
We can define the weight arbitrarily. For this example we follow the recipe in the original paper, [7]
and use the expectation value of the Hamiltonian multiplied by -1. The overlap between the two ansatzes is defined as another Computable
object, using the OverlapSquared
class.
from inquanto.computables import OverlapSquared
weight_expression = ExpectationValue(ansatz_2, -1 * qubit_hamiltonian)
overlap_expression = OverlapSquared(ansatz, ansatz_2)
Finally we must instantiate the VQD object, build and run the algorithm.
Unlike the AlgorithmVQE
object, AlgorithmVQD
takes in a number of different objective expressions;
expectation value, overlap and weight, which define the energy function, penalty function and weight of the penalty, respectively.
Similarly to AlgorithmVQE
, we also provide some initial parameters.
Importantly, for every expression there is a corresponding protocol supplied to the
build()
method. In this case, for efficiency
we use the same instance of the state-vector protocol class.
from inquanto.algorithms import AlgorithmVQD
protocol = SparseStatevectorProtocol(AerStateBackend())
vqd = (
AlgorithmVQD(
expectation_value,
overlap_expression,
weight_expression,
minimizer,
ansatz_2.state_symbols.construct_random(seed=0),
vqe._final_value,
vqe._final_parameters,
3,
)
.build(
objective_protocol=protocol,
overlap_protocol=protocol,
weight_protocol=protocol,
)
.run()
)
print("state_energies:", vqd.final_values)
print("state_parameters:", vqd.final_parameters)
# TIMER BLOCK-1 BEGINS AT 2024-07-19 14:21:25.749211
# TIMER BLOCK-1 ENDS - DURATION (s): 9.8300413 [0:00:09.830041]
# TIMER BLOCK-2 BEGINS AT 2024-07-19 14:21:35.579355
# TIMER BLOCK-2 ENDS - DURATION (s): 32.1604717 [0:00:32.160472]
# TIMER BLOCK-3 BEGINS AT 2024-07-19 14:22:07.740011
# TIMER BLOCK-3 ENDS - DURATION (s): 29.0156718 [0:00:29.015672]
state_energies: [-1.1368465754720403, -0.4951737702568078, -0.13583641112894052, 0.5515572309172028]
state_parameters: [SymbolDict({gd0k0: -0.05361669853564202, gd0k1: -0.05361671023590805, gs0k0: 0.0, gs0k1: 3.2583709632091666e-09, gs1k0: 0.0, gs1k1: 0.0}), SymbolDict({gd0k0_2: 1.1747769552095249, gd0k1_2: -1.3965780196731665, gs0k0_2: 0.5697167739358567, gs0k1_2: -0.09512095014865696, gs1k0_2: -0.5697169485125216, gs1k1_2: -0.9683784638588371}), SymbolDict({gd0k0_2: 1.1453236280226993, gd0k1_2: -1.3965782324329263, gs0k0_2: -1.0205477702015853, gs0k1_2: 0.6657162374751725, gs1k0_2: -1.0205492974567867, gs1k1_2: 0.22309281155224706}), SymbolDict({gd0k0_2: 1.1329762430809611, gd0k1_2: -1.3705336684850953, gs0k0_2: -1.1930167073994606, gs0k1_2: 0.24699109737515762, gs1k0_2: -1.6442914460657463, gs1k1_2: -0.48790180127562305})]
This algorithm is more expensive than VQE as it requires evaluation of more quantum circuits per step, due to the overlap and weight expressions, and the procedure must also be repeated as many times as the number of desired excited states.