1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
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+
1627import numbers
1728
1829import sympy
3546
3647@value .value_equality (approximate = True )
3748class 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.
3954
4055 The -1 eigenstates of the Pauli string will have their amplitude multiplied
4156 by e^(i pi exponent_neg) while +1 eigenstates of the Pauli string will have
4257 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.
4362 """
4463
4564 def __init__ (
4665 self ,
4766 pauli_string : ps .PauliString ,
67+ qubits : Optional [Sequence ['cirq.Qid' ]] = None ,
4868 * ,
4969 exponent_neg : Union [int , float , sympy .Expr ] = 1 ,
5070 exponent_pos : Union [int , float , sympy .Expr ] = 0 ,
@@ -54,20 +74,36 @@ def __init__(
5474 Args:
5575 pauli_string: The PauliString defining the positive and negative
5676 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`.
5784 exponent_neg: How much to phase vectors in the negative eigenspace,
5885 in the form of the t in (-1)**t = exp(i pi t).
5986 exponent_pos: How much to phase vectors in the positive eigenspace,
6087 in the form of the t in (-1)**t = exp(i pi t).
6188
6289 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`.
6492 """
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`
65103 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
69105 )
70- super ().__init__ (gate , pauli_string . qubits )
106+ super ().__init__ (gate , qubits )
71107 self ._pauli_string = gate .dense_pauli_string .on (* self .qubits )
72108
73109 @property
@@ -76,17 +112,17 @@ def gate(self) -> 'cirq.PauliStringPhasorGate':
76112 return cast (PauliStringPhasorGate , self ._gate )
77113
78114 @property
79- def exponent_neg (self ):
115+ def exponent_neg (self ) -> Union [ int , float , sympy . Expr ] :
80116 """The negative exponent."""
81117 return self .gate .exponent_neg
82118
83119 @property
84- def exponent_pos (self ):
120+ def exponent_pos (self ) -> Union [ int , float , sympy . Expr ] :
85121 """The positive exponent."""
86122 return self .gate .exponent_pos
87123
88124 @property
89- def pauli_string (self ):
125+ def pauli_string (self ) -> 'cirq.PauliString' :
90126 """The underlying pauli string."""
91127 return self ._pauli_string
92128
@@ -96,41 +132,70 @@ def exponent_relative(self) -> Union[int, float, sympy.Expr]:
96132 return self .gate .exponent_relative
97133
98134 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 )
100136
101- def equal_up_to_global_phase (self , other ) :
137+ def equal_up_to_global_phase (self , other : 'PauliStringPhasor' ) -> bool :
102138 """Checks equality of two PauliStringPhasors, up to global phase."""
103139 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+ )
107145 return False
108146
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+ )
111165 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 ],
113168 exponent_neg = self .exponent_neg ,
114169 exponent_pos = self .exponent_pos ,
115170 )
116171
117172 def can_merge_with (self , op : 'PauliStringPhasor' ) -> bool :
118173 """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+ )
120177
121178 def merged_with (self , op : 'PauliStringPhasor' ) -> 'PauliStringPhasor' :
122179 """Merges two PauliStringPhasors."""
123180 if not self .can_merge_with (op ):
124181 raise ValueError (f'Cannot merge operations: { self } , { op } ' )
125182 pp = self .exponent_pos + op .exponent_pos
126183 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+ )
128187
129188 def _circuit_diagram_info_ (
130189 self , args : 'cirq.CircuitDiagramInfoArgs'
131190 ) -> 'cirq.CircuitDiagramInfo' :
132191 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 )
134199 return protocols .CircuitDiagramInfo (wire_symbols = syms , exponent = self .exponent_relative )
135200
136201 def pass_operations_over (
@@ -170,6 +235,7 @@ def pass_operations_over(
170235 def __repr__ (self ) -> str :
171236 return (
172237 f'cirq.PauliStringPhasor({ self .pauli_string !r} , '
238+ f'qubits={ self .qubits !r} , '
173239 f'exponent_neg={ proper_repr (self .exponent_neg )} , '
174240 f'exponent_pos={ proper_repr (self .exponent_pos )} )'
175241 )
@@ -182,7 +248,19 @@ def __str__(self) -> str:
182248 return f'({ self .pauli_string } )**{ self .exponent_relative } '
183249
184250 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+ )
186264
187265
188266@value .value_equality (approximate = True )
@@ -234,24 +312,24 @@ def exponent_relative(self) -> Union[int, float, sympy.Expr]:
234312 return value .canonicalize_half_turns (self .exponent_neg - self .exponent_pos )
235313
236314 @property
237- def exponent_neg (self ):
315+ def exponent_neg (self ) -> Union [ int , float , sympy . Expr ] :
238316 """The negative exponent."""
239317 return self ._exponent_neg
240318
241319 @property
242- def exponent_pos (self ):
320+ def exponent_pos (self ) -> Union [ int , float , sympy . Expr ] :
243321 """The positive exponent."""
244322 return self ._exponent_pos
245323
246324 @property
247- def dense_pauli_string (self ):
325+ def dense_pauli_string (self ) -> 'cirq.DensePauliString' :
248326 """The underlying DensePauliString."""
249327 return self ._dense_pauli_string
250328
251329 def _value_equality_values_ (self ):
252330 return (self .dense_pauli_string , self .exponent_neg , self .exponent_pos )
253331
254- def equal_up_to_global_phase (self , other ) :
332+ def equal_up_to_global_phase (self , other : 'cirq.PauliStringPhasorGate' ) -> bool :
255333 """Checks equality of two PauliStringPhasors, up to global phase."""
256334 if isinstance (other , PauliStringPhasorGate ):
257335 rel1 = self .exponent_relative
@@ -266,7 +344,7 @@ def __pow__(self, exponent: Union[float, sympy.Symbol]) -> 'PauliStringPhasorGat
266344 return NotImplemented
267345 return PauliStringPhasorGate (self .dense_pauli_string , exponent_neg = pn , exponent_pos = pp )
268346
269- def _has_unitary_ (self ):
347+ def _has_unitary_ (self ) -> bool :
270348 return not self ._is_parameterized_ ()
271349
272350 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':
352430 """Creates a PauliStringPhasor on the qubits."""
353431 return PauliStringPhasor (
354432 self .dense_pauli_string .on (* qubits ),
433+ qubits = qubits ,
355434 exponent_pos = self .exponent_pos ,
356435 exponent_neg = self .exponent_neg ,
357436 )
0 commit comments