12
12
# See the License for the specific language governing permissions and
13
13
# limitations under the License.
14
14
15
- from typing import AbstractSet , cast , Dict , Iterable , Union , TYPE_CHECKING , Sequence , Iterator
15
+ from typing import (
16
+ AbstractSet ,
17
+ cast ,
18
+ Dict ,
19
+ Iterable ,
20
+ Iterator ,
21
+ Optional ,
22
+ Sequence ,
23
+ TYPE_CHECKING ,
24
+ Union ,
25
+ )
26
+
16
27
import numbers
17
28
18
29
import sympy
35
46
36
47
@value .value_equality (approximate = True )
37
48
class PauliStringPhasor (gate_operation .GateOperation ):
38
- """An operation that phases the eigenstates of a Pauli string.
49
+ r"""An operation that phases the eigenstates of a Pauli string.
50
+
51
+ This class takes `PauliString`, which is a sequence of non-identity
52
+ Pauli operators, potentially with a $\pm 1$ valued coefficient,
53
+ acting on qubits.
39
54
40
55
The -1 eigenstates of the Pauli string will have their amplitude multiplied
41
56
by e^(i pi exponent_neg) while +1 eigenstates of the Pauli string will have
42
57
their amplitude multiplied by e^(i pi exponent_pos).
58
+
59
+ The class also takes a list of qubits, which can be a superset of those
60
+ acted on by the provided `PauliString`. Those extra qubits are assumed to be
61
+ acted upon via identity.
43
62
"""
44
63
45
64
def __init__ (
46
65
self ,
47
66
pauli_string : ps .PauliString ,
67
+ qubits : Optional [Sequence ['cirq.Qid' ]] = None ,
48
68
* ,
49
69
exponent_neg : Union [int , float , sympy .Expr ] = 1 ,
50
70
exponent_pos : Union [int , float , sympy .Expr ] = 0 ,
@@ -54,20 +74,36 @@ def __init__(
54
74
Args:
55
75
pauli_string: The PauliString defining the positive and negative
56
76
eigenspaces that will be independently phased.
77
+ qubits: The qubits upon which the PauliStringPhasor acts. This
78
+ must be a superset of the qubits of `pauli_string`.
79
+ If None, it will use the qubits from `pauli_string`
80
+ The `pauli_string` contains only the non-identity component
81
+ of the phasor, while the qubits supplied here and not in
82
+ `pauli_string` are acted upon by identity. The order of
83
+ these qubits must match the order in `pauli_string`.
57
84
exponent_neg: How much to phase vectors in the negative eigenspace,
58
85
in the form of the t in (-1)**t = exp(i pi t).
59
86
exponent_pos: How much to phase vectors in the positive eigenspace,
60
87
in the form of the t in (-1)**t = exp(i pi t).
61
88
62
89
Raises:
63
- ValueError: If coefficient is not 1 or -1.
90
+ ValueError: If coefficient is not 1 or -1 or the qubits of
91
+ `pauli_string` are not a subset of `qubits`.
64
92
"""
93
+ if qubits is not None :
94
+ it = iter (qubits )
95
+ if any (not any (q0 == q1 for q1 in it ) for q0 in pauli_string .qubits ):
96
+ raise ValueError (
97
+ f"PauliStringPhasor's pauli string qubits ({ pauli_string .qubits } ) "
98
+ f"are not an ordered subset of the explicit qubits ({ qubits } )."
99
+ )
100
+ else :
101
+ qubits = pauli_string .qubits
102
+ # Use qubits below instead of `qubits or pauli_string.qubits`
65
103
gate = PauliStringPhasorGate (
66
- pauli_string .dense (pauli_string .qubits ),
67
- exponent_neg = exponent_neg ,
68
- exponent_pos = exponent_pos ,
104
+ pauli_string .dense (qubits ), exponent_neg = exponent_neg , exponent_pos = exponent_pos
69
105
)
70
- super ().__init__ (gate , pauli_string . qubits )
106
+ super ().__init__ (gate , qubits )
71
107
self ._pauli_string = gate .dense_pauli_string .on (* self .qubits )
72
108
73
109
@property
@@ -76,17 +112,17 @@ def gate(self) -> 'cirq.PauliStringPhasorGate':
76
112
return cast (PauliStringPhasorGate , self ._gate )
77
113
78
114
@property
79
- def exponent_neg (self ):
115
+ def exponent_neg (self ) -> Union [ int , float , sympy . Expr ] :
80
116
"""The negative exponent."""
81
117
return self .gate .exponent_neg
82
118
83
119
@property
84
- def exponent_pos (self ):
120
+ def exponent_pos (self ) -> Union [ int , float , sympy . Expr ] :
85
121
"""The positive exponent."""
86
122
return self .gate .exponent_pos
87
123
88
124
@property
89
- def pauli_string (self ):
125
+ def pauli_string (self ) -> 'cirq.PauliString' :
90
126
"""The underlying pauli string."""
91
127
return self ._pauli_string
92
128
@@ -96,41 +132,70 @@ def exponent_relative(self) -> Union[int, float, sympy.Expr]:
96
132
return self .gate .exponent_relative
97
133
98
134
def _value_equality_values_ (self ):
99
- return (self .pauli_string , self .exponent_neg , self .exponent_pos )
135
+ return (self .pauli_string , self .qubits , self . exponent_neg , self .exponent_pos )
100
136
101
- def equal_up_to_global_phase (self , other ) :
137
+ def equal_up_to_global_phase (self , other : 'PauliStringPhasor' ) -> bool :
102
138
"""Checks equality of two PauliStringPhasors, up to global phase."""
103
139
if isinstance (other , PauliStringPhasor ):
104
- rel1 = self .exponent_relative
105
- rel2 = other .exponent_relative
106
- return rel1 == rel2 and self .pauli_string == other .pauli_string
140
+ return (
141
+ self .exponent_relative == other .exponent_relative
142
+ and self .pauli_string == other .pauli_string
143
+ and self .qubits == other .qubits
144
+ )
107
145
return False
108
146
109
- def map_qubits (self , qubit_map : Dict [raw_types .Qid , raw_types .Qid ]):
110
- """Maps the qubits inside the PauliString."""
147
+ def map_qubits (self , qubit_map : Dict [raw_types .Qid , raw_types .Qid ]) -> 'PauliStringPhasor' :
148
+ """Maps the qubits inside the PauliStringPhasor.
149
+
150
+ Args:
151
+ qubit_map: A map from the qubits in the phasor to new qubits.
152
+
153
+ Returns:
154
+ A new PauliStringPhasor with remapped qubits.
155
+
156
+ Raises:
157
+ ValueError: If the map does not contain an entry for all
158
+ the qubits in the phasor.
159
+ """
160
+ if not set (self .qubits ) <= qubit_map .keys ():
161
+ raise ValueError (
162
+ "qubit_map must have a key for every qubit in the phasors qubits. "
163
+ f"keys: { qubit_map .keys ()} phasor qubits: { self .qubits } "
164
+ )
111
165
return PauliStringPhasor (
112
- self .pauli_string .map_qubits (qubit_map ),
166
+ pauli_string = self .pauli_string .map_qubits (qubit_map ),
167
+ qubits = [qubit_map [q ] for q in self .qubits ],
113
168
exponent_neg = self .exponent_neg ,
114
169
exponent_pos = self .exponent_pos ,
115
170
)
116
171
117
172
def can_merge_with (self , op : 'PauliStringPhasor' ) -> bool :
118
173
"""Checks whether the underlying PauliStrings can be merged."""
119
- return self .pauli_string .equal_up_to_coefficient (op .pauli_string )
174
+ return (
175
+ self .pauli_string .equal_up_to_coefficient (op .pauli_string ) and self .qubits == op .qubits
176
+ )
120
177
121
178
def merged_with (self , op : 'PauliStringPhasor' ) -> 'PauliStringPhasor' :
122
179
"""Merges two PauliStringPhasors."""
123
180
if not self .can_merge_with (op ):
124
181
raise ValueError (f'Cannot merge operations: { self } , { op } ' )
125
182
pp = self .exponent_pos + op .exponent_pos
126
183
pn = self .exponent_neg + op .exponent_neg
127
- return PauliStringPhasor (self .pauli_string , exponent_pos = pp , exponent_neg = pn )
184
+ return PauliStringPhasor (
185
+ self .pauli_string , qubits = self .qubits , exponent_pos = pp , exponent_neg = pn
186
+ )
128
187
129
188
def _circuit_diagram_info_ (
130
189
self , args : 'cirq.CircuitDiagramInfoArgs'
131
190
) -> 'cirq.CircuitDiagramInfo' :
132
191
qubits = self .qubits if args .known_qubits is None else args .known_qubits
133
- syms = tuple (f'[{ self .pauli_string [qubit ]} ]' for qubit in qubits )
192
+
193
+ def sym (qubit ):
194
+ if qubit in self .pauli_string :
195
+ return f'[{ self .pauli_string [qubit ]} ]'
196
+ return '[I]'
197
+
198
+ syms = tuple (sym (qubit ) for qubit in qubits )
134
199
return protocols .CircuitDiagramInfo (wire_symbols = syms , exponent = self .exponent_relative )
135
200
136
201
def pass_operations_over (
@@ -170,6 +235,7 @@ def pass_operations_over(
170
235
def __repr__ (self ) -> str :
171
236
return (
172
237
f'cirq.PauliStringPhasor({ self .pauli_string !r} , '
238
+ f'qubits={ self .qubits !r} , '
173
239
f'exponent_neg={ proper_repr (self .exponent_neg )} , '
174
240
f'exponent_pos={ proper_repr (self .exponent_pos )} )'
175
241
)
@@ -182,7 +248,19 @@ def __str__(self) -> str:
182
248
return f'({ self .pauli_string } )**{ self .exponent_relative } '
183
249
184
250
def _json_dict_ (self ):
185
- return protocols .obj_to_dict_helper (self , ['pauli_string' , 'exponent_neg' , 'exponent_pos' ])
251
+ return protocols .obj_to_dict_helper (
252
+ self , ['pauli_string' , 'qubits' , 'exponent_neg' , 'exponent_pos' ]
253
+ )
254
+
255
+ @classmethod
256
+ def _from_json_dict_ (cls , pauli_string , exponent_neg , exponent_pos , ** kwargs ):
257
+ qubits = kwargs ['qubits' ] if 'qubits' in kwargs else None
258
+ return PauliStringPhasor (
259
+ pauli_string = pauli_string ,
260
+ qubits = qubits ,
261
+ exponent_neg = exponent_neg ,
262
+ exponent_pos = exponent_pos ,
263
+ )
186
264
187
265
188
266
@value .value_equality (approximate = True )
@@ -234,24 +312,24 @@ def exponent_relative(self) -> Union[int, float, sympy.Expr]:
234
312
return value .canonicalize_half_turns (self .exponent_neg - self .exponent_pos )
235
313
236
314
@property
237
- def exponent_neg (self ):
315
+ def exponent_neg (self ) -> Union [ int , float , sympy . Expr ] :
238
316
"""The negative exponent."""
239
317
return self ._exponent_neg
240
318
241
319
@property
242
- def exponent_pos (self ):
320
+ def exponent_pos (self ) -> Union [ int , float , sympy . Expr ] :
243
321
"""The positive exponent."""
244
322
return self ._exponent_pos
245
323
246
324
@property
247
- def dense_pauli_string (self ):
325
+ def dense_pauli_string (self ) -> 'cirq.DensePauliString' :
248
326
"""The underlying DensePauliString."""
249
327
return self ._dense_pauli_string
250
328
251
329
def _value_equality_values_ (self ):
252
330
return (self .dense_pauli_string , self .exponent_neg , self .exponent_pos )
253
331
254
- def equal_up_to_global_phase (self , other ) :
332
+ def equal_up_to_global_phase (self , other : 'cirq.PauliStringPhasorGate' ) -> bool :
255
333
"""Checks equality of two PauliStringPhasors, up to global phase."""
256
334
if isinstance (other , PauliStringPhasorGate ):
257
335
rel1 = self .exponent_relative
@@ -266,7 +344,7 @@ def __pow__(self, exponent: Union[float, sympy.Symbol]) -> 'PauliStringPhasorGat
266
344
return NotImplemented
267
345
return PauliStringPhasorGate (self .dense_pauli_string , exponent_neg = pn , exponent_pos = pp )
268
346
269
- def _has_unitary_ (self ):
347
+ def _has_unitary_ (self ) -> bool :
270
348
return not self ._is_parameterized_ ()
271
349
272
350
def _to_z_basis_ops (self , qubits : Sequence ['cirq.Qid' ]) -> Iterator [raw_types .Operation ]:
@@ -352,6 +430,7 @@ def on(self, *qubits: 'cirq.Qid') -> 'cirq.PauliStringPhasor':
352
430
"""Creates a PauliStringPhasor on the qubits."""
353
431
return PauliStringPhasor (
354
432
self .dense_pauli_string .on (* qubits ),
433
+ qubits = qubits ,
355
434
exponent_pos = self .exponent_pos ,
356
435
exponent_neg = self .exponent_neg ,
357
436
)
0 commit comments