@@ -902,7 +902,7 @@ def conjugated_by(self, clifford: cirq.OP_TREE) -> PauliString:
902902 The product-of-Paulis $P$ conjugated by the Clifford operation $C$ is
903903
904904 $$
905- C^\ dagger P C
905+ C^dagger P C
906906 $$
907907
908908 For example, conjugating a +Y operation by an S operation results in a
@@ -967,50 +967,13 @@ def conjugated_by(self, clifford: cirq.OP_TREE) -> PauliString:
967967 # Decompose P = Pc⊗R, where Pc acts on the same qubits as C, R acts on the remaining.
968968 # Then the conjugation = (C^{-1}⊗I·Pc⊗R·C⊗I) = (C^{-1}·Pc·C)⊗R.
969969
970- # Isolate R
971- 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 (
972974 * (pauli (q ) for q in all_qubits - set (op .qubits ) if (pauli := ps .get (q )) is not None )
973975 )
974-
975- # Initialize the conjugation of Pc.
976- conjugated = dense_pauli_string .DensePauliString ('I' * len (op .qubits )) * ps .coefficient
977-
978- # Calculate the conjugation via CliffordGate's clifford_tableau.
979- # Note the clifford_tableau in CliffordGate represents C·P·C^-1 instead of C^-1·P·C.
980- # So we take the inverse of the tableau to match the definition of the conjugation here.
981- gate_in_clifford : cirq .CliffordGate
982- if isinstance (op .gate , clifford_gate .CliffordGate ):
983- gate_in_clifford = op .gate
984- else :
985- # Convert the clifford gate to CliffordGate type.
986- gate_in_clifford = clifford_gate .CliffordGate .from_op_list ([op ], op .qubits )
987- tableau = gate_in_clifford .clifford_tableau .inverse ()
988-
989- # Calculate the conjugation by `op` via mutiplying the conjugation of each Pauli:
990- # C^{-1}·(P_1⊗...⊗P_n)·C
991- # = C^{-1}·(P_1⊗I) ...·(P_n⊗I)·C
992- # = (C^{-1}(P_1⊗I)C)·...·(C^{-1}(P_n⊗I)C)
993- # For the Pauli on the kth qubit P_k. The conjugation is calculated as following.
994- # Puali X_k's conjugation is from the destabilzer table;
995- # Puali Z_k's conjugation is from the stabilzer table;
996- # Puali Y_k's conjugation is calcluated according to Y = iXZ. E.g., for the kth qubit,
997- # 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).
998- for qid , qubit in enumerate (op .qubits ):
999- pauli = ps .get (qubit )
1000- match pauli :
1001- case None :
1002- continue
1003- case pauli_gates .X :
1004- conjugated *= tableau .destabilizers ()[qid ]
1005- case pauli_gates .Z :
1006- conjugated *= tableau .stabilizers ()[qid ]
1007- case pauli_gates .Y :
1008- conjugated *= (
1009- 1j
1010- * tableau .destabilizers ()[qid ] # conj X first
1011- * tableau .stabilizers ()[qid ] # then conj Z
1012- )
1013- ps = remain * conjugated .on (* op .qubits )
976+ ps = remain * conjugated
1014977 return ps
1015978
1016979 def after (self , ops : cirq .OP_TREE ) -> cirq .PauliString :
@@ -1370,32 +1333,10 @@ def inplace_before(self, ops: cirq.OP_TREE) -> cirq.MutablePauliString:
13701333 # An inplace impl of PauliString.conjugated_by().
13711334 flattened_ops = list (op_tree .flatten_to_ops (ops ))
13721335 for op in flattened_ops [::- 1 ]:
1373- conjugated = dense_pauli_string .DensePauliString ('I' * len (op .qubits ))
1374- gate_in_clifford : cirq .CliffordGate
1375- if isinstance (op .gate , clifford_gate .CliffordGate ):
1376- gate_in_clifford = op .gate
1377- else :
1378- gate_in_clifford = clifford_gate .CliffordGate .from_op_list ([op ], op .qubits )
1379- tableau = gate_in_clifford .clifford_tableau .inverse ()
1380-
1381- for qid , q in enumerate (op .qubits ):
1382- pauli = self .get (cast (TKey , q ), None )
1383- match pauli :
1384- case pauli_gates .X :
1385- conjugated *= tableau .destabilizers ()[qid ]
1386- case pauli_gates .Z :
1387- conjugated *= tableau .stabilizers ()[qid ]
1388- case pauli_gates .Y :
1389- conjugated *= (
1390- 1j
1391- * tableau .destabilizers ()[qid ] # conj X first
1392- * tableau .stabilizers ()[qid ] # then conj Z
1393- )
1394- case _:
1395- continue
1396- self .coefficient *= conjugated .coefficient
1397- for qid , q in enumerate (op .qubits ):
1398- new_pauli_int = PAULI_GATE_LIKE_TO_INDEX_MAP [conjugated [qid ]]
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 ]
13991340 if new_pauli_int == 0 :
14001341 self .pauli_int_dict .pop (cast (TKey , q ), None )
14011342 else :
@@ -1631,3 +1572,52 @@ def _pauli_like_to_pauli_int(key: Any, pauli_gate_like: PAULI_GATE_LIKE):
16311572 f"{ set (PAULI_GATE_LIKE_TO_INDEX_MAP .keys ())!r} "
16321573 )
16331574 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