qml.fourier.reconstruct¶
- reconstruct(qnode, ids=None, nums_frequency=None, spectra=None, shifts=None)[source]¶
Reconstruct an expectation value QNode along a single parameter direction. This means we restrict the QNode to vary only one parameter, a univariate restriction. For common quantum gates, such restrictions are finite Fourier series with known frequency spectra. Thus they may be reconstructed using Dirichlet kernels or a non-uniform Fourier transform.
- Parameters
qnode (pennylane.QNode) – Quantum node to be reconstructed, representing a circuit that outputs an expectation value.
ids (dict or Sequence or str) – Indices for the QNode parameters with respect to which the QNode should be reconstructed as a univariate function, per QNode argument. Each key of the dict, entry of the list, or the single
str
has to be the name of an argument ofqnode
. If adict
, the values ofids
have to contain the parameter indices for the respective array-valued QNode argument represented by the key. These indices always are tuples, i.e.,()
for scalar and(i,)
for one-dimensional arguments. If alist
, the parameter indices are inferred fromnums_frequency
if given orspectra
else. IfNone
, all keys present innums_frequency
/spectra
are considered.nums_frequency (dict[dict]) – Numbers of integer frequencies – and biggest frequency – per QNode parameter. The keys have to be argument names of
qnode
and the inner dictionaries have to be mappings from parameter indices to the respective integer number of frequencies. If the QNode frequencies are not contiguous integers, the argumentspectra
should be used to save evaluations ofqnode
. Takes precedence overspectra
and leads to usage of equidistant shifts.spectra (dict[dict]) – Frequency spectra per QNode parameter. The keys have to be argument names of
qnode
and the inner dictionaries have to be mappings from parameter indices to the respective frequency spectrum for that parameter. Ignored ifnums_frequency!=None
.shifts (dict[dict]) – Shift angles for the reconstruction per QNode parameter. The keys have to be argument names of
qnode
and the inner dictionaries have to be mappings from parameter indices to the respective shift angles to be used for that parameter. For \(R\) non-zero frequencies, there must be \(2R+1\) shifts given. Ignored ifnums_frequency!=None
.
- Returns
Function which accepts the same arguments as the QNode and one additional keyword argument
f0
to provide the QNode value at the given arguments. When called, this function will return a dictionary of dictionaries, formatted likenums_frequency
orspectra
, that contains the univariate reconstructions per QNode parameter.- Return type
function
For each provided
id
inids
, the QNode is restricted to varying the single QNode parameter corresponding to theid
. This univariate function is then reconstructed via a Fourier transform or Dirichlet kernels, depending on the provided input. Either the frequencyspectra
of the QNode with respect to its input parameters or the numbers of frequencies,nums_frequency
, per parameter must be provided.For quantum-circuit specific details, we refer the reader to Vidal and Theis (2018) , Vidal and Theis (2020) , Schuld, Sweke and Meyer (2021) , and Wierichs, Izaac, Wang and Lin (2022) . An introduction to the concept of quantum circuits as Fourier series can also be found in the Quantum models as Fourier series and General parameter-shift rules demos as well as the
qml.fourier
module docstring.Example
Consider the following QNode:
dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x, Y): qml.RX(x, wires=0) qml.RY(Y[0], wires=0) qml.RY(Y[1], wires=1) qml.CNOT(wires=[0, 1]) qml.RY(5* Y[1], wires=1) return qml.expval(qml.Z(0) @ qml.Z(1)) x = 0.4 Y = np.array([1.9, -0.5]) f = 2.3 circuit_value = circuit(x, Y)
It has three variational parameters
x
(a scalar) and two entries ofY
(an array-like). A reconstruction job could then be with respect to the two entries ofY
, which enter the circuit with one and six integer frequencies, respectively (see the additional examples below for details on how to obtain the frequency spectrum if it is not known):>>> nums_frequency = {"Y": {(0,): 1, (1,): 6}} >>> with qml.Tracker(circuit.device) as tracker: ... rec = qml.fourier.reconstruct(circuit, {"Y": [(0,), (1,)]}, nums_frequency)(x, Y) >>> rec.keys() dict_keys(['Y']) >>> print(*rec["Y"].items(), sep="\n") ((0,), <function _reconstruct_equ.<locals>._reconstruction at 0x7fbd685aee50>) ((1,), <function _reconstruct_equ.<locals>._reconstruction at 0x7fbd6866eee0>) >>> recon_Y0 = rec["Y"][(0,)] >>> recon_Y1 = rec["Y"][(1,)] >>> np.isclose(recon_Y0(Y[0]), circuit_value) True >>> np.isclose(recon_Y1(Y[1]+1.3), circuit(x, Y+np.eye(2)[1]*1.3)) True
We successfully reconstructed the dependence on the two entries of
Y
, keepingx
and the respective other entry inY
at their initial values. Let us also see how many executions of the device were used to obtain the reconstructions:>>> tracker.totals {'batches': 15, 'simulations': 15, 'executions': 15}
The example above used that we already knew the frequency spectra of the QNode of interest. However, this is in general not the case and we may need to compute the spectrum first. This can be done with
fourier.qnode_spectrum()
:>>> spectra = qml.fourier.qnode_spectrum(circuit)(x, Y) >>> spectra.keys() dict_keys(['x', 'Y']) >>> spectra["x"] {(): [-1.0, 0.0, 1.0]} >>> print(*spectra["Y"].items(), sep="\n") ((0,), [-1.0, 0.0, 1.0]) ((1,), [-6.0, -5.0, -4.0, -1.0, 0.0, 1.0, 4.0, 5.0, 6.0])
For more detailed explanations, usage details and additional examples, see the usage details section below.
Usage Details
Input formatting
As described briefly above, the essential inputs to
reconstruct
that provide information about the QNode are given as dictionaries of dictionaries, where the outer keys reference the argument names ofqnode
and the inner keys reference the parameter indices within each array-valued QNode argument. These parameter indices always are tuples, so that for scalar-valued QNode parameters, the parameter index is()
by convention and thei
-th parameter of a one-dimensional array can be accessed via(i,)
. For example, providingnums_frequency
for a scalar argument:
nums_frequency = {"x": {(): 4}}
for a one-dimensional argument:
nums_frequency = {"Y": {(0,): 2, (1,): 9, (4,): 1}}
for a three-dimensional argument:
nums_frequency = {"Z": {(0, 2, 5): 2, (1, 1, 4): 1}}
This applies to
nums_frequency
,spectra
, andshifts
.Note that the information provided in
nums_frequency
/spectra
is essential for the correctness of the reconstruction.On the other hand, the input format for
ids
is flexible and allows a collection of parameter indices for each QNode argument name (as adict
), a collection of argument names (as alist
,set
,tuple
or similar), or a single argument name (as astr
) to be defined. Forids=None
, all argument names contained innums_frequency
– orspectra
ifnums_frequency
is not used – are considered. For inputs that do not specify parameter indices per QNode argument name (all formats butdict
), these parameter indices are inferred fromnums_frequency
/spectra
.Reconstruction cost
The reconstruction cost – in terms of calls to
qnode
– depend on the number of frequencies given vianums_frequency
orspectra
. A univariate reconstruction for \(R\) frequencies takes \(2R+1\) evaluations. If multiple univariate reconstructions are performed at the same point with various numbers of frequencies \(R_k\) , the cost are \(1+2\sum_k R_k\) if the shift \(0\) is used in all of them. This is in particular the case ifnums_frequency
orspectra
withshifts=None
is used.If the number of frequencies is too large or the given frequency spectrum contains more than the spectrum of
qnode
, the reconstruction is performed suboptimally but remains correct. For integer-valued spectra with gaps, the equidistant reconstruction is thus suboptimal and the non-equidistant version method be used (also see the examples below).Numerical stability
In general, the reconstruction with equidistant shifts for equidistant frequencies (used if
nums_frequency
is provided) is more stable numerically than the more general Fourier reconstruction (used ifnums_frequency=None
). If the system of equations to be solved in the Fourier transform is ill-conditioned, a warning is raised as the output might become unstable. Examples for this are shift values or frequencies that lie very close to each other.Differentiability
The returned scalar functions are differentiable in all interfaces with respect to their scalar input variable. They expect these inputs to be in the same interface as the one used by the QNode. More advanced differentiability, for example of the reconstructions with respect to QNode properties, is not supported reliably yet.
Warning
When using
TensorFlow
orAutograd
andnums_frequency
, the reconstructed functions are not differentiable at the point of reconstruction. One workaround for this is to usespectra
as input instead and to thereby use the Fourier transform instead of Dirichlet kernels. Alternatively, the original QNode evaluation can be used.More examples
Consider the QNode from the example above, now with an additional, tunable frequency
f
for the Pauli-X rotation that is controlled byx
:@qml.qnode(dev) def circuit(x, Y, f=1.0): qml.RX(f * x, wires=0) qml.RY(Y[0], wires=0) qml.RY(Y[1], wires=1) qml.CNOT(wires=[0, 1]) qml.RY(5* Y[1], wires=1) return qml.expval(qml.Z(0) @ qml.Z(1)) f = 2.3 circuit_value = circuit(x, Y)
We repeat the reconstruction job for the dependence on
Y[1]
. Note that even though information aboutY[0]
is contained innums_frequency
,ids
determines which reconstructions are performed.>>> with qml.Tracker(circuit.device) as tracker: ... rec = qml.fourier.reconstruct(circuit, {"Y": [(1,)]}, nums_frequency)(x, Y) >>> tracker.totals {'executions': 13}
As expected, we required \(2R+1=2\cdot 6+1=13\) circuit executions. However, not all frequencies below \(f_\text{max}=6\) are present in the circuit, so that a reconstruction using knowledge of the full frequency spectrum will be cheaper:
>>> spectra = {"Y": {(1,): [0., 1., 4., 5., 6.]}} >>> with tracker: ... rec = qml.fourier.reconstruct(circuit, {"Y": [(1,)]}, None, spectra)(x, Y) >>> tracker.totals {'executions': 9}
We again obtain the full univariate dependence on
Y[1]
but with considerably fewer executions on the quantum device. Once we obtained the classical function that describes the dependence, no additional circuit evaluations are performed:>>> with tracker: ... for Y1 in np.arange(-np.pi, np.pi, 20): ... rec["Y"][(1,)](-2.1) >>> tracker.totals {}
If we want to reconstruct the dependence of
circuit
onx
, we cannot usenums_frequency
iff
is not an integer. One could rescalex
to obtain the frequency \(1\) again, or directly usespectra
. We will combine the latter with another reconstruction with respect toY[0]
:>>> spectra = {"x": {(): [0., f]}, "Y": {(0,): [0., 1.]}} >>> with tracker: ... rec = qml.fourier.reconstruct(circuit, None, None, spectra)(x, Y, f=f) >>> tracker.totals {'executions': 5} >>> recon_x = rec["x"][()] >>> np.isclose(recon_x(x+0.5), circuit(x+0.5, Y, f=f) True
Note that by convention, the parameter index for a scalar variable is
()
and that the frequency \(0\) always needs to be included in the spectra. Furthermore, we here skipped the inputids
so that the reconstruction was performed for all keys inspectra
. The reconstruction with a single non-zero frequency costs three evaluations ofcircuit
for each,x
andY[0]
. Performing both reconstructions at the same position allowed us to save one of the evaluations and reduce the number of calls to \(5\).