Skip to content

Commit 3997ec7

Browse files
committed
Update pass_operations_over with fixed conjugated_by
1 parent d9c9d1f commit 3997ec7

File tree

2 files changed

+78
-36
lines changed

2 files changed

+78
-36
lines changed

cirq-core/cirq/ops/pauli_string.py

+13-19
Original file line numberDiff line numberDiff line change
@@ -979,10 +979,7 @@ def conjugated_by(self, clifford: 'cirq.OP_TREE') -> 'PauliString':
979979
# Initialize the ps the same as self.
980980
ps = PauliString(qubit_pauli_map=self._qubit_pauli_map, coefficient=self.coefficient)
981981
all_ops = list(op_tree.flatten_to_ops(clifford))
982-
all_qubits: set[TKey] = set.union(
983-
set(self.qubits), [q for op in all_ops for q in op.qubits]
984-
)
985-
982+
all_qubits = set.union(set(self.qubits), [q for op in all_ops for q in op.qubits])
986983
# Iteratively calculate the conjugation in reverse order of ops.
987984
for op in all_ops[::-1]:
988985
# To calcuate the conjugation of P (`ps`) with respect to C (`op`)
@@ -997,7 +994,7 @@ def conjugated_by(self, clifford: 'cirq.OP_TREE') -> 'PauliString':
997994
# Initialize the conjugation of Pc.
998995
conjugated: 'cirq.DensePauliString' = (
999996
dense_pauli_string.DensePauliString(pauli_mask=[identity.I for _ in op.qubits])
1000-
* self.coefficient
997+
* ps.coefficient
1001998
)
1002999

