Skip to content

Commit 4292cea

Browse files
committed
Update pass_operations_over with fixed conjugated_by
1 parent d9c9d1f commit 4292cea

File tree

2 files changed

+70
-32
lines changed

2 files changed

+70
-32
lines changed

cirq-core/cirq/ops/pauli_string.py

+11-15
Original file line numberDiff line numberDiff line change
@@ -997,7 +997,7 @@ def conjugated_by(self, clifford: 'cirq.OP_TREE') -> 'PauliString':
997997
# Initialize the conjugation of Pc.
998998
conjugated: 'cirq.DensePauliString' = (
999999
dense_pauli_string.DensePauliString(pauli_mask=[identity.I for _ in op.qubits])
1000-
* self.coefficient
1000+
* ps.coefficient
10011001
)
10021002

10031003
# Calculate the conjugation via CliffordGate's clifford_tableau.
@@ -1099,20 +1099,16 @@ def pass_operations_over(
10991099
pauli string, instead of before (and so are moving in the
11001100
opposite direction).
11011101
"""
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)
1102+
# TODO(#6946): deprecate this method.
1103+
# Note: This method is supposed to be replaced by conjugated_by()
1104+
# (see #2351 for details).
1105+
if after_to_before:
1106+
return self.after(ops)
1107+
1108+
if isinstance(ops, gate_operation.GateOperation):
1109+
return self.before(ops)
1110+
1111+
return self.before(ops[::-1])
11161112

11171113
def _is_parameterized_(self) -> bool:
11181114
return protocols.is_parameterized(self.coefficient)

cirq-core/cirq/ops/pauli_string_test.py

+59-17
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,47 @@ 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+
def _ps_on_qubits(ps: cirq.PauliString, qubits: list[cirq.Qid]):
67+
"""Gets a sub PauliString from the given PauliString on a subset of qubits
68+
on the original PauliString and preserves the coefficient.
69+
"""
70+
pauli_map = {}
71+
for q, pauli in ps.items():
72+
if q in qubits:
73+
pauli_map[q] = pauli
74+
return cirq.PauliString(qubit_pauli_map=pauli_map, coefficient=ps.coefficient)
75+
76+
conjugation = input_ps.conjugated_by(op)
77+
if expected is None or force_checking_unitary:
78+
# Compares the unitary of the conjugation result and the expected unitary.
79+
clifford = cirq.CliffordGate.from_op_list([op], op.qubits)
80+
actual_unitary = cirq.unitary(_ps_on_qubits(conjugation, op.qubits).dense(op.qubits))
81+
c = cirq.unitary(clifford)
82+
expected_unitary = (
83+
np.conj(c.T) @ cirq.unitary(_ps_on_qubits(input_ps, op.qubits).dense(op.qubits)) @ c
84+
)
85+
assert np.allclose(actual_unitary, expected_unitary, atol=1e-8)
86+
if expected is not None:
87+
assert conjugation == expected
88+
89+
90+
def assert_conjugation_multi_ops(
91+
input_ps: cirq.PauliString, ops: list[cirq.Operation], expected: cirq.PauliString | None = None
6292
):
6393
conjugation = input_ps.conjugated_by(ops)
6494
if expected is not None:
6595
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)
96+
# conj_by(op_{n-1}).conj_by(op_{n-1}).....conj_by(op_0)
97+
conj_in_order = input_ps
98+
for op in ops[::-1]:
99+
assert_conjugation(conj_in_order, op)
100+
conj_in_order = conj_in_order.conjugated_by(op)
101+
assert conjugation == conj_in_order
74102

75103

76104
def test_eq_ne_hash():
@@ -741,26 +769,31 @@ def test_pass_operations_over_double(shift: int, t_or_f1: bool, t_or_f2: bool, n
741769
op0 = cirq.PauliInteractionGate(Z, t_or_f1, X, t_or_f2)(q0, q1)
742770
ps_before = cirq.PauliString(qubit_pauli_map={q0: Z, q2: Y}, coefficient=sign)
743771
ps_after = cirq.PauliString(qubit_pauli_map={q0: Z, q2: Y}, coefficient=sign)
772+
assert_conjugation(ps_before, op0, ps_after, True)
744773
_assert_pass_over([op0], ps_before, ps_after)
745774

746775
op0 = cirq.PauliInteractionGate(Y, t_or_f1, X, t_or_f2)(q0, q1)
747776
ps_before = cirq.PauliString({q0: Z, q2: Y}, sign)
748-
ps_after = cirq.PauliString({q0: Z, q2: Y, q1: X}, sign)
777+
ps_after = cirq.PauliString({q0: Z, q2: Y, q1: X}, -sign if t_or_f2 else sign)
778+
assert_conjugation(ps_before, op0, ps_after, True)
749779
_assert_pass_over([op0], ps_before, ps_after)
750780

751781
op0 = cirq.PauliInteractionGate(Z, t_or_f1, X, t_or_f2)(q0, q1)
752782
ps_before = cirq.PauliString({q0: Z, q1: Y}, sign)
753-
ps_after = cirq.PauliString({q1: Y}, sign)
783+
ps_after = cirq.PauliString({q1: Y}, -sign if t_or_f1 else sign)
784+
assert_conjugation(ps_before, op0, ps_after, True)
754785
_assert_pass_over([op0], ps_before, ps_after)
755786

756787
op0 = cirq.PauliInteractionGate(Y, t_or_f1, X, t_or_f2)(q0, q1)
757788
ps_before = cirq.PauliString({q0: Z, q1: Y}, sign)
758789
ps_after = cirq.PauliString({q0: X, q1: Z}, -1 if neg ^ t_or_f1 ^ t_or_f2 else +1)
790+
assert_conjugation(ps_before, op0, ps_after, True)
759791
_assert_pass_over([op0], ps_before, ps_after)
760792

761793
op0 = cirq.PauliInteractionGate(X, t_or_f1, X, t_or_f2)(q0, q1)
762794
ps_before = cirq.PauliString({q0: Z, q1: Y}, sign)
763795
ps_after = cirq.PauliString({q0: Y, q1: Z}, +1 if neg ^ t_or_f1 ^ t_or_f2 else -1)
796+
assert_conjugation(ps_before, op0, ps_after, True)
764797
_assert_pass_over([op0], ps_before, ps_after)
765798

766799

@@ -774,7 +807,12 @@ def test_pass_operations_over_cz():
774807

775808
def test_pass_operations_over_no_common_qubits():
776809
class ExampleGate(cirq.testing.SingleQubitGate):
777-
pass
810+
811+
def num_qubits(self):
812+
return 1
813+
814+
def _decompose_(self, qubits):
815+
return cirq.X(qubits[0])
778816

779817
q0, q1 = _make_qubits(2)
780818
op0 = ExampleGate()(q1)
@@ -786,7 +824,11 @@ class ExampleGate(cirq.testing.SingleQubitGate):
786824
def test_pass_unsupported_operations_over():
787825
(q0,) = _make_qubits(1)
788826
pauli_string = cirq.PauliString({q0: cirq.X})
789-
with pytest.raises(TypeError, match='not a known Clifford'):
827+
with pytest.raises(
828+
ValueError,
829+
match='Clifford Gate can only be constructed from the operations'
830+
' that has stabilizer effect.',
831+
):
790832
pauli_string.pass_operations_over([cirq.T(q0)])
791833

792834

@@ -1523,8 +1565,8 @@ def _decompose_(self, qubits):
15231565
def test_conjugated_by_move_into_uninvolved():
15241566
a, b, c, d = cirq.LineQubit.range(4)
15251567
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))
1568+
assert_conjugation_multi_ops(ps, [cirq.SWAP(c, d), cirq.SWAP(b, c)], cirq.X(a) * cirq.Z(d))
1569+
assert_conjugation_multi_ops(ps, [cirq.SWAP(b, c), cirq.SWAP(c, d)], cirq.X(a) * cirq.Z(c))
15281570

15291571

15301572
def test_conjugated_by_common_single_qubit_gates():
@@ -1549,7 +1591,7 @@ def test_conjugated_by_common_single_qubit_gates():
15491591
# pauli gate on a, clifford on b: pauli gate preserves.
15501592
assert_conjugation(p(a), g(b), p(a))
15511593
# pauli gate on a, clifford on a: check conjugation in matrices.
1552-
assert_conjugation(p(a), g(a), None)
1594+
assert_conjugation(p(a), g(a))
15531595

15541596

15551597
def test_conjugated_by_common_two_qubit_gates():
@@ -1580,7 +1622,7 @@ def test_conjugated_by_common_two_qubit_gates():
15801622
assert_conjugation(p, g(c, d), p)
15811623
# pauli_string on (a,b), clifford on (a,b): compare unitaries of
15821624
# the conjugated_by and actual matrix conjugation.
1583-
assert_conjugation(p, g.on(a, b), None)
1625+
assert_conjugation(p, g.on(a, b))
15841626

15851627

15861628
def test_conjugated_by_ordering():

0 commit comments

Comments
 (0)