Skip to content

Fix: Allow subtraction of IdentityGate in PauliSum.__sub__ #7276

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 9 commits into
base: main
Choose a base branch
from
5 changes: 4 additions & 1 deletion cirq-core/cirq/ops/linear_combinations.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,10 @@ def __isub__(self, other):

def __sub__(self, other):
if not isinstance(other, (numbers.Complex, PauliString, PauliSum)):
return NotImplemented
if hasattr(other, 'gate') and isinstance(other.gate, identity.IdentityGate):
other = PauliString(other)
else:
return NotImplemented
result = self.copy()
result -= other
return result
Expand Down
22 changes: 22 additions & 0 deletions cirq-core/cirq/ops/pauli_string_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2137,3 +2137,25 @@ def test_resolve(resolve_fn):
pst = cirq.PauliString({q: 'x'}, coefficient=t)
ps1 = cirq.PauliString({q: 'x'}, coefficient=1j)
assert resolve_fn(pst, {'t': 1j}) == ps1

def test_pauli_sum_identity_operations():
q = cirq.LineQubit(0)
paulis = tuple(cirq.PauliString(p) for p in (cirq.I(q), cirq.X(q), cirq.Y(q), cirq.Z(q)))

def add(x, y):
return x + y

def sub(x, y):
return x - y

def addm(x, y):
return x + (-1.0) * y

for p1 in paulis:
for p2 in paulis:
for op in (add, sub, addm):
try:
_ = op(p1, p2)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I+I and I-I will still fail here, for reasons described in the linked issue thread. If so, probably have to add a conditional check here to skip those cases.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh. Surprisingly I+I passes now even though it didn't in the linked issue and this PR didn't change __add__. Any idea what changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My test wraps all gates in PauliString, so operations like PauliString(I(q)) + PauliString(I(q)) are handled correctly and the test passes. If you use raw I(q) + I(q) (without PauliString), it will fail, but that's not the intent of this test.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see. But wouldn't those all also pass prior to your change? IIUC the change fixes unwrapped paulis by implicitly wrapping them during subtraction. So IIUC this test where they're explicitly being wrapped beforehand wouldn't actually test the fix. Am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No you were right. Those tests didn't test anything new. Just added new tests that should cover the changes. Let me know if that looks good.

In terms of the I+I, it's still not passing on my end.

Copy link
Collaborator

@daxfohl daxfohl Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I+I being out of scope was discussed in the linked issue. It might not be that hard to create an IdentityGate.__add__/__sub__ that replaces self with an empty PauliString first (or maybe DensePauliString since it's a gate?). Now that I think of it, that approach might even remove the need for these instance checks in PauliSum.__add__/__sub__? Or it may not work at all and/or break some other tests. Haven't tried.

If you want to give that a shot and see if it works, go ahead. Otherwise, this looks good, just open up a separate issue to track the I+I case and mention the issue number in your unit test's comment about bypassing the I+I case.

Copy link
Collaborator

@daxfohl daxfohl Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, check/format-incremental( --apply), check/pylint-changed-files, check/pytest(-changed-files), check/mypy, check/all from the command line are useful for avoiding having to wait for CI just to find silly mistakes. https://github.com/quantumlib/Cirq/tree/main/check

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, thanks! Updated the new issue to #7280

except Exception as e:
import pytest
pytest.fail(f"Operation {p1.gate}{getattr(op, 'sym', op.__name__)}{p2.gate} raised {e}")
Loading