Variational Quantum Time Evolution (VarQTE)
Motivation
The VarQTE module provides tools to simulate quantum processes through two complementary approaches:
- Variational Quantum Imaginary Time Evolution (VarQITE)
- Variational Quantum Real Time Evolution (VarQRTE)
Both methods allow the user to approximate observables of a quantum system by updating the parameters of a parameterized ansatz circuit.
Protocol for using VarQTE in practice:
- Construct the problem Hamiltonian as a
qiskit.SparsePauliOp
object - Construct the initial state as a
qiskit.QuantumCircuit
- Run VarQITE or VarQRTE with the desired hyperparameters (layers, total time, timestep)
- Obtain ansatz parameters at each timestep
-
Measure observables, either:
-
directly with the
ansatz_energy
function (for energy), or - by constructing the ansatz circuit with
Construct_Ansatz
(for custom observables).
VarQITE and VarQRTE both employ a TwoLocal ansatz, returning the optimal parameter values at each timestep, which can then be analyzed to study system properties.
Import Dependencies
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qflux.closed_systems.VarQTE import VarQITE, VarQRTE, ansatz_energy, Construct_Ansatz
Variational Quantum Imaginary Time Evolution (VarQITE)
We begin with the Wick-rotated Schrödinger equation and apply McLachlan’s Variational Principle:
For a parameterized ansatz state,
this leads to a system of linear equations:
where
These $A_{ij}$ and $C_i$ values are measured on a quantum device and used to update ansatz parameters:
Demonstrations
Example 1: Simple Hamiltonian
Focusing on a simple Hamiltonian, consisting of a spin chain defined by the Pauli-Z matrix, we setup the initial state using a qiskit QuantumCircuit and the hyperparameters for VarQITE, such as number of layers in the ansatz, the timestep and total simulation time. Finally, we call VarQITE to run the time evolution and output the ansatz parameter values.
# Define the problem Hamiltonian
H = SparsePauliOp.from_list([("Z", 1.0)])
# Set up the initial state
qc = QuantumCircuit(3)
# Hyperparameters for VarQITE
layers = 0
total_time = 10
timestep = 0.1
# Run VarQITE
params = VarQITE(layers, H, total_time, timestep, init_circ=qc)
Furethermore, we can use the parameters to measure observables with a quantum circuit:
# Measure energy at a given timestep
my_energy, my_stdev = ansatz_energy(qc, params[i], H)
# Measure a custom observable
observable = SparsePauliOp.from_list([("Z", 1.0)])
ansatz = Construct_Ansatz(qc, params[i], H.num_qubits)
result = estimator.run(ansatz, observables=observable).result()
Example 2: Hamiltonian with couplings
Similarly, we can apply this protocol for a more sophisticated Hamiltonian, containing couplings between different sites:
H = SparsePauliOp.from_list([
("IIZ", 1.0), ("IZI", 1.0), ("ZII", 0.65),
("IXX", 1.0), ("IYY", 1.0),
("XXI", 0.75), ("YYI", 0.75)
])
Initial state and execution:
qc = QuantumCircuit(3)
qc.rx(0.5, 0)
qc.rx(0.5, 1)
qc.rx(0.5, 2)
params = VarQITE(layers, H, total_time, timestep, init_circ=qc)
Params now holds the parameter values for the ansatz at each timestep for Imaginary-Time Evolution
Plotting the dynamics:
all_energies = []
for i in range(len(params)):
print(f"Timestep {i} Energy: {ansatz_energy(qc, params[i], H)}")
all_energies.append(ansatz_energy(qc, params[i], H)[0])
plt.title("VarQITE Energy Over Imaginary Time")
plt.plot([i*timestep for i in range(int(total_time/timestep)+1)], all_energies)
plt.xlabel("Imaginary Time")
plt.ylabel("Energy (eV)")
plt.show()
Interpretation: VarQITE drives the system toward the ground state as imaginary time increases, provided the ansatz is expressive enough. This enables estimation of the ground-state energy by sampling the long-time behavior.
Variational Quantum Real Time Evolution (VarQRTE)
We now consider real-time dynamics by starting with the Schrödinger equation and applying McLachlan’s Variational Principle:
For a parameterized ansatz state,
this simplifies to:
with
The updated parameters evolve as:
Demonstration
We proceed by defining the Hamiltonian and initial state, along with instantiating the VarQRTE class with appropriate hyper-parameters.
H = SparsePauliOp.from_list([("X", 1.0)])
qc = QuantumCircuit(1)
qc.x(0) # Prepare |1> state
layers = 1
total_time = 12
timestep = 0.1
params = VarQRTE(layers, H, total_time, timestep, init_circ=qc)
We can measure observables over time using an Estimator object, supplied with the optimized circuit parameters and the observable circuit.
from qiskit.primitives import Estimator
estimator = Estimator()
observable = SparsePauliOp.from_list([("Z", 1.0)])
spin_values = []
for i in range(len(params)):
ansatz = Construct_Ansatz(qc, params[i], H.num_qubits)
result = estimator.run(ansatz, observables=observable).result()
spin_values.append(result.values[0])
plt.title("Spin Expectation Value Over Time")
plt.plot([i*timestep for i in range(int(total_time/timestep)+1)], spin_values)
plt.xlabel("Time")
plt.ylabel("Expectation Value")
plt.show()
Interpretation: VarQRTE captures the change in state of quantum systems in real-time. As seen in the example, the expectation value of spin follows the expected sinusoidal pattern of coherent quantum evolution.