Code
qcr:2606.73084.1

Variational Quantum Time Evolution

This Qiskit tutorial demonstrates Variational Quantum Time Evolution (VarQTE), a family of variational algorithms for simulating both real-time and imaginary-time evolution of quantum systems on near-term hardware. Time evolution under a Hamiltonian governs quantum dynamics (real time) and, in imaginary time, drives a system toward its ground state, making these methods useful for both simulation and ground-state preparation. Rather than compiling the evolution into deep Trotterized circuits, VarQTE keeps a fixed parameterized ansatz and derives equations of motion for its parameters, using McLachlan's variational principle, that reproduce the true evolution step by step; integrating these ordinary differential equations advances the state while the circuit depth stays constant. The tutorial shows how to use Qiskit's real-time (VarQRTE) and imaginary-time (VarQITE) implementations: specifying a Hamiltonian and ansatz, computing the required quantum geometric tensor and gradients via Qiskit's gradient framework and Estimator primitive, evolving the parameters over time, and tracking observables and energies. It also touches on error bounds for the variational evolution. It is a thorough, hands-on guide to variational time evolution for both dynamics and ground-state problems in Qiskit.
Quantum Simulation
Qubit
Circuit-based
Uploaded 2 days ago
13
Views
GitHub183
Citing this entry? Use this QCR ID
Uploaded by
QL
QCR Librarian

Overview

qiskit-community/qiskit-algorithms
18380
In [ ]:
# --- Setup cell added by QCR (not part of the original tutorial) ---
# Source: qiskit-community/qiskit-algorithms @ 0.4.0, Apache License 2.0.
# Installs the example's dependencies. If a later cell still reports a missing
# package, restart the runtime/kernel and run again from the top.
%pip install -q qiskit-algorithms==0.4.0 qiskit-aer matplotlib pylatexenc qiskit-algorithms

Variational Quantum Time Evolution

This notebook demonstrates how to use the Variational Quantum Time Evolution (VarQTE) algorithm for computing the time evolving state under a given Hamiltonian. Specifically, it introduces variational quantum imaginary and real time evolution based on McLachlan's variational principle, and shows how this can be leveraged using the time_evolvers.variational sub-module.

Introduction

The time evolution of a quantum state is governed by the Schrödinger equation (here with )

or without the factor for imaginary time dynamics.

In VarQTE, the time evolution of the state is replaced by the evolution of parameters in a variational ansatz (Yuan et al. Quantum 3, 191). Using the McLachlan variational principle, the algorithm updates the parameters by minimizing the distance between the right-hand side and left-hand side of the equation above, that is

with the norm . This is equivalent to solving the following linear system of equations

where

,

and

.

Running VarQTE

In this tutorial, we will use two classes that extend VarQTE, VarQITE (Variational Quantum Imaginary Time Evolution) and VarQRTE (Variational Quantum Real Time Evolution) for time evolution. We can use a simple Ising model on a spin chain to illustrate this. Let us consider the following Hamiltonian:

where stands for the interaction energy, and represents an external field which is orthogonal to the transverse direction. and are the Pauli operators on the spins. Taking , , and , the Hamiltonian and the magnetization can be constructed using SparsePauliOp as follows:

In [1]:
from qiskit.quantum_info import SparsePauliOp

hamiltonian = SparsePauliOp(["ZZ", "IX", "XI"], coeffs=[-0.2, -1, -1])

magnetization = SparsePauliOp(["IZ", "ZI"], coeffs=[1, 1])

Imaginary Time Evolution

Imaginary time evolution can be used, for example, to find the ground state or calculate the finite temperature expectation value of the system. Here, we will use the VarQITE class from time_evolvers.variational to compute a ground state energy. Firstly, we need to choose an ansatz. We can use efficient_su2 to easily construct an ansatz, setting the number of repetitions using reps.

In [2]:
from qiskit.circuit.library import efficient_su2

ansatz = efficient_su2(hamiltonian.num_qubits, reps=1)
ansatz.draw("mpl")

Here, we prepare a dictionary to store the initial parameters we set up, which determine the initial state.

In [3]:
import numpy as np

init_param_values = {}
for i in range(len(ansatz.parameters)):
    init_param_values[ansatz.parameters[i]] = np.pi / 2

Note that the initial state should be in overlap with the ground state.

Next, we choose ImaginaryMcLachlanPrinciple as the variational principle we'll use later.

In [4]:
from qiskit_algorithms.time_evolvers.variational import ImaginaryMcLachlanPrinciple

var_principle = ImaginaryMcLachlanPrinciple()

We set a target imaginary time , and set the Hamiltonian as an auxiliary operator. We create a TimeEvolutionProblem instance with hamiltonian, time, and aux_operators as arguments.

In [5]:
from qiskit_algorithms import TimeEvolutionProblem

time = 5.0
aux_ops = [hamiltonian]
evolution_problem = TimeEvolutionProblem(hamiltonian, time, aux_operators=aux_ops)

