<jupyter_start><jupyter_text>QuTiP Example: Stinespring Dilation of Quantum Channels [<NAME>](http://www.cgranade.com/) University of Sydney$\newcommand{\ket}[1]{\left|1\right\rangle}$$\newcommand{\bra}[1]{\left\langle1\right|}$$\newcommand{\cnot}{{\scriptstyle \rm CNOT}}$$\newcommand{\TT}{\operatorname{T}}$$\newcommand{\Tr}{\operatorname{Tr}}$$\newcommand{\Uni}{\operatorname{U}}$$\newcommand{\Chan}{\operatorname{C}}$$\newcommand{\Hil}{\mathcal{H}}$  Introduction In this notebook, we will demonstrate the ``to_stinespring`` function, which converts a ``Qobj`` representing the quantum channel $\Phi$ to a pair of partial isometries that describe the action of $\Phi$.In introducing the Stinespring dilation, it is helpful to first adopt some notation. Let $\Hil_X$, $\Hil_Y$ and $\Hil_Z$ be finite-dimensional Hilbert spaces. Let $\Uni(\Hil_X, \Hil_Y)$ be the set of partial isometries from $\Hil_X$ to $\Hil_Y$, and $\TT(\Hil_X, \Hil_Y)$ be the set of maps (that is, superoperators) from operators on $\Hil_X$ to those acting on $\Hil_Y$. Finally, let $\Chan(\Hil_X, \Hil_Y) \subset \TT(\Hil_X, \Hil_Y)$ be the set of *channels*; that is, those maps which are completely positive and trace-preserving.For a quantum map $\Phi \in \TT(\Hil_X, \Hil_Y)$, then, the Stinespring dilation of $\Phi$ is a pair of partial isometries $A, B \in \Uni(\Hil_X, \Hil_Y \otimes \Hil_Z)$ for some space $\Hil_Z$ such that\begin{equation}    \Phi(X) = \Tr_Z (A X B^\dagger).    \label{eq:stinespring-action}\end{equation}If $\Phi$ is completely positive, then $A = B$. We do not insist on complete positivity, however, as it is common to consider the Stinespring dilation for the *difference* between two quantum channels, $\Phi - \Phi'$. By construction, such a map is not completely positive, nor is it trace-preserving. Informally, the ancillary space $\Hil_Z$ serves the same role as the Kraus index. If $\Phi$ is a channel with Kraus operators $\{K_i : i \in \{0, \dots, k - 1\}\}$, then $A = B = \sum_i K_i \otimes \ket{i}$, such that summation over $i$ is performed by the partial trace in \eqref{eq:stinespring-action}.The Stinespring representation is useful for [evaluating norms](http://theoryofcomputing.org/articles/v005a011/) and for building system/environment models of a quantum channel.  Further Resources- Watrous. [CS 766: Theory of Quantum Information (lecture notes)](https://cs.uwaterloo.ca/~watrous/CS766/) (2013).-  <NAME> and Cory. *Tensor networks and graphical calculus for open quantum systems*. [arXiv:1111.6950](https://scirate.com/arxiv/1111.6950) (2011).- Watrous. *Semidefinite Programs for Completely Bounded Norms*. [Theory of Computing **5** 217-238](http://theoryofcomputing.org/articles/v005a011/), [arXiv:0901.4709](https://scirate.com/arxiv/0901.4709) (2009).  Preamble  Features We enable a few features such that this notebook runs in both Python 2 and 3.<jupyter_code>from __future__ import division, print_function<jupyter_output><empty_output><jupyter_text>Imports<jupyter_code>import numpy as np
import qutip as qt

from qutip.ipynbtools import version_table<jupyter_output><empty_output><jupyter_text>Plotting Support<jupyter_code>%matplotlib inline<jupyter_output><empty_output><jupyter_text>Settings<jupyter_code>qt.settings.colorblind_safe = True<jupyter_output><empty_output><jupyter_text>Dilations of CPTP Maps We'll start by considering the case in which $\Phi(X) = U X U^\dagger$ for a random unitary operator $U \in \Uni(\Hil_X, \Hil_X)$.<jupyter_code>phi = qt.to_super(qt.rand_unitary_haar(4))
qt.visualization.hinton(phi)<jupyter_output><empty_output><jupyter_text>By construction, $\Phi$ has one Kraus operator--- the random unitary itself. Because of numerical precision in finding eigenvalues of the Choi matrix, a decomposition will result in many operators with very small norms.<jupyter_code>for K in qt.to_kraus(phi):
    print(K.norm())<jupyter_output>1.44965724751e-08
4.0
2.17301265733e-08
1.52222408748e-08
1.5017315825e-08
1.76642230882e-08
1.37895174864e-08
1.40385925527e-08
1.07348372039e-08
9.50757741411e-09
6.71512203218e-09
1.14139195174e-08
1.01210695111e-08
6.00690608844e-09
3.38230626595e-09
4.01926307771e-10<jupyter_text>Because $\Phi$ only has one Kraus operator, the Stinespring dilation will use a 1-dimensional ancillary space.<jupyter_code>A, B = qt.to_stinespring(phi)
print(A.dims)<jupyter_output>[[4, 1], [4]]<jupyter_text>If we ask for a random channel of Choi rank 2 (that is, with 2 Kraus operators), then the corresponding space will be two-dimensional instead.<jupyter_code>A, B = qt.to_stinespring(qt.rand_super_bcsz(4, rank=2))
print(A.dims)<jupyter_output>[[4, 2], [4]]<jupyter_text>Moreover, since we have demanded that $A$ and $B$ are the Stinespring pair for a channel, we can easily verify that $A = B$.<jupyter_code>A - B<jupyter_output><empty_output><jupyter_text>Differences of Random Channels Next, let's consider two random qubit channels from the [BCSZ distribution]([``qutip.visualization.hinton``](http://qutip.org/docs/3.0.1/apidoc/functions.htmlqutip.random_objects.rand_super_bcsz).<jupyter_code>phi1 = qt.rand_super_bcsz(2)
phi2 = qt.rand_super_bcsz(2)<jupyter_output><empty_output><jupyter_text>Looking at the Pauli-basis Hinton diagram for the difference between these channels gives some insight into how this map behaves.<jupyter_code>qt.visualization.hinton(phi1 - phi2)<jupyter_output><empty_output><jupyter_text>In particular, note that $\Delta\Phi := \Phi_1 - \Phi_2$ annilates the traceful parts of its input, leaving an operator with negative eigenvalues.<jupyter_code>d_phi = qt.to_super(phi1 - phi2)
rho_in = qt.rand_dm(2)
rho = qt.vector_to_operator(d_phi * qt.operator_to_vector(rho_in))
print("Trace: {}\nEigenvalues:{}".format(rho.tr(), rho.eigenenergies()))
rho<jupyter_output>Trace: -3.885780586188048e-16
Eigenvalues:[-0.39781299  0.39781299]<jupyter_text>Because $\Delta \Phi$ is not completely positive, we cannot obtain a Kraus decomposition where the left and right operators are the same:<jupyter_code>Ks = qt.to_kraus(d_phi)
rho - sum(K * rho_in * K.dag() for K in Ks)<jupyter_output><empty_output><jupyter_text>On the other hand, if we allow the left and right Kraus operators to be different, or consider a Stinespring dilation, we get the right answer:<jupyter_code>A, B = qt.to_stinespring(d_phi)
rho - (A * rho_in * B.dag()).ptrace((0,))<jupyter_output><empty_output><jupyter_text>Constructing System-Environment Models For a channel $\Phi \in \Chan(\Hil_X, \Hil_X)$, once we have found the Stinespring dilation $A$, we then represent $\Phi$ as a *unitary* on the full space, $U \in \Uni(\Hil_X \otimes \Hil_Z)$, interpreting $\Hil_Z$ as a preparation of an environment. Concretely, we want to find $U$ such that for a preparation $\ket{\psi}$ of the environment,\begin{equation}    \Phi(X) = \Tr_Z(U \rho \otimes \ket{\psi}\bra{\psi} U^\dagger).\end{equation}By convention, we'll choose a basis for $\Hil_Z$ such that $\ket{\psi} = \ket{0}$. Then, if we let $V = A \otimes \bra{0}$,\begin{equation}    \Tr_Z(V \rho \otimes \ket{0}\bra{0} V^\dagger) =    \Tr_Z(A \rho \otimes \bra{0}\ket{0}\bra{0}\ket{0} A) =    \Tr_Z(A \rho A^\dagger)\end{equation}as desired.<jupyter_code>A, B = qt.to_stinespring(qt.rand_super_bcsz(3))
V = qt.tensor(A, qt.basis(A.dims[0][-1], 0).dag())
# This adds an annoying left index, so let's drop it now.
del V.dims[0][-1]
V
rho = qt.rand_dm_ginibre(3)
(A * rho * A.dag()).ptrace((0,)) - (V * qt.tensor(rho, qt.ket2dm(qt.basis(A.dims[0][-1], 0))) * V.dag()).ptrace((0,))<jupyter_output><empty_output><jupyter_text>It thus remains to extend $V$ to act non-trivially on the full space. This is done by noting that the singular values of $V$ are each one or zero, convienently partitioning $\Hil_X \otimes \Hil_Z$ into the null space of $V$ and its complement.<jupyter_code>Vu, Vs, Vv = np.linalg.svd(V.data.todense())
U = qt.Qobj(Vu, dims=V.dims) * qt.Qobj(Vv, dims=V.dims)
U * U.dag()<jupyter_output><empty_output><jupyter_text>We finish by verifying that preparing an environment state, evolving under the system/environment unitary $U$, then partial tracing over the environment produces the same map as the original channel.<jupyter_code>(A * rho * A.dag()).ptrace((0,)) - (U * qt.tensor(rho, qt.ket2dm(qt.basis(A.dims[0][-1], 0))) * U.dag()).ptrace((0,))<jupyter_output><empty_output><jupyter_text>Epilouge<jupyter_code>version_table()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13739914570479356, 'fraction_numerical': 0.044850498338870434, 'mean_word_length': 5.503858024691358, 'pattern_counts': {'":': 0, '<': 71, '<?xml version=': 0, '>': 71, 'https://': 3, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '18', 'dir': 'jupyter-structured-clean-dedup', 'id': '606714', 'license': 'MIT', 'n_tokens_mistral': 3289, 'n_tokens_neox': 3018, 'n_words': 838, 'path': 'qutip-notebooks-master/examples/stinespring.ipynb', 'repo_name': 'OliverDudgeon/QSync', 'size': '8449'}