From 327dbefc2d241a5fb0e0b259360694d4f0c8ceff Mon Sep 17 00:00:00 2001 From: georgios-ts Date: Fri, 2 Jun 2023 01:39:48 +0300 Subject: [PATCH] Use `channel_dim`. There are places where we failed to retrieve the input and output dimensions of a general channel. This commit uses the recently added function `channel_dim` and fixes: 1. `kraus_to_choi` if non square input. Additionally flat list for CPTP channels are now supported. 2. `is_unital` if unequal input and output dims. 3. `dual_channel` if non square input and output. --- tests/test_channel_ops/test_dual_channel.py | 21 +++++++-- tests/test_channel_ops/test_kraus_to_choi.py | 48 ++++++++++++++++++++ tests/test_channel_props/test_is_unital.py | 22 +++++++++ toqito/channel_ops/dual_channel.py | 22 ++++----- toqito/channel_ops/kraus_to_choi.py | 5 +- toqito/channel_props/is_unital.py | 23 ++++++---- 6 files changed, 111 insertions(+), 30 deletions(-) diff --git a/tests/test_channel_ops/test_dual_channel.py b/tests/test_channel_ops/test_dual_channel.py index 41bb95135..815819e53 100644 --- a/tests/test_channel_ops/test_dual_channel.py +++ b/tests/test_channel_ops/test_dual_channel.py @@ -3,6 +3,7 @@ from toqito.channel_ops import dual_channel from toqito.channels import choi +from toqito.perms import swap_operator def test_dual_channel_kraus1(): @@ -73,10 +74,22 @@ def test_dual_channel_choi_dims(): def test_dual_channel_nonsquare_matrix(): - """If the channel is represented as a Choi matrix, it must be square.""" - with np.testing.assert_raises(ValueError): - j = np.array([[1, 2, 3, 4], [4, 3, 2, 1]]) - dual_channel(j) + """Dual of a channel that transposes 3x2 matrices.""" + choi = swap_operator([2, 3]) + choi_dual = dual_channel(choi, dims=[[3, 2], [2, 3]]) + expected_choi_dual = np.array( + [ + [1, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0], + [0, 1, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 1], + ] + ) + + bool_mat = np.isclose(choi_dual, expected_choi_dual) + np.testing.assert_equal(np.all(bool_mat), True) def test_dual_channel_not_matrix(): diff --git a/tests/test_channel_ops/test_kraus_to_choi.py b/tests/test_channel_ops/test_kraus_to_choi.py index a0a89b077..f2b55183f 100644 --- a/tests/test_channel_ops/test_kraus_to_choi.py +++ b/tests/test_channel_ops/test_kraus_to_choi.py @@ -96,5 +96,53 @@ def test_kraus_to_choi_depolarizing_channel(): np.testing.assert_equal(np.all(bool_mat), True) +def test_kraus_to_choi_isometry(): + """Kraus operators for an isometry.""" + v_mat = np.array([[1, 0, 0], [0, 1, 0]]) + + choi_res = kraus_to_choi([v_mat]) + expected_choi_res = np.array( + [ + [1, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [1, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ] + ) + + bool_mat = np.isclose(choi_res, expected_choi_res) + np.testing.assert_equal(np.all(bool_mat), True) + + +def test_kraus_to_choi_non_square(): + """Kraus operators for non square inputs and outputs.""" + kraus_1 = np.array([[1, 0, 0], [0, 1, 0]]) + + kraus_2 = np.array( + [ + [0, 0, 1, 0], + [0, 1, 0, 0], + [1, 0, 0, 1], + ] + ) + + choi_res = kraus_to_choi([[kraus_1, kraus_2]]) + expected_choi_res = np.array( + [ + [0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ] + ) + + bool_mat = np.isclose(choi_res, expected_choi_res) + np.testing.assert_equal(np.all(bool_mat), True) + + if __name__ == "__main__": np.testing.run_module_suite() diff --git a/tests/test_channel_props/test_is_unital.py b/tests/test_channel_props/test_is_unital.py index 95510241b..ce55bd472 100644 --- a/tests/test_channel_props/test_is_unital.py +++ b/tests/test_channel_props/test_is_unital.py @@ -1,6 +1,7 @@ """Tests for is_unital.""" import numpy as np +from toqito.channel_ops import kraus_to_choi from toqito.channel_props import is_unital from toqito.channels import depolarizing from toqito.perms import swap_operator @@ -36,5 +37,26 @@ def test_is_unital_depolarizing_choi_true(): np.testing.assert_equal(is_unital(depolarizing(4)), True) +def test_is_unital_isometry_true(): + """Verify isometry channel is unital.""" + v_mat = np.array([[1, 0, 0], [0, 1, 0]]) + np.testing.assert_equal(is_unital([v_mat], dim=[3, 2]), True) + + +def test_is_unital_choi_isometry_true(): + """Verify isometry channel with Choi matrix is unital.""" + v_mat = np.array([[1, 0, 0], [0, 1, 0]]) + choi = kraus_to_choi([v_mat]) + np.testing.assert_equal(is_unital(choi, dim=[3, 2]), True) + + +def test_is_unital_isometry_true_unspecified_dim(): + """Verify isometry channel with Choi matrix raises if dim is unspecified.""" + v_mat = np.array([[1, 0, 0], [0, 1, 0]]) + choi = kraus_to_choi([v_mat]) + with np.testing.assert_raises(ValueError): + is_unital(choi) + + if __name__ == "__main__": np.testing.run_module_suite() diff --git a/toqito/channel_ops/dual_channel.py b/toqito/channel_ops/dual_channel.py index 6f5eddde4..ae5f75a29 100644 --- a/toqito/channel_ops/dual_channel.py +++ b/toqito/channel_ops/dual_channel.py @@ -2,6 +2,7 @@ from __future__ import annotations import numpy as np +from toqito.helper import channel_dim from toqito.matrix_props import is_square from toqito.perms import swap @@ -13,9 +14,11 @@ def dual_channel( Compute the dual of a map (quantum channel) [WatDChan18]_. The map can be represented as a Choi matrix, with optional specification of input - and output dimensions. In this case the Choi matrix of the dual channel is - returned, obtained by swapping input and output (see :func:`toqito.perms.swap`), - and complex conjugating all elements. + and output dimensions. If the input channel maps :math:`M_{r,c}` to :math:`M_{x,y}` + then :code:`dim` should be the list :code:`[[r,x], [c,y]]`. If it maps :math:`M_m` + to :math:`M_n`, then :code:`dim` can simply be the vector :code:`[m,n]`. In this + case the Choi matrix of the dual channel is returned, obtained by swapping input and + output (see :func:`toqito.perms.swap`), and complex conjugating all elements. The map can also be represented as a list of Kraus operators. A list of lists, each containing two elements, corresponds to the families @@ -56,17 +59,8 @@ def dual_channel( # If phi_op is a `ndarray`, assume it is a Choi matrix. if isinstance(phi_op, np.ndarray): if len(phi_op.shape) == 2: - if not is_square(phi_op): - raise ValueError("Invalid: `phi_op` is not a valid Choi matrix (not square).") - if dims is None: - sqr = np.sqrt(phi_op.shape[0]) - if sqr.is_integer(): - dims = [int(round(sqr))] * 2 - else: - raise ValueError( - "The dimensions `dims` of the input and output should be specified." - ) - return swap(phi_op.conj(), dim=dims) + d_in, d_out, _ = channel_dim(phi_op, dim=dims, compute_env_dim=False) + return swap(phi_op.conj(), dim=[[d_in[0], d_out[0]], [d_in[1], d_out[1]]]) raise ValueError( "Invalid: The variable `phi_op` must either be a list of " "Kraus operators or as a Choi matrix." diff --git a/toqito/channel_ops/kraus_to_choi.py b/toqito/channel_ops/kraus_to_choi.py index 617f4e1a5..ee0190418 100644 --- a/toqito/channel_ops/kraus_to_choi.py +++ b/toqito/channel_ops/kraus_to_choi.py @@ -3,6 +3,7 @@ from toqito.states import max_entangled from toqito.channel_ops import partial_channel +from toqito.helper import channel_dim def kraus_to_choi(kraus_ops: list[list[np.ndarray]], sys: int = 2) -> np.ndarray: @@ -61,8 +62,8 @@ def kraus_to_choi(kraus_ops: list[list[np.ndarray]], sys: int = 2) -> np.ndarray :param sys: The dimension of the system (default is 2). :return: The corresponding Choi matrix of the provided Kraus operators. """ - dim_op_1 = kraus_ops[0][0].shape[0] - dim_op_2 = kraus_ops[0][0].shape[1] + dim_in, _, _ = channel_dim(kraus_ops) + dim_op_1, dim_op_2 = dim_in choi_mat = partial_channel( max_entangled(dim_op_1, False, False) * max_entangled(dim_op_2, False, False).conj().T, diff --git a/toqito/channel_props/is_unital.py b/toqito/channel_props/is_unital.py index d6af910f9..3b8bf7da7 100644 --- a/toqito/channel_props/is_unital.py +++ b/toqito/channel_props/is_unital.py @@ -3,14 +3,16 @@ import numpy as np -from toqito.channel_ops import apply_channel, kraus_to_choi +from toqito.channel_ops import apply_channel from toqito.matrix_props import is_identity +from toqito.helper import channel_dim def is_unital( phi: np.ndarray | list[list[np.ndarray]], rtol: float = 1e-05, atol: float = 1e-08, + dim: int | list[int] | np.ndarray = None, ) -> bool: r""" Determine whether the given channel is unital [WatUnital18]_. @@ -21,6 +23,10 @@ def is_unital( .. math:: \Phi(\mathbb{I}_{\mathcal{X}}) = \mathbb{I}_{\mathcal{Y}}. + If the input channel maps :math:`M_{r,c}` to :math:`M_{x,y}` then :code:`dim` should be the + list :code:`[[r,x], [c,y]]`. If it maps :math:`M_m` to :math:`M_n`, then :code:`dim` can simply + be the vector :code:`[m,n]`. + Examples ========== @@ -34,15 +40,15 @@ def is_unital( >>> is_unital(choi) True - Alternatively, the channel whose Choi matrix is the depolarizing channel is an example of a - non-unital channel. + Additionally, the channel whose Choi matrix is the depolarizing channel is another example of + a unital channel. >>> from toqito.channels import depolarizing >>> from toqito.channel_props import is_unital >>> >>> choi = depolarizing(4) >>> is_unital(choi) - False + True References ========== @@ -54,14 +60,11 @@ def is_unital( :param phi: The channel provided as either a Choi matrix or a list of Kraus operators. :param rtol: The relative tolerance parameter (default 1e-05). :param atol: The absolute tolerance parameter (default 1e-08). + :param dim: A scalar, vector or matrix containing the input and output dimensions of PHI. :return: :code:`True` if the channel is unital, and :code:`False` otherwise. """ - # If the variable `phi` is provided as a list, we assume this is a list of Kraus operators. - if isinstance(phi, list): - phi = kraus_to_choi(phi) - - dim = int(np.sqrt(phi.shape[0])) + dim_in, _, _ = channel_dim(phi, dim=dim, allow_rect=False, compute_env_dim=False) # Channel is unital if :code:`mat` is the identity matrix. - mat = apply_channel(np.identity(dim), phi) + mat = apply_channel(np.identity(dim_in), phi) return is_identity(mat, rtol=rtol, atol=atol)