from qiskit import QuantumCircuit
= QuantumCircuit(1, 1)
qc
'mpl') qc.draw(
Appendix D — Interesting characteristics
D.1 Expressivity
One simple approach is to compare the distribution of fidelities obtainer from sampling \(n\) number of times with randomly selected parameters given a target PQC to be evaluated in comparison with the uniform distribution of fidelities for the same domain, like the ensemble of Haar random states.
Thus, \[ \text{Expr} = D_{KL}\left( \hat{P}_{PQC}(F; \theta) \| P_{Haar}(F)\right) = \sum_{j} \hat{P}_{PQC}(F_j; \theta)\log\left(\frac{\hat{P}_{PQC}(F_j; \theta)}{P_{\text{Haar}(F_j)}}\right) \]
where \(\hat{P}_{PQC}(F; \theta)\) is the estimated probability distribution of fidelities resulting while sampling states from a PQC with parameters \(\theta\). \(F_j\) represents the fidelity at \(j\)th bin.
Thus, we need to generate a histogram of the elements of \(F\). The output of this histogram is a set of bins \(B = \{(l_1, u_1), (l_2, u_2), \cdots \}\) where \(l_{j}\) (\(u_j\)) denotes the lower (upper) limit of bin \(j\). It also produces an empirical probability distribution function \(\mathrm{P}_{\text{PQC}}(j)\), which is simply the probability that a given value of \(F\) falls in a bin \(j\).
Let’s take the ansatz defined by an idle circuit as an example.
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector
= Statevector.from_instruction(qc)
psi plot_bloch_multivector(psi)
No matter what we do, the ansatz won’t change, therefore its expressivity should be equal to none.
import numpy as np
import matplotlib.pyplot as plt
from qiskit_aer import AerSimulator
# Size of our histogram
= 100
dims = 1
num_qubits
# Possible Bin
= []
bins_list for i in range(dims):
/ (dims - 1))
bins_list.append((i)
# Harr histogram
= []
p_haar_hist for i in range(dims - 1):
p_haar_hist.append(1 - bins_list[i]) ** (2**num_qubits - 1) - (1 - bins_list[i + 1]) ** (2**num_qubits - 1)
(
)
# Select the AerSimulator from the Aer provider
= AerSimulator(method='matrix_product_state')
simulator
# Sample from circuit
=1_024
nshot=1_000
nsamples=[]
fidelitiesfor _ in range(nsamples):
= QuantumCircuit(1, 1)
qc 0,0)
qc.measure(
= simulator.run([qc], shots = nshot)
job = job.result()
result = result.get_counts()
count
# Fidelity
if '0' in count:
=count['0']/nshot
ratioelse:
=0
ratio
fidelities.append(ratio)
= np.ones_like(fidelities) / float(len(fidelities))
weights
# Plot
= []
bins_x for i in range(dims - 1):
1] + bins_list[i])
bins_x.append(bins_list[
plt.hist(
fidelities,=bins_list,
bins=weights,
weights="Circuit",
labelrange=[0, 1],
)="Haar")
plt.plot(bins_x, p_haar_hist, label="upper right")
plt.legend(loc plt.show()
Zero means maximal expressivity
We can see how all fidelities are probability of zero except for fidelity 1, which means there is only one state we can render with this circuit. The expressivity works backwards, meaning a distance of zero represents the fidelity probability landscape equal to the uniform distribution. Therefore, all states (pure) in the Hilbert space can be produced with the right set of parameters (\(\theta\)). Thus the expressivity will take the maximum value as the \(log(N)\) where \(N\) is the number of bins selected for the histogram.
from scipy.special import rel_entr # For entropy calculation
= np.histogram(fidelities, bins=bins_list, weights=weights, range=[0, 1])[0]
pi_hist print("Expr = ", sum(rel_entr(pi_hist, p_haar_hist)))
Expr = 4.595119850134598
This will numerically approximate the value of
np.log(dims)
np.float64(4.605170185988092)
hence, 0 is the maximum expressivity we can get.
We can see what would be the effect if we introduce a parameterized gate instead, something more complex with a free parameter such as:
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
= Parameter('a')
a
= QuantumCircuit(1, 1)
qc 0)
qc.h(0)
qc.rz(a,
'mpl') qc.draw(
from random import random
# Possible Bin
= []
bins_list for i in range(dims):
/ (dims - 1))
bins_list.append((i)
# Haar histogram
= []
p_haar_hist for i in range(dims - 1):
p_haar_hist.append(1 - bins_list[i]) ** (2**num_qubits - 1) - (1 - bins_list[i + 1]) ** (2**num_qubits - 1)
(
)
# Select the AerSimulator from the Aer provider
= AerSimulator(method='matrix_product_state')
simulator
# Sample from circuit
=[]
fidelitiesfor _ in range(nsamples):
= QuantumCircuit(1, 1)
qc 0)
qc.h(2 * np.pi * random(), 0)
qc.rz(# Inverse of the circuit
2 * np.pi * random(), 0)
qc.rz(0)
qc.h(0,0)
qc.measure(
= simulator.run([qc], shots = nshot)
job = job.result()
result = result.get_counts()
count
# Fidelity
if '0' in count:
=count['0']/nshot
ratioelse:
=0
ratio
fidelities.append(ratio)
= np.ones_like(fidelities) / float(len(fidelities))
weights
# Plot
= []
bins_x for i in range(dims - 1):
1] + bins_list[i])
bins_x.append(bins_list[
plt.hist(
fidelities,=bins_list,
bins=weights,
weights="Circuit",
labelrange=[0, 1],
)="Haar")
plt.plot(bins_x, p_haar_hist, label="upper right")
plt.legend(loc plt.show()
= np.histogram(fidelities, bins=bins_list, weights=weights, range=[0, 1])[0]
pi_hist print("Expr = ", sum(rel_entr(pi_hist, p_haar_hist)))
Expr = 0.21766234960533026
We can extend this acting on more than one axis, which should end up with the maximum coverage over the bloch sphere for this one single qubit case.
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
= Parameter('a')
a = Parameter('b')
b
= QuantumCircuit(1, 1)
qc 0)
qc.h(0)
qc.rz(a, 0)
qc.rx(b,
'mpl') qc.draw(
# Possible Bin
= []
bins_list for i in range(dims):
/ (dims - 1))
bins_list.append((i)
# Haar histogram
= []
p_haar_hist for i in range(dims - 1):
p_haar_hist.append(1 - bins_list[i]) ** (2**num_qubits - 1) - (1 - bins_list[i + 1]) ** (2**num_qubits - 1)
(
)
# Select the AerSimulator from the Aer provider
= AerSimulator(method='matrix_product_state')
simulator
# Sample from circuit
=[]
fidelitiesfor _ in range(nsamples):
= QuantumCircuit(1, 1)
qc 0)
qc.h(2 * np.pi * random(), 0)
qc.rz(2 * np.pi * random(), 0)
qc.rx(# Inverse of the circuit
2 * np.pi * random(), 0)
qc.rx(2 * np.pi * random(), 0)
qc.rz(0)
qc.h(0,0)
qc.measure(
= simulator.run([qc], shots = nshot)
job = job.result()
result = result.get_counts()
count
# Fidelity
if '0' in count:
=count['0']/nshot
ratioelse:
=0
ratio
fidelities.append(ratio)
= np.ones_like(fidelities) / float(len(fidelities))
weights
# Plot
= []
bins_x for i in range(dims - 1):
1] + bins_list[i])
bins_x.append(bins_list[
plt.hist(
fidelities,=bins_list,
bins=weights,
weights="Circuit",
labelrange=[0, 1],
)="Haar")
plt.plot(bins_x, p_haar_hist, label="upper right")
plt.legend(loc plt.show()
= np.histogram(fidelities, bins=bins_list, weights=weights, range=[0, 1])[0]
pi_hist print("Expr = ", sum(rel_entr(pi_hist, p_haar_hist)))
Expr = 0.06728209189372705
These plots should resemble those at the original work (Sim, Johnson, and Aspuru-Guzik 2019).