We now use the VarQITE class to calculate the imaginary time evolving state. We can use VarQITE.evolve to get the results. Note this cell may take around minutes to finish.

In [6]:
from qiskit_algorithms import VarQITE
from qiskit.primitives import StatevectorEstimator

var_qite = VarQITE(ansatz, init_param_values, var_principle, StatevectorEstimator())
# an EstimatorV2 instance is necessary, if we want to calculate the expectation value of auxiliary operators.
evolution_result = var_qite.evolve(evolution_problem)

Exact Classical Solution

In order to check whether our calculation using VarQITE is correct or not, we also call SciPyImaginaryEvolver to help us calculate the exact solution.

Firstly, we can use qiskit.quantum_info.Statevector to help us get the statevector from the quantum circuit.

In [7]:
from qiskit.quantum_info import Statevector

init_state = Statevector(ansatz.assign_parameters(init_param_values))

Then we can set up the evolving problem using SciPyImaginaryEvolver. Here we set number of time steps as .

In [8]:
from qiskit_algorithms import SciPyImaginaryEvolver

evolution_problem = TimeEvolutionProblem(
    hamiltonian, time, initial_state=init_state, aux_operators=aux_ops
)
exact_evol = SciPyImaginaryEvolver(num_timesteps=501)
sol = exact_evol.evolve(evolution_problem)

Results and Comparison

We use evolution_result.observables to get the variation over time of the expectation values of the Hamiltonian.

In [9]:
import pylab

h_exp_val = np.array([ele[0][0] for ele in evolution_result.observables])

exact_h_exp_val = sol.observables[0][0].real

times = evolution_result.times
pylab.plot(times, h_exp_val, label="VarQITE")
pylab.plot(times, exact_h_exp_val, label="Exact", linestyle="--")
pylab.xlabel("Time")
pylab.ylabel(r"$\langle H \rangle$ (energy)")
pylab.legend(loc="upper right");
In [10]:
print("Ground state energy", h_exp_val[-1])
Ground state energy -2.0097479079521197

As the above figure indicates, we have obtained the converged ground state energy.

Efficient classical calculation of gradients with VarQITE

You can use classically efficient gradient calculations to speed up the time evolution simulation by setting qgt to ReverseQGT() and gradient to ReverseEstimatorGradient().

In [11]:
from qiskit_algorithms.gradients import ReverseEstimatorGradient, ReverseQGT

var_principle = ImaginaryMcLachlanPrinciple(qgt=ReverseQGT(), gradient=ReverseEstimatorGradient())
evolution_problem = TimeEvolutionProblem(hamiltonian, time, aux_operators=aux_ops)
var_qite = VarQITE(ansatz, init_param_values, var_principle, StatevectorEstimator())
evolution_result_eff = var_qite.evolve(evolution_problem)

In this example, it takes only minute to calculate imaginary time evolution. The execution time is reduced by about 33% (this may vary for each execution, but generally results in a speedup).

In [12]:
h_exp_val_eff = np.array([ele[0][0] for ele in evolution_result_eff.observables])

exact_h_exp_val_eff = sol.observables[0][0].real

times = evolution_result_eff.times
pylab.plot(times, h_exp_val_eff, label=r"VarQITE$_{eff}$")
pylab.plot(times, exact_h_exp_val_eff, label="Exact", linestyle="--")
pylab.xlabel("Time")
pylab.ylabel(r"$\langle H \rangle$ (energy)")
pylab.legend(loc="upper right");

We can also get a converged result, which is consistent with the previous one.

In [13]:
print("Ground state energy", h_exp_val_eff[-1])
Ground state energy -2.0097479079521183

Let us compare the performance of different methods. The error is defined as the difference between the results and exact solution for each method.

In [14]:
pylab.plot(times, (h_exp_val - exact_h_exp_val), label="VarQITE")
pylab.plot(times, (h_exp_val_eff - exact_h_exp_val_eff), label=r"VarQITE$_{eff}$", linestyle="--")
pylab.xlabel("Time")
pylab.ylabel(r"$\Delta \langle H \rangle$")
pylab.legend(loc="upper right");

In this task, the accuracies of VarQITE with both gradient methods are very close, but ReverseEstimatorGradient() takes a considerably shorter time to run.

We can do the same comparison for VarQRTE for simulating the magnetization of the Ising model.

Real Time Evolution

Real time evolution is more suitable for tasks such as simulating quantum dynamics. For example, one can use VarQRTE to get time evolving expectation values of the magnetization.

VarQRTE

Again, the first step is to select an ansatz.

In [15]:
ansatz = efficient_su2(hamiltonian.num_qubits, reps=1)
ansatz.draw("mpl")

We set all initial parameters as .

In [16]:
init_param_values = {}

