PennyLane
Install
Install

Contents

  1. 1-RDM Operator
  2. The Molecular System
  3. The Electronic Structure and Hamiltonian
  4. The Quantum Circuit and Observables
  5. The Cost Function
  6. The Parameter Optimization Loop
  7. The VQE-LD optimization loop
  8. Conclusion
  9. References
  10. About the authors

Downloads

  • Download Python script
  • Download Notebook
  • View on GitHub
  1. Demos/
  2. Algorithms/
  3. VQE-LD

VQE-LD

Erico Souza Teixeira

Erico Souza Teixeira

Amanda Marques de Lima

Amanda Marques de Lima

Published: April 26, 2026. Last updated: April 26, 2026.

The variational quantum eigensolver (VQE) is a relevant method for simulating molecular systems on near-term quantum computers. While its primary application is the estimation of ground-state energies, VQE also produces the one-particle reduced density matrix (1-RDM), from which other relevant molecular properties can be obtained. The accuracy of these properties depends on the reliability and convergence of the 1-RDM, which is not guaranteed by energy-only optimization.

Thus, a new algorithm is introduced: VQE-LD (VQE with Loss function associated with a reduced Density matrix) that modifies the cost function by adding to the energy a weighted term involving the RMSD of 1-RDM [1].

In this tutorial, you will learn how to implement the VQE-LD algorithm. As an illustrative example, we use it to find the ground state of the \(\mathrm{CH}_5^+\) molecule. First, we define a set of auxiliary functions to constructs the operator needed to compute the 1-RDM. Then, we set up the molecular system using PySCF to obtain the Hartree-Fock reference and PennyLane to calculate the Hamiltonian.

Next, we define the a quantum circuit using the GateFabric ansatz and the expectation value function to compute the energy. We also define the function to compute the 1-RDM from the expectation values of the one-body excitation operators. Finally, we implement the VQE-LD optimization loop, combining the gradients of the energy and the 1-RDM terms in the cost function.

Let’s get started setting up the environment!

import jax
import optax
import pennylane as qml
from pyscf import gto, scf, ci
import jax.numpy as jnp

jax.config.update("jax_platform_name", "cpu")
jax.config.update("jax_enable_x64", True)

# Constants
Ang2Bohr = 1.8897259886
CREATION = 0
ANNIHILATION = 1

1-RDM Operator

The 1-RDM is defined as

\[D_{pq} = \langle \Psi | \hat{a}_p^\dagger \hat{a}_q | \Psi \rangle,\]

where \(\hat{a}_p^\dagger\) and \(\hat{a}_q\) are the fermionic creation and annihilation operators. This matrix encodes the occupation of molecular orbitals through its diagonal elements and the quantum coherence between orbitals through its off-diagonal elements.

Experimentally, the 1-RDM is reconstructed from the expectation values of fermionic operators mapped to qubits via the Jordan-Wigner transformation (function pauli_string_fermion_operator).

Since the operator \(\hat{a}_p^\dagger \hat{a}_q\) is not Hermitian for \(p \neq q\), its expectation value may, in general, be complex. Furthermore, as only Hermitian operators correspond to physical observables, it cannot be directly measured on a quantum computer. To make these quantities measurable, the operator is decomposed into a sum of Hermitian operators: the symmetric \(\frac{1}{2}({a}_p^\dagger \hat{a}_q + {a}_q^\dagger \hat{a}_p)\) and antisymmetric \(\frac{1}{2}({a}_p^\dagger \hat{a}_q - {a}_q^\dagger \hat{a}_p)\) combinations, whose expectation values correspond to the real and imaginary parts of the 1-RDM, respectively. After the Jordan-Wigner transformation, these quantities can be expressed as combinations of Pauli strings (functions rdm1_real_operator and rdm1_imag_operator):

\[\begin{split}\mathrm{Re}[D_{pq}] &= \tfrac{1}{2}\,\langle \Psi(\theta) | \hat{a}_p^\dagger \hat{a}_q + \hat{a}_q^\dagger \hat{a}_p, | \Psi(\theta) \rangle \\ &= \frac{1}{4} \langle \Psi(\theta) | \left( X_p X_q + Y_p Y_q \right) \prod_{j=p+1}^{q-1} Z_j | \Psi(\theta) \rangle\end{split}\]
\[\begin{split}\mathrm{Im}[D_{pq}] &= \frac{1}{2i}\,\langle \Psi(\theta) | \hat{a}_p^\dagger \hat{a}_q - \hat{a}_q^\dagger \hat{a}_p | \Psi(\theta) \rangle \\ &= \frac{1}{4} \langle \Psi(\theta) | \left( X_p Y_q - Y_p X_q \right) \prod_{j=p+1}^{q-1} Z_j | \Psi(\theta) \rangle\end{split}\]

For the particular cases of the diagonal elements (\(p = q\)), the imaginary term vanishes and the real part reduces to the expectation value of the number operator, that can be expressed as combinations of Pauli strings after the Jordan-Wigner transformation:

