24
24
Iterator ,
25
25
List ,
26
26
Optional ,
27
+ overload ,
27
28
Sequence ,
28
29
Tuple ,
29
30
Type ,
45
46
46
47
# Order is important! Index equals numeric value.
47
48
PAULI_CHARS = 'IXYZ'
48
- PAULI_GATES : List ['cirq.Gate' ] = [
49
- # mypy false positive "Cannot determine type of 'I'"
50
- identity .I , # type: ignore
49
+ PAULI_GATES : List [Union ['cirq.Pauli' , 'cirq.IdentityGate' ]] = [
50
+ identity .I ,
51
51
pauli_gates .X ,
52
52
pauli_gates .Y ,
53
53
pauli_gates .Z ,
58
58
59
59
@value .value_equality (approximate = True , distinct_child_types = True )
60
60
class BaseDensePauliString (raw_types .Gate , metaclass = abc .ABCMeta ):
61
- """Parent class for `cirq.DensePauliString` and `cirq.MutableDensePauliString`."""
61
+ """Parent class for `cirq.DensePauliString` and `cirq.MutableDensePauliString`.
62
+
63
+ `cirq.BaseDensePauliString` is an abstract base class, which is used to implement
64
+ `cirq.DensePauliString` and `cirq.MutableDensePauliString`. The non-mutable version
65
+ is used as the corresponding gate for `cirq.PauliString` operation and the mutable
66
+ version is mainly used for efficiently manipulating dense pauli strings.
67
+
68
+ See the docstrings of `cirq.DensePauliString` and `cirq.MutableDensePauliString` for more
69
+ details.
70
+
71
+ Examples:
72
+ >>> print(cirq.DensePauliString('XXIY'))
73
+ +XXIY
74
+
75
+ >>> print(cirq.MutableDensePauliString('IZII', coefficient=-1))
76
+ -IZII (mutable)
77
+
78
+ >>> print(cirq.DensePauliString([0, 1, 2, 3],
79
+ ... coefficient=sympy.Symbol('t')))
80
+ t*IXYZ
81
+ """
62
82
63
83
I_VAL = 0
64
84
X_VAL = 1
@@ -69,7 +89,7 @@ def __init__(
69
89
self ,
70
90
pauli_mask : Union [Iterable ['cirq.PAULI_GATE_LIKE' ], np .ndarray ],
71
91
* ,
72
- coefficient : Union [ sympy . Expr , int , float , 'cirq.TParamValComplex' ] = 1 ,
92
+ coefficient : 'cirq.TParamValComplex' = 1 ,
73
93
):
74
94
"""Initializes a new dense pauli string.
75
95
@@ -84,17 +104,6 @@ def __init__(
84
104
instead of being copied.
85
105
coefficient: A complex number. Usually +1, -1, 1j, or -1j but other
86
106
values are supported.
87
-
88
- Examples:
89
- >>> print(cirq.DensePauliString('XXIY'))
90
- +XXIY
91
-
92
- >>> print(cirq.MutableDensePauliString('IZII', coefficient=-1))
93
- -IZII (mutable)
94
-
95
- >>> print(cirq.DensePauliString([0, 1, 2, 3],
96
- ... coefficient=sympy.Symbol('t')))
97
- t*IXYZ
98
107
"""
99
108
self ._pauli_mask = _as_pauli_mask (pauli_mask )
100
109
self ._coefficient : Union [complex , sympy .Expr ] = (
@@ -106,10 +115,12 @@ def __init__(
106
115
107
116
@property
108
117
def pauli_mask (self ) -> np .ndarray :
118
+ """A 1-dimensional uint8 numpy array giving a specification of Pauli gates to use."""
109
119
return self ._pauli_mask
110
120
111
121
@property
112
122
def coefficient (self ) -> Union [sympy .Expr , complex ]:
123
+ """A complex coefficient or symbol."""
113
124
return self ._coefficient
114
125
115
126
def _json_dict_ (self ) -> Dict [str , Any ]:
@@ -147,28 +158,31 @@ def eye(cls: Type[TCls], length: int) -> TCls:
147
158
concrete_cls = cast (Callable , DensePauliString if cls is BaseDensePauliString else cls )
148
159
return concrete_cls (pauli_mask = np .zeros (length , dtype = np .uint8 ))
149
160
150
- def _num_qubits_ (self ):
161
+ def _num_qubits_ (self ) -> int :
151
162
return len (self )
152
163
153
- def _has_unitary_ (self ):
164
+ def _has_unitary_ (self ) -> bool :
154
165
return not self ._is_parameterized_ () and (abs (abs (self .coefficient ) - 1 ) < 1e-8 )
155
166
156
- def _unitary_ (self ):
167
+ def _unitary_ (self ) -> Union [ np . ndarray , NotImplementedType ] :
157
168
if not self ._has_unitary_ ():
158
169
return NotImplemented
159
170
return self .coefficient * linalg .kron (
160
171
* [protocols .unitary (PAULI_GATES [p ]) for p in self .pauli_mask ]
161
172
)
162
173
163
- def _apply_unitary_ (self , args ):
174
+ def _apply_unitary_ (self , args ) -> Union [ np . ndarray , None , NotImplementedType ] :
164
175
if not self ._has_unitary_ ():
165
176
return NotImplemented
166
177
from cirq import devices
167
178
168
179
qubits = devices .LineQubit .range (len (self ))
169
- return protocols .apply_unitaries (self ._decompose_ (qubits ), qubits , args )
180
+ decomposed_ops = cast (Iterable ['cirq.OP_TREE' ], self ._decompose_ (qubits ))
181
+ return protocols .apply_unitaries (decomposed_ops , qubits , args )
170
182
171
- def _decompose_ (self , qubits ):
183
+ def _decompose_ (
184
+ self , qubits : Sequence ['cirq.Qid' ]
185
+ ) -> Union [NotImplementedType , 'cirq.OP_TREE' ]:
172
186
if not self ._has_unitary_ ():
173
187
return NotImplemented
174
188
result = [PAULI_GATES [p ].on (q ) for p , q in zip (self .pauli_mask , qubits ) if p ]
@@ -190,18 +204,27 @@ def _resolve_parameters_(self: TCls, resolver: 'cirq.ParamResolver', recursive:
190
204
def __pos__ (self ):
191
205
return self
192
206
193
- def __pow__ (self , power ):
207
+ def __pow__ (self : TCls , power : Union [int , float ]) -> Union [NotImplementedType , TCls ]:
208
+ concrete_class = type (self )
194
209
if isinstance (power , int ):
195
210
i_group = [1 , + 1j , - 1 , - 1j ]
196
211
if self .coefficient in i_group :
197
- coef = i_group [i_group .index (self .coefficient ) * power % 4 ]
212
+ coef = i_group [i_group .index (cast ( int , self .coefficient ) ) * power % 4 ]
198
213
else :
199
214
coef = self .coefficient ** power
200
215
if power % 2 == 0 :
201
- return coef * DensePauliString .eye (len (self ))
202
- return DensePauliString (coefficient = coef , pauli_mask = self .pauli_mask )
216
+ return concrete_class .eye (len (self )). __mul__ ( coef )
217
+ return concrete_class (coefficient = coef , pauli_mask = self .pauli_mask )
203
218
return NotImplemented
204
219
220
+ @overload
221
+ def __getitem__ (self : TCls , item : int ) -> Union ['cirq.Pauli' , 'cirq.IdentityGate' ]:
222
+ pass
223
+
224
+ @overload
225
+ def __getitem__ (self : TCls , item : slice ) -> TCls :
226
+ pass
227
+
205
228
def __getitem__ (self , item ):
206
229
if isinstance (item , int ):
207
230
return PAULI_GATES [self .pauli_mask [item ]]
@@ -211,15 +234,15 @@ def __getitem__(self, item):
211
234
212
235
raise TypeError (f'indices must be integers or slices, not { type (item )} ' )
213
236
214
- def __iter__ (self ) -> Iterator ['cirq.Gate' ]:
237
+ def __iter__ (self ) -> Iterator [Union [ 'cirq.Pauli' , 'cirq.IdentityGate' ] ]:
215
238
for i in range (len (self )):
216
239
yield self [i ]
217
240
218
- def __len__ (self ):
241
+ def __len__ (self ) -> int :
219
242
return len (self .pauli_mask )
220
243
221
244
def __neg__ (self ):
222
- return DensePauliString (coefficient = - self .coefficient , pauli_mask = self .pauli_mask )
245
+ return type ( self ) (coefficient = - self .coefficient , pauli_mask = self .pauli_mask )
223
246
224
247
def __truediv__ (self , other ):
225
248
if isinstance (other , (sympy .Basic , numbers .Number )):
@@ -228,7 +251,10 @@ def __truediv__(self, other):
228
251
return NotImplemented
229
252
230
253
def __mul__ (self , other ):
254
+ concrete_class = type (self )
231
255
if isinstance (other , BaseDensePauliString ):
256
+ if isinstance (other , MutableDensePauliString ):
257
+ concrete_class = MutableDensePauliString
232
258
max_len = max (len (self .pauli_mask ), len (other .pauli_mask ))
233
259
min_len = min (len (self .pauli_mask ), len (other .pauli_mask ))
234
260
new_mask = np .zeros (max_len , dtype = np .uint8 )
@@ -237,22 +263,22 @@ def __mul__(self, other):
237
263
tweak = _vectorized_pauli_mul_phase (
238
264
self .pauli_mask [:min_len ], other .pauli_mask [:min_len ]
239
265
)
240
- return DensePauliString (
266
+ return concrete_class (
241
267
pauli_mask = new_mask , coefficient = self .coefficient * other .coefficient * tweak
242
268
)
243
269
244
270
if isinstance (other , (sympy .Basic , numbers .Number )):
245
271
new_coef = protocols .mul (self .coefficient , other , default = None )
246
272
if new_coef is None :
247
273
return NotImplemented
248
- return DensePauliString (pauli_mask = self .pauli_mask , coefficient = new_coef )
274
+ return concrete_class (pauli_mask = self .pauli_mask , coefficient = new_coef )
249
275
250
276
split = _attempt_value_to_pauli_index (other )
251
277
if split is not None :
252
278
p , i = split
253
279
mask = np .copy (self .pauli_mask )
254
280
mask [i ] ^= p
255
- return DensePauliString (
281
+ return concrete_class (
256
282
pauli_mask = mask ,
257
283
coefficient = self .coefficient * _vectorized_pauli_mul_phase (self .pauli_mask [i ], p ),
258
284
)
@@ -268,14 +294,14 @@ def __rmul__(self, other):
268
294
p , i = split
269
295
mask = np .copy (self .pauli_mask )
270
296
mask [i ] ^= p
271
- return DensePauliString (
297
+ return type ( self ) (
272
298
pauli_mask = mask ,
273
299
coefficient = self .coefficient * _vectorized_pauli_mul_phase (p , self .pauli_mask [i ]),
274
300
)
275
301
276
302
return NotImplemented
277
303
278
- def tensor_product (self , other : 'BaseDensePauliString' ) -> 'DensePauliString' :
304
+ def tensor_product (self : TCls , other : 'BaseDensePauliString' ) -> TCls :
279
305
"""Concatenates dense pauli strings and multiplies their coefficients.
280
306
281
307
Args:
@@ -285,13 +311,13 @@ def tensor_product(self, other: 'BaseDensePauliString') -> 'DensePauliString':
285
311
A dense pauli string with the concatenation of the paulis from the
286
312
two input pauli strings, and the product of their coefficients.
287
313
"""
288
- return DensePauliString (
314
+ return type ( self ) (
289
315
coefficient = self .coefficient * other .coefficient ,
290
316
pauli_mask = np .concatenate ([self .pauli_mask , other .pauli_mask ]),
291
317
)
292
318
293
- def __abs__ (self ) :
294
- return DensePauliString (coefficient = abs (self .coefficient ), pauli_mask = self .pauli_mask )
319
+ def __abs__ (self : TCls ) -> TCls :
320
+ return type ( self ) (coefficient = abs (self .coefficient ), pauli_mask = self .pauli_mask )
295
321
296
322
def on (self , * qubits : 'cirq.Qid' ) -> 'cirq.PauliString' :
297
323
return self .sparse (qubits )
@@ -392,17 +418,36 @@ def copy(
392
418
class DensePauliString (BaseDensePauliString ):
393
419
"""An immutable string of Paulis, like `XIXY`, with a coefficient.
394
420
395
- This represents a Pauli operator acting on qubits.
421
+ A `DensePauliString` represents a multi-qubit pauli operator, i.e. a tensor product of single
422
+ qubits Pauli gates (including the `cirq.IdentityGate`), each of which would act on a
423
+ different qubit. When applied on qubits, a `DensePauliString` results in `cirq.PauliString`
424
+ as an operation.
425
+
426
+ Note that `cirq.PauliString` only stores a tensor product of non-identity `cirq.Pauli`
427
+ operations whereas `cirq.DensePauliString` also supports storing the `cirq.IdentityGate`.
428
+
429
+ For example,
430
+
431
+ >>> dps = cirq.DensePauliString('XXIY')
432
+ >>> print(dps) # 4 qubit pauli operator with 'X' on first 2 qubits, 'I' on 3rd and 'Y' on 4th.
433
+ +XXIY
434
+ >>> ps = dps.on(*cirq.LineQubit.range(4)) # When applied on qubits, we get a `cirq.PauliString`.
435
+ >>> print(ps) # Note that `cirq.PauliString` only preserves non-identity operations.
436
+ X(q(0))*X(q(1))*Y(q(3))
396
437
397
- For example, `cirq.MutableDensePauliString("XXY")` represents a
398
- three qubit operation that acts with `X` on the first two qubits, and
399
- `Y` on the last.
438
+ This can optionally take a coefficient, for example:
400
439
401
- This can optionally take a coefficient, for example,
402
- `cirq.MutableDensePauliString("XX", 3)`, which represents 3 times
403
- the operator acting on X on two qubits.
440
+ >>> dps = cirq.DensePauliString("XX", coefficient=3)
441
+ >>> print(dps) # Represents 3 times the operator XX acting on two qubits.
442
+ (3+0j)*XX
443
+ >>> print(dps.on(*cirq.LineQubit.range(2))) # Coefficient is propagated to `cirq.PauliString`.
444
+ (3+0j)*X(q(0))*X(q(1))
404
445
405
- If the coefficient has magnitude of 1, then this is also a `cirq.Gate`.
446
+ If the coefficient has magnitude of 1, the resulting operator is a unitary and thus is
447
+ also a `cirq.Gate`.
448
+
449
+ Note that `DensePauliString` is an immutable object. If you need a mutable version of
450
+ dense pauli strings, see `cirq.MutableDensePauliString`.
406
451
"""
407
452
408
453
def frozen (self ) -> 'DensePauliString' :
@@ -425,19 +470,34 @@ def copy(
425
470
class MutableDensePauliString (BaseDensePauliString ):
426
471
"""A mutable string of Paulis, like `XIXY`, with a coefficient.
427
472
428
- This represents a Pauli operator acting on qubits.
473
+ `cirq.MutableDensePauliString` is a mutable version of `cirq.DensePauliString`.
474
+ It exists mainly to help mutate dense pauli strings efficiently, instead of always creating
475
+ a copy, and then converting back to a frozen `cirq.DensePauliString` representation.
429
476
430
- For example, `cirq.MutableDensePauliString("XXY")` represents a
431
- three qubit operation that acts with `X` on the first two qubits, and
432
- `Y` on the last.
477
+ For example:
433
478
434
- This can optionally take a coefficient, for example,
435
- `cirq.MutableDensePauliString("XX", 3)`, which represents 3 times
436
- the operator acting on X on two qubits.
479
+ >>> mutable_dps = cirq.MutableDensePauliString('XXZZ')
480
+ >>> mutable_dps[:2] = 'YY' # `cirq.MutableDensePauliString` supports item assignment.
481
+ >>> print(mutable_dps)
482
+ +YYZZ (mutable)
437
483
438
- If the coefficient has magnitude of 1, then this is also a `cirq.Gate` .
484
+ See docstrings of `cirq.DensePauliString` for more details on dense pauli strings .
439
485
"""
440
486
487
+ @overload
488
+ def __setitem__ (
489
+ self : 'MutableDensePauliString' , key : int , value : 'cirq.PAULI_GATE_LIKE'
490
+ ) -> 'MutableDensePauliString' :
491
+ pass
492
+
493
+ @overload
494
+ def __setitem__ (
495
+ self : 'MutableDensePauliString' ,
496
+ key : slice ,
497
+ value : Union [Iterable ['cirq.PAULI_GATE_LIKE' ], np .ndarray , BaseDensePauliString ],
498
+ ) -> 'MutableDensePauliString' :
499
+ pass
500
+
441
501
def __setitem__ (self , key , value ):
442
502
if isinstance (key , int ):
443
503
self .pauli_mask [key ] = _pauli_index (value )
@@ -557,7 +617,7 @@ def _as_pauli_mask(val: Union[Iterable['cirq.PAULI_GATE_LIKE'], np.ndarray]) ->
557
617
return np .array ([_pauli_index (v ) for v in val ], dtype = np .uint8 )
558
618
559
619
560
- def _attempt_value_to_pauli_index (v : Any ) -> Optional [Tuple [int , int ]]:
620
+ def _attempt_value_to_pauli_index (v : 'cirq.Operation' ) -> Optional [Tuple [int , int ]]:
561
621
if not isinstance (v , raw_types .Operation ):
562
622
return None
563
623
0 commit comments