Skip to content

Commit 7065c6d

Browse files
authored
Channel matrix (#4146)
1 parent 0311e17 commit 7065c6d

File tree

4 files changed

+109
-19
lines changed

4 files changed

+109
-19
lines changed

cirq-core/cirq/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,10 @@
336336
dirac_notation,
337337
eye_tensor,
338338
fidelity,
339+
kraus_to_channel_matrix,
339340
kraus_to_choi,
340341
one_hot,
342+
operation_to_channel_matrix,
341343
operation_to_choi,
342344
QUANTUM_STATE_LIKE,
343345
QuantumState,

cirq-core/cirq/qis/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
"""Tools and methods for quantum information science."""
1616

1717
from cirq.qis.channels import (
18+
kraus_to_channel_matrix,
1819
kraus_to_choi,
20+
operation_to_channel_matrix,
1921
operation_to_choi,
2022
)
2123

cirq-core/cirq/qis/channels.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ def kraus_to_choi(kraus_operators: Sequence[np.ndarray]) -> np.ndarray:
2929
return c
3030

3131

32+
def kraus_to_channel_matrix(kraus_operators: Sequence[np.ndarray]) -> np.ndarray:
33+
"""Returns the matrix representation of the linear map with given Kraus operators."""
34+
d_out, d_in = kraus_operators[0].shape
35+
m = np.zeros((d_out * d_out, d_in * d_in), dtype=np.complex128)
36+
for k in kraus_operators:
37+
m += np.kron(k, k.conj())
38+
return m
39+
40+
3241
def operation_to_choi(operation: 'protocols.SupportsChannel') -> np.ndarray:
3342
r"""Returns the unique Choi matrix associated with a superoperator.
3443
@@ -49,3 +58,20 @@ def operation_to_choi(operation: 'protocols.SupportsChannel') -> np.ndarray:
4958
Choi matrix corresponding to operation.
5059
"""
5160
return kraus_to_choi(protocols.channel(operation))
61+
62+
63+
def operation_to_channel_matrix(operation: 'protocols.SupportsChannel') -> np.ndarray:
64+
"""Returns the matrix representation of a superoperator in standard basis.
65+
66+
Let E: L(H1) -> L(H2) denote a linear map which takes linear operators on Hilbert space H1
67+
to linear operators on Hilbert space H2 and let d1 = dim H1 and d2 = dim H2. Also, let Fij
68+
denote an operator whose matrix has one in ith row and jth column and zeros everywhere else.
69+
Note that d1-by-d1 operators Fij form a basis of L(H1). Similarly, d2-by-d2 operators Fij
70+
form a basis of L(H2). This function returns the matrix of E in these bases.
71+
72+
Args:
73+
operation: Quantum channel.
74+
Returns:
75+
Matrix representation of operation.
76+
"""
77+
return kraus_to_channel_matrix(protocols.channel(operation))

cirq-core/cirq/qis/channels_test.py

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
"""Tests for channels."""
15+
from typing import Iterable
16+
1517
import numpy as np
1618
import pytest
1719

@@ -28,32 +30,38 @@ def apply_channel(channel: cirq.SupportsChannel, rho: np.ndarray) -> np.ndarray:
2830
return out
2931

3032

31-
def expected_choi(channel: cirq.SupportsChannel) -> np.ndarray:
33+
def generate_standard_operator_basis(d_out: int, d_in: int) -> Iterable[np.ndarray]:
34+
for i in range(d_out):
35+
for j in range(d_in):
36+
e_ij = np.zeros((d_out, d_in))
37+
e_ij[i, j] = 1
38+
yield e_ij
39+
40+
41+
def compute_choi(channel: cirq.SupportsChannel) -> np.ndarray:
3242
ks = cirq.channel(channel)
3343
d_out, d_in = ks[0].shape
3444
d = d_in * d_out
3545
c = np.zeros((d, d), dtype=np.complex128)
36-
for i in range(d_in):
37-
for j in range(d_in):
38-
e_ij = np.zeros((d_in, d_in))
39-
e_ij[i, j] = 1
40-
c += np.kron(apply_channel(channel, e_ij), e_ij)
46+
for e in generate_standard_operator_basis(d_in, d_in):
47+
c += np.kron(apply_channel(channel, e), e)
4148
return c
4249

4350

51+
def compute_channel_matrix(channel: cirq.SupportsChannel) -> np.ndarray:
52+
ks = cirq.channel(channel)
53+
d_out, d_in = ks[0].shape
54+
m = np.zeros((d_out * d_out, d_in * d_in), dtype=np.complex128)
55+
for k, e_in in enumerate(generate_standard_operator_basis(d_in, d_in)):
56+
m[:, k] = np.reshape(apply_channel(channel, e_in), d_out * d_out)
57+
return m
58+
59+
4460
@pytest.mark.parametrize(
4561
'kraus_operators, expected_choi',
4662
(
4763
([np.eye(2)], np.array([[1, 0, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 1]])),
48-
(
49-
[
50-
np.eye(2) / 2,
51-
np.array([[0, 1], [1, 0]]) / 2,
52-
np.array([[0, -1j], [1j, 0]]) / 2,
53-
np.diag([1, -1]) / 2,
54-
],
55-
np.eye(4) / 2,
56-
),
64+
(cirq.channel(cirq.depolarize(0.75)), np.eye(4) / 2),
5765
(
5866
[
5967
np.array([[1, 0, 0], [0, 0, 1]]) / np.sqrt(2),
@@ -80,14 +88,66 @@ def test_kraus_to_choi(kraus_operators, expected_choi):
8088
),
8189
)
8290
def test_operation_to_choi(channel):
83-
"""Verifies that cirq.choi correctly computes the Choi matrix."""
91+
"""Verifies that cirq.operation_to_choi correctly computes the Choi matrix."""
8492
n_qubits = cirq.num_qubits(channel)
8593
actual = cirq.operation_to_choi(channel)
86-
expected = expected_choi(channel)
94+
expected = compute_choi(channel)
8795
assert np.isclose(np.trace(actual), 2 ** n_qubits)
8896
assert np.all(actual == expected)
8997

9098

91-
def test_choi_on_completely_dephasing_channel():
92-
"""Checks that cirq.choi returns the right matrix for the completely dephasing channel."""
99+
def test_choi_for_completely_dephasing_channel():
100+
"""Checks cirq.operation_to_choi on the completely dephasing channel."""
93101
assert np.all(cirq.operation_to_choi(cirq.phase_damp(1)) == np.diag([1, 0, 0, 1]))
102+
103+
104+
@pytest.mark.parametrize(
105+
'kraus_operators, expected_channel_matrix',
106+
(
107+
([np.eye(2)], np.eye(4)),
108+
(
109+
cirq.channel(cirq.depolarize(0.75)),
110+
np.array([[1, 0, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 1]]) / 2,
111+
),
112+
(
113+
[
114+
np.array([[0, 1, 0], [0, 0, 1]]) / np.sqrt(2),
115+
np.array([[0, 1, 0], [0, 0, -1]]) / np.sqrt(2),
116+
],
117+
np.array(
118+
[
119+
[0, 0, 0, 0, 1, 0, 0, 0, 0],
120+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
121+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
122+
[0, 0, 0, 0, 0, 0, 0, 0, 1],
123+
]
124+
),
125+
),
126+
),
127+
)
128+
def test_kraus_to_channel_matrix(kraus_operators, expected_channel_matrix):
129+
"""Verifies that cirq.kraus_to_channel_matrix computes the correct channel matrix."""
130+
assert np.allclose(cirq.kraus_to_channel_matrix(kraus_operators), expected_channel_matrix)
131+
132+
133+
@pytest.mark.parametrize(
134+
'channel',
135+
(
136+
cirq.I,
137+
cirq.X,
138+
cirq.CNOT,
139+
cirq.depolarize(0.1),
140+
cirq.depolarize(0.1, n_qubits=2),
141+
cirq.amplitude_damp(0.2),
142+
),
143+
)
144+
def test_operation_to_channel_matrix(channel):
145+
"""Verifies that cirq.channel_matrix correctly computes the channel matrix."""
146+
actual = cirq.operation_to_channel_matrix(channel)
147+
expected = compute_channel_matrix(channel)
148+
assert np.all(actual == expected)
149+
150+
151+
def test_channel_matrix_for_completely_dephasing_channel():
152+
"""Checks cirq.operation_to_channel_matrix on the completely dephasing channel."""
153+
assert np.all(cirq.operation_to_channel_matrix(cirq.phase_damp(1)) == np.diag([1, 0, 0, 1]))

0 commit comments

Comments
 (0)