for i in range(len(ansatz.parameters)):
    init_param_values[ansatz.parameters[i]] = (
        np.pi / 2
    )  # initialize the parameters which also decide the initial state

We also define an initial state:

In [17]:
init_state = Statevector(ansatz.assign_parameters(init_param_values))
print(init_state)
Statevector([-5.00000000e-01+0.00000000e+00j,
             -5.00000000e-01-5.55111512e-17j,
              0.00000000e+00-5.00000000e-01j,
              1.66533454e-16+5.00000000e-01j],
            dims=(2, 2))

In order to use the real time McLachlan principle, we instantiate the RealMcLachlanPrinciple class.

In [18]:
from qiskit_algorithms.time_evolvers.variational import RealMcLachlanPrinciple

var_principle = RealMcLachlanPrinciple()

We also set the target time as , and set the auxiliary operator to be the magnetization operator. The following steps are similar to VarQITE.

In [19]:
aux_ops = [magnetization]
In [20]:
from qiskit_algorithms import VarQRTE

time = 10.0
evolution_problem = TimeEvolutionProblem(hamiltonian, time, aux_operators=aux_ops)
var_qrte = VarQRTE(ansatz, init_param_values, var_principle, StatevectorEstimator())
evolution_result_re = var_qrte.evolve(evolution_problem)

We can also obtain the exact solution with SciPyRealEvolver. We first create the corresponding initial state for the exact classical method.

In [21]:
init_circ = ansatz.assign_parameters(init_param_values)

SciPyRealEvolver can help us get the classical exact result.

In [22]:
from qiskit_algorithms import SciPyRealEvolver

evolution_problem = TimeEvolutionProblem(
    hamiltonian, time, initial_state=init_circ, aux_operators=aux_ops
)
rtev = SciPyRealEvolver(1001)
sol = rtev.evolve(evolution_problem)

We can compare the results, where represents the magnetization.

In [23]:
mz_exp_val_re = np.array([ele[0][0] for ele in evolution_result_re.observables])
exact_mz_exp_val_re = sol.observables[0][0].real
times = evolution_result_re.times
pylab.plot(times, mz_exp_val_re, label="VarQRTE")
pylab.plot(times, exact_mz_exp_val_re, label="Exact", linestyle="--")
pylab.xlabel("Time")
pylab.ylabel(r"$\langle m_z \rangle$")
pylab.legend(loc="upper right");

Efficient Way to run VarQRTE

Similarly, we can set qpt as ReverseQGT() and gradient as ReverseEstimatorGradient() to speed up the calculation.

In [24]:
from qiskit_algorithms.gradients import DerivativeType

var_principle = RealMcLachlanPrinciple(
    qgt=ReverseQGT(), gradient=ReverseEstimatorGradient(derivative_type=DerivativeType.IMAG)
)
time = 10.0
evolution_problem = TimeEvolutionProblem(hamiltonian, time, aux_operators=aux_ops)
var_qrte = VarQRTE(ansatz, init_param_values, var_principle, StatevectorEstimator())
evolution_result_re_eff = var_qrte.evolve(evolution_problem)
In [25]:
mz_exp_val_re_eff = np.array([ele[0][0] for ele in evolution_result_re_eff.observables])
pylab.plot(times, mz_exp_val_re_eff, label=r"VarQRTE$_{eff}$")
pylab.plot(times, exact_mz_exp_val_re, label="Exact", linestyle="--")
pylab.xlabel("Time")
pylab.ylabel(r"$\langle m_z \rangle$")
pylab.legend(loc="upper right");

Again, the accuracies of VarQRTE with both gradient methods are very similar, while the ReverseEstimatorGradient() shows a speedup of about in this execution.

In [26]:
pylab.plot(times, (mz_exp_val_re - exact_mz_exp_val_re), label="VarQRTE")
pylab.plot(
    times, (mz_exp_val_re_eff - exact_mz_exp_val_re), label=r"VarQRTE$_{eff}$", linestyle="--"
)
pylab.xlabel("Time")
pylab.ylabel(r"$\Delta \langle m_z \rangle$")
pylab.legend(loc="upper right");
In [27]:
import tutorial_magics

%qiskit_version_table
%qiskit_copyright

Version Information

SoftwareVersion
qiskit2.0.2
qiskit_algorithms0.4.0
System information
Python version3.13.3
OSLinux
Tue Jun 17 10:49:38 2025 CEST

This code is a part of a Qiskit project

© Copyright IBM 2017, 2025.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

Join the Discussion

Comments (0)

No comments yet. Be the first to share your thoughts!

Indexed by QCR Librarian

This entry was created automatically from publicly available records. QCR links to public sources and only stores repository content where the license permits redistribution.

Versions

v1 Latest
Jun 17, 2026
qcr:2606.73084.1

Cite all versions? Use the base QCR ID to always reference the latest version of this entry.

Tools used

Qiskit

Keywords

varqte
time-evolution
imaginary-time
variational
qiskit

You may also like5