diff --git a/cirq-core/cirq/contrib/paulistring/clifford_optimize.py b/cirq-core/cirq/contrib/paulistring/clifford_optimize.py index 39400b93ca4..0284cc0b7f6 100644 --- a/cirq-core/cirq/contrib/paulistring/clifford_optimize.py +++ b/cirq-core/cirq/contrib/paulistring/clifford_optimize.py @@ -65,7 +65,7 @@ def continue_condition( furthest_i = i break if cont_cond == CONTINUE: - modified_op = modified_op.pass_operations_over([op], after_to_before=True) + modified_op = modified_op.conjugated_by(protocols.inverse(op)) num_passed_over += 1 if len(modified_op.pauli_string) == 1: furthest_op = modified_op @@ -122,7 +122,7 @@ def try_merge_clifford(cliff_op: ops.GateOperation, start_i: int) -> bool: all_ops.insert(merge_i + 1, part_cliff_gate(qubit)) elif isinstance(other_op, ops.PauliStringPhasor): # Pass over a non-Clifford gate - mod_op = other_op.pass_operations_over([part_cliff_gate(qubit)]) + mod_op = other_op.conjugated_by([part_cliff_gate(qubit)]) all_ops[merge_i] = mod_op all_ops.insert(merge_i + 1, part_cliff_gate(qubit)) elif merge_i > start_i + 1 and num_passed > 0: diff --git a/cirq-core/cirq/contrib/paulistring/recombine.py b/cirq-core/cirq/contrib/paulistring/recombine.py index 718ed47f84f..140ea9075ee 100644 --- a/cirq-core/cirq/contrib/paulistring/recombine.py +++ b/cirq-core/cirq/contrib/paulistring/recombine.py @@ -53,7 +53,7 @@ def _sorted_best_string_placements( ): # This is as far through as this Pauli string can move break - string_op = string_op.pass_operations_over([out_op], after_to_before=True) + string_op = string_op.conjugated_by(protocols.inverse(out_op)) curr = (string_op, i + 1, possible_node) if sort_key(curr) > sort_key(node_max): node_max = curr diff --git a/cirq-core/cirq/contrib/paulistring/separate.py b/cirq-core/cirq/contrib/paulistring/separate.py index f7b7343bc06..35db477d655 100644 --- a/cirq-core/cirq/contrib/paulistring/separate.py +++ b/cirq-core/cirq/contrib/paulistring/separate.py @@ -89,8 +89,9 @@ def pauli_string_half(circuit: circuits.Circuit) -> circuits.Circuit: def _pull_non_clifford_before(circuit: circuits.Circuit) -> Iterator[ops.OP_TREE]: - def _iter_ops_range_reversed(moment_end): - for i in reversed(range(moment_end)): + + def _iter_ops_range(moment_end): + for i in range(moment_end): moment = circuit[i] for op in moment.operations: if not isinstance(op, ops.PauliStringPhasor): @@ -99,5 +100,5 @@ def _iter_ops_range_reversed(moment_end): for i, moment in enumerate(circuit): for op in moment.operations: if isinstance(op, ops.PauliStringPhasor): - ops_to_cross = _iter_ops_range_reversed(i) - yield op.pass_operations_over(ops_to_cross) + ops_to_cross = _iter_ops_range(i) + yield op.conjugated_by(ops_to_cross) diff --git a/cirq-core/cirq/ops/pauli_string.py b/cirq-core/cirq/ops/pauli_string.py index e957f7c12c9..e84b49f65fc 100644 --- a/cirq-core/cirq/ops/pauli_string.py +++ b/cirq-core/cirq/ops/pauli_string.py @@ -45,6 +45,7 @@ import sympy from cirq import _compat, linalg, protocols, qis, value +from cirq._compat import deprecated from cirq._doc import document from cirq._import import LazyLoader from cirq.ops import ( @@ -1070,9 +1071,10 @@ def before(self, ops: cirq.OP_TREE) -> cirq.PauliString: """ return self.conjugated_by(ops) + @deprecated(deadline="v2.0", fix="Use conjuagetd_by()/before()/after() instead.") def pass_operations_over( self, ops: Iterable[cirq.Operation], after_to_before: bool = False - ) -> PauliString: + ) -> PauliString: # pragma: no cover """Determines how the Pauli string changes when conjugated by Cliffords. The output and input pauli strings are related by a circuit equivalence. @@ -1099,9 +1101,6 @@ def pass_operations_over( pauli string, instead of before (and so are moving in the opposite direction). """ - # TODO(#6946): deprecate this method. - # Note: This method is supposed to be replaced by conjugated_by() - # (see #2351 for details). if after_to_before: return self.after(ops) diff --git a/cirq-core/cirq/ops/pauli_string_phasor.py b/cirq-core/cirq/ops/pauli_string_phasor.py index ce962df7333..5e717fb4c28 100644 --- a/cirq-core/cirq/ops/pauli_string_phasor.py +++ b/cirq-core/cirq/ops/pauli_string_phasor.py @@ -30,7 +30,7 @@ import sympy from cirq import protocols, value -from cirq._compat import proper_repr +from cirq._compat import deprecated, proper_repr from cirq.ops import ( common_gates, dense_pauli_string as dps, @@ -199,9 +199,21 @@ def sym(qubit): syms = tuple(sym(qubit) for qubit in qubits) return protocols.CircuitDiagramInfo(wire_symbols=syms, exponent=self.exponent_relative) + def conjugated_by(self, clifford: 'cirq.OP_TREE') -> 'PauliStringPhasor': + r"""Returns the Pauli string conjugated by a clifford operation. + + The PauliStringPhasor $P$ conjugated by the Clifford operation $C$ is + $C^\dagger P C$. + """ + new_pauli_string: ps.PauliString = self.pauli_string.conjugated_by(clifford) + pp = self.exponent_pos + pn = self.exponent_neg + return PauliStringPhasor(new_pauli_string, exponent_pos=pp, exponent_neg=pn) + + @deprecated(deadline="v2.0", fix="Use conjuagetd_by() instead.") def pass_operations_over( self, ops: Iterable[raw_types.Operation], after_to_before: bool = False - ) -> PauliStringPhasor: + ) -> PauliStringPhasor: # pragma: no cover """Determines how the Pauli phasor changes when conjugated by Cliffords. The output and input pauli phasors are related by a circuit equivalence. @@ -228,7 +240,12 @@ def pass_operations_over( pauli string, instead of before (and so are moving in the opposite direction). """ - new_pauli_string = self.pauli_string.pass_operations_over(ops, after_to_before) + new_pauli_string: ps.PauliString = ps.PauliString() + if after_to_before: + new_pauli_string = self.pauli_string.after(ops) + else: + all_ops = list(op_tree.flatten_to_ops(ops)) + new_pauli_string = self.pauli_string.before(all_ops[::-1]) pp = self.exponent_pos pn = self.exponent_neg return PauliStringPhasor(new_pauli_string, exponent_pos=pp, exponent_neg=pn) diff --git a/cirq-core/cirq/ops/pauli_string_phasor_test.py b/cirq-core/cirq/ops/pauli_string_phasor_test.py index 976cd2b46f8..29e84eaedfc 100644 --- a/cirq-core/cirq/ops/pauli_string_phasor_test.py +++ b/cirq-core/cirq/ops/pauli_string_phasor_test.py @@ -155,7 +155,7 @@ def test_consistent(): cirq.testing.assert_implements_consistent_protocols(p) -def test_pass_operations_over(): +def test_conjugated_by(): q0, q1 = _make_qubits(2) op = cirq.SingleQubitCliffordGate.from_double_map( {cirq.Z: (cirq.X, False), cirq.X: (cirq.Z, False)} @@ -164,10 +164,7 @@ def test_pass_operations_over(): ps_after = cirq.PauliString({q0: cirq.Z, q1: cirq.Y}, -1) before = cirq.PauliStringPhasor(ps_before, exponent_neg=0.1) after = cirq.PauliStringPhasor(ps_after, exponent_neg=0.1) - assert before.pass_operations_over([op]).pauli_string == after.pauli_string - assert ( - after.pass_operations_over([op], after_to_before=True).pauli_string == before.pauli_string - ) + assert before.conjugated_by(op).pauli_string == after.pauli_string def test_extrapolate_effect(): diff --git a/cirq-core/cirq/ops/pauli_string_test.py b/cirq-core/cirq/ops/pauli_string_test.py index 13231df9409..4df66e44811 100644 --- a/cirq-core/cirq/ops/pauli_string_test.py +++ b/cirq-core/cirq/ops/pauli_string_test.py @@ -14,7 +14,6 @@ import itertools import math -from typing import List import numpy as np import pytest @@ -723,118 +722,6 @@ def test_to_z_basis_ops_product_state(): ) -def _assert_pass_over(ops: List[cirq.Operation], before: cirq.PauliString, after: cirq.PauliString): - assert before.pass_operations_over(ops[::-1]) == after - assert after.pass_operations_over(ops, after_to_before=True) == before - - -@pytest.mark.parametrize('shift,sign', itertools.product(range(3), (-1, +1))) -def test_pass_operations_over_single(shift: int, sign: int): - q0, q1 = _make_qubits(2) - X, Y, Z = (cirq.Pauli.by_relative_index(pauli, shift) for pauli in (cirq.X, cirq.Y, cirq.Z)) - - op0 = cirq.SingleQubitCliffordGate.from_pauli(Y)(q1) - ps_before: cirq.PauliString[cirq.Qid] = cirq.PauliString({q0: X}, sign) - ps_after = ps_before - _assert_pass_over([op0], ps_before, ps_after) - - op0 = cirq.SingleQubitCliffordGate.from_pauli(X)(q0) - op1 = cirq.SingleQubitCliffordGate.from_pauli(Y)(q1) - ps_before = cirq.PauliString({q0: X, q1: Y}, sign) - ps_after = ps_before - _assert_pass_over([op0, op1], ps_before, ps_after) - - op0 = cirq.SingleQubitCliffordGate.from_double_map({Z: (X, False), X: (Z, False)})(q0) - ps_before = cirq.PauliString({q0: X, q1: Y}, sign) - ps_after = cirq.PauliString({q0: Z, q1: Y}, sign) - _assert_pass_over([op0], ps_before, ps_after) - - op1 = cirq.SingleQubitCliffordGate.from_pauli(X)(q1) - ps_before = cirq.PauliString({q0: X, q1: Y}, sign) - ps_after = -ps_before - _assert_pass_over([op1], ps_before, ps_after) - - ps_after = cirq.PauliString({q0: Z, q1: Y}, -sign) - _assert_pass_over([op0, op1], ps_before, ps_after) - - op0 = cirq.SingleQubitCliffordGate.from_pauli(Z, True)(q0) - op1 = cirq.SingleQubitCliffordGate.from_pauli(X, True)(q0) - ps_before = cirq.PauliString({q0: X}, sign) - ps_after = cirq.PauliString({q0: Y}, -sign) - _assert_pass_over([op0, op1], ps_before, ps_after) - - -@pytest.mark.parametrize( - 'shift,t_or_f1, t_or_f2,neg', itertools.product(range(3), *((True, False),) * 3) -) -def test_pass_operations_over_double(shift: int, t_or_f1: bool, t_or_f2: bool, neg: bool): - sign = -1 if neg else +1 - q0, q1, q2 = _make_qubits(3) - X, Y, Z = (cirq.Pauli.by_relative_index(pauli, shift) for pauli in (cirq.X, cirq.Y, cirq.Z)) - - op0 = cirq.PauliInteractionGate(Z, t_or_f1, X, t_or_f2)(q0, q1) - ps_before = cirq.PauliString(qubit_pauli_map={q0: Z, q2: Y}, coefficient=sign) - ps_after = cirq.PauliString(qubit_pauli_map={q0: Z, q2: Y}, coefficient=sign) - assert_conjugation(ps_before, op0, ps_after, True) - _assert_pass_over([op0], ps_before, ps_after) - - op0 = cirq.PauliInteractionGate(Y, t_or_f1, X, t_or_f2)(q0, q1) - ps_before = cirq.PauliString({q0: Z, q2: Y}, sign) - ps_after = cirq.PauliString({q0: Z, q2: Y, q1: X}, -sign if t_or_f2 else sign) - assert_conjugation(ps_before, op0, ps_after, True) - _assert_pass_over([op0], ps_before, ps_after) - - op0 = cirq.PauliInteractionGate(Z, t_or_f1, X, t_or_f2)(q0, q1) - ps_before = cirq.PauliString({q0: Z, q1: Y}, sign) - ps_after = cirq.PauliString({q1: Y}, -sign if t_or_f1 else sign) - assert_conjugation(ps_before, op0, ps_after, True) - _assert_pass_over([op0], ps_before, ps_after) - - op0 = cirq.PauliInteractionGate(Y, t_or_f1, X, t_or_f2)(q0, q1) - ps_before = cirq.PauliString({q0: Z, q1: Y}, sign) - ps_after = cirq.PauliString({q0: X, q1: Z}, -1 if neg ^ t_or_f1 ^ t_or_f2 else +1) - assert_conjugation(ps_before, op0, ps_after, True) - _assert_pass_over([op0], ps_before, ps_after) - - op0 = cirq.PauliInteractionGate(X, t_or_f1, X, t_or_f2)(q0, q1) - ps_before = cirq.PauliString({q0: Z, q1: Y}, sign) - ps_after = cirq.PauliString({q0: Y, q1: Z}, +1 if neg ^ t_or_f1 ^ t_or_f2 else -1) - assert_conjugation(ps_before, op0, ps_after, True) - _assert_pass_over([op0], ps_before, ps_after) - - -def test_pass_operations_over_cz(): - q0, q1 = _make_qubits(2) - op0 = cirq.CZ(q0, q1) - ps_before = cirq.PauliString({q0: cirq.Z, q1: cirq.Y}) - ps_after = cirq.PauliString({q1: cirq.Y}) - _assert_pass_over([op0], ps_before, ps_after) - - -def test_pass_operations_over_no_common_qubits(): - class ExampleGate(cirq.testing.SingleQubitGate): - - def _decompose_(self, qubits): - return cirq.X(qubits[0]) - - q0, q1 = _make_qubits(2) - op0 = ExampleGate()(q1) - ps_before = cirq.PauliString({q0: cirq.Z}) - ps_after = cirq.PauliString({q0: cirq.Z}) - _assert_pass_over([op0], ps_before, ps_after) - - -def test_pass_unsupported_operations_over(): - (q0,) = _make_qubits(1) - pauli_string = cirq.PauliString({q0: cirq.X}) - with pytest.raises( - ValueError, - match='Clifford Gate can only be constructed from the operations' - ' that has stabilizer effect.', - ): - pauli_string.pass_operations_over([cirq.T(q0)]) - - def test_with_qubits(): old_qubits = cirq.LineQubit.range(9) new_qubits = cirq.LineQubit.range(9, 18) @@ -1637,40 +1524,6 @@ def test_conjugated_by_ordering(): assert out1 == out2 == cirq.X(a) * cirq.Z(b) -def test_pass_operations_over_ordering(): - class OrderSensitiveGate(cirq.Gate): - def num_qubits(self): - return 2 - - def _decompose_(self, qubits): - return [cirq.Y(qubits[0]) ** -0.5, cirq.CNOT(*qubits)] - - a, b = cirq.LineQubit.range(2) - inp = cirq.Z(b) - out1 = inp.pass_operations_over(OrderSensitiveGate().on(a, b)) - out2 = inp.pass_operations_over([cirq.CNOT(a, b), cirq.Y(a) ** -0.5]) - out3 = inp.pass_operations_over([cirq.CNOT(a, b)]).pass_operations_over([cirq.Y(a) ** -0.5]) - assert out1 == out2 == out3 == cirq.X(a) * cirq.Z(b) - - -def test_pass_operations_over_ordering_reversed(): - class OrderSensitiveGate(cirq.Gate): - def num_qubits(self): - return 2 - - def _decompose_(self, qubits): - return [cirq.Y(qubits[0]) ** -0.5, cirq.CNOT(*qubits)] - - a, b = cirq.LineQubit.range(2) - inp = cirq.X(a) * cirq.Z(b) - out1 = inp.pass_operations_over(OrderSensitiveGate().on(a, b), after_to_before=True) - out2 = inp.pass_operations_over([cirq.Y(a) ** -0.5, cirq.CNOT(a, b)], after_to_before=True) - out3 = inp.pass_operations_over([cirq.Y(a) ** -0.5], after_to_before=True).pass_operations_over( - [cirq.CNOT(a, b)], after_to_before=True - ) - assert out1 == out2 == out3 == cirq.Z(b) - - def test_pretty_print(): a, b, c = cirq.LineQubit.range(3) result = cirq.PauliString({a: 'x', b: 'y', c: 'z'})