Skip to content
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

Added the ability to control slackness in physicality conditions. #524

Open
wants to merge 2 commits into
base: develop
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
18 changes: 11 additions & 7 deletions mrmustard/lab_dev/states/dm.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ class DM(State):

short_name = "DM"

@property
def is_positive(self) -> bool:
def is_positive(self, atol: float = settings.ATOL) -> bool:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Rather than adding a parameter what if instead we encouraged users to use

with settings(ATOL=1e-8):
     ...

Of course we would then have to make that clear in our documentation.

r"""
Whether this DM is a positive operator.

Arg:
atol: The tolerance we allow for positivity conditions.
"""
batch_dim = self.ansatz.batch_size
if batch_dim > 1:
Expand All @@ -64,18 +66,20 @@ def is_positive(self) -> bool:
gamma_A = A[:m, m:]

if (
math.real(math.norm(gamma_A - math.conj(gamma_A.T))) > settings.ATOL
math.real(math.norm(gamma_A - math.conj(gamma_A.T))) > atol
): # checks if gamma_A is Hermitian
return False

return all(math.real(math.eigvals(gamma_A)) >= 0)
return all(math.real(math.eigvals(gamma_A)) >= -atol)

@property
def is_physical(self) -> bool:
def is_physical(self, atol: float = settings.ATOL) -> bool:
r"""
Whether this DM is a physical density operator.

Arg:
atol: the tolerance we allow for physicality conditions.
"""
return self.is_positive and math.allclose(self.probability, 1, settings.ATOL)
return self.is_positive(atol) and math.allclose(self.probability, 1, atol)

@property
def probability(self) -> float:
Expand Down
11 changes: 6 additions & 5 deletions mrmustard/lab_dev/states/ket.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,14 @@ class Ket(State):

short_name = "Ket"

@property
def is_physical(self) -> bool:
def is_physical(self, atol: float = settings.ATOL) -> bool:
r"""
Whether the ket object is a physical one.

Arg:
atol: The tolerance we allow for physicality conditions. The default value is settings.ATOL.
"""

batch_dim = self.ansatz.batch_size
if batch_dim > 1:
raise ValueError(
Expand All @@ -69,9 +72,7 @@ def is_physical(self) -> bool:

A = self.ansatz.A[0]

return all(math.abs(math.eigvals(A)) < 1) and math.allclose(
self.probability, 1, settings.ATOL
)
return all(math.abs(math.eigvals(A)) < 1) and math.allclose(self.probability, 1, atol)

@property
def probability(self) -> float:
Expand Down
26 changes: 16 additions & 10 deletions mrmustard/lab_dev/transformations/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,10 +356,12 @@ class Channel(Map):

short_name = "Ch"

@property
def is_CP(self) -> bool:
def is_CP(self, atol: float = settings.ATOL) -> bool:
r"""
Whether this channel is completely positive (CP).

Arg:
atol: The tolerance we allow for CP conditions (default value is settings.ATOL)
"""
batch_dim = self.ansatz.batch_size
if batch_dim > 1:
Expand All @@ -371,30 +373,34 @@ def is_CP(self) -> bool:
gamma_A = A[0, :m, m:]

if (
math.real(math.norm(gamma_A - math.conj(gamma_A.T))) > settings.ATOL
math.real(math.norm(gamma_A - math.conj(gamma_A.T))) > atol
): # checks if gamma_A is Hermitian
return False

return all(math.real(math.eigvals(gamma_A)) > -settings.ATOL)
return all(math.real(math.eigvals(gamma_A)) > -atol)

@property
def is_TP(self) -> bool:
def is_TP(self, atol: float = settings.ATOL) -> bool:
r"""
Whether this channel is trace preserving (TP).

Arg:
atol: The tolerance we allow for TP condition (default value is settings.ATOL)
"""
A = self.ansatz.A
m = A.shape[-1] // 2
gamma_A = A[0, :m, m:]
lambda_A = A[0, m:, m:]
temp_A = gamma_A + math.conj(lambda_A.T) @ math.inv(math.eye(m) - gamma_A.T) @ lambda_A
return math.real(math.norm(temp_A - math.eye(m))) < settings.ATOL
return math.real(math.norm(temp_A - math.eye(m))) < atol

@property
def is_physical(self) -> bool:
def is_physical(self, atol: float = settings.ATOL) -> bool:
r"""
Whether this channel is physical (i.e. CPTP).

Arg:
atol: The tolerance we allow for CPTP conditions (default value is settings.ATOL)
"""
return self.is_CP and self.is_TP
return self.is_CP(atol) and self.is_TP(atol)

@property
def XY(self) -> tuple[ComplexMatrix, ComplexMatrix]:
Expand Down
10 changes: 5 additions & 5 deletions tests/test_lab_dev/test_states/test_dm.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,18 +380,18 @@ def test_random(self, modes):

@pytest.mark.parametrize("modes", [[9, 2], [0, 1, 2, 3, 4]])
def test_is_positive(self, modes):
assert (Ket.random(modes) >> Attenuator(modes)).is_positive
assert (Ket.random(modes) >> Attenuator(modes)).is_positive()
A = np.zeros([2 * len(modes), 2 * len(modes)])
A[0, -1] = 1.0
rho = DM.from_bargmann(
modes, [A, [complex(0)] * 2 * len(modes), [complex(1)]]
) # this test fails at the hermitian check
assert not rho.is_positive
assert not rho.is_positive()

@pytest.mark.parametrize("modes", [range(10), [0, 1]])
def test_is_physical(self, modes):
rho = DM.random(modes)
assert rho.is_physical
assert rho.is_physical()
rho = 2 * rho
assert not rho.is_physical
assert Ket.random(modes).dm().is_physical
assert not rho.is_physical()
assert Ket.random(modes).dm().is_physical()
2 changes: 1 addition & 1 deletion tests/test_lab_dev/test_states/test_ket.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,4 +447,4 @@ def test_ipython_repr_too_many_dims(self):
assert isinstance(wires, HTML)

def test_is_physical(self):
assert Ket.random([0, 1]).is_physical
assert Ket.random([0, 1]).is_physical()
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,13 @@ def test_random(self):
def test_is_CP(self, modes):
u = Unitary.random(modes).ansatz
kraus = u @ u.conj
assert Channel.from_bargmann(modes, modes, kraus.triple).is_CP
assert Channel.from_bargmann(modes, modes, kraus.triple).is_CP()

def test_is_TP(self):
assert Attenuator([0, 1], 0.5).is_CP
assert Attenuator([0, 1], 0.5).is_TP()

def test_is_physical(self):
assert Channel.random(range(5)).is_physical
assert Channel.random(range(5)).is_physical()

def test_XY(self):
U = Unitary.random([0, 1])
Expand Down
Loading