How to Plot Energy vs. Number of Circuits

Hi PennyLane Community,

I am trying to track Quantum State Preparations (circuit executions) in my optimization and plot Energy vs. Number of Circuits, similar to Fig. 4 in “Quantum 5, 567 (2021)”.

I have the following working code that plots Energy vs. Optimization Steps using Gradient Descent (GD) and QNSPSA:

# import pennylane as qml
from pennylane import numpy as np
import matplotlib.pyplot as plt

# Set up the quantum device
n_qubits = 2
shots = 8192  # Fixed number of shots per execution
dev = qml.device("lightning.qubit", wires=n_qubits, shots=shots)

# Define a simple variational circuit
@qml.qnode(dev)
def cost_fn(params):
    qml.RX(params[0], wires=0)
    qml.RY(params[1], wires=1)
    qml.CNOT(wires=[0, 1])
    return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))  # Measure ZZ

# Compute the exact ground state energy
exact_energy = -1.0  # Example target energy (update if needed)

# Optimizers
gd_optimizer = qml.GradientDescentOptimizer(stepsize=0.1)
qnspsa_optimizer = qml.QNSPSAOptimizer(stepsize=0.1, resamplings=10)

# Initialize parameters
np.random.seed(42)
params_gd = np.random.uniform(-np.pi, np.pi, size=(2,))
params_qnspsa = np.copy(params_gd)

# Store energy history
energy_history_gd = []
energy_history_qnspsa = []

# Number of optimization steps
n_iterations = 50

# Optimization loop
for i in range(n_iterations):
    # GD step
    params_gd, energy_gd = gd_optimizer.step_and_cost(cost_fn, params_gd)
    energy_history_gd.append(energy_gd)

    # QNSPSA step
    params_qnspsa, energy_qnspsa = qnspsa_optimizer.step_and_cost(cost_fn, params_qnspsa)
    energy_history_qnspsa.append(energy_qnspsa)

# Plot Energy vs. Optimization Iterations
plt.figure(figsize=(6, 4))
plt.plot(range(n_iterations), energy_history_gd, label="GD", color="blue")  # Solid line for GD
plt.plot(range(n_iterations), energy_history_qnspsa, label="QNSPSA", color="orange")  # Solid line for QNSPSA
plt.axhline(y=exact_energy, color='red', linestyle='dashed', label="Target Energy")  # Dashed red target line
plt.xlabel("Optimization Iterations")
plt.ylabel("Energy")
plt.title("Energy vs. Optimization Steps")
plt.legend()
plt.grid()
plt.show()

What I Need Help With:

Instead of "Energy vs. Optimization Steps ", I want to track and plot “Energy vs. Quantum State Preparations (circuit executions)”.

Any guidance or example code would be greatly appreciated!

Thanks in advance! :blush:

Hi @QuantumH,

You can try using qml.Tracker or qml.specs.

Using Tracker might be more straightforward. You can see some examples of how to use it in the docs.

Let us know if this is what you were looking for.

I hope this helps!

Thanks for your response! :blush:
qml.Tracker works well for me.

Just to confirm, does qml.Tracker automatically count all circuit executions, including those for gradient and Hessian estimation, depending on the optimization method? Or do I need to track them separately?

For example:

  • Gradient Descent with the parameter-shift rule requires two evaluations per gradient component.
  • QNSPSA requires 2 evaluations for the gradient and 4 for the Hessian.

I want to be sure that tracker.totals["executions"] correctly reflects the total quantum state preparations automatically, without needing to track how many evaluations each optimizer requires or understand the math behind each method.

Thanks again!

Hi @QuantumH ,

qml.Tracker does track all executions including those required for gradients.
For example, the following code shows that one call of the circuit is 1 execution, while calling the gradient requires 3 executions, one for the forward pass and one for the actual gradient.

import pennylane as qml
from pennylane import numpy as np

dev = qml.device('default.qubit', wires=1, shots=100)

@qml.qnode(dev, diff_method="parameter-shift")
def circuit(x):
    qml.RX(x, wires=0)
    return qml.expval(qml.Z(0))

x = np.array(0.1, requires_grad=True)

with qml.Tracker(dev) as tracker1:
    circuit(x)

with qml.Tracker(dev) as tracker2:
    qml.grad(circuit)(x)

print("Tracker 1: \n",tracker1.totals, "\n")
print("Tracker 2: \n",tracker2.totals,"\n")

Out:

Tracker 1: 
 {'batches': 1, 'simulations': 1, 'executions': 1, 'results': 1.0, 'shots': 100} 

Tracker 2: 
 {'batches': 2, 'simulations': 3, 'executions': 3, 'results': 1.24, 'shots': 300} 

If you do something similar with Gradient Descent you’ll notice that computing one step will require 3 executions, while using QNSPSA will require 7 executions (1 for the forward pass, 2 for the gradient, 4 for the Hessian).

It gets more complicated when you have several gates with differentiable parameters acting on the same qubit, but in principle the tracker should indeed track all the executions.

Note that there may be edge cases, so if you get an answer that seems off please let us know!