-
Notifications
You must be signed in to change notification settings - Fork 110
Adding Kraus to channel #1071
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
base: master
Are you sure you want to change the base?
Adding Kraus to channel #1071
Changes from all commits
2a9f93e
056d24d
84e4e2b
658aab3
af2a2ad
8c1fe83
90ae877
de40b5b
5eef4c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
"""Converts Kraus operators into the corressponding quantum channel (i.e. superoperator).""" | ||
|
||
import numpy as np | ||
|
||
from toqito.matrix_ops import tensor | ||
|
||
|
||
def kraus_to_channel( | ||
kraus_list: list[tuple[np.ndarray, np.ndarray]] | ||
) -> np.ndarray: | ||
r"""Convert a collection of Kraus operators into the corresponding quantum channel (superoperator). | ||
|
||
(Section: Kraus Representations of :cite:`Watrous_2018_TQI`). | ||
|
||
This function computes the superoperator representation of a quantum channel from its Kraus representation. | ||
Given a list of Kraus operators \(\{A_i, B_i\}\), the superoperator \(\mathcal{E}\) is computed as: | ||
|
||
\[ | ||
\mathcal{E}(\rho) = \sum_i B_i \rho A_i^\dagger | ||
\] | ||
|
||
The resulting quantum channel can be applied to density matrices by reshaping them into vectorized form. | ||
|
||
Examples | ||
======== | ||
|
||
Constructing a simple quantum channel from Kraus operators: | ||
|
||
>>> import numpy as np | ||
>>> from toqito.channel_ops import kraus_to_channel | ||
>>> kraus_1 = np.array([[1, 0], [0, 0]]) | ||
>>> kraus_2 = np.array([[0, 1], [0, 0]]) | ||
>>> kraus_list = [(kraus_1, kraus_1), (kraus_2, kraus_2)] | ||
>>> kraus_to_channel(kraus_list) | ||
array([[1, 0, 0, 1], | ||
[0, 0, 0, 0], | ||
[0, 0, 0, 0], | ||
[0, 0, 0, 0]]) | ||
|
||
See Also | ||
======== | ||
func:`.choi_to_kraus`, func:`.kraus_to_choi` | ||
|
||
References | ||
========== | ||
.. bibliography:: | ||
:filter: docname in docnames | ||
|
||
:param kraus_list: List of tuples (A, B) where A and B are Kraus operators as numpy arrays. | ||
:return: The superoperator as a numpy array. | ||
|
||
""" | ||
super_op = sum(tensor(B, A.conj()) for A, B in kraus_list) | ||
return super_op |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
"""Tests for kraus to channel.""" | ||
|
||
import numpy as np | ||
import pytest | ||
|
||
import toqito.state_ops | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a unit test where the superoperator acts on a state? See Section 2.2 of https://arxiv.org/pdf/1509.02921 for more info. It would also be pretty easy to construct a superoperator from the channels in Section 2.1.1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before going into that, can you tell me if the function has been implemented correctly. Because I was not very sure about it. If you read the previous discussion on the issue, you will see that the two results, one from using the function on a vector, and other from directly applying a series of kraus operators were coming out to be transpose of each other.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @Shivansh20128 , Thanks for your response. From what I can tell (and what I recall) I believe your approach here is sensible. Perhaps one way to sanity check your approach would be to convert a set of Kraus operators to a quantum channel. Then, if you can use some of the existing functionality in I understand that some of the above was a bit vague, but in general, do you think that's a sensible approach? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I tried this approach here:
And I am getting a match on the results:
If you also think this is good enough, we can go ahead. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You might want to try other values of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay. I will add more values of |
||
from toqito.channel_ops import apply_channel, kraus_to_channel | ||
|
||
dim = 2**2 | ||
kraus_list = [np.random.randint(-1, 4, (2, dim, dim)) for _ in range(12)] | ||
|
||
vector = np.random.randint(-3, 3, (dim, 1)) | ||
dm = toqito.matrix_ops.to_density_matrix(vector) | ||
vec_dm = toqito.matrix_ops.vec(dm) | ||
|
||
# Random quantum test states (density matrices) | ||
rho_1 = np.array([[0.5, 0.5], [0.5, 0.5]]) | ||
rho_2 = np.array([[0.5, 0], [0, 0.5]]) | ||
rho_3 = np.array([[1, 0], [0, 0]]) | ||
rho_4 = np.array([[0, 0], [0, 1]]) | ||
|
||
A = np.random.rand(2, 2) + 1j * np.random.rand(2, 2) | ||
rho_5 = A @ A.conj().T | ||
rho_5 /= np.trace(rho_5) # Normalize trace to 1 | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have quite a few functions to generate random things. See if those are useful, instead of manually defining your own randomly generated input. https://toqito--1071.org.readthedocs.build/en/1071/autoapi/rand/index.html There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will work on this later. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think specifically in this case the |
||
|
||
p_1 = 0.1 # Probability of bit flip (Bit-Flip Channel) | ||
kraus_operators_1 = [ | ||
(np.sqrt(1 - p_1) * np.array([[1, 0], [0, 1]]), np.sqrt(1 - p_1) * np.array([[1, 0], [0, 1]])), # Identity | ||
(np.sqrt(p_1) * np.array([[0, 1], [1, 0]]), np.sqrt(p_1) * np.array([[0, 1], [1, 0]])), # Bit-flip (X) | ||
] | ||
|
||
p_2 = 0.2 # Depolarizing Channel | ||
kraus_operators_2 = [ | ||
(np.sqrt(1 - 3 * p_2 / 4) * np.eye(2), np.sqrt(1 - 3 * p_2 / 4) * np.eye(2)), # Identity | ||
(np.sqrt(p_2 / 4) * np.array([[0, 1], [1, 0]]), np.sqrt(p_2 / 4) * np.array([[0, 1], [1, 0]])), # X | ||
(np.sqrt(p_2 / 4) * np.array([[0, -1j], [1j, 0]]), np.sqrt(p_2 / 4) * np.array([[0, -1j], [1j, 0]])), # Y | ||
(np.sqrt(p_2 / 4) * np.array([[1, 0], [0, -1]]), np.sqrt(p_2 / 4) * np.array([[1, 0], [0, -1]])), # Z | ||
] | ||
|
||
kraus_operators_3 = [ | ||
(np.array([[1, 0], [0, 0]]), np.array([[1, 0], [0, 0]])), # Projection onto |0⟩ | ||
(np.array([[0, 1], [0, 0]]), np.array([[0, 1], [0, 0]])), # Projection onto |1⟩ | ||
] | ||
Comment on lines
+27
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have commonly predefined channels in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will work on this later. |
||
|
||
@pytest.mark.parametrize( | ||
"kraus_list", | ||
[ | ||
(kraus_list) | ||
], | ||
) | ||
def test_kraus_to_channel(kraus_list): | ||
"""Test kraus_tochannel works as expected for valid inputs.""" | ||
calculated = kraus_to_channel(kraus_list) | ||
|
||
value = sum(A @ dm @ B.conj().T for A, B in kraus_list) | ||
|
||
assert toqito.matrix_ops.unvec(calculated @ vec_dm).all() == value.all() | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"rho, kraus_operators", | ||
[ | ||
(rho_1, kraus_operators_1), (rho_2, kraus_operators_1), (rho_3, kraus_operators_2), | ||
(rho_4, kraus_operators_2), (rho_5, kraus_operators_3), (rho_1, kraus_operators_3) | ||
], | ||
) | ||
def test_kraus_to_channel_on_quantumStates(rho, kraus_operators): | ||
"""Test kraus_to_channel works as expected for valid inputs.""" | ||
# Generate the quantum channel using your function | ||
quantum_channel = kraus_to_channel(kraus_operators) | ||
|
||
# Apply the quantum channel using Toqito's apply_channel function | ||
rho_after_channel = apply_channel(rho, kraus_operators) | ||
|
||
# Apply the superoperator to the vectorized form of rho | ||
rho_vec = rho.flatten("F") # Column-major order | ||
rho_after_super_op = quantum_channel @ rho_vec | ||
rho_after_super_op = rho_after_super_op.reshape(2, 2, order="F") # Reshape back | ||
|
||
# Compare both methods | ||
print("Using apply_channel:\n", rho_after_channel) | ||
print("Using superoperator:\n", rho_after_super_op) | ||
print("Difference:\n", rho_after_channel - rho_after_super_op) | ||
|
||
# The difference should be close to zero | ||
assert np.allclose(rho_after_channel, rho_after_super_op) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Shivansh20128 something has gone wrong with the formatting for this. I was trying to make these clickable similar to what I did in #1091
Each function should be
:func:.name_function
where.name_function
is sandwiched between `My apologies!