Quick Start Guide¶
This guide shows how to compute the QGT, McLachlan gradients and the energy variance of a parameterized circuit.
Basic Usage¶
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector
from qiskit.quantum_info import SparsePauliOp
from bowtie_qgt.bowtieqgt import BowtieQGT
num_qubits = 4
qc = QuantumCircuit(num_qubits)
params = ParameterVector("θ", 8)
for i in range(num_qubits):
qc.ry(params[i], i)
for i in range(num_qubits - 1):
qc.cx(i, i + 1)
for i in range(num_qubits):
qc.ry(params[i + 4], i)
hamiltonian = SparsePauliOp.from_list(
[("ZIII", -1.0), ("IZII", -1.0), ("IIZI", -1.0), ("IIIZ", -1.0), ("XXII", -0.5)]
)
bowtie = BowtieQGT(qc, hamiltonian)
generalized_qgt, energy = bowtie.evaluate([0.1] * qc.num_parameters)
qgt = bowtie.extract_qgt(generalized_qgt) # real part by default
b_real = bowtie.extract_gradient(generalized_qgt, which="real_time")
b_imag = bowtie.extract_gradient(generalized_qgt, which="imag_time")
variance = bowtie.extract_variance(generalized_qgt)
evaluate accepts either a sequence of values ordered like qc.parameters or a
{Parameter: value} mapping. Every parameter must be bound.
Supported Circuits¶
Every parameter must be the bare angle of exactly one rx, ry, rz, rxx,
ryy or rzz gate. Any non-parameterized gates are allowed. The constructor raises a
ValueError for parameter expressions (2*θ), other parameterized gates, a parameter
that appears in several gates, classical bits, control flow, or a parameterized global
phase.
Identity terms in the Hamiltonian are allowed: they shift the energy and nothing else.
Real and Imaginary Time¶
The same BowtieQGT object serves both. You only choose the direction when you extract
the gradient:
Real time (VarQRTE) |
Imaginary time (VarQITE) |
|
|---|---|---|
Target |
\(|\psi(t)\rangle = e^{-iHt}|\psi_0\rangle\) |
\(|\psi(\tau)\rangle \propto e^{-H\tau}|\psi_0\rangle\) |
Use |
Quantum dynamics |
Ground-state preparation |
Call |
|
|
\(b_i\) |
\(\mathrm{Im}(\langle\partial_i\psi|H|\psi\rangle - \langle\partial_i\psi|\psi\rangle E)\) |
\(-\mathrm{Re}\langle\partial_i\psi|H|\psi\rangle = -\tfrac12\partial_i E\) |
McLachlan’s variational principle gives \(\mathrm{Re}(G)\,\dot\theta = b\) in both cases. The QGT is the same; only \(b\) changes:
qgt = bowtie.extract_qgt(generalized_qgt)
# Real time: follow e^{-iHt}
b = bowtie.extract_gradient(generalized_qgt, which="real_time")
theta_dot = np.linalg.solve(qgt + 1e-6 * np.eye(len(b)), b)
# Imaginary time: flow towards the ground state
b = bowtie.extract_gradient(generalized_qgt, which="imag_time")
theta_dot = np.linalg.solve(qgt + 1e-6 * np.eye(len(b)), b)
The names refer to the evolution, not to the part of the matrix that is taken: the
real-time gradient happens to be the real part of the mixed block of the generalized QGT,
but in terms of the raw overlap \(\langle\partial_i\psi|H|\psi\rangle\) it is the
imaginary part. which= hides this bookkeeping. Examples has a full run of both.
Choosing the Simulator¶
BowtieQGT runs the bowtie circuits on a default statevector AerSimulator. Choose
the device with "CPU" (default) or "GPU":
bowtie = BowtieQGT(qc, hamiltonian) # CPU
bowtie = BowtieQGT(qc, hamiltonian, simulator="GPU") # needs qiskit-aer-gpu
For anything else (precision, number of threads, …), pass your own noiseless statevector simulator:
from qiskit_aer import AerSimulator
simulator = AerSimulator(method="statevector", precision="single", max_parallel_threads=8)
bowtie = BowtieQGT(qc, hamiltonian, simulator=simulator)
Progress and Timings¶
verbosity=1 shows progress bars while building the bowties and prints their light-cone
widths. verbosity=2 also reports every evaluate call. The time spent in the last
call is always available:
bowtie.evaluate(values)
bowtie.last_timings # {"simulation": seconds, "assembly": seconds}
Understanding the Output¶
The generalized QGT is the connected QGT of the circuit extended by one first-order
Trotter step \(e^{-i c_j h_j t}\) per Hamiltonian term. It is complex Hermitian, with
N circuit parameters followed by M non-identity Hamiltonian terms:
┌─────────────┬──────────────┐
│ QGT │ mixed │
│ (N×N) │ (N×M) │
├─────────────┼──────────────┤
│ mixed† │ Hamiltonian │
│ (M×N) │ (M×M) │
└─────────────┴──────────────┘
The parameter tangents are \(|\partial_i\psi\rangle\) and the Hamiltonian tangents are \(-i h_j|\psi\rangle\). For \(H = \sum_j c_j h_j\):
with \(b^{\text{real}}_i = \mathrm{Im}(\langle\partial_i\psi|H|\psi\rangle - \langle\partial_i\psi|\psi\rangle E)\) and \(b^{\text{imag}}_i = -\mathrm{Re}(\langle\partial_i\psi|H|\psi\rangle - \langle\partial_i\psi|\psi\rangle E)\). For Hermitian \(H\) the last term is real and drops out, so \(b^{\text{imag}} = -\tfrac12\nabla_\theta E\).
extract_qgt returns \(\mathrm{Re}\,G\) by default, the Fubini-Study metric that
McLachlan’s principle uses. part="imag" gives the Berry curvature and
part="complex" the full QGT, for other variational principles. Rotations follow
Qiskit’s convention \(e^{-i\theta P/2}\), so G matches
qiskit_algorithms.gradients.ReverseQGT.
Next Steps¶
Examples: VarQRTE and VarQITE of an Ising chain
API Reference: full API reference
Changelog: release notes and migration guide