Skip to content

Generalized quantum signal processing

In generalized quantum signal processing (QSP) we have the following construction:

\[ e^{i\lambda Z} e^{i\phi_0 X} e^{i\theta_0 Z} \tilde{w} e^{i\phi_1 X} e^{i\theta_1 Z} \tilde{w} \cdots \tilde{w} e^{i\phi_n X} e^{i\theta_n Z} = \begin{pmatrix} P(z) & Q(z) \\ \cdot & \cdot \end{pmatrix} \]

where \(\tilde{w} = \mathrm{diag}(z, 1)\) is the signal operator and \(X, Y, Z\) are the Pauli matrices. Any pair \((P, Q)\) satisfying \(|P(z)|^2 + |Q(z)|^2 = 1\) for \(|z| = 1\) admits such a decomposition, and in particular any desired \(P\) with \(|P(z)| \le 1\) in this domain can be constructed.

Note

This is slightly different from the original ansatz by Motlagh, Wiebe! See the subsection below to see how to convert phase factors to their convention.

Computing the generated polynomials

The GQSPPhaseFactors class provides a variety of methods to handle the GQSP ansatz. In particular, it is possible, given a set of phase factors \(\lambda, \{ \phi_k \}, \{ \theta_k \}\) to generate the corresponding polynomials \(P(z), Q(z)\).

from nlft_qsp import *


qsp = GQSPPhaseFactors(phi=[0.1, 0.7, 0.2], lbd=0.2, theta=[0.5, 0.1, 0.3])

P, Q = qsp.polynomials()

print(P)
print(Q)
(-0.015093970502224184+0.0015144485808081206j) z^0 + (-0.1418776474514093-0.10604699680151528j) z^1 + (0.338315304935914+0.6647082625678592j) z^2
(0.04820987359552721+0.05723677865414935j) z^0 + (-0.19177805563707637+0.5889502301119975j) z^1 + (-0.07248509748298483+0.13268308094973394j) z^2

Note that the degrees of \(P, Q\) is 2 since \(\vec{\phi}\) and \(\vec{\theta}\) have length 3.

Laurent and analytic quantum signal processing

It is possible to pass mode='laurent' to the polynomials() method to use the signal operator \(\tilde{v} = \mathrm{diag}(z, z^{-1})\) instead of \(\tilde{w} = \mathrm{diag}(z, 1)\) in the QSP construction. This has just the effect to distribute the coefficients of \(P, Q\) in \(\{ -n, -n+2, \ldots, n-2, n \}\) instead of \(\{ 0, 1, \ldots, n \}\) (see the difference between analytic and Laurent QSP here).

from nlft_qsp import *

qsp = GQSPPhaseFactors(phi=[0.1, 0.7, 0.2], lbd=0.2, theta=[0.5, 0.1, 0.3])

P, Q = qsp.polynomials()

print(P)
print(Q)

P, Q = qsp.polynomials(mode='laurent')

print(P)
print(Q)
(-0.015093970502224184+0.0015144485808081206j) z^0 + (-0.1418776474514093-0.10604699680151528j) z^1 + (0.338315304935914+0.6647082625678592j) z^2
(0.04820987359552721+0.05723677865414935j) z^0 + (-0.19177805563707637+0.5889502301119975j) z^1 + (-0.07248509748298483+0.13268308094973394j) z^2
(-0.015093970502224184+0.0015144485808081206j) z^-2 + (-4.625929269271485e-18+0j) z^-1 + (-0.1418776474514093-0.10604699680151528j) z^0 + 0j z^1 + (0.338315304935914+0.6647082625678592j) z^2
(0.04820987359552721+0.05723677865414935j) z^-2 + (-3.700743415417188e-17+0j) z^-1 + (-0.19177805563707637+0.5889502301119975j) z^0 + (-9.25185853854297e-18+1.850371707708594e-17j) z^1 + (-0.07248509748298483+0.13268308094973394j) z^2

Using the GQSP solver

Computing the phase factors for a desired Polynomial \(P(z)\) can be done using the GQSPPhaseFactors.solve() function. Note that \(P(z)\) is assumed to be a polynomial with non-negative frequencies.

from nlft_qsp import *

P = Polynomial([1/3, 1/7, 1/5])

qsp = GQSPPhaseFactors.solve(P)