\[D_{pp} = \langle \Psi(\theta) | \hat{a}_p^\dagger \hat{a}_p | \Psi(\theta) \rangle = \frac{1 - \langle \Psi(\theta) | Z_p | \Psi(\theta) \rangle}{2}\]

The full 1-RDM can be constructed by evaluating only the independent elements, which correspond to the aforementioned diagonal elements, and in the upper triangular part of the matrix (\(p \leq q\)) (function rdm1_observables):

\[D_{pq} = \mathrm{Re}[D_{pq}] + i\,\mathrm{Im}[D_{pq}], \qquad p \leq q,\]

and completing the matrix by Hermitian conjugation,

\[D_{qp} = D_{pq}^*, \qquad p < q.\]

This procedure avoids redundant calculations and explicitly preserves the Hermitian character of the 1-RDM.

def pauli_string_fermion_operator(p, op_type):
    """
    Map a fermionic creation (0) or annihilation (1) operator at orbital p
    to its equivalent Pauli string via Jordan-Wigner transform.

    Parameters:
        p (int): orbital index
        op_type (int): 0 for creation, 1 for annihilation

    Returns:
        qml.operation.Tensor: Pauli string operator
    """
    f = (2 * op_type - 1) * 1j  # +i for creation, -i for annihilation
    Q_p = 0.5 * (qml.PauliX(p) + f * qml.PauliY(p))

    for i in range(p):
        Q_p = qml.PauliZ(i) @ Q_p

    return Q_p


def pauli_string_excitation_operator(p, q):
    """
    Constructs the Pauli string representation of the fermionic excitation operator a_p† a_q
    using the Jordan-Wigner transformation.

    This operator corresponds to exciting an electron from orbital q to orbital p
    (i.e., annihilation at q followed by creation at p).

    Args:
        p (int): Index of the target orbital (creation operator a_p†).
        q (int): Index of the source orbital (annihilation operator a_q).

    Returns:
        qml.operation.Tensor: The corresponding Pauli string operator representing a_p† a_q.

    Notes:
        - This is a non-Hermitian operator representing a single excitation.
        - To obtain a Hermitian excitation generator (for variational ansätze), you would
          need to combine this operator with its Hermitian conjugate (a_q† a_p).
        - The mapping relies on the Jordan-Wigner transformation, implemented via the
          `pauli_string_fermion_operator` function for single fermionic operators.
    """
    return pauli_string_fermion_operator(p, CREATION) @ pauli_string_fermion_operator(
        q, ANNIHILATION
    )


def rdm1_real_operator(p, q):
    """
    Constructs the Hermitian operator O_real = 1/2 (a_p† a_q + a_q† a_p),
    whose expectation value yields the real part of the 1-RDM element.

    Args:
        p (int): Index of the first orbital.
        q (int): Index of the second orbital.

    Returns: The Hermitian qubit operator corresponding to 1/2 (a_p† a_q + a_q† a_p).
    """
    op = pauli_string_excitation_operator(p, q)
    op_dag = pauli_string_excitation_operator(q, p)
    O_real = 0.5 * (op + op_dag)
    return O_real


def rdm1_imag_operator(p, q):
    """
    Constructs the Hermitian operator O_imag = (1/2i)(a_p† a_q - a_q† a_p),
    whose expectation value yields the imaginary part of the 1-RDM element.

    Args:
        p (int): Index of the first orbital.
        q (int): Index of the second orbital.

    Returns: The Hermitian qubit operator corresponding to (1/2i)(a_p† a_q - a_q† a_p).
    """
    op = pauli_string_excitation_operator(p, q)
    op_dag = pauli_string_excitation_operator(q, p)
    O_imag = 0.5j * (op - op_dag)  # same as (1/(2i))(op - op_dag)
    return O_imag


def rdm1_observables(qubits_active):
    """
    Construct all the observables needed to compute the 1-RDM.
    Diagonal elements correspond to occupation numbers,
    while off-diagonal elements are separated into real and imaginary components.

    Args: Number of active qubits in the system.

    Returns:
        observables (dict): Dictionary mapping keys to PennyLane observables.
        ordered_keys (list): Sorted list of keys for consistent ordering.
    """
    observables = {}
    for p in range(qubits_active):
        for q in range(p, qubits_active):
            observables[(p, q, "re")] = rdm1_real_operator(p, q)
            observables[(p, q, "im")] = rdm1_imag_operator(p, q)

    return observables, observables.keys()

The Molecular System

We begin by defining the molecular geometry of the system under study. In this example, we consider the \(\mathrm{CH}_5^+\) molecule. It is specified in Cartesian coordinates (in Angstrom). To interface with quantum chemistry backends such as PySCF and PennyLane, the coordinates are converted from Angstrom to atomic units (Bohr).

