├── .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 |
12 | 13 |
14 | 15 | Please see the [arXiv manuscript](https://arxiv.org/abs/2003.02831) for more details. 16 | 17 | ## Code Structure and Usage 18 | 19 | 20 | * `angle_sequence.py` is the main module of the algorithm. 21 | * `LPoly.py` defines two classes `LPoly` and `LAlg`, representing Laurent polynomials and Low algebra elements respectively. 22 | * `completion.py` describes the completion algorithm: Given a Laurent polynomial element $F(\tilde{w})$, find its counterpart $G(\tilde{w})$ such that $F(\tilde{w})+G(\tilde{w})*iX$ is a unitary element. 23 | * `decomposition.py` describes the halving algorithm: Given a unitary parity Low algebra element $V(\tilde{w})$, decompose it as a unique product of degree-0 rotations $\exp\{i\theta X\}$ and degree-1 monomials $w$. 24 | * `ham_sim.py` shows an example of how the angle sequence for Hamiltonian simulation can be found. 25 | 26 | To find the angle sequence corresponding to a real Laurent polynomial $A(\tilde{w}) = \sum_{i=-n}\^n a_i\tilde{w}\^i$, simply run: 27 | 28 | from angle_sequence import angle_sequence 29 | ang_seq = angle_sequence([a_{-n}, a_{-n+2}, ..., a_n]) 30 | print(ang_seq) 31 | 32 | 33 | ## Publication 34 | * Rui Chao, Dawei Ding, András Gilyén, Cupjin Huang, and Mario Szegedy. [Finding Angles for Quantum Signal Processing with Machine Precision](https://arxiv.org/abs/2003.02831). arXiv preprint arXiv:2003.02831 (2020). 35 | -------------------------------------------------------------------------------- /angle_sequence.py: -------------------------------------------------------------------------------- 1 | import completion 2 | import decomposition 3 | import LPoly 4 | import time 5 | 6 | def angle_sequence(p, eps=1e-4, suc=1-1e-4): 7 | """ 8 | Solve for the angle sequence corresponding to the array p, with eps error budget and suc success probability. 9 | The bigger the error budget and the smaller the success probability, the better the numerical stability of the process. 10 | """ 11 | p = LPoly.LPoly(p, -len(p) + 1) 12 | # Capitalization: eps/2 amount of error budget is put to the highest power for sake of numerical stability. 13 | p_new = suc * (p + LPoly.LPoly([eps / 4], p.degree) + LPoly.LPoly([eps / 4], -p.degree)) 14 | 15 | # Completion phase 16 | t = time.time() 17 | g = completion.completion_from_root_finding(p_new) 18 | t_comp = time.time() 19 | print("Completion part finished within time ", t_comp - t) 20 | 21 | # Decomposition phase 22 | seq = decomposition.angseq(g) 23 | t_dec = time.time() 24 | print("Decomposition part finished within time ", t_dec - t_comp) 25 | print(seq) 26 | 27 | # Make sure that the reconstructed element lies in the desired error tolerance regime 28 | g_recon = LPoly.LAlg.unitary_from_angles(seq) 29 | final_error = (1/suc * g_recon.IPoly - p).inf_norm 30 | if final_error < eps: 31 | return seq 32 | else: 33 | raise ValueError("The angle finding program failed on given instance, with an error of {}. Please relax the error budget and/ or the success probability.".format(final_error)) -------------------------------------------------------------------------------- /completion.py: -------------------------------------------------------------------------------- 1 | from LPoly import LPoly, LAlg, Id 2 | import numpy 3 | 4 | def root_classes(p): 5 | ''' 6 | Find all the roots of Id - (p * ~p) that lies within the upper unit circle. 7 | ''' 8 | poly = (Id - (p * ~p)).coefs 9 | roots = numpy.roots(poly) 10 | # poly is a real, self-inverse Laurent polynomial with no root on the unit circle. All real roots come in reciprocal pairs, 11 | # and all complex roots come in quadruples (r, r*, 1/r, 1/r*). 12 | # For each pair of real roots, select the one within the unit circle. 13 | # For each quadruple of complex roots, select the pair within the unit circle. 14 | imag_roots = [] 15 | real_roots = [] 16 | for i in roots: 17 | if (numpy.abs(i) < 1) and (numpy.imag(i) > -1e-8): 18 | if numpy.imag(i) == 0.: 19 | real_roots.append(numpy.real(i)) 20 | else: 21 | imag_roots.append(i) 22 | norm = poly[-1] 23 | return real_roots, imag_roots, norm 24 | 25 | def poly_from_roots(real_roots, imag_roots, norm, seed=None): 26 | ''' 27 | Construct the counter part with the roots and the norm. 28 | ''' 29 | # Randomly choose whether to pick the real root (the pair of complex roots) inside or outside the unit circle. 30 | # This is to reduce the range of the coefficients appearing in the final product. 31 | degree = len(real_roots) + 2 * len(imag_roots) 32 | lst = [] 33 | if seed is None: 34 | seed = numpy.random.randint(2, size=len(imag_roots) + len(real_roots)) 35 | for i, root in enumerate(imag_roots): 36 | if seed[i]: 37 | root = 1 / root 38 | lst.append(LPoly([numpy.abs(root) ** 2, -2 * numpy.real(root), 1])) 39 | for i, root in enumerate(real_roots): 40 | if seed[i + len(imag_roots)]: 41 | root = 1 / root 42 | lst.append(LPoly([-root, 1])) 43 | 44 | # Multiply all the polynomial factors via fft for numerical stability. 45 | pp = int(numpy.floor(numpy.log2(degree)))+1 46 | lst_fft = numpy.pi * numpy.linspace(0, 1 - 1/2**pp, 2**pp) 47 | coef_mat = numpy.log(numpy.array([i.eval(lst_fft) for i in lst])) 48 | coef_fft = numpy.exp(numpy.sum(coef_mat, axis=0)) 49 | coefs = numpy.real(numpy.fft.fft(coef_fft, 1 << pp))[:degree+1] / (1 << pp) 50 | 51 | 52 | # Normalization 53 | xpoly = LPoly(coefs * numpy.sqrt(norm / coefs[0]), -len(coefs) + 1) 54 | return xpoly 55 | 56 | def completion_from_root_finding(p, seed=None): 57 | """ 58 | Find a Low Algebra element g such that the identity components are given by the input p. 59 | """ 60 | real_roots, imag_roots, norm = root_classes(p) 61 | xpoly = poly_from_roots(real_roots, imag_roots, norm) 62 | return LAlg(p, xpoly) 63 | -------------------------------------------------------------------------------- /decomposition.py: -------------------------------------------------------------------------------- 1 | from LPoly import LPoly, LAlg, w 2 | import numpy 3 | from scipy.linalg import toeplitz 4 | 5 | def linear_system(g, ldeg): 6 | """ 7 | Let vec(l) = l.IPoly.coefs + l.XPoly.coefs, i.e. to regard a Low Algebra element l of degree ldeg as a real vector in R^{2n+2}. 8 | M is the matrix representation of g in the sense that M * vec(l) = vec(l * g) for all l with degree ldeg. 9 | The linear system m, s consist of two parts; the first part ensures that the degree of g * l does not exceed deg - ldeg; 10 | the second part ensures that l(Id) = Id, fixing the SU(2) freedom left from the previous set of constraints. 11 | """ 12 | deg = g.degree 13 | aligned_icoefs = g.IPoly.aligned(-deg, deg) 14 | aligned_xcoefs = g.XPoly.aligned(-deg, deg) 15 | def vec_to_mat(vec): 16 | return toeplitz(numpy.hstack((vec, [0] * ldeg)), [0] * (ldeg + 1)) 17 | M = numpy.vstack((numpy.hstack((vec_to_mat(aligned_icoefs), 18 | vec_to_mat(-aligned_xcoefs[::-1]))), 19 | numpy.hstack((vec_to_mat(aligned_xcoefs), 20 | vec_to_mat(aligned_icoefs[::-1]))))) 21 | 22 | a = numpy.ones(ldeg + 1, dtype=float) 23 | b = numpy.zeros(ldeg + 1, dtype=float) 24 | m = numpy.vstack((numpy.hstack((a, b)), numpy.hstack((b, a)), M[:ldeg, :], M[deg+1: deg+2*ldeg+1, :], M[-ldeg:, :])) 25 | s = numpy.zeros(m.shape[0], dtype=float) 26 | s[0] = 1 27 | return m, s 28 | 29 | def decompose(g, ldeg): 30 | """ 31 | Let 32 | g = exp(iθ_0 * X) * w * exp(iθ_1 * X) * ... w * exp(iθ_deg * X) 33 | One wants to solve for 34 | l = exp(iθ_0 * X) * w * exp(iθ_1 * X) * ... w * exp(-i(Σ_{i=1}^{deg-ldeg-1}θ_j) * X), 35 | and 36 | r = ~l * g = (exp(iΣ_{j=1}^{deg-ldeg}θ_j * X) * w * exp(iθ_{deg-ldeg+1} * X) * ... w * exp(iθ_deg * X). 37 | 38 | The linear system (m, s) is such that 39 | 40 | deg(l * g) <= deg - ldeg 41 | l(Id) = Id 42 | 43 | One can show that such a system has a unique solution. 44 | """ 45 | deg = g.degree 46 | m, s = linear_system(g, ldeg) 47 | lstsq = numpy.linalg.lstsq(m, s, rcond=-1) 48 | v = lstsq[0] 49 | 50 | li = v[:ldeg+1] 51 | lx = v[-ldeg-1:] 52 | l = LAlg(LPoly(li, -ldeg), LPoly(lx, -ldeg)) 53 | r = LAlg.truncate(l * g, -g.degree + ldeg, g.degree - ldeg) 54 | 55 | return ~l, r 56 | 57 | def angseq(g): 58 | ''' 59 | Divide-and-conquer approach to solve for the whole angle sequence. 60 | ''' 61 | deg = g.degree 62 | if deg == 1: 63 | return g.left_and_right_angles 64 | else: 65 | l, r = decompose(g, deg//2) 66 | a = angseq(l) 67 | b = angseq(r) 68 | return a[:-1] + [a[-1] + b[0]] + b[1:] 69 | -------------------------------------------------------------------------------- /ham_sim.py: -------------------------------------------------------------------------------- 1 | from scipy.special import jn 2 | import numpy 3 | import time 4 | from angle_sequence import angle_sequence 5 | from LPoly import LPoly 6 | 7 | 8 | def hamiltonian_coefficients(tau, eps): 9 | n = 2*int(numpy.e / 4 * tau - numpy.log(eps) / 2) 10 | return jn(numpy.arange(-n, n + 1, 1), tau) 11 | 12 | def ham_sim(tau, eps, suc): 13 | t = time.time() 14 | a = hamiltonian_coefficients(tau, eps / 10) 15 | return angle_sequence(a, .9 * eps, suc), time.time()-t 16 | 17 | ham_sim(100, 1e-4, 1-1e-4) 18 | -------------------------------------------------------------------------------- /time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba-edu/angle-sequence/39b8e605b995d94ed620cba035cdc8e4ef07dc95/time.png --------------------------------------------------------------------------------