Skip to content

Starts deprecating pass_operations_over for PauliString and PauliStringPhasor #7294

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cirq-core/cirq/contrib/paulistring/clifford_optimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion cirq-core/cirq/contrib/paulistring/recombine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions cirq-core/cirq/contrib/paulistring/separate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
7 changes: 3 additions & 4 deletions cirq-core/cirq/ops/pauli_string.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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.
Expand All @@ -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)

Expand Down
23 changes: 20 additions & 3 deletions cirq-core/cirq/ops/pauli_string_phasor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand Down
7 changes: 2 additions & 5 deletions cirq-core/cirq/ops/pauli_string_phasor_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
Expand All @@ -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():
Expand Down
147 changes: 0 additions & 147 deletions cirq-core/cirq/ops/pauli_string_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import itertools
import math
from typing import List

import numpy as np
import pytest
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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'})
Expand Down