5353 identity ,
5454 op_tree ,
5555 pauli_gates ,
56- pauli_interaction_gate ,
5756 raw_types ,
5857)
5958
@@ -968,53 +967,13 @@ def conjugated_by(self, clifford: cirq.OP_TREE) -> PauliString:
968967 # Decompose P = Pc⊗R, where Pc acts on the same qubits as C, R acts on the remaining.
969968 # Then the conjugation = (C^{-1}⊗I·Pc⊗R·C⊗I) = (C^{-1}·Pc·C)⊗R.
970969
971- # Isolate R
972- remain : cirq .PauliString = PauliString (
970+ # Conjugation on the qubits of op
971+ conjugated = _calc_conjugation (ps , op )
972+ # The pauli string on the remaining qubits
973+ remain : PauliString = PauliString (
973974 * (pauli (q ) for q in all_qubits - set (op .qubits ) if (pauli := ps .get (q )) is not None )
974975 )
975-
976- # Initialize the conjugation of Pc.
977- conjugated : cirq .DensePauliString = (
978- dense_pauli_string .DensePauliString (pauli_mask = [identity .I for _ in op .qubits ])
979- * ps .coefficient
980- )
981-
982- # Calculate the conjugation via CliffordGate's clifford_tableau.
983- # Note the clifford_tableau in CliffordGate represents C·P·C^-1 instead of C^-1·P·C.
984- # So we take the inverse of the tableau to match the definition of the conjugation here.
985- gate_in_clifford : cirq .CliffordGate
986- if isinstance (op .gate , clifford_gate .CliffordGate ):
987- gate_in_clifford = op .gate
988- else :
989- # Convert the clifford gate to CliffordGate type.
990- gate_in_clifford = clifford_gate .CliffordGate .from_op_list ([op ], op .qubits )
991- tableau = gate_in_clifford .clifford_tableau .inverse ()
992-
993- # Calculate the conjugation by `op` via mutiplying the conjugation of each Pauli:
994- # C^{-1}·(P_1⊗...⊗P_n)·C
995- # = C^{-1}·(P_1⊗I) ...·(P_n⊗I)·C
996- # = (C^{-1}(P_1⊗I)C)·...·(C^{-1}(P_n⊗I)C)
997- # For the Pauli on the kth qubit P_k. The conjugation is calculated as following.
998- # Puali X_k's conjugation is from the destabilzer table;
999- # Puali Z_k's conjugation is from the stabilzer table;
1000- # Puali Y_k's conjugation is calcluated according to Y = iXZ. E.g., for the kth qubit,
1001- # C^{-1}·Y_k⊗I·C = C^{-1}·(iX_k⊗I·Z_k⊗I)·C = i (C^{-1}·X_k⊗I·C)·(C^{-1}·Z_k⊗I·C).
1002- for qid , qubit in enumerate (op .qubits ):
1003- pauli = ps .get (qubit )
1004- match pauli :
1005- case None :
1006- continue
1007- case pauli_gates .X :
1008- conjugated *= tableau .destabilizers ()[qid ]
1009- case pauli_gates .Z :
1010- conjugated *= tableau .stabilizers ()[qid ]
1011- case pauli_gates .Y :
1012- conjugated *= (
1013- 1j
1014- * tableau .destabilizers ()[qid ] # conj X first
1015- * tableau .stabilizers ()[qid ] # then conj Z
1016- )
1017- ps = remain * conjugated .on (* op .qubits )
976+ ps = remain * conjugated
1018977 return ps
1019978
1020979 def after (self , ops : cirq .OP_TREE ) -> cirq .PauliString :
@@ -1371,7 +1330,18 @@ def inplace_before(self, ops: cirq.OP_TREE) -> cirq.MutablePauliString:
13711330 Returns:
13721331 The mutable pauli string that was mutated.
13731332 """
1374- return self .inplace_after (protocols .inverse (ops ))
1333+ # An inplace impl of PauliString.conjugated_by().
1334+ flattened_ops = list (op_tree .flatten_to_ops (ops ))
1335+ for op in flattened_ops [::- 1 ]:
1336+ conjugated = _calc_conjugation (self .frozen (), op )
1337+ self .coefficient = conjugated .coefficient
1338+ for q in op .qubits :
1339+ new_pauli_int = PAULI_GATE_LIKE_TO_INDEX_MAP [conjugated .get (q ) or 0 ]
1340+ if new_pauli_int == 0 :
1341+ self .pauli_int_dict .pop (cast (TKey , q ), None )
1342+ else :
1343+ self .pauli_int_dict [cast (TKey , q )] = new_pauli_int
1344+ return self
13751345
13761346 def inplace_after (self , ops : cirq .OP_TREE ) -> cirq .MutablePauliString :
13771347 r"""Propagates the pauli string from before to after a Clifford effect.
@@ -1391,43 +1361,7 @@ def inplace_after(self, ops: cirq.OP_TREE) -> cirq.MutablePauliString:
13911361 NotImplementedError: If any ops decompose into an unsupported
13921362 Clifford gate.
13931363 """
1394- for clifford in op_tree .flatten_to_ops (ops ):
1395- for op in _decompose_into_cliffords (clifford ):
1396- ps = [self .pauli_int_dict .pop (cast (TKey , q ), 0 ) for q in op .qubits ]
1397- if not any (ps ):
1398- continue
1399- gate = op .gate
1400-
1401- if isinstance (gate , clifford_gate .SingleQubitCliffordGate ):
1402- out = gate .pauli_tuple (_INT_TO_PAULI [ps [0 ] - 1 ])
1403- if out [1 ]:
1404- self .coefficient *= - 1
1405- self .pauli_int_dict [cast (TKey , op .qubits [0 ])] = PAULI_GATE_LIKE_TO_INDEX_MAP [
1406- out [0 ]
1407- ]
1408-
1409- elif isinstance (gate , pauli_interaction_gate .PauliInteractionGate ):
1410- q0 , q1 = op .qubits
1411- p0 = _INT_TO_PAULI_OR_IDENTITY [ps [0 ]]
1412- p1 = _INT_TO_PAULI_OR_IDENTITY [ps [1 ]]
1413-
1414- # Kick across Paulis that anti-commute with the controls.
1415- kickback_0_to_1 = not protocols .commutes (p0 , gate .pauli0 )
1416- kickback_1_to_0 = not protocols .commutes (p1 , gate .pauli1 )
1417- kick0 = gate .pauli1 if kickback_0_to_1 else identity .I
1418- kick1 = gate .pauli0 if kickback_1_to_0 else identity .I
1419- self .__imul__ ({q0 : p0 , q1 : kick0 })
1420- self .__imul__ ({q0 : kick1 , q1 : p1 })
1421-
1422- # Decompose inverted controls into single-qubit operations.
1423- if gate .invert0 :
1424- self .inplace_after (gate .pauli1 (q1 ))
1425- if gate .invert1 :
1426- self .inplace_after (gate .pauli0 (q0 ))
1427-
1428- else : # pragma: no cover
1429- raise NotImplementedError (f"Unrecognized decomposed Clifford: { op !r} " )
1430- return self
1364+ return self .inplace_before (protocols .inverse (ops ))
14311365
14321366 def _imul_helper (self , other : cirq .PAULI_STRING_LIKE , sign : int ):
14331367 """Left-multiplies or right-multiplies by a PAULI_STRING_LIKE.
@@ -1594,35 +1528,6 @@ def __repr__(self) -> str:
15941528 return f'{ self .frozen ()!r} .mutable_copy()'
15951529
15961530
1597- def _decompose_into_cliffords (op : cirq .Operation ) -> list [cirq .Operation ]:
1598- # An operation that can be ignored?
1599- if isinstance (op .gate , global_phase_op .GlobalPhaseGate ):
1600- return []
1601-
1602- # Already a known Clifford?
1603- if isinstance (
1604- op .gate ,
1605- (clifford_gate .SingleQubitCliffordGate , pauli_interaction_gate .PauliInteractionGate ),
1606- ):
1607- return [op ]
1608-
1609- # Specifies a decomposition into Cliffords?
1610- v = getattr (op , '_decompose_into_clifford_' , None )
1611- if v is not None :
1612- result = v ()
1613- if result is not None and result is not NotImplemented :
1614- return list (op_tree .flatten_to_ops (result ))
1615-
1616- # Specifies a decomposition that happens to contain only Cliffords?
1617- decomposed = protocols .decompose_once (op , None )
1618- if decomposed is not None :
1619- return [out for sub_op in decomposed for out in _decompose_into_cliffords (sub_op )]
1620-
1621- raise TypeError ( # pragma: no cover
1622- f'Operation is not a known Clifford and did not decompose into known Cliffords: { op !r} '
1623- )
1624-
1625-
16261531# Mypy has extreme difficulty with these constants for some reason.
16271532_i = cast (identity .IdentityGate , identity .I ) # type: ignore
16281533_x = cast (pauli_gates .Pauli , pauli_gates .X ) # type: ignore
@@ -1667,3 +1572,52 @@ def _pauli_like_to_pauli_int(key: Any, pauli_gate_like: PAULI_GATE_LIKE):
16671572 f"{ set (PAULI_GATE_LIKE_TO_INDEX_MAP .keys ())!r} "
16681573 )
16691574 return pauli_int
1575+
1576+
1577+ def _calc_conjugation (ps : cirq .PauliString , clifford_op : cirq .Operation ) -> cirq .PauliString :
1578+ """Computes the conjugation of a Pauli string by a single Clifford operation.
1579+
1580+ It computes $C^-1 P C$ where P is the Pauli string `ps` and C is the `clifford_op`.
1581+ """
1582+
1583+ # Initialize the conjugation of the pauli string.
1584+ conjugated = dense_pauli_string .DensePauliString ('I' * len (clifford_op .qubits )) * ps .coefficient
1585+
1586+ # Calculate the conjugation via CliffordGate's clifford_tableau.
1587+ # Note the clifford_tableau in CliffordGate represents C·P·C^-1 instead of C^-1·P·C.
1588+ # So we take the inverse of the tableau to match the definition of the conjugation here.
1589+ if isinstance (clifford_op .gate , clifford_gate .CliffordGate ):
1590+ gate_in_clifford = clifford_op .gate
1591+ else :
1592+ # Convert the clifford gate to CliffordGate type.
1593+ gate_in_clifford = clifford_gate .CliffordGate .from_op_list (
1594+ [clifford_op ], clifford_op .qubits
1595+ )
1596+ tableau = gate_in_clifford .clifford_tableau .inverse ()
1597+
1598+ # Calculate the conjugation by `clifford_op` via mutiplying the conjugation of each Pauli:
1599+ # C^{-1}·(P_1⊗...⊗P_n)·C
1600+ # = C^{-1}·(P_1⊗I) ...·(P_n⊗I)·C
1601+ # = (C^{-1}(P_1⊗I)C)·...·(C^{-1}(P_n⊗I)C)
1602+ # For the Pauli on the kth qubit P_k. The conjugation is calculated as following.
1603+ # Pauli X_k's conjugation is from the destabilizer table;
1604+ # Pauli Z_k's conjugation is from the stabilizer table;
1605+ # Pauli Y_k's conjugation is calculated according to Y = iXZ. E.g., for the kth qubit,
1606+ # C^{-1}·Y_k⊗I·C = C^{-1}·(iX_k⊗I·Z_k⊗I)·C = i (C^{-1}·X_k⊗I·C)·(C^{-1}·Z_k⊗I·C).
1607+ for qid , qubit in enumerate (clifford_op .qubits ):
1608+ pauli = ps .get (qubit )
1609+ match pauli :
1610+ case None :
1611+ continue
1612+ case pauli_gates .X :
1613+ conjugated *= tableau .destabilizers ()[qid ]
1614+ case pauli_gates .Z :
1615+ conjugated *= tableau .stabilizers ()[qid ]
1616+ case pauli_gates .Y :
1617+ conjugated *= (
1618+ 1j
1619+ * tableau .destabilizers ()[qid ] # conj X first
1620+ * tableau .stabilizers ()[qid ] # then conj Z
1621+ )
1622+
1623+ return conjugated .on (* clifford_op .qubits )
0 commit comments