sf.utils.extract_channel

extract_channel(prog, cutoff_dim, representation='choi', vectorize_modes=False)[source]

Numerical array representation of the channel corresponding to a quantum circuit.

The representation choices include the Choi state representation, the Liouville representation, and the Kraus representation.

Note

Channel extraction can currently only be performed using the 'fock' backend.

Tensor shapes

  • If vectorize_modes=True:

    • representation='choi' and representation='liouville' return an array with 4 indices

    • representation='kraus' returns an array of Kraus operators in matrix form

  • If vectorize_modes=False:

    • representation='choi' and representation='liouville' return an array with \(4N\) indices

    • representation='kraus' returns an array of Kraus operators with \(2N\) indices each, where \(N\) is the number of modes that the Program is created with

Note that the Kraus representation automatically returns only the non-zero Kraus operators. One can reduce the number of operators by discarding Kraus operators with small norm (thus approximating the channel).

Choi representation

Mathematically, the Choi representation of a channel is a bipartite state \(\Lambda_{AB}\) which contains a complete description of the channel. The way we use it to compute the action of the channel \(\mathcal{C}\) on an input state \(\mathcal{\rho}\) is as follows:

\[\mathcal{C}(\rho) = \mathrm{Tr}[(\rho_A^T\otimes\mathbb{1}_B)\Lambda_{AB}]\]

The indices of the non-vectorized Choi operator match exactly those of the state, so that the action of the channel can be computed as (e.g., for one mode or for vectorize_modes=True):

>>> rho_out = np.einsum('ab,abcd', rho_in, choi)

Notice that this respects the transpose operation.

For two modes:

>>> rho_out = np.einsum('abcd,abcdefgh', rho_in, choi)

Combining consecutive channels (in the order \(1,2,3,\dots\)) is also straightforward with the Choi operator:

>>> choi_combined = np.einsum('abcd,cdef,efgh', choi_1, choi_2, choi_3)

Liouville operator

The Liouville operator is a partial transpose of the Choi operator, such that the first half of consecutive index pairs are the output-input right modes (i.e., acting on the “bra” part of the state) and the second half are the output-input left modes (i.e., acting on the “ket” part of the state).

Therefore, the action of the Liouville operator (e.g., for one mode or for vectorize_modes=True) is

\[\mathcal{C}(\rho) = \mathrm{unvec}[\mathcal{L}\mathrm{vec}(\rho)]\]

where vec() and unvec() are the operations that stack the columns of a matrix to form a vector and vice versa. In code:

>>> rho_out = np.einsum('abcd,bd->ca', liouville, rho_in)

Notice that the state contracts with the second index of each pair and that we output the ket on the left (c) and the bra on the right (a).

For two modes we have:

>>> rho_out = np.einsum('abcdefgh,fbhd->eagc', liouville, rho_in)

The Liouville representation has the property that if the channel is unitary, the operator is separable. On the other hand, even if the channel were the identity, the Choi operator would correspond to a maximally entangled state.

The choi and liouville operators in matrix form (i.e., with two indices) can be found as follows, where D is the dimension of each vectorized index (i.e., for \(N\) modes, D=cutoff_dim**N):

>>> choi_matrix = liouville.reshape(D**2, D**2).T
>>> liouville_matrix = choi.reshape(D**2, D**2).T

Kraus representation

The Kraus representation is perhaps the most well known:

\[\mathcal{C}(\rho) = \sum_k A_k\rho A_k^\dagger\]

So to define a channel in the Kraus representation one needs to supply a list of Kraus operators \(\{A_k\}\). In fact, the result of extract_channel in the Kraus representation is a rank-3 tensor, where the first index is the one indexing the list of operators.

Adjacent indices of each Kraus operator correspond to output-input pairs of the same mode, so the action of the channel can be written as (here for one mode or for vectorize_modes=True):

>>> rho_out = np.einsum('abc,cd,aed->be', kraus, rho_in, np.conj(kraus))

Notice the transpose on the third index string (aed rather than ade), as the last operator should be the conjugate transpose of the first, and we cannot just do np.conj(kraus).T because kraus has 3 indices and we just need to transpose the last two.

Example

Here we show that the Choi operator of the identity channel is proportional to a maximally entangled Bell \(\ket{\phi^+}\) state:

>>> prog = sf.Program(num_subsystems=1)
>>> C = extract_channel(prog, cutoff_dim=2, representation='choi')
>>> print(abs(C).reshape((4,4)))
[[1. 0. 0. 1.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [1. 0. 0. 1.]]
Parameters
  • prog (Program) – program containing the circuit

  • cutoff_dim (int) – dimension of each index

  • representation (str) – choice between 'choi', 'liouville' or 'kraus'

  • vectorize_modes (bool) – if True, reshapes the result into rank-4 tensor, otherwise it returns a rank-4N tensor, where N is the number of modes

Returns

channel, according to the specified options

Return type

array

Raises

TypeError – if the gates used to construct the circuit are not all unitary or channels