VQE
In Divi, we offer two different VQE modes. The first one is a standard single-instance ground-state energy estimation, and the latter is the hyperparameter sweep mode. We will provide examples to demonstrate modes in this section.
Ansätze
An Ansatz must be specified for any VQE application, and Divi offers a small suite of native Ansätze as part of its batteries-included philosophy, which includes:
HartreeFockAnsatz.UCCSDAnsatz: A simple wrapper around pennylane’s UCCSD ansatz for chemistry applications.QAOAAnsatz: A simple wrapper around pennylane’s QAOAEmbedding operator, where thefeaturesargument is always set to[].GenericLayerAnsatz: A generic entangling ansatz. Users have the ability to pass a list of parametric single-qubit gate classes from pennylane that get applied to all qubits, before getting entangled through either the CNOT or the CZ gates, according to the chosen entangling layout. The entangling layout can either be one oflinear,circular,brick, orall-to-all, or simply a list of tuples of qubit pairs a user wishes to entangle. For example, the following would generate a layer ofRYs followed by a layer ofRXs, before a layer of entangling CZ gates.GenericLayerAnsatz(gate_sequence=[qml.RY, qml.RX], entangler=qml.CZ, entangling_layout="brick")
One can easily implement their own Ansatz that would be immediately compatible with Divi’s execution routine by inheriting the abstract Ansatz class and implementing two main methods, as seen in the class definition below. It should be noted that the build function should contain pennylane quantum operations for it to work properly. Refer to the definition of the other ansätze in our repo whenever in doubt.
class Ansatz(ABC):
"""Abstract base class for all VQE ansaetze."""
@property
def name(self) -> str:
"""Returns the human-readable name of the ansatz."""
return self.__class__.__name__
@staticmethod
@abstractmethod
def n_params_per_layer(n_qubits: int, **kwargs) -> int:
"""Returns the number of parameters required by the ansatz for one layer."""
raise NotImplementedError
@abstractmethod
def build(self, params, n_qubits: int, n_layers: int, **kwargs):
"""
Builds the ansatz circuit.
Args:
params (array): The parameters (weights) for the ansatz.
n_qubits (int): The number of qubits.
n_layers (int): The number of layers.
**kwargs: Additional arguments like n_electrons for chemistry ansaetze.
"""
raise NotImplementedErrorVanilla VQE
For our VQE implementation, we integrate tightly with PennyLane’s qchem module and their Hamiltonian objects. As such, the VQE constructor accepts either a Molecule object, out of which the molecular Hamiltonian is generated, or the Hamiltonian itself.
Apart from the molecular information, the constructor also takes as input an ansatz, which can be selected from the available ansatze in the VQEAnsatz class, as well as the number of ansatz layers, the optimizer and the maximum number of optimization iterations.In the case where the input is a Hamiltonian, the number of electrons present in the given system must be provided when the chosen ansatz is UCCSD or Hartree-Fock.
An example of how to initialize a VQE object with a molecule is shown below:
import numpy as np
import pennylane as qml
from divi.backends import ParallelSimulator
from divi.qprog import VQE, HartreeFockAnsatz
from divi.qprog.optimizers import ScipyMethod, ScipyOptimizer
h2_mol = qml.qchem.Molecule(
symbols=["H", "H"], coordinates=np.array([(0, 0, 0), (0, 0, 0.5)])
)
vqe_problem = VQE(
molecule=h2_mol,
ansatz=HartreeFockAnsatz(),
n_layers=1,
optimizer=ScipyOptimizer(method=ScipyMethod.L_BFGS_B),
max_iterations=3,
backend=ParallelSimulator(),
)
vqe_problem.run()
energies = vqe_problem.losses[-1]
print(f"Minimum Energy Achieved: {min(energies.values()):.4f}")
print(f"Total circuits: {vqe_problem.total_circuit_count}")In the case of a Hamiltonian input, the input would be passed to the constructor as follows:
ham, _ = qml.qchem.molecular_hamiltonian(h2_mol)
vqe_problem = VQE(
hamiltonian=ham,
n_electrons=h2_mol.n_electrons,
### Remaining inputs remain the same ###
)In the example above, we attempt to compute the ground state energy of a hydrogen molecule (H₂). To extract the energy at the
end of the optimization step, we simply access the last item of the losses class variable, which stores the
losses of each iteration in the form of a dictionary mapping a parameter’s ID to its actual values.
For L-BFGS-B, an iteration uses one set of parameters, and so the min(energies.values())
bit you see in the example is a bit redundant. If we were to use Monte-Carlo sampling, we would have as many losses
as the sample points, and so the use of the min function becomes more salient.
VQE Hyperparameter Sweep
By sweeping over physical parameters like bond length and varying the ansatz, this mode enables large-scale quantum chemistry simulations — efficiently distributing the workload across cloud or hybrid backends.
This mode is particularly useful for the study molecular behavior and reaction dynamics. It also allows one to compare ansatz performance and optimizer robustness. All through a single class!
Configuring the Molecular Transformations
Divi uses Z-matrices to correctly and accurately modify molecules according to the users needs. These modifications can be declared and configured using the MoleculeTransformer class, which takes as input the base molecule onto which the transformations are applied. Additionally, these arguments are used to define the specifics of the modifications:
-
atom_connectivity: The connectivity structure of the molecule, provided as a list of tuples of indices of the atoms that have a bond between them. When not provided, the molecule would be assumed to have a chain structure (i.e. the connectivity would look like[(0, 1), (1, 2), ...]). -
bonds_to_transform: A subset of the bonds listed inatom_connectivityto be modified. If this argument is not provided, all bonds will be affected. -
bond_modifiers: A list of actual numeric changes to apply to the chosen bonds. This has two modes:scaleanddelta. If the provided list contains only strictly positive values,scalemode will be activated, where the values represent a multiplier to apply to the original bond length. Otherwise, thedeltamode is enabled, where the provided values act as additives to the original bond length. One can trivially provide1and0for thescaleanddeltamodes respectively to include the base molecule as an experiment. -
alignment_atoms: For debugging purposes, the output molecules can be aligned using Kabsch algorithm, where users provide a list of indices of reference atoms that act as the “spine” of the whole molecule. An example of such would be the carbon chain of an alkane group.
Code Example
The example below demonstrates how to use Divi’s VQEHyperparameterSweep class to
run a parallelized VQE simulation across multiple bond lengths and ansatz types for a hydrogen molecule (H₂).
import numpy as np
import pennylane as qml
from divi.backends import QoroService
from divi.qprog import (
HartreeFockAnsatz,
MoleculeTransformer,
UCCSDAnsatz,
VQEHyperparameterSweep,
)
from divi.qprog.optimizers import MonteCarloOptimizer
q_service = QoroService(QORO_API_KEY, shots=5000)
mol = qml.qchem.Molecule(
symbols=["H", "H"], coordinates=np.array([(0, 0, 0), (0, 0, 0.5)])
)
transformer = MoleculeTransformer(
base_molecule=mol, bond_modifiers=[-0.4, -0.25, 0, 0.25, 0.4]
)
optim = MonteCarloOptimizer(n_param_sets=10, n_best_sets=3)
vqe_problem = VQEHyperparameterSweep(
molecule_transformer=transformer,
ansatze=[HartreeFockAnsatz(), UCCSDAnsatz()],
max_iterations=1,
optimizer=optim,
backend=q_service,
)
vqe_problem.create_programs()
vqe_problem.run(blocking=True)
vqe_problem.aggregate_results()
print(f"Total circuits: {vqe_problem.total_circuit_count}")
print(f"Simulation time: {vqe_problem.total_run_time}")
vqe_problem.visualize_results()What’s Happening?
| Step | Description |
|---|---|
VQEHyperparameterSweep(...) |
Initializes a batch of VQE programs over a range of bond lengths and ansatz strategies. |
molecule_transformer=... |
The transformer declaring the changes to apply to the molecule. In this instance, we are contracting all bonds by -0.4, -0.25 Å and stretching them by 0.25 and 0.4 Å, in addition to the base molecule |
ansatze=[HartreeFockAnsatz(), UCCSDAnsatz()] |
Runs two different quantum circuit models for comparison. |
create_programs() |
Constructs all circuits for each (bond modifier, ansatz) pair. |
run(blocking=True) |
Executes all VQE circuits — possibly in parallel. Block the script until all programs finish executing. |
aggregate_results() |
Collects and merges the final energy values for plotting. |
visualize_results() |
Displays a graph of energy vs. bond length for each ansatz. |
Visualization
Divi comes built with visualization tools that allows the user to compare the approaches. The above example produces this plot for example. This is an ongoing effort, the goal is to provide dashboards for better visualization and a more in-depth comparison.

The result of running parallelized VQE
Why Parallelize VQE?
- VQE is an iterative algorithm requiring multiple circuit evaluations per step.
- Sweeping over bond lengths and ansatze creates hundreds of circuits.
- Parallelizing execution reduces total compute time and helps saturate available QPU/GPU/CPU resources.