Skip to content

Add is_cptp predicate #4365

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

Merged
merged 4 commits into from
Jul 29, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
dot,
expand_matrix_in_orthogonal_basis,
hilbert_schmidt_inner_product,
is_cptp,
is_diagonal,
is_hermitian,
is_normal,
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/linalg/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@

from cirq.linalg.predicates import (
allclose_up_to_global_phase,
is_cptp,
is_diagonal,
is_hermitian,
is_normal,
Expand Down
15 changes: 15 additions & 0 deletions cirq-core/cirq/linalg/predicates.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,21 @@ def is_normal(matrix: np.ndarray, *, rtol: float = 1e-5, atol: float = 1e-8) ->
return matrix_commutes(matrix, matrix.T.conj(), rtol=rtol, atol=atol)


def is_cptp(kraus_ops: Sequence[np.ndarray], *, rtol: float = 1e-5, atol: float = 1e-8):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do people like @viathor or @Strilanc have any strong opinions on whether we should try to support kraus ops and choi or other things one could use to specify a map ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If we'd like to add such support, I recommend we make a note of it in #2271 and follow up later. AFAIK there's no Choi / superoperator equivalent of #4194 in the works yet, but it would be good to have this if and when that appears.

Copy link
Contributor

Choose a reason for hiding this comment

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

A reasonable way to ensure this is forward compatible with changing that choice is to force keyword args for kraus_ops. We can weaken it later that way if we decide to stick with it, or add other args if we want to extend.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good point - moved kraus_ops into kwargs.

"""Determines if a channel is completely positive trace preserving (CPTP).

A channel composed of Kraus operators K[0:n] is a CPTP map if the sum of
the products `adjoint(K[i]) * K[i])` is equal to 1.

Args:
kraus_ops: The Kraus operators of the channel to check.
rtol: The relative tolerance on equality.
atol: The absolute tolerance on equality.
"""
sum_ndarray = np.sum(matrix.T.conj() @ matrix for matrix in kraus_ops)
return np.allclose(sum_ndarray, np.eye(*sum_ndarray.shape), rtol=rtol, atol=atol)


def matrix_commutes(
m1: np.ndarray, m2: np.ndarray, *, rtol: float = 1e-5, atol: float = 1e-8
) -> bool:
Expand Down
39 changes: 39 additions & 0 deletions cirq-core/cirq/linalg/predicates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,45 @@ def test_is_normal_tolerance():
assert not cirq.is_normal(np.array([[0, 0.5, 0], [0, 0, 0.6], [0, 0, 0]]), atol=atol)


def test_is_cptp():
rt2 = np.sqrt(0.5)
# amplitude damping with gamma=0.5
assert cirq.is_cptp([np.array([[1, 0], [0, rt2]]), np.array([[0, rt2], [0, 0]])])
assert cirq.is_cptp(cirq.kraus(cirq.amplitude_damp(0.5)))
# depolarizing channel with p=0.75
assert cirq.is_cptp(
[
np.array([[1, 0], [0, 1]]) * 0.5,
np.array([[0, 1], [1, 0]]) * 0.5,
np.array([[0, -1j], [1j, 0]]) * 0.5,
np.array([[1, 0], [0, -1]]) * 0.5,
]
)
assert cirq.is_cptp(cirq.kraus(cirq.depolarize(0.75)))

assert not cirq.is_cptp([np.array([[1, 0], [0, 1]]), np.array([[0, 1], [1, 0]])])
assert not cirq.is_cptp(
[
np.array([[1, 0], [0, 1]]),
np.array([[0, 1], [1, 0]]),
np.array([[0, -1j], [1j, 0]]),
np.array([[1, 0], [0, -1]]),
]
)


def test_is_cptp_tolerance():
rt2_ish = np.sqrt(0.5) - 0.01
atol = 0.25
# moderately-incorrect amplitude damping with gamma=0.5
assert cirq.is_cptp(
[np.array([[1, 0], [0, rt2_ish]]), np.array([[0, rt2_ish], [0, 0]])], atol=atol
)
assert not cirq.is_cptp(
[np.array([[1, 0], [0, rt2_ish]]), np.array([[0, rt2_ish], [0, 0]])], atol=1e-8
)


def test_commutes():
assert matrix_commutes(np.empty((0, 0)), np.empty((0, 0)))
assert not matrix_commutes(np.empty((1, 0)), np.empty((0, 1)))
Expand Down