@@ -902,7 +902,7 @@ def conjugated_by(self, clifford: cirq.OP_TREE) -> PauliString:
902
902
The product-of-Paulis $P$ conjugated by the Clifford operation $C$ is
903
903
904
904
$$
905
- C^\ dagger P C
905
+ C^dagger P C
906
906
$$
907
907
908
908
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:
967
967
# Decompose P = Pc⊗R, where Pc acts on the same qubits as C, R acts on the remaining.
968
968
# Then the conjugation = (C^{-1}⊗I·Pc⊗R·C⊗I) = (C^{-1}·Pc·C)⊗R.
969
969
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 (
972
974
* (pauli (q ) for q in all_qubits - set (op .qubits ) if (pauli := ps .get (q )) is not None )
973
975
)
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
1014
977
return ps
1015
978
1016
979
def after (self , ops : cirq .OP_TREE ) -> cirq .PauliString :
@@ -1370,32 +1333,10 @@ def inplace_before(self, ops: cirq.OP_TREE) -> cirq.MutablePauliString:
1370
1333
# An inplace impl of PauliString.conjugated_by().
1371
1334
flattened_ops = list (op_tree .flatten_to_ops (ops ))
1372
1335
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 ]
1399
1340
if new_pauli_int == 0 :
1400
1341
self .pauli_int_dict .pop (cast (TKey , q ), None )
1401
1342
else :
@@ -1631,3 +1572,52 @@ def _pauli_like_to_pauli_int(key: Any, pauli_gate_like: PAULI_GATE_LIKE):
1631
1572
f"{ set (PAULI_GATE_LIKE_TO_INDEX_MAP .keys ())!r} "
1632
1573
)
1633
1574
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