P2, _ = qsp.polynomials()

print(P)
print(P2)
(0.3333333333333333+0j) z^0 + (0.14285714285714285+0j) z^1 + (0.2+0j) z^2
(0.3333333333333332+4.914059863442753e-19j) z^0 + (0.14285714285714274+9.055503839565064e-19j) z^1 + (0.20000000000000018+4.141443976122311e-19j) z^2

Warning

Make sure that \(|P(z)| < 1 - \eta\) on the unit circle, for some sufficiently large gap \(\eta > 0\). You can estimate this with the Polynomial.sup_norm() method. The complexity and numerical stability degrades as \(\eta \rightarrow 0\), and the package might raise a WeissConvergenceError as it would not be able to find a complementary polynomial to \(P\). This is an instrinsic instability of the problem, not a limitation of the package. It is possible in principle to partially overcome this problem by increasing the floating point precision, e.g., setting the underlying complex_type to numpy.complex256, but this is not supported by all architectures.

XQSP and YQSP

XQSP and YQSP are defined respectively in the following ways:

\[ e^{i\phi_0 X} \tilde{w} e^{i\phi_1 X} \tilde{w} \cdots \tilde{w} e^{i\phi_n X} = \begin{pmatrix} P(z) & Q(z) \\ \cdot & \cdot \end{pmatrix} \]
\[ e^{i\phi_0 Y} \tilde{w} e^{i\phi_1 Y} \tilde{w} \cdots \tilde{w} e^{i\phi_n Y} = \begin{pmatrix} P(z) & Q(z) \\ \cdot & \cdot \end{pmatrix} \]

Both are special cases of GQSP where we set \(\lambda = \theta_k = 0\) for XQSP and \(\lambda = -\frac{\pi}{4}, \theta_n = \frac{\pi}{4}\) and \(\theta_0 = \cdots = \theta_{n-1} = 0\) for YQSP. As a consequence we obtain that \(P(z)\) has real coefficients and \(Q(z)\) has imaginary coefficients (for XQSP), or both have real coefficients (for YQSP).

nlft-qsp provides the XQSPPhaseFactor and YQSPPhaseFactor classes, as well as the solvers:

The first two functions work exactly like GQSPPhaseFactors.solve, while *qsp_solve_laurent are the counterparts for Laurent QSP, expecting a definite-parity Laurent polynomial \(P(z)\) with frequencies in \(\{ -n, -n+2, \ldots, n-2, n \}\).

Original GQSP ansatz

While the construction given above relates more naturally with the other QSP variants, the original formulation was slightly different.

\[ R(\theta_0, \phi_0, \lambda) \tilde{w} R(\theta_1, \phi_1, 0) \tilde{w} \cdots \tilde{w} R(\phi_n, \theta_n, 0) = \begin{pmatrix} P(z) & Q(z) \\ \cdot & \cdot \end{pmatrix} \]

where the matrix \(R\) is of the form

\[ R(\theta, \phi, \lambda) = \begin{pmatrix} e^{i(\lambda + \phi)} \cos \theta & e^{i\phi} \sin \theta \\ e^{i\lambda} \sin \theta & -\cos \theta \end{pmatrix} \]

The GQSPPhaseFactors class provides a to_mw_gqsp() method, which returns the tuple \((\vec{\theta}, \vec{\phi}, \lambda)\) in this order.

Note

This conversion only preserves \(P\). \(Q\) will turn out to be equal up to a phase.

Using the Command Line Interface

The subcommand qspx solve can be used to produce protocols for given polynomial transformations:

qspx solve -tag P.qspx -o protocol.qspx

The --type/-t option is used to specify the QSP variant. In the above example it is analytic generalized QSP. Options are [a|l][g|x|y], corresponding to analytic/Laurent generalized/X/Y QSP.

If analytic mode is chosen, the given polynomial will be taken ignoring any negative frequencies. If Laurent mode is chosen then a check on definite parity is done.

Given a QSP protocol in a file protocol.qspx, it is possible to compute the implementing polynomial.

qspx make protocol.qspx

The file contains the information about the QSP variant, but not whether it should be regarded as analytic or Laurent QSP. The --mode/-m can be used to specify it (default is analytic for Generalized QSP, laurent for XQSP and YQSP).

qspx make -ml protocol.qspx