10031000
# Calculate the conjugation via CliffordGate's clifford_tableau.
@@ -1099,20 +1096,17 @@ def pass_operations_over(
10991096
pauli string, instead of before (and so are moving in the
11001097
opposite direction).
11011098
"""
1102-
pauli_map = dict(self._qubit_pauli_map)
1103-
should_negate = False
1104-
for op in ops:
1105-
if pauli_map.keys().isdisjoint(set(op.qubits)):
1106-
continue
1107-
decomposed = _decompose_into_cliffords(op)
1108-
if not after_to_before:
1109-
decomposed = decomposed[::-1]
1110-
for clifford_op in decomposed:
1111-
if pauli_map.keys().isdisjoint(set(clifford_op.qubits)):
1112-
continue
1113-
should_negate ^= _pass_operation_over(pauli_map, clifford_op, after_to_before)
1114-
coef = -self._coefficient if should_negate else self.coefficient
1115-
return PauliString(qubit_pauli_map=pauli_map, coefficient=coef)
1099+
# TODO(#6946): deprecate this method.
1100+
# Note: This method is supposed to be replaced by conjugated_by()
1101+
# (see #2351 for details).
1102+
if after_to_before:
1103+
return self.after(ops)
1104+
1105+
if isinstance(ops, gate_operation.GateOperation):
1106+
return self.before(ops)
1107+
1108+
all_ops = list(op_tree.flatten_to_ops(ops))
1109+
return self.before(all_ops[::-1])
11161110

11171111
def _is_parameterized_(self) -> bool:
11181112
return protocols.is_parameterized(self.coefficient)

cirq-core/cirq/ops/pauli_string_test.py

+65-17
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,53 @@ def _small_sample_qubit_pauli_maps():
5858

5959

6060
def assert_conjugation(
61-
input_ps: cirq.PauliString, ops: cirq.OP_TREE, expected: cirq.PauliString | None
61+
input_ps: cirq.PauliString,
62+
op: cirq.Operation,
63+
expected: cirq.PauliString | None = None,
64+
force_checking_unitary=True,
65+
):
66+
"""Verifies that conjugating `input_ps` by `op` results in `expected`.
67+
68+
Also ensures that the unitary representation of the Pauli string is
69+
preserved under the conjugation.
70+
"""
71+
72+
def _ps_on_qubits(ps: cirq.PauliString, qubits: tuple[cirq.Qid, ...]):
73+
"""Extracts a sub-PauliString from a given PauliString, restricted to
74+
a specified subset of qubits.
75+
"""
76+
pauli_map = {}
77+
for q, pauli in ps.items():
78+
if q in qubits:
79+
pauli_map[q] = pauli
80+
return cirq.PauliString(qubit_pauli_map=pauli_map, coefficient=ps.coefficient)
81+
82+
conjugation = input_ps.conjugated_by(op)
83+
if expected is None or force_checking_unitary:
84+
# Compares the unitary of the conjugation result and the expected unitary.
85+
clifford = cirq.CliffordGate.from_op_list([op], op.qubits)
86+
actual_unitary = cirq.unitary(_ps_on_qubits(conjugation, op.qubits).dense(op.qubits))
87+
c = cirq.unitary(clifford)
88+
expected_unitary = (
89+
np.conj(c.T) @ cirq.unitary(_ps_on_qubits(input_ps, op.qubits).dense(op.qubits)) @ c
90+
)
91+
assert np.allclose(actual_unitary, expected_unitary, atol=1e-8)
92+
if expected is not None:
93+
assert conjugation == expected
94+
95+
96+
def assert_conjugation_multi_ops(
97+
input_ps: cirq.PauliString, ops: list[cirq.Operation], expected: cirq.PauliString | None = None
6298
):
6399
conjugation = input_ps.conjugated_by(ops)
64100
if expected is not None:
65101
assert conjugation == expected
66-
else: # Compares the unitary of the conjugation result and the expected unitary.
67-
op_list = list(cirq.flatten_to_ops(ops))
68-
qubits_of_clifford = [q for op in op_list for q in op.qubits]
69-
clifford = cirq.CliffordGate.from_op_list(op_list, qubits_of_clifford)
70-
actual_unitary = cirq.unitary(conjugation.dense(qubits_of_clifford))
71-
c = cirq.unitary(clifford)
72-
expected_unitary = np.conj(c.T) @ cirq.unitary(input_ps.dense(qubits_of_clifford)) @ c
73-
assert np.allclose(actual_unitary, expected_unitary, atol=1e-8)
102+
# conj_by(op_{n-1}).conj_by(op_{n-1}).....conj_by(op_0)
103+
conj_in_order = input_ps
104+
for op in ops[::-1]:
105+
assert_conjugation(conj_in_order, op)
106+
conj_in_order = conj_in_order.conjugated_by(op)
107+
assert conjugation == conj_in_order
74108

75109

76110
def test_eq_ne_hash():
@@ -741,26 +775,31 @@ def test_pass_operations_over_double(shift: int, t_or_f1: bool, t_or_f2: bool, n
741775
op0 = cirq.PauliInteractionGate(Z, t_or_f1, X, t_or_f2)(q0, q1)
742776
ps_before = cirq.PauliString(qubit_pauli_map={q0: Z, q2: Y}, coefficient=sign)
743777
ps_after = cirq.PauliString(qubit_pauli_map={q0: Z, q2: Y}, coefficient=sign)
778+
assert_conjugation(ps_before, op0, ps_after, True)
744779
_assert_pass_over([op0], ps_before, ps_after)
745780

746781
op0 = cirq.PauliInteractionGate(Y, t_or_f1, X, t_or_f2)(q0, q1)
747782
ps_before = cirq.PauliString({q0: Z, q2: Y}, sign)
748-
ps_after = cirq.PauliString({q0: Z, q2: Y, q1: X}, sign)
783+
ps_after = cirq.PauliString({q0: Z, q2: Y, q1: X}, -sign if t_or_f2 else sign)
784+
assert_conjugation(ps_before, op0, ps_after, True)
749785
_assert_pass_over([op0], ps_before, ps_after)
750786

751787
op0 = cirq.PauliInteractionGate(Z, t_or_f1, X, t_or_f2)(q0, q1)
752788
ps_before = cirq.PauliString({q0: Z, q1: Y}, sign)
753-
ps_after = cirq.PauliString({q1: Y}, sign)
789+
ps_after = cirq.PauliString({q1: Y}, -sign if t_or_f1 else sign)
790+
assert_conjugation(ps_before, op0, ps_after, True)
754791
_assert_pass_over([op0], ps_before, ps_after)
755792

756793
op0 = cirq.PauliInteractionGate(Y, t_or_f1, X, t_or_f2)(q0, q1)
757794
ps_before = cirq.PauliString({q0: Z, q1: Y}, sign)
758795
ps_after = cirq.PauliString({q0: X, q1: Z}, -1 if neg ^ t_or_f1 ^ t_or_f2 else +1)
796+
assert_conjugation(ps_before, op0, ps_after, True)
759797
_assert_pass_over([op0], ps_before, ps_after)
760798

761799
op0 = cirq.PauliInteractionGate(X, t_or_f1, X, t_or_f2)(q0, q1)
762800
ps_before = cirq.PauliString({q0: Z, q1: Y}, sign)
763801
ps_after = cirq.PauliString({q0: Y, q1: Z}, +1 if neg ^ t_or_f1 ^ t_or_f2 else -1)
802+
assert_conjugation(ps_before, op0, ps_after, True)
764803
_assert_pass_over([op0], ps_before, ps_after)
765804

766805

@@ -774,7 +813,12 @@ def test_pass_operations_over_cz():
774813

775814
def test_pass_operations_over_no_common_qubits():
776815
class ExampleGate(cirq.testing.SingleQubitGate):
777-
pass
816+
817+
def num_qubits(self):
818+
return 1
819+
820+
def _decompose_(self, qubits):
821+
return cirq.X(qubits[0])
778822

779823
q0, q1 = _make_qubits(2)
780824
op0 = ExampleGate()(q1)
@@ -786,7 +830,11 @@ class ExampleGate(cirq.testing.SingleQubitGate):
786830
def test_pass_unsupported_operations_over():
787831
(q0,) = _make_qubits(1)
788832
pauli_string = cirq.PauliString({q0: cirq.X})
789-
with pytest.raises(TypeError, match='not a known Clifford'):
833+
with pytest.raises(
834+
ValueError,
835+
match='Clifford Gate can only be constructed from the operations'
836+
' that has stabilizer effect.',
837+
):
790838
pauli_string.pass_operations_over([cirq.T(q0)])
791839

792840

@@ -1523,8 +1571,8 @@ def _decompose_(self, qubits):
15231571
def test_conjugated_by_move_into_uninvolved():
15241572
a, b, c, d = cirq.LineQubit.range(4)
15251573
ps = cirq.X(a) * cirq.Z(b)
1526-
assert_conjugation(ps, [cirq.SWAP(c, d), cirq.SWAP(b, c)], cirq.X(a) * cirq.Z(d))
1527-
assert_conjugation(ps, [cirq.SWAP(b, c), cirq.SWAP(c, d)], cirq.X(a) * cirq.Z(c))
1574+
assert_conjugation_multi_ops(ps, [cirq.SWAP(c, d), cirq.SWAP(b, c)], cirq.X(a) * cirq.Z(d))
1575+
assert_conjugation_multi_ops(ps, [cirq.SWAP(b, c), cirq.SWAP(c, d)], cirq.X(a) * cirq.Z(c))
15281576

15291577

15301578
def test_conjugated_by_common_single_qubit_gates():
@@ -1549,7 +1597,7 @@ def test_conjugated_by_common_single_qubit_gates():
15491597
# pauli gate on a, clifford on b: pauli gate preserves.
15501598
assert_conjugation(p(a), g(b), p(a))
15511599
# pauli gate on a, clifford on a: check conjugation in matrices.
1552-
assert_conjugation(p(a), g(a), None)
1600+
assert_conjugation(p(a), g(a))
15531601

15541602

15551603
def test_conjugated_by_common_two_qubit_gates():
@@ -1580,7 +1628,7 @@ def test_conjugated_by_common_two_qubit_gates():
15801628
assert_conjugation(p, g(c, d), p)
15811629
# pauli_string on (a,b), clifford on (a,b): compare unitaries of
15821630
# the conjugated_by and actual matrix conjugation.
1583-
assert_conjugation(p, g.on(a, b), None)
1631+
assert_conjugation(p, g.on(a, b))
15841632

15851633

15861634
def test_conjugated_by_ordering():

0 commit comments

Comments
 (0)