geom = """
     C  0.0000000  0.1525520  0.0000000
     H  1.1165590  0.3217700  0.0000000
     H -0.5550270 -1.0611280  0.0000000
     H  0.3813020 -1.1309260  0.0000000
     H -0.4714170  0.4774870  0.9592110
     H -0.4714170  0.4774870 -0.9592110
     """

geom_data = geom.split()
symbols = tuple(geom_data[4 * i] for i in range(len(geom_data) // 4))
coordinates = tuple(geom_data[4 * i + 1 : 4 * i + 4] for i in range(len(geom_data) // 4))
coordinates = jnp.array(coordinates, dtype=float).reshape((-1, 3)) * Ang2Bohr

The Electronic Structure and Hamiltonian

The next step is to formulate the electronic structure associated with the system. We begin by specifying the fundamental parameters: the total charge of the molecule, the spin multiplicity, the number of electrons, and the choice of basis set. ITo reduce the computational cost we use the minimal STO-3G basis and further restrict the problem by selecting an active space.

Using PennyLane’s quantum chemistry module, the molecular Hamiltonian is constructed in second quantization and then mapped to a qubit Hamiltonian via the Jordan–Wigner transformation. Finally, we prepare the Hartree–Fock reference state in the active space, which serves as the initial state for the variational quantum algorithm.

charge = 1
mult = 1
nelec = 10
basis = "STO-3G"
active_electrons = 2
active_orbitals = 2

# Define the Molecule in PennyLane
qml_mol = qml.qchem.Molecule(symbols, coordinates, charge=charge, mult=mult, basis_name=basis)

# Calculate the Hamiltonian
H_active, qubits_active = qml.qchem.molecular_hamiltonian(
    qml_mol, active_electrons=active_electrons, active_orbitals=active_orbitals
)

# Get the Hartree-Fock state
ref_active = qml.qchem.hf_state(active_electrons, qubits_active)

The Quantum Circuit and Observables

We now construct the variational quantum circuit based on a hardware-efficient ansatz (GateFabric), initialized with the Hartree–Fock reference state. In addition to the energy, our goal is to reconstruct the 1-RDM. This requires measuring a set of one-body operators, previously defined. For this reason, we define two quantum nodes (QNodes):

  • One for evaluating the expectation value of the Hamiltonian, yielding the energy.

  • Another for evaluating the expectation values of the 1-RDM observables, which are later assembled into the full matrix.

# Define the device from PennyLane
dev = qml.device("default.qubit", wires=range(qubits_active))


@qml.qnode(dev, interface="jax")
def circuit(weights):
    """
    Executes a variational quantum circuit using an ansatz and computes
    the expectation value of the active space Hamiltonian.

    Args:
        weights (array): Variational parameters for the ansatz.

    Returns:
        float: Expectation value ⟨H_active⟩, i.e., the estimated energy of the quantum state.
    """
    qml.GateFabric(weights, wires=range(qubits_active), init_state=ref_active, include_pi=True)
    return qml.expval(H_active)


param_shape = qml.GateFabric.shape(n_layers=10, n_wires=qubits_active)

# Set observables to 1-RDM
observables, keys = rdm1_observables(qubits_active)
for k in keys:
    observables[k] = observables[k].simplify()


# Expectation value of the Hermitian one-body excitation operators
@qml.qnode(dev, interface="jax")
def rdm1_expectation(params):
    """
    Executes the variational quantum circuit and computes the expectation values of the
    one-body excitation operators needed to reconstruct the real part of the
    1-RDM for all orbital pairs (p, q).

    Args:
        params (array): Array of trainable parameters for the GateFabric ansatz.

    Returns:
        list[float]: A flat list of real-valued expectation values ⟨O_real⟩ for every
                     pair (p, q), corresponding to the real part of the 1-RDM.

    Key Steps:
        1. Prepares the quantum state using the GateFabric ansatz.
        2. For every pair of orbitals (p, q), construct the O_real and O_imag operators.
        3. Measures the expectation value.
        4. Returns all expectation values as a list.
    """
    qml.GateFabric(params, wires=range(qubits_active), init_state=ref_active, include_pi=True)
    return [qml.expval(observables[k]) for k in keys]


def compute_rdm1_active(params):
    """
    Computes the expectation values of the 1-RDM operators and organizes the results
    into a square matrix of shape (qubits_active × qubits_active).

    Args:
        params (array): Variational parameters for the quantum circuit.

    Returns:
        jnp.ndarray: The reconstructed real part of the one-electron 1-RDM
                     in the active space, as a (qubits_active, qubits_active) matrix.
    """
    values = rdm1_expectation(params)
    n = qubits_active
    rdm = jnp.zeros((n, n), dtype=complex)

    for val, key in zip(values, keys):
        p, q, part = key
        if part == "re":
            rdm = rdm.at[p, q].add(val)
            if p != q:
                rdm = rdm.at[q, p].add(val)
        else:  # Imaginary component
            rdm = rdm.at[p, q].add(1j * val)
            if p != q:
                rdm = rdm.at[q, p].add(-1j * val)

    return rdm

The Cost Function

The cost function in the VQE-LD approach is defined as a combination of two terms:

  1. the energy expectation value, and

  2. a penalty term that enforces consistency and convergence of the 1-RDM across optimization steps.

The 1-RDM term is constructed as the root-mean-square deviation (RMSD) between the 1-RDM of consecutive iterations. This promotes smooth convergence of the density matrix and mitigates situations where energy convergence alone leads to inaccurate electronic properties.

def term_energy(params):
    return jnp.real(circuit(params))


def term_rdm1(params, rdm1_prev):
    rdm1 = compute_rdm1_active(params)
    diff = rdm1 - rdm1_prev
    return jnp.sqrt(jnp.mean(jnp.abs(diff) ** 2))


grad_energy_fn = jax.jit(jax.grad(term_energy))
grad_rdm1_fn = jax.jit(lambda p, r_prev: jax.grad(term_rdm1, argnums=0)(p, r_prev))

The Parameter Optimization Loop

We define the parameters and hyperparameters that control the training process. This includes the total number of optimization steps, convergence thresholds for both the energy and the 1-RDM, and the initial values of the variational parameters. The parameters are typically initialized to zero, corresponding to the Hartree–Fock reference state when using structured ansätze.

We also choose a classical optimizer, in this case, stochastic gradient descent (SGD), which updates the circuit parameters based on the combined gradients of the energy and the 1-RDM term.

To monitor convergence, we keep track of the energy and the 1-RDM throughout the optimization. We also store the 1-RDM from the previous iteration. This allows us to evaluate both the energy difference and the variation in the 1-RDM between successive steps. Additionally, a scaling factor is introduced to control the relative influence of the 1-RDM term.

# Iterations
total_iterations = 50000

# Threshold
Etol = 1e-6  # for energy
RDM1_tol = 1e-6  # for rmd1

# Initial value of variational parameters
params = jnp.zeros(param_shape)

# Classical optimizer
opt = optax.sgd(0.4)

# Keep track of the energies
energies = [circuit(params)]

# Set parameters
opt_state = opt.init(params)
current_opt = opt
rdm1_active_prev = compute_rdm1_active(jnp.zeros(param_shape)).copy()
rdm1_active = compute_rdm1_active(params)
rate = 0.6

The VQE-LD optimization loop

We now implement the core optimization loop of the VQE-LD algorithm. The procedure can be summarized as follows:

  1. Compute the gradient of the energy with respect to the circuit parameters.

  2. Compute the gradient of the 1-RDM penalty term, which depends on the deviation from the previous iteration.

  3. Estimate the norm of each gradient and define a dynamic weight that balances their relative contributions.

  4. Combine the gradients into a single update direction.

  5. Apply the optimizer step to update the variational parameters.

The adaptive weighting plays a central role in the VQE-LD method. By scaling the 1-RDM gradient according to the ratio of gradient norms, the algorithm ensures that both terms contribute meaningfully throughout the optimization, avoiding regimes where either energy minimization or density consistency dominates.

After each update, we evaluate the new energy and reconstruct the 1-RDM. Convergence is assessed using two criteria:

  • The change in energy between successive iterations.

  • The root-mean-square deviation (RMSD) between consecutive 1-RDMs.

The optimization terminates only when both quantities fall below predefined thresholds, ensuring not only energetic convergence but also stability of the underlying electronic structure.

f = open("log.txt", "a", encoding="utf-8")

for n in range(1, total_iterations + 1):
    # Gradient of the energy
    grad_E = grad_energy_fn(params)

    # Gradient of the RDM1 -> Only apply the RDM1 penalty from the second iteration
    # If it's the first iteration, grad_R is a zero vector
    if n > 1:
        grad_R = grad_rdm1_fn(params, rdm1_active_prev)
    else:
        grad_R = jnp.zeros_like(params)

    # Calculate the norm of each gradient
    norm_grad_E = jnp.linalg.norm(grad_E)
    norm_grad_R = jnp.linalg.norm(grad_R)

    # Define automatic weight for RDM1.
    # The goal is to balance the magnitude of grad_R with grad_E
    if norm_grad_R == 0:
        w_rdm1_auto = (norm_grad_E) * rate
    else:
        w_rdm1_auto = (norm_grad_E / (norm_grad_R)) * rate

    # Combine gradients
    grads = grad_E + w_rdm1_auto * grad_R

    # Update circuit parameters
    updates, opt_state = current_opt.update(grads, opt_state)
    params = optax.apply_updates(params, updates)

    # Compute updated energy and RDM1
    energy = circuit(params)
    rdm1_trial = compute_rdm1_active(params)  # new RDM1

    # Update references
    energies.append(energy)
    rdm1_active_prev = rdm1_active.copy()
    rdm1_active = rdm1_trial.copy()

    # Difference in RDM1
    diff = rdm1_trial - rdm1_active_prev
    rdm1_diff = jnp.sqrt(jnp.mean(diff**2))

    grad_norm = jnp.linalg.norm(grads)
    print(f"Iter {n} | Energy: {energy:.9f} Ha | ΔRDM1: {rdm1_diff:.6e} | ‖∇C‖: {grad_norm:.3e}")
    print(
        f"Iter {n} | Energy: {energy:.9f} Ha | ΔRDM1: {rdm1_diff:.6e} | ‖∇C‖: {grad_norm:.3e}",
        file=f,
    )

    # Convergence criterion
    if len(energies) > 1:
        delta_E = jnp.abs(energies[-1] - energies[-2])
        # If both energy and RDM1 converge, stop the loop
        if delta_E < Etol and rdm1_diff < RDM1_tol:
            print("Complete convergence in energy and RDM1.")
            break
f.close()

print(f"Iter {n} | Energy: {energy:.9f} Ha | ΔRDM1: {rdm1_diff:.6e} | ‖∇C‖: {grad_norm:.3e}")
Iter 1 | Energy: -39.916864274 Ha | ΔRDM1: 9.273605e-04+0.000000e+00j | ‖∇C‖: 6.812e-02
/home/runner/work/demos/demos/.venv-build/lib/python3.11/site-packages/jax/_src/lax/lax.py:5422: ComplexWarning: Casting complex values to real discards the imaginary part
  x_bar = _convert_element_type(x_bar, x.aval.dtype, x.aval.weak_type)
Iter 2 | Energy: -39.912028839 Ha | ΔRDM1: 3.445493e-04+0.000000e+00j | ‖∇C‖: 1.479e-01
Iter 3 | Energy: -39.875651332 Ha | ΔRDM1: 2.005428e-02+0.000000e+00j | ‖∇C‖: 4.087e-01
Iter 4 | Energy: -39.637623826 Ha | ΔRDM1: 8.972576e-02+0.000000e+00j | ‖∇C‖: 1.105e+00
Iter 5 | Energy: -38.984747809 Ha | ΔRDM1: 2.919138e-01+0.000000e+00j | ‖∇C‖: 2.538e+00
Iter 6 | Energy: -39.706600490 Ha | ΔRDM1: 3.203775e-01+0.000000e+00j | ‖∇C‖: 2.424e+00
Iter 7 | Energy: -39.910921009 Ha | ΔRDM1: 8.096522e-02+0.000000e+00j | ‖∇C‖: 5.716e-01
Iter 8 | Energy: -39.917565076 Ha | ΔRDM1: 1.528862e-03+0.000000e+00j | ‖∇C‖: 1.118e-01
Iter 9 | Energy: -39.917401981 Ha | ΔRDM1: 3.936635e-04+0.000000e+00j | ‖∇C‖: 2.713e-02
Iter 10 | Energy: -39.916724551 Ha | ΔRDM1: 8.835150e-03+0.000000e+00j | ‖∇C‖: 6.084e-02
Iter 11 | Energy: -39.913452488 Ha | ΔRDM1: 3.625034e-02+0.000000e+00j | ‖∇C‖: 1.423e-01
Iter 12 | Energy: -39.898640087 Ha | ΔRDM1: 9.205745e-02+0.000000e+00j | ‖∇C‖: 3.111e-01
Iter 13 | Energy: -39.824099596 Ha | ΔRDM1: 2.072009e-01+0.000000e+00j | ‖∇C‖: 7.209e-01
Iter 14 | Energy: -39.646916648 Ha | ΔRDM1: 3.642005e-01+0.000000e+00j | ‖∇C‖: 1.393e+00
Iter 15 | Energy: -39.448431779 Ha | ΔRDM1: 4.483204e-01+0.000000e+00j | ‖∇C‖: 2.311e+00
Iter 16 | Energy: -39.423256906 Ha | ΔRDM1: 2.387211e-01+0.000000e+00j | ‖∇C‖: 2.251e+00
Iter 17 | Energy: -39.579860536 Ha | ΔRDM1: 1.464396e-01+0.000000e+00j | ‖∇C‖: 5.086e-01
Iter 18 | Energy: -39.660450705 Ha | ΔRDM1: 1.160431e-01+0.000000e+00j | ‖∇C‖: 7.414e-01
Iter 19 | Energy: -39.346394756 Ha | ΔRDM1: 2.087747e-01+0.000000e+00j | ‖∇C‖: 1.493e+00
Iter 20 | Energy: -39.635141048 Ha | ΔRDM1: 4.387033e-01+0.000000e+00j | ‖∇C‖: 2.986e+00
Iter 21 | Energy: -39.678043173 Ha | ΔRDM1: 2.137179e-01+0.000000e+00j | ‖∇C‖: 1.389e+00
Iter 22 | Energy: -39.622179228 Ha | ΔRDM1: 9.897597e-02+0.000000e+00j | ‖∇C‖: 1.609e+00
Iter 23 | Energy: -39.917074318 Ha | ΔRDM1: 1.598093e-01+0.000000e+00j | ‖∇C‖: 1.307e+00
Iter 24 | Energy: -39.916099397 Ha | ΔRDM1: 1.234058e-02+0.000000e+00j | ‖∇C‖: 1.020e-01
Iter 25 | Energy: -39.913552514 Ha | ΔRDM1: 2.100348e-02+0.000000e+00j | ‖∇C‖: 1.708e-01
Iter 26 | Energy: -39.906106690 Ha | ΔRDM1: 3.478962e-02+0.000000e+00j | ‖∇C‖: 2.871e-01
Iter 27 | Energy: -39.887744622 Ha | ΔRDM1: 5.517728e-02+0.000000e+00j | ‖∇C‖: 4.660e-01
Iter 28 | Energy: -39.834967018 Ha | ΔRDM1: 8.740064e-02+0.000000e+00j | ‖∇C‖: 7.856e-01
Iter 29 | Energy: -39.739818325 Ha | ΔRDM1: 1.086510e-01+0.000000e+00j | ‖∇C‖: 1.180e+00
Iter 30 | Energy: -39.494413980 Ha | ΔRDM1: 1.413574e-01+0.000000e+00j | ‖∇C‖: 1.841e+00
Iter 31 | Energy: -39.033925540 Ha | ΔRDM1: 1.992597e-01+0.000000e+00j | ‖∇C‖: 2.755e+00
Iter 32 | Energy: -39.764703370 Ha | ΔRDM1: 3.392932e-01+0.000000e+00j | ‖∇C‖: 2.373e+00
Iter 33 | Energy: -39.885346370 Ha | ΔRDM1: 6.796398e-02+0.000000e+00j | ‖∇C‖: 4.539e-01
Iter 34 | Energy: -39.901008901 Ha | ΔRDM1: 1.160243e-02+0.000000e+00j | ‖∇C‖: 2.474e-01
Iter 35 | Energy: -39.906274321 Ha | ΔRDM1: 8.699627e-03+0.000000e+00j | ‖∇C‖: 1.237e-01
Iter 36 | Energy: -39.910497107 Ha | ΔRDM1: 1.240905e-02+0.000000e+00j | ‖∇C‖: 9.251e-02
Iter 37 | Energy: -39.912503428 Ha | ΔRDM1: 5.046468e-03+0.000000e+00j | ‖∇C‖: 8.679e-02
Iter 38 | Energy: -39.914293414 Ha | ΔRDM1: 8.154760e-03+0.000000e+00j | ‖∇C‖: 6.891e-02
Iter 39 | Energy: -39.915000353 Ha | ΔRDM1: 2.205020e-03+0.000000e+00j | ‖∇C‖: 6.713e-02
Iter 40 | Energy: -39.915829229 Ha | ΔRDM1: 5.583309e-03+0.000000e+00j | ‖∇C‖: 5.693e-02
Iter 41 | Energy: -39.916034439 Ha | ΔRDM1: 6.183654e-04+0.000000e+00j | ‖∇C‖: 5.693e-02
Iter 42 | Energy: -39.916340818 Ha | ΔRDM1: 4.164030e-03+0.000000e+00j | ‖∇C‖: 5.732e-02
Iter 43 | Energy: -39.916141147 Ha | ΔRDM1: 2.349346e-03+0.000000e+00j | ‖∇C‖: 6.462e-02
Iter 44 | Energy: -39.915704315 Ha | ΔRDM1: 2.206499e-02+0.000000e+00j | ‖∇C‖: 1.431e-01
Iter 45 | Energy: -39.912131121 Ha | ΔRDM1: 2.454812e-02+0.000000e+00j | ‖∇C‖: 1.941e-01
Iter 46 | Energy: -39.904792072 Ha | ΔRDM1: 4.381540e-02+0.000000e+00j | ‖∇C‖: 3.201e-01
Iter 47 | Energy: -39.882536765 Ha | ΔRDM1: 6.572594e-02+0.000000e+00j | ‖∇C‖: 5.148e-01
Iter 48 | Energy: -39.843567370 Ha | ΔRDM1: 9.850431e-02+0.000000e+00j | ‖∇C‖: 7.804e-01
Iter 49 | Energy: -39.729890493 Ha | ΔRDM1: 1.298638e-01+0.000000e+00j | ‖∇C‖: 1.229e+00
Iter 50 | Energy: -39.603195960 Ha | ΔRDM1: 1.344628e-01+0.000000e+00j | ‖∇C‖: 1.676e+00
Iter 51 | Energy: -39.328467684 Ha | ΔRDM1: 1.516008e-01+0.000000e+00j | ‖∇C‖: 2.261e+00
Iter 52 | Energy: -39.118510474 Ha | ΔRDM1: 1.277427e-01+0.000000e+00j | ‖∇C‖: 2.965e+00
Iter 53 | Energy: -39.794667910 Ha | ΔRDM1: 3.506337e-01+0.000000e+00j | ‖∇C‖: 2.255e+00
Iter 54 | Energy: -39.893019431 Ha | ΔRDM1: 8.045454e-02+0.000000e+00j | ‖∇C‖: 5.045e-01
Iter 55 | Energy: -39.904355363 Ha | ΔRDM1: 8.474798e-03+0.000000e+00j | ‖∇C‖: 2.792e-01
Iter 56 | Energy: -39.908222606 Ha | ΔRDM1: 3.446492e-03+0.000000e+00j | ‖∇C‖: 1.350e-01
Iter 57 | Energy: -39.912112505 Ha | ΔRDM1: 1.224603e-02+0.000000e+00j | ‖∇C‖: 8.870e-02
Iter 58 | Energy: -39.914018720 Ha | ΔRDM1: 5.442898e-03+0.000000e+00j | ‖∇C‖: 7.397e-02
Iter 59 | Energy: -39.915528753 Ha | ΔRDM1: 7.760247e-03+0.000000e+00j | ‖∇C‖: 5.078e-02
Iter 60 | Energy: -39.916268652 Ha | ΔRDM1: 3.596621e-03+0.000000e+00j | ‖∇C‖: 4.304e-02
Iter 61 | Energy: -39.916831735 Ha | ΔRDM1: 4.762458e-03+0.000000e+00j | ‖∇C‖: 2.950e-02
Iter 62 | Energy: -39.917109824 Ha | ΔRDM1: 2.293120e-03+0.000000e+00j | ‖∇C‖: 2.516e-02
Iter 63 | Energy: -39.917315180 Ha | ΔRDM1: 2.876009e-03+0.000000e+00j | ‖∇C‖: 1.724e-02
Iter 64 | Energy: -39.917417599 Ha | ΔRDM1: 1.434875e-03+0.000000e+00j | ‖∇C‖: 1.472e-02
Iter 65 | Energy: -39.917491366 Ha | ΔRDM1: 1.719888e-03+0.000000e+00j | ‖∇C‖: 1.009e-02
Iter 66 | Energy: -39.917528524 Ha | ΔRDM1: 8.864545e-04+0.000000e+00j | ‖∇C‖: 8.601e-03
Iter 67 | Energy: -39.917554721 Ha | ΔRDM1: 1.021623e-03+0.000000e+00j | ‖∇C‖: 5.910e-03
Iter 68 | Energy: -39.917568042 Ha | ΔRDM1: 5.422959e-04+0.000000e+00j | ‖∇C‖: 5.021e-03
Iter 69 | Energy: -39.917577262 Ha | ΔRDM1: 6.038396e-04+0.000000e+00j | ‖∇C‖: 3.459e-03
Iter 70 | Energy: -39.917581991 Ha | ΔRDM1: 3.291325e-04+0.000000e+00j | ‖∇C‖: 2.928e-03
Iter 71 | Energy: -39.917585212 Ha | ΔRDM1: 3.555314e-04+0.000000e+00j | ‖∇C‖: 2.023e-03
Iter 72 | Energy: -39.917586877 Ha | ΔRDM1: 1.984538e-04+0.000000e+00j | ‖∇C‖: 1.705e-03
Iter 73 | Energy: -39.917587995 Ha | ΔRDM1: 2.086867e-04+0.000000e+00j | ‖∇C‖: 1.182e-03
Iter 74 | Energy: -39.917588578 Ha | ΔRDM1: 1.190067e-04+0.000000e+00j | ‖∇C‖: 9.920e-04
Iter 75 | Energy: -39.917588964 Ha | ΔRDM1: 1.221847e-04+0.000000e+00j | ‖∇C‖: 6.900e-04
Iter 76 | Energy: -39.917589166 Ha | ΔRDM1: 7.103677e-05+0.000000e+00j | ‖∇C‖: 5.766e-04
Iter 77 | Energy: -39.917589299 Ha | ΔRDM1: 7.138889e-05+0.000000e+00j | ‖∇C‖: 4.024e-04
Iter 78 | Energy: -39.917589369 Ha | ΔRDM1: 4.223773e-05+0.000000e+00j | ‖∇C‖: 3.348e-04
Iter 79 | Energy: -39.917589415 Ha | ΔRDM1: 4.163731e-05+0.000000e+00j | ‖∇C‖: 2.345e-04
Iter 80 | Energy: -39.917589439 Ha | ΔRDM1: 2.503077e-05+0.000000e+00j | ‖∇C‖: 1.943e-04
Iter 81 | Energy: -39.917589454 Ha | ΔRDM1: 2.424885e-05+0.000000e+00j | ‖∇C‖: 1.366e-04
Iter 82 | Energy: -39.917589463 Ha | ΔRDM1: 1.479143e-05+0.000000e+00j | ‖∇C‖: 1.127e-04
Iter 83 | Energy: -39.917589468 Ha | ΔRDM1: 1.410433e-05+0.000000e+00j | ‖∇C‖: 7.949e-05
Iter 84 | Energy: -39.917589471 Ha | ΔRDM1: 8.719256e-06+0.000000e+00j | ‖∇C‖: 6.532e-05
Iter 85 | Energy: -39.917589473 Ha | ΔRDM1: 8.194953e-06+0.000000e+00j | ‖∇C‖: 4.623e-05
Iter 86 | Energy: -39.917589474 Ha | ΔRDM1: 5.128905e-06+0.000000e+00j | ‖∇C‖: 3.784e-05
Iter 87 | Energy: -39.917589474 Ha | ΔRDM1: 4.757080e-06+0.000000e+00j | ‖∇C‖: 2.687e-05
Iter 88 | Energy: -39.917589475 Ha | ΔRDM1: 3.011377e-06+0.000000e+00j | ‖∇C‖: 2.192e-05
Iter 89 | Energy: -39.917589475 Ha | ΔRDM1: 2.759248e-06+0.000000e+00j | ‖∇C‖: 1.561e-05
Iter 90 | Energy: -39.917589475 Ha | ΔRDM1: 1.765231e-06+0.000000e+00j | ‖∇C‖: 1.269e-05
Iter 91 | Energy: -39.917589475 Ha | ΔRDM1: 1.599357e-06+0.000000e+00j | ‖∇C‖: 9.064e-06
Iter 92 | Energy: -39.917589475 Ha | ΔRDM1: 1.033284e-06+0.000000e+00j | ‖∇C‖: 7.342e-06
Iter 93 | Energy: -39.917589475 Ha | ΔRDM1: 9.264992e-07+0.000000e+00j | ‖∇C‖: 5.261e-06
Complete convergence in energy and RDM1.
Iter 93 | Energy: -39.917589475 Ha | ΔRDM1: 9.264992e-07+0.000000e+00j | ‖∇C‖: 5.261e-06

In this case, the VQE-LD algorithm converges to an energy of −39.917589475 Ha, while the CASCI calculation in the (4,4)-active space yields −39.91925976 Ha. Therefore, our approach achieves chemical accuracy with respect to this high-quality reference method. In contrast, a standard VQE optimization results in an error of 2.61 x \(10^{-1}\) Ha relative to the CASCI value, which is well above the chemical accuracy threshold of 1 kcal/mol (1.6 x \(10^{-3}\) Ha).

Conclusion

In this tutorial, we presented the VQE-LD algorithm, an extension of the Variational Quantum Eigensolver that incorporates information from the one-particle reduced density matrix (1-RDM) directly into the optimization process.

By augmenting the standard energy-based cost function with a term that enforces consistency of the 1-RDM, the method promotes not only convergence in energy but also stability in the underlying electronic structure. This is particularly important for accurately computing molecular properties that are more sensitive to fluctuations of the electronic density.

A key feature of VQE-LD is the use of an adaptive weighting strategy based on gradient norms, which dynamically balances the contributions of the energy and 1-RDM terms throughout the optimization. This avoids the need for manual hyperparameter tuning and leads to a more robust optimization landscape.

The framework presented here is general and can be extended in several directions, including larger active spaces, more expressive ansätze, and higher-order reduced density matrices. It also opens the door to improving estimation of observables beyond total energy, which is a central goal in quantum computational chemistry.

Overall, VQE-LD provides a promising route toward enhancing the reliability of near-term quantum simulations for molecular systems.

References

[1]

Amanda Marques de Lima, Erico Souza Teixeira, Eivson Darlivam Rodrigues de Aguiar Silva, Ricardo Luiz Longo, Improving Energy and Molecular Properties by Convergence of the One-Particle Reduced Density Matrix in Variational Quantum Eigensolvers (VQE). Journal of Computational Chemistry 47 (2026). https://doi.org/10.1002/jcc.70289

About the authors

Erico Souza Teixeira
Erico Souza Teixeira

Erico Souza Teixeira

Quantum Computing Researcher

Amanda Marques de Lima
Amanda Marques de Lima

Amanda Marques de Lima

Chemistry PhD student

Total running time of the script: (0 minutes 49.822 seconds)

Share demo

Ask a question on the forum
PennyLane

PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Built by researchers, for research. Created with ❤️ by Xanadu.

Research

  • Research
  • Performance
  • Hardware & Simulators
  • Demos
  • Compilation Hub
  • Quantum Datasets

Education

  • Teach
  • Learn
  • Codebook
  • Coding Challenges
  • Videos
  • Glossary

Software

  • Install PennyLane
  • Features
  • Documentation
  • Catalyst Compilation Docs
  • Development Guide
  • API
  • GitHub
Stay updated with our newsletter

© Copyright 2026 | Xanadu | All rights reserved

TensorFlow, the TensorFlow logo and any related marks are trademarks of Google Inc.

Privacy Policy|Terms of Service|Cookie Policy|Code of Conduct