19
19
from typing import cast
20
20
21
21
import numpy as np
22
+ from attrs import field , frozen
22
23
23
24
from cirq import circuits , ops
24
25
from cirq .transformers .gauge_compiling .multi_moment_gauge_compiling import (
25
26
MultiMomentGaugeTransformer ,
26
27
)
27
28
29
+ _PAULIS : np .ndarray = np .array ((ops .I , ops .X , ops .Y , ops .Z ), dtype = object )
30
+ _COMMUTING_GATES = {ops .I , ops .Z } # I,Z Commute with ZPowGate and CZPowGate; X,Y anti-commute.
28
31
32
+
33
+ def _merge_pauliandzpow (left : _PauliAndZPow , right : _PauliAndZPow ) -> _PauliAndZPow :
34
+ # 1. Commute left.zpow and right.pauli:
35
+ # ─left.pauli─left.zpow─right.pauli─right.zpow─
36
+ # ==> ─left.pauli─right.pauli─(+/-left.zpow─right.zpow)─
37
+ if right .pauli in _COMMUTING_GATES :
38
+ new_zpow_exp = left .zpow .exponent + right .zpow .exponent
39
+ else :
40
+ new_zpow_exp = - left .zpow .exponent + right .zpow .exponent
41
+
42
+ # 2. Merge left.pauli and right.pauli
43
+ new_pauli = left .pauli
44
+ if right .pauli is not ops .I :
45
+ if new_pauli is ops .I :
46
+ new_pauli = right .pauli
47
+ else :
48
+ # left.pauli * right.pauli
49
+ new_pauli = cast (ops .Pauli , new_pauli ).phased_pauli_product (
50
+ cast (ops .Pauli , right .pauli )
51
+ )[1 ]
52
+
53
+ return _PauliAndZPow (pauli = new_pauli , zpow = ops .ZPowGate (exponent = new_zpow_exp ))
54
+
55
+
56
+ @frozen
29
57
class _PauliAndZPow :
30
- """In pulling through, one qubit gate can be represented by a Pauli and an Rz gate.
31
- The order is --Pauli--ZPowGate--.
58
+ """A gate represented by a Pauli followed by a ZPowGate.
59
+
60
+ The order is ─Pauli──ZPowGate─.
61
+
62
+ Attributes:
63
+ pauli: The Pauli gate.
64
+ zpow: The ZPowGate.
32
65
"""
33
66
34
67
pauli : ops .Pauli | ops .IdentityGate = ops .I
35
68
zpow : ops .ZPowGate = ops .ZPowGate (exponent = 0 )
36
69
37
- commuting_gates = {ops .I , ops .Z } # I,Z Commute with ZPowGate and CZPowGate; X,Y anti-commute.
38
-
39
- def __init__ (
40
- self ,
41
- pauli : ops .Pauli | ops .IdentityGate = ops .I ,
42
- zpow : ops .ZPowGate = ops .ZPowGate (exponent = 0 ),
43
- ) -> None :
44
- self .pauli = pauli
45
- self .zpow = zpow
46
-
47
- def _merge_left_zpow (self , left : ops .ZPowGate ):
48
- """Merges ZPowGate from left."""
49
- if self .pauli in self .commuting_gates :
50
- self .zpow = ops .ZPowGate (exponent = left .exponent + self .zpow .exponent )
51
- else :
52
- self .zpow = ops .ZPowGate (exponent = - left .exponent + self .zpow .exponent )
70
+ def merge_left (self , left : _PauliAndZPow ) -> _PauliAndZPow :
71
+ """Merges another `_PauliAndZPow` from the left.
53
72
54
- def _merge_right_zpow ( self , right : ops . ZPowGate ):
55
- """Merges ZPowGate from right."""
56
- self . zpow = ops . ZPowGate ( exponent = right . exponent + self . zpow . exponent )
73
+ Calculates `─left─ self─` and returns a new `_PauliAndZPow` instance.
74
+ """
75
+ return _merge_pauliandzpow ( left , self )
57
76
58
- def _merge_left_pauli (self , left : ops .Pauli ):
59
- """Merges --left_pauli--self--."""
60
- if self .pauli == ops .I :
61
- self .pauli = left
62
- else :
63
- self .pauli = left .phased_pauli_product (self .pauli )[1 ]
77
+ def merge_right (self , right : _PauliAndZPow ) -> _PauliAndZPow :
78
+ """Merges another `_PauliAndZPow` from the right.
64
79
65
- def _merge_right_pauli (self , right : ops .Pauli ):
66
- """Merges --self--right_pauli--."""
67
- if self .pauli == ops .I :
68
- self .pauli = right
69
- else :
70
- self .pauli = right .phased_pauli_product (self .pauli )[1 ]
71
- if right not in self .commuting_gates :
72
- self .zpow = ops .ZPowGate (exponent = - self .zpow .exponent )
73
-
74
- def merge_left (self , left : _PauliAndZPow ) -> None :
75
- """Inplace merge other from left."""
76
- self ._merge_left_zpow (left .zpow )
77
- if left .pauli != ops .I :
78
- self ._merge_left_pauli (cast (ops .Pauli , left .pauli ))
79
-
80
- def merge_right (self , right : _PauliAndZPow ) -> None :
81
- """Inplace merge other from right."""
82
- if right .pauli != ops .I :
83
- self ._merge_right_pauli (cast (ops .Pauli , right .pauli ))
84
- self ._merge_right_zpow (right .zpow )
80
+ Calculates `─self─right─` and returns a new `_PauliAndZPow` instance.
81
+ """
82
+ return _merge_pauliandzpow (self , right )
85
83
86
84
def after_cphase (
87
85
self , cphase : ops .CZPowGate
@@ -92,8 +90,8 @@ def after_cphase(
92
90
A tuple of
93
91
(updated cphase gate, pull_through of this qubit, pull_through of the other qubit).
94
92
"""
95
- if self .pauli in self . commuting_gates :
96
- return cphase , self , _PauliAndZPow ()
93
+ if self .pauli in _COMMUTING_GATES :
94
+ return cphase , _PauliAndZPow ( self . pauli , self . zpow ) , _PauliAndZPow ()
97
95
else :
98
96
# Taking self.pauli==X gate as an example:
99
97
# 0: ─X─Z^t──@────── 0: ─X──@─────Z^t─ 0: ─@──────X──Z^t──
@@ -103,29 +101,29 @@ def after_cphase(
103
101
# add an extra Rz rotation on the other qubit.
104
102
return (
105
103
cast (ops .CZPowGate , cphase ** - 1 ),
106
- self ,
104
+ _PauliAndZPow ( self . pauli , self . zpow ) ,
107
105
_PauliAndZPow (zpow = ops .ZPowGate (exponent = cphase .exponent )),
108
106
)
109
107
110
108
def after_pauli (self , pauli : ops .Pauli | ops .IdentityGate ) -> _PauliAndZPow :
111
109
"""Calculates ─self─pauli─ ==> ─pauli─output─."""
112
- if pauli in self . commuting_gates :
110
+ if pauli in _COMMUTING_GATES :
113
111
return _PauliAndZPow (self .pauli , self .zpow )
114
112
else :
115
113
return _PauliAndZPow (self .pauli , ops .ZPowGate (exponent = - self .zpow .exponent ))
116
114
117
115
def after_zpow (self , zpow : ops .ZPowGate ) -> tuple [ops .ZPowGate , _PauliAndZPow ]:
118
- """Calculates ─self─zpow─ ==> ─zpow' ─output─."""
119
- if self .pauli in self . commuting_gates :
120
- return zpow , self
116
+ """Calculates ─self─zpow─ ==> ─+/- zpow─output─."""
117
+ if self .pauli in _COMMUTING_GATES :
118
+ return zpow , _PauliAndZPow ( self . pauli , self . zpow )
121
119
else :
122
120
return ops .ZPowGate (exponent = - zpow .exponent ), self
123
121
124
122
def __str__ (self ) -> str :
125
123
return f"─{ self .pauli } ──{ self .zpow } ─"
126
124
127
125
def to_single_qubit_gate (self ) -> ops .PhasedXZGate | ops .ZPowGate | ops .IdentityGate :
128
- """Converts the _PhasedXYAndRz to a single-qubit gate."""
126
+ """Converts the _PauliAndZPow to a single-qubit gate."""
129
127
exp = self .zpow .exponent
130
128
match self .pauli :
131
129
case ops .I :
@@ -137,23 +135,17 @@ def to_single_qubit_gate(self) -> ops.PhasedXZGate | ops.ZPowGate | ops.Identity
137
135
case ops .Y :
138
136
return ops .PhasedXZGate (x_exponent = 1 , z_exponent = exp - 1 , axis_phase_exponent = 0 )
139
137
case _: # ops.Z
140
- if (exp + 1 ) % 2 == 0 :
141
- return ops .I
142
138
return ops .ZPowGate (exponent = 1 + exp )
143
139
144
140
145
141
def _pull_through_single_cphase (
146
142
cphase : ops .CZPowGate , input0 : _PauliAndZPow , input1 : _PauliAndZPow
147
143
) -> tuple [ops .CZPowGate , _PauliAndZPow , _PauliAndZPow ]:
148
144
"""Pulls input0 and input1 through a CZPowGate.
149
- Input:
150
- 0: ─(input0)─@─────
151
- │
152
- 1: ─(input1)─@^exp─
153
- Output:
154
- 0: ─@────────(output0)─
155
- │
156
- 1: ─@^+/-exp─(output1)─
145
+ Input: Output:
146
+ 0: ─(input0)─@───── 0: ─@────────(output0)─
147
+ │ ==> │
148
+ 1: ─(input1)─@^exp─ 1: ─@^+/-exp─(output1)─
157
149
"""
158
150
159
151
# Step 1; pull input0 through CZPowGate.
@@ -167,37 +159,45 @@ def _pull_through_single_cphase(
167
159
# ==> │ ==> │
168
160
# 1: ─@^+/-exp───pulled1────output1─ 1: ─@^+/-exp─output1─
169
161
output_cphase , pulled1 , pulled0 = input1 .after_cphase (output_cphase )
170
- output0 .merge_left (pulled0 )
171
- output1 .merge_left (pulled1 )
162
+ output0 = output0 .merge_left (pulled0 )
163
+ output1 = output1 .merge_left (pulled1 )
172
164
173
165
return output_cphase , output0 , output1
174
166
175
167
176
168
_TARGET_GATESET : ops .Gateset = ops .Gateset (ops .CZPowGate )
177
- _SUPPORTED_GATESET : ops .Gateset = ops .Gateset (ops .Pauli , ops .IdentityGate , ops .Rz , ops . ZPowGate )
169
+ _SUPPORTED_GATESET : ops .Gateset = ops .Gateset (ops .Pauli , ops .IdentityGate , ops .ZPowGate )
178
170
179
171
172
+ @frozen
180
173
class CPhaseGaugeTransformerMM (MultiMomentGaugeTransformer ):
174
+ """A gauge transformer for the cphase gate."""
181
175
182
- def __init__ ( self , supported_gates = _SUPPORTED_GATESET ):
183
- super (). __init__ ( target = _TARGET_GATESET , supported_gates = supported_gates )
176
+ target : ops . GateFamily | ops . Gateset = field ( default = _TARGET_GATESET , init = False )
177
+ supported_gates : ops . GateFamily | ops . Gateset = field ( default = _SUPPORTED_GATESET )
184
178
185
179
def sample_left_moment (
186
- self , active_qubits : frozenset [ops .Qid ], rng : np .random .Generator = np . random . default_rng ()
180
+ self , active_qubits : frozenset [ops .Qid ], rng : np .random .Generator
187
181
) -> circuits .Moment :
188
- return circuits .Moment (
189
- [
190
- rng .choice (
191
- np .array ([ops .I , ops .X , ops .Y , ops .Z ], dtype = ops .Gate ),
192
- p = [0.25 , 0.25 , 0.25 , 0.25 ],
193
- ).on (q )
194
- for q in active_qubits
195
- ]
196
- )
182
+ """Samples a random single-qubit moment to be inserted before the target block."""
183
+ return circuits .Moment ([cast (ops .Gate , rng .choice (_PAULIS )).on (q ) for q in active_qubits ])
197
184
198
- def gauge_on_moments (self , moments_to_gauge ) -> list [circuits .Moment ]:
185
+ def gauge_on_moments (
186
+ self ,
187
+ moments_to_gauge : list [circuits .Moment ],
188
+ prng : np .random .Generator = np .random .default_rng (),
189
+ ) -> list [circuits .Moment ]:
190
+ """Gauges a block of moments that contains at least a cphase gate in each of the moment.
191
+
192
+ Args:
193
+ moments_to_gauge: A list of moments to be gauged.
194
+ prng: A pseudorandom number generator.
195
+
196
+ Returns:
197
+ A list of moments after gauging.
198
+ """
199
199
active_qubits = circuits .Circuit .from_moments (* moments_to_gauge ).all_qubits ()
200
- left_moment = self .sample_left_moment (active_qubits )
200
+ left_moment = self .sample_left_moment (active_qubits , prng )
201
201
pulled : dict [ops .Qid , _PauliAndZPow ] = {
202
202
op .qubits [0 ]: _PauliAndZPow (pauli = cast (ops .Pauli | ops .IdentityGate , op .gate ))
203
203
for op in left_moment
@@ -233,10 +233,10 @@ def gauge_on_moments(self, moments_to_gauge) -> list[circuits.Moment]:
233
233
ops_at_updated_moment .append (new_zpow .on (q ))
234
234
case _:
235
235
raise ValueError (f"Gate type { type (op .gate )} is not supported." )
236
- # Keep the other ops of prev
237
- for q , gate in prev .items ():
238
- if q not in pulled :
239
- pulled [q ] = gate
236
+ # Keep the other ops of prev
237
+ for q , gate in prev .items ():
238
+ if q not in pulled :
239
+ pulled [q ] = gate
240
240
ret .append (circuits .Moment (ops_at_updated_moment ))
241
241
last_moment = circuits .Moment (
242
242
[gate .to_single_qubit_gate ().on (q ) for q , gate in pulled .items ()]
0 commit comments