Skip to content

Commit c3a6ead

Browse files
committed
address comments
1 parent 092f62f commit c3a6ead

File tree

1 file changed

+59
-69
lines changed

1 file changed

+59
-69
lines changed

cirq-core/cirq/ops/pauli_string.py

Lines changed: 59 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)