12
12
# See the License for the specific language governing permissions and
13
13
# limitations under the License.
14
14
from functools import cached_property
15
- from typing import Optional , Sequence , Tuple
15
+ from typing import Dict , Sequence , Set , Tuple , TYPE_CHECKING
16
16
17
17
import cirq
18
18
import numpy as np
19
+ import scipy
19
20
from attrs import field , frozen
20
21
from numpy .polynomial import Polynomial
21
22
from numpy .typing import NDArray
22
23
23
- from qualtran import GateWithRegisters , QBit , Register , Signature , Adjoint
24
+ from qualtran import Bloq , GateWithRegisters , QBit , Register , Signature
25
+ from qualtran .bloqs .basic_gates import Ry , ZPowGate
26
+ from qualtran .bloqs .qubitization_walk_operator import QubitizationWalkOperator
24
27
25
- # TODO refactor using Bloq instead of cirq-ft infra
28
+ if TYPE_CHECKING :
29
+ from qualtran import BloqBuilder , SoquetT
30
+ from qualtran .resource_counting import BloqCountT , SympySymbolAllocator
26
31
27
32
28
33
@frozen
29
- class SU2RotationGate (GateWithRegisters ):
34
+ class SU2RotationGate (Bloq ):
30
35
theta : float
31
36
phi : float
32
37
lambd : float
@@ -65,15 +70,12 @@ def rotation_matrix(self):
65
70
]
66
71
)
67
72
68
- def decompose_from_registers (
69
- self , * , context : cirq .DecompositionContext , q : NDArray [cirq .Qid ]
70
- ) -> cirq .OP_TREE :
71
- qubit = q [0 ]
72
-
73
- yield cirq .Rz (rads = np .pi - self .lambd ).on (qubit )
74
- yield cirq .Ry (rads = 2 * self .theta ).on (qubit )
75
- yield cirq .Rz (rads = - self .phi ).on (qubit )
76
- yield cirq .GlobalPhaseGate (np .exp (1j * (np .pi + self .lambd + self .phi ) / 2 )).on ()
73
+ def build_composite_bloq (self , bb : 'BloqBuilder' , q : 'SoquetT' ) -> Dict [str , 'SoquetT' ]:
74
+ q = bb .add (ZPowGate (exponent = 2 , global_shift = 0.5 ), q = q )
75
+ q = bb .add (ZPowGate (exponent = 1 - self .lambd / np .pi , global_shift = - 1 ), q = q )
76
+ q = bb .add (Ry (angle = 2 * self .theta ), q = q )
77
+ q = bb .add (ZPowGate (exponent = - self .phi / np .pi , global_shift = - 1 ), q = q )
78
+ return {'q' : q }
77
79
78
80
79
81
def qsp_complementary_polynomial (
@@ -261,7 +263,7 @@ class GeneralizedQSP(GateWithRegisters):
261
263
to obtain $U^{-k} P(U)$. (Theorem 6)
262
264
This gate represents the following unitary:
263
265
264
- $$ \begin{bmatrix} U^{-k} P(U) & \cdot \\ \cdot & \cdot \end{bmatrix} $$
266
+ $$ \begin{bmatrix} U^{-k} P(U) & \cdot \\ Q(U) & \cdot \end{bmatrix} $$
265
267
266
268
The polynomial $P$ must satisfy:
267
269
$\abs{P(e^{i \theta})}^2 \le 1$ for every $\theta \in \mathbb{R}$.
@@ -280,18 +282,19 @@ class GeneralizedQSP(GateWithRegisters):
280
282
281
283
U : GateWithRegisters
282
284
P : Sequence [complex ]
283
- _precomputed_Q : Optional [ Sequence [complex ]] = None
285
+ Q : Sequence [complex ]
284
286
negative_power : int = field (default = 0 , kw_only = True )
285
287
286
288
@cached_property
287
289
def signature (self ) -> Signature :
288
290
return Signature ([Register ('signal' , QBit ()), * self .U .signature ])
289
291
290
- @cached_property
291
- def Q (self ):
292
- if self ._precomputed_Q is not None :
293
- return self ._precomputed_Q
294
- return qsp_complementary_polynomial (self .P )
292
+ @staticmethod
293
+ def from_qsp_polynomial (
294
+ U : GateWithRegisters , P : Sequence [complex ], * , negative_power : int = 0
295
+ ) -> 'GeneralizedQSP' :
296
+ Q = qsp_complementary_polynomial (P )
297
+ return GeneralizedQSP (U , P , Q , negative_power = negative_power )
295
298
296
299
@cached_property
297
300
def _qsp_phases (self ) -> Tuple [Sequence [float ], Sequence [float ], float ]:
@@ -309,16 +312,24 @@ def _phi(self) -> Sequence[float]:
309
312
def _lambda (self ) -> float :
310
313
return self ._qsp_phases [2 ]
311
314
315
+ @cached_property
316
+ def signal_rotations (self ) -> NDArray [SU2RotationGate ]:
317
+ return np .array (
318
+ [
319
+ SU2RotationGate (theta , phi , self ._lambda if i == 0 else 0 )
320
+ for i , (theta , phi ) in enumerate (zip (self ._theta , self ._phi ))
321
+ ]
322
+ )
323
+
312
324
def decompose_from_registers (
313
325
self , * , context : cirq .DecompositionContext , signal , ** quregs : NDArray [cirq .Qid ]
314
326
) -> cirq .OP_TREE :
315
- assert len (signal ) == 1
316
327
signal_qubit = signal [0 ]
317
328
318
329
num_inverse_applications = self .negative_power
319
330
320
- yield SU2RotationGate ( self ._theta [0 ], self . _phi [ 0 ], self . _lambda ) .on (signal_qubit )
321
- for theta , phi in zip ( self ._theta [1 :], self . _phi [ 1 :]) :
331
+ yield self .signal_rotations [0 ].on (signal_qubit )
332
+ for signal_rotation in self .signal_rotations [1 :]:
322
333
# TODO debug controlled adjoint bloq serialization
323
334
# if num_inverse_applications > 0:
324
335
# # apply C-U^\dagger
@@ -327,12 +338,49 @@ def decompose_from_registers(
327
338
# else:
328
339
# apply C[0]-U
329
340
yield self .U .on_registers (** quregs ).controlled_by (signal_qubit , control_values = [0 ])
330
- yield SU2RotationGate ( theta , phi , 0 ) .on (signal_qubit )
341
+ yield signal_rotation .on (signal_qubit )
331
342
332
- while num_inverse_applications > 0 :
343
+ for _ in range ( num_inverse_applications ) :
333
344
yield self .U .adjoint ().on_registers (** quregs )
334
- num_inverse_applications -= 1
335
345
336
346
def __hash__ (self ):
337
- # TODO hash properly
338
- return hash ((self .U , * list (self .P ), * list (self .Q ), self .negative_power ))
347
+ return hash ((self .U , * self .signal_rotations , self .negative_power ))
348
+
349
+ def build_call_graph (self , ssa : 'SympySymbolAllocator' ) -> Set ['BloqCountT' ]:
350
+ return {(self .U , max (len (self .P ), self .negative_power ))} | {
351
+ (rotation , 1 ) for rotation in self .signal_rotations
352
+ }
353
+
354
+
355
+ @frozen
356
+ class HamiltonianSimulationByGQSP (GateWithRegisters ):
357
+ WalkOperator : QubitizationWalkOperator
358
+ t : float = field (kw_only = True )
359
+ alpha : float = field (kw_only = True )
360
+ precision : float = field (kw_only = True )
361
+
362
+ @cached_property
363
+ def degree (self ) -> int :
364
+ log_precision = np .log (1 / self .precision )
365
+ degree = self .t * self .alpha + log_precision / np .log (log_precision )
366
+ return int (np .ceil (degree ))
367
+
368
+ @cached_property
369
+ def gqsp (self ) -> GeneralizedQSP :
370
+ coeff_indices = np .arange (- self .degree , self .degree + 1 )
371
+ approx_cos = 1j ** coeff_indices * scipy .special .jv (coeff_indices , self .t * self .alpha )
372
+
373
+ return GeneralizedQSP (
374
+ self .WalkOperator , approx_cos , np .zeros (2 * self .degree + 1 ), negative_power = self .degree
375
+ )
376
+
377
+ @cached_property
378
+ def signature (self ) -> 'Signature' :
379
+ return self .gqsp .signature
380
+
381
+ def build_composite_bloq (self , bb : 'BloqBuilder' , ** soqs : 'SoquetT' ) -> Dict [str , 'SoquetT' ]:
382
+ # TODO call prepare oracle
383
+ # soqs |= bb.add_d(self.WalkOperator.prepare, selection=soqs['selection'])
384
+ soqs = bb .add_d (self .gqsp , ** soqs )
385
+ # soqs |= bb.add_d(self.WalkOperator.prepare.adjoint(), selection=soqs['selection'])
386
+ return soqs
0 commit comments