├── .gitignore ├── LICENSE ├── LPoly.py ├── README.md ├── angle_sequence.py ├── completion.py ├── decomposition.py ├── ham_sim.py └── time.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 alibaba-edu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LPoly.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | PRES = 5000 3 | 4 | class LPoly(): 5 | ''' 6 | Laurent polynomials with parity constraint. 7 | ''' 8 | 9 | def __init__(self, coefs, dmin=0): 10 | self.coefs = numpy.array(coefs) 11 | if len(self.coefs) == 0: 12 | self.dmin = dmin 13 | self.iszero = True 14 | self.coefs = [0] 15 | else: 16 | assert(len(self.coefs.shape) == 1), self.coefs 17 | self.dmin = dmin 18 | self.iszero = False 19 | 20 | @property 21 | def dmax(self): 22 | return 2 * len(self.coefs) + self.dmin - 2 23 | 24 | @property 25 | def degree(self): 26 | """ 27 | The degree of a Laurent polynomial is defined to be the maximum absolute value of the powers over all nonzero terms. 28 | """ 29 | return max(-self.dmin, self.dmax) 30 | 31 | @property 32 | def norm(self): 33 | """ 34 | The norm of a Laurent polynomial is defined to be the l2 norm of the coefficients regarded as a vector. 35 | """ 36 | return numpy.linalg.norm(self.coefs) 37 | 38 | @property 39 | def inf_norm(self): 40 | """ 41 | The infinity norm of a Laurent polynomial is defined to be the maximum modulus over the unit circle. 42 | """ 43 | i, x = self.curve 44 | return numpy.amax( 45 | numpy.absolute(i + 1j * x)) 46 | 47 | @property 48 | def parity(self): 49 | ''' 50 | Parity of the polynomial. 0 for even parity Laurent polynomials and 1 for odd parity Laurent polynomials. 51 | ''' 52 | return self.dmin % 2 53 | 54 | @property 55 | def curve(self): 56 | values = numpy.exp(numpy.outer(numpy.linspace(-self.parity * 1j * numpy.pi, 1j*numpy.pi, PRES), range(self.dmin, self.dmax+1, 2))).dot(self.coefs) 57 | return numpy.real(values), numpy.imag(values) 58 | 59 | def __getitem__(self, key): 60 | if (key - self.dmin) % 2: 61 | return 0 62 | pos = (key - self.dmin) // 2 63 | if (pos < len(self.coefs)) and (pos >= 0): 64 | return self.coefs[pos] 65 | else: 66 | return 0 67 | 68 | def __mul__(self, other): 69 | if isinstance(other, LAlg): 70 | return LAlg(self * other.IPoly, self * other.XPoly) 71 | if not isinstance(other, LPoly): 72 | return LPoly(other * self.coefs, self.dmin) 73 | if self.iszero or other.iszero: 74 | return LPoly([]) 75 | dmin = self.dmin + other.dmin 76 | coefs = numpy.convolve(self.coefs, other.coefs) 77 | return LPoly(coefs, dmin) 78 | 79 | def __rmul__(self, other): 80 | if isinstance(other, LAlg): 81 | return LAlg(self * other.IPoly, ~self * other.XPoly) 82 | elif not isinstance(other, LPoly): 83 | return LPoly(other * self.coefs, self.dmin) 84 | 85 | def __add__(self, other): 86 | ''' 87 | Only Laurent polynomials of the same parity can be added together in order to preserve parity. 88 | ''' 89 | if self.iszero: 90 | return LPoly(other.coefs, other.dmin) 91 | if other.iszero: 92 | return LPoly(self.coefs, self.dmin) 93 | assert (self.parity == other.parity), "not of the same parity" 94 | dmin = min(self.dmin, other.dmin) 95 | dmax = max(self.dmax, other.dmax) 96 | coefs = self.aligned(dmin, dmax) + other.aligned(dmin, dmax) 97 | return LPoly(coefs, dmin) 98 | 99 | def __neg__(self): 100 | return LPoly(-1 * self.coefs, self.dmin) 101 | 102 | def __invert__(self): 103 | ''' 104 | Conjugation of a Laurent polynomial maps f(w) to f(w^-1). 105 | ''' 106 | dmin = -self.dmax 107 | coefs = self.coefs[::-1] 108 | return LPoly(coefs, dmin) 109 | 110 | def __sub__(self, other): 111 | return self + (-other) 112 | 113 | def __str__(self): 114 | return " + ".join(["{} * w ^ ({})".format(self.coefs[i], 115 | self.dmin + 2 * i) 116 | for i in range(len(self.coefs))]) 117 | 118 | def aligned(self, dmin, dmax): 119 | if(self.iszero): 120 | return numpy.zeros((dmax-dmin)//2 + 1) 121 | else: 122 | assert (dmin <= self.dmin) and (dmax >= self.dmax), "interval not valid" 123 | return numpy.hstack((numpy.zeros((self.dmin - dmin)//2), 124 | numpy.array(self.coefs), 125 | numpy.zeros((dmax - self.dmax)//2))) 126 | 127 | def eval(self, angles): 128 | ''' 129 | Evalute the Laurent polynomial f(w) at w = exp(i * angle) for angle iterating over angles. Returns a complex array . 130 | ''' 131 | if self.iszero: 132 | return 1 133 | res = self.coefs.dot(numpy.exp(1j*numpy.outer(numpy.arange(self.dmin, self.dmax + 1, 2), angles))) 134 | return res 135 | 136 | @classmethod 137 | def truncate(cls, p, dmin, dmax): 138 | lb = min(dmin, p.dmin) 139 | ub = max(dmax, p.dmax) 140 | return LPoly(p.aligned(lb, ub+2)[(dmin-lb) // 2:(dmax-ub)//2-1], dmin) 141 | 142 | @classmethod 143 | def isconsistent(cls, a, b): 144 | if a.iszero: 145 | return True 146 | if b.iszero: 147 | return True 148 | return a.parity == b.parity 149 | 150 | class LAlg(): 151 | ''' 152 | Low algebra elements with parity constraints. 153 | A Low algebra element g is a matrix-valued Laurent polynomial with the given form 154 | g(w) = IPoly(w) + XPoly(w) * iX, 155 | where IPoly and XPoly are real Laurent polynomials sharing the same parity, 156 | and the elements w and X satisfy the relations (iX)^2 = Id and XwXw = Id. 157 | ''' 158 | 159 | def __init__(self, IPoly=LPoly([], 0), XPoly=LPoly([], 0)): 160 | self.IPoly = IPoly 161 | self.XPoly = XPoly 162 | assert LPoly.isconsistent(self.IPoly, self.XPoly),\ 163 | "The algebra element does not have a consistent parity" 164 | 165 | @property 166 | def degree(self): 167 | return max(self.IPoly.degree, self.XPoly.degree) 168 | 169 | @property 170 | def norm(self): 171 | return numpy.sqrt(self.IPoly.norm**2 + self.XPoly.norm**2) 172 | 173 | @property 174 | def parity(self): 175 | return self.IPoly.parity 176 | 177 | def __str__(self): 178 | res = [] 179 | if not self.IPoly.iszero: 180 | res.append(str(self.IPoly)) 181 | if not self.XPoly.iszero: 182 | res.append("( " + str(self.XPoly) + " ) * iX") 183 | return " + ".join(res) 184 | 185 | def __add__(self, other): 186 | if isinstance(other, LPoly): 187 | return LAlg(self.IPoly + other, self.XPoly) 188 | return LAlg(self.IPoly + other.IPoly, self.XPoly + other.XPoly) 189 | 190 | def __neg__(self): 191 | return LAlg(-self.IPoly, -self.XPoly) 192 | 193 | def __sub__(self, other): 194 | return self + (-other) 195 | 196 | def __invert__(self): 197 | ''' 198 | Conjugation of g(w) = A(w) + B(w) * iX is ~g(w) = A(w^-1) - B(w) * iX. 199 | ''' 200 | return LAlg(~self.IPoly, -self.XPoly) 201 | 202 | def __mul__(self, other): 203 | if isinstance(other, LPoly): 204 | return LAlg(self.IPoly * other, self.XPoly * ~other) 205 | if not isinstance(other, LAlg): 206 | return LAlg(self.IPoly * other, self.XPoly * other) 207 | return LAlg(self.IPoly * other.IPoly - self.XPoly * (~other.XPoly), 208 | self.IPoly * other.XPoly + self.XPoly * (~other.IPoly)) 209 | 210 | @property 211 | def pnorm(self): 212 | return (self * (~self)).IPoly 213 | 214 | @property 215 | def unitarity(self): 216 | ''' 217 | Unitarity of an element A is defined to be ||Id-a*~a||_2^2. 218 | ''' 219 | return (LPoly([1]) - self.pnorm).norm 220 | 221 | @property 222 | def angle(self): 223 | ''' 224 | For degree 0 element M, the corresponding angle is defined such that M \propto exp(angle * iX). 225 | ''' 226 | assert (self.degree == 0), "deg = {}".format(self.degree) 227 | return numpy.angle(self.IPoly[0]+self.XPoly[0]*1j) 228 | 229 | @property 230 | def left_and_right_angles(self): 231 | ''' 232 | For a degree 1 element g(w) = exp(a * iX) * w * exp(b * iX), return the left and right rotation angles [a, b]. 233 | Note that g(Id) = exp((a + b) * iX) and g(iZ) = exp((a-b) * iX) * iZ. 234 | ''' 235 | assert self.degree == 1 236 | summation = numpy.angle(self.IPoly.eval(0)[0] + 1j * self.XPoly.eval(0)[0]) 237 | difference = numpy.angle(self.IPoly.eval(numpy.pi / 2)[0] - 1j * self.XPoly.eval(numpy.pi / 2)[0]) - numpy.pi / 2 238 | res = [(summation + difference) / 2, (summation - difference) / 2] 239 | return res 240 | 241 | @classmethod 242 | def rotation(cls, ang): 243 | ''' 244 | Degree 0 rotation with a certain angle. Inverse of `LAlg.angle`. 245 | ''' 246 | return LAlg(LPoly([numpy.cos(ang)]), LPoly([numpy.sin(ang)])) 247 | 248 | @property 249 | def curve(self): 250 | i, z = self.IPoly.curve 251 | y, x = self.XPoly.curve 252 | return numpy.angle(1j * i - y) * numpy.sqrt(1 - x**2 - z**2), z, x 253 | 254 | @classmethod 255 | def truncate(cls, g, dmin, dmax): 256 | return LAlg(LPoly.truncate(g.IPoly, dmin, dmax), 257 | LPoly.truncate(g.XPoly, dmin, dmax)) 258 | 259 | @classmethod 260 | def generator(cls, ang): 261 | ''' 262 | Generator elements of the unitary group; each one is w conjugated by some rotation. 263 | ''' 264 | return cls.rotation(ang) * w * cls.rotation(-ang) 265 | 266 | @classmethod 267 | def unitary_from_conjugations(cls, ang): 268 | ''' 269 | Generating a special unitary element from the conjugation angles. 270 | ''' 271 | res = Id 272 | for i in ang: 273 | res *= cls.generator(i) 274 | return res 275 | 276 | @classmethod 277 | def unitary_from_angles(cls, ang): 278 | ''' 279 | Generating a unitary element from the rotation angles sandwiching w. 280 | ''' 281 | res = cls.rotation(ang[0]) 282 | for i in ang[1:]: 283 | res = res * w * cls.rotation(i) 284 | return res 285 | 286 | 287 | 288 | # Definition of elements 289 | 290 | Id = LPoly([1]) 291 | w = LPoly([1], 1) 292 | iX = LAlg(XPoly=LPoly([1])) 293 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angle Sequence: Finding Angles for Quantum Signal Processing 2 | 3 | ## Introduction 4 | 5 | [Quantum signal processing](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.118.010501) is a framework for quantum algorithms including Hamiltonian simulation, quantum linear system solving, amplitude amplification, etc. 6 | 7 | Quantum signal processing performs spectral transformation of any unitary $U$, given access to an ancilla qubit, a controlled version of $U$ and single-qubit rotations on the ancilla qubit. It first truncates an arbitrary spectral transformation function into a Laurent polynomial, then finds a way to decompose the Laurent polynomial into a sequence of products of controlled-$U$ and single qubit rotations on the ancilla. Such routines achieve optimal gate complexity for many of the quantum algorithmic tasks mentioned above. 8 | 9 | Our software package provides a lightweight solution for classically solving for the single-qubit rotation angles given the Laurent polynomial, a task called *angle sequence finding* or *angle finding*. Our package only depends on `numpy` and `scipy` and works under machine precision. Please see below for a chart giving the performance of our algorithm for the task of Hamiltonian simulation: 10 | 11 |