-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #863 from Shivansh20128/31-complementary-channel
Adding Complementary Map feature
- Loading branch information
Showing
3 changed files
with
180 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
"""Computes the complementary channel/map of a superoperator.""" | ||
|
||
import numpy as np | ||
|
||
|
||
def complementary_channel(kraus_ops: list[np.ndarray]) -> list[np.ndarray]: | ||
r"""Compute the Kraus operators for the complementary map of a quantum channel. | ||
(Section: Representations and Characterizations of Channels from :cite:`Watrous_2018_TQI`). | ||
The complementary map is derived from the given quantum channel's Kraus operators by | ||
rearranging the rows of the input Kraus operators into the Kraus operators of the | ||
complementary map. | ||
Specifically, for each Kraus operator :math:`K_i` in the input channel :math:`\Phi`, | ||
we define the complementary Kraus operators :math:`K_i^C` by stacking the rows of | ||
:math:`K_i` from all Kraus operators vertically. | ||
Examples | ||
========== | ||
Suppose the following Kraus operators define a quantum channel: | ||
.. math:: | ||
K_1 = \frac{1}{\sqrt{2}} \begin{pmatrix} | ||
1 & 0 \\ | ||
0 & 0 | ||
\end{pmatrix}, | ||
K_2 = \frac{1}{\sqrt{2}} \begin{pmatrix} | ||
0 & 1 \\ | ||
0 & 0 | ||
\end{pmatrix}, | ||
K_3 = \frac{1}{\sqrt{2}} \begin{pmatrix} | ||
0 & 0 \\ | ||
1 & 0 | ||
\end{pmatrix}, | ||
K_4 = \frac{1}{\sqrt{2}} \begin{pmatrix} | ||
0 & 0 \\ | ||
0 & 1 | ||
\end{pmatrix} | ||
To compute the Kraus operators for the complementary map, we rearrange the rows of these | ||
Kraus operators as follows: | ||
>>> import numpy as np | ||
>>> kraus_ops_Phi = [ | ||
... np.array([[1, 0], [0, 0]]), | ||
... np.array([[0, 1], [0, 0]]), | ||
... np.array([[0, 0], [1, 0]]), | ||
... np.array([[0, 0], [0, 1]]) | ||
... ] | ||
>>> comp_kraus_ops = complementary_map(kraus_ops_Phi) | ||
>>> for i, op in enumerate(comp_kraus_ops): | ||
... print(f"Kraus operator {i + 1}:\n{op}\n") | ||
The output would be: | ||
.. math:: | ||
K_1^C = \frac{1}{\sqrt{2}} \begin{pmatrix} | ||
1 & 0 \\ | ||
0 & 1 \\ | ||
0 & 0 \\ | ||
0 & 0 | ||
\end{pmatrix}, | ||
K_2^C = \frac{1}{\sqrt{2}} \begin{pmatrix} | ||
0 & 0 \\ | ||
0 & 0 \\ | ||
1 & 0 \\ | ||
0 & 1 | ||
\end{pmatrix} | ||
References | ||
========== | ||
.. bibliography:: | ||
:filter: docname in docnames | ||
:raises ValueError: If the input is not a valid list of Kraus operators. | ||
:param kraus_ops: A list of numpy arrays representing the Kraus operators of a quantum channel. | ||
Each Kraus operator is assumed to be a square matrix. | ||
:return: A list of numpy arrays representing the Kraus operators of the complementary map. | ||
""" | ||
num_kraus = len(kraus_ops) | ||
if num_kraus==0: | ||
raise ValueError("All Kraus operators must be non-empty matrices.") | ||
|
||
op_dim = kraus_ops[0].shape[0] | ||
|
||
if any(k.shape[0] != k.shape[1] for k in kraus_ops): | ||
raise ValueError("All Kraus operators must be square matrices.") | ||
|
||
if any(k.shape[0] != op_dim for k in kraus_ops): | ||
raise ValueError("All Kraus operators must be equal size matrices.") | ||
|
||
# Check the Kraus completeness relation: ∑ K_i† K_i = I | ||
identity = np.eye(op_dim, dtype=kraus_ops[0].dtype) | ||
sum_k_dagger_k = sum(k.T.conj() @ k for k in kraus_ops) | ||
|
||
if not np.allclose(sum_k_dagger_k, identity): | ||
raise ValueError("The Kraus operators do not satisfy the completeness relation ∑ K_i† K_i = I.") | ||
|
||
comp_kraus_ops = [] | ||
|
||
for row in range(op_dim): | ||
comp_kraus_op = np.vstack([kraus_ops[i][row, :] for i in range(num_kraus)]) | ||
comp_kraus_ops.append(comp_kraus_op) | ||
|
||
return comp_kraus_ops |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
"""Tests for complementary_channel.""" | ||
|
||
import numpy as np | ||
import pytest | ||
|
||
from toqito.channel_ops import complementary_channel | ||
|
||
# Define test cases for complementary map | ||
kraus_1 = np.array([[1, 0], [0, 0]]) / np.sqrt(2) | ||
kraus_2 = np.array([[0, 1], [0, 0]]) / np.sqrt(2) | ||
kraus_3 = np.array([[0, 0], [1, 0]]) / np.sqrt(2) | ||
kraus_4 = np.array([[0, 0], [0, 1]]) / np.sqrt(2) | ||
|
||
# Expected results for the complementary map | ||
expected_res_comp = [ | ||
np.array([[1, 0], [0, 1], [0, 0], [0, 0]]) / np.sqrt(2), | ||
np.array([[0, 0], [0, 0], [1, 0], [0, 1]]) / np.sqrt(2), | ||
] | ||
|
||
# Higher-dimensional Kraus operators (3x3) | ||
kraus_5 = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) / np.sqrt(3) | ||
kraus_6 = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) / np.sqrt(3) | ||
kraus_7 = np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) / np.sqrt(3) | ||
|
||
expected_res_comp_high_dim = [ | ||
np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) / np.sqrt(3), | ||
np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) / np.sqrt(3), | ||
np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) / np.sqrt(3), | ||
] | ||
|
||
# Single Kraus operator (edge case) | ||
kraus_single = np.array([[1, 0], [0, 1]]) | ||
|
||
expected_res_single =[ | ||
np.array([[1, 0]]), | ||
np.array([[0, 1]]), | ||
] | ||
|
||
@pytest.mark.parametrize( | ||
"kraus_ops, expected", | ||
[ | ||
# Test complementary_channel on a set of 2x2 Kraus operators (the ones you gave). | ||
([kraus_1, kraus_2, kraus_3, kraus_4], expected_res_comp), | ||
# Test complementary_channel with higher-dimensional (3x3) Kraus operators. | ||
([kraus_5, kraus_6, kraus_7], expected_res_comp_high_dim), | ||
# Test complementary_channel with a single Kraus operator (edge case). | ||
([kraus_single], expected_res_single), | ||
], | ||
) | ||
def test_complementary_channel(kraus_ops, expected): | ||
"""Test complementary_channel works as expected for valid inputs.""" | ||
calculated = complementary_channel(kraus_ops) | ||
|
||
# Compare the shapes first to debug broadcasting issues | ||
assert len(calculated) == len(expected), "Mismatch in number of Kraus operators" | ||
for calc_op, exp_op in zip(calculated, expected): | ||
assert np.isclose(calc_op, exp_op, atol=1e-6).all() | ||
|
||
@pytest.mark.parametrize( | ||
"kraus_ops", | ||
[ | ||
# Invalid test case: non-square matrices | ||
([np.array([[1, 0, 0], [0, 1, 0]])]), # Not a square matrix | ||
# Invalid test case: empty list of Kraus operators | ||
([]), | ||
# Invalid test case: single row matrix (not a square) | ||
([np.array([[1, 0]])]), | ||
# Different dimenisions for kraus operators in a set | ||
([np.array([[1, 0, 0], [0, 1, 0], [0, 1, 1]]), np.array([[1, 0], [0, 1]])]), | ||
# Invalid test case: Kraus operators that do not satisfy the completeness relation | ||
([np.array([[1, 0], [0, 0.5]]), np.array([[0, 0.5], [0, 0.5]])]), # Sum != I | ||
], | ||
) | ||
def test_complementary_channel_error(kraus_ops): | ||
"""Test function raises error as expected for invalid inputs.""" | ||
with pytest.raises(ValueError): | ||
complementary_channel(kraus_ops) |