53
53
identity ,
54
54
op_tree ,
55
55
pauli_gates ,
56
- pauli_interaction_gate ,
57
56
raw_types ,
58
57
)
59
58
@@ -968,53 +967,13 @@ def conjugated_by(self, clifford: cirq.OP_TREE) -> PauliString:
968
967
# Decompose P = Pc⊗R, where Pc acts on the same qubits as C, R acts on the remaining.
969
968
# Then the conjugation = (C^{-1}⊗I·Pc⊗R·C⊗I) = (C^{-1}·Pc·C)⊗R.
970
969
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 (
973
974
* (pauli (q ) for q in all_qubits - set (op .qubits ) if (pauli := ps .get (q )) is not None )
974
975
)
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
1018
977
return ps
1019
978
1020
979
def after (self , ops : cirq .OP_TREE ) -> cirq .PauliString :
@@ -1371,7 +1330,18 @@ def inplace_before(self, ops: cirq.OP_TREE) -> cirq.MutablePauliString:
1371
1330
Returns:
1372
1331
The mutable pauli string that was mutated.
1373
1332
"""
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
1375
1345
1376
1346
def inplace_after (self , ops : cirq .OP_TREE ) -> cirq .MutablePauliString :
1377
1347
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:
1391
1361
NotImplementedError: If any ops decompose into an unsupported
1392
1362
Clifford gate.
1393
1363
"""
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 ))
1431
1365
1432
1366
def _imul_helper (self , other : cirq .PAULI_STRING_LIKE , sign : int ):
1433
1367
"""Left-multiplies or right-multiplies by a PAULI_STRING_LIKE.
@@ -1594,35 +1528,6 @@ def __repr__(self) -> str:
1594
1528
return f'{ self .frozen ()!r} .mutable_copy()'
1595
1529
1596
1530
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
-
1626
1531
# Mypy has extreme difficulty with these constants for some reason.
1627
1532
_i = cast (identity .IdentityGate , identity .I ) # type: ignore
1628
1533
_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):
1667
1572
f"{ set (PAULI_GATE_LIKE_TO_INDEX_MAP .keys ())!r} "
1668
1573
)
1669
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