1# -*- coding: utf-8 -*-
2# This file is part of QuTiP: Quantum Toolbox in Python.
3#
4#    Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson.
5#    All rights reserved.
6#
7#    Redistribution and use in source and binary forms, with or without
8#    modification, are permitted provided that the following conditions are
9#    met:
10#
11#    1. Redistributions of source code must retain the above copyright notice,
12#       this list of conditions and the following disclaimer.
13#
14#    2. Redistributions in binary form must reproduce the above copyright
15#       notice, this list of conditions and the following disclaimer in the
16#       documentation and/or other materials provided with the distribution.
17#
18#    3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names
19#       of its contributors may be used to endorse or promote products derived
20#       from this software without specific prior written permission.
21#
22#    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23#    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24#    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25#    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26#    HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27#    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28#    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29#    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30#    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31#    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32#    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33###############################################################################
34#
35# This module was initially contributed by Ben Criger.
36#
37"""
38This module implements transformations between superoperator representations,
39including supermatrix, Kraus, Choi and Chi (process) matrix formalisms.
40"""
41
42__all__ = ['super_to_choi', 'choi_to_super', 'choi_to_kraus', 'kraus_to_choi',
43           'kraus_to_super', 'choi_to_chi', 'chi_to_choi', 'to_choi',
44           'to_chi', 'to_super', 'to_kraus', 'to_stinespring'
45           ]
46
47# Python Standard Library
48from itertools import starmap, product
49
50# NumPy/SciPy
51from numpy.core.multiarray import array, zeros
52from numpy.core.shape_base import hstack
53from numpy.matrixlib.defmatrix import matrix
54from numpy import sqrt, floor, log2
55from numpy import dot
56from scipy.linalg import eig, svd
57# Needed to avoid conflict with itertools.product.
58import numpy as np
59
60# Other QuTiP functions and classes
61from qutip.superoperator import vec2mat, operator_to_vector, sprepost
62from qutip.operators import identity, sigmax, sigmay, sigmaz
63from qutip.tensor import tensor, flatten
64from qutip.qobj import Qobj
65from qutip.states import basis
66
67
68# SPECIFIC SUPEROPERATORS -----------------------------------------------------
69
70def _dep_super(pe):
71    """
72    Returns the superoperator corresponding to qubit depolarization for a
73    given parameter pe.
74
75    TODO: if this is going into production (hopefully it isn't) then check
76    CPTP, expand to arbitrary dimensional systems, etc.
77    """
78    return Qobj(dims=[[[2], [2]], [[2], [2]]],
79                inpt=array([[1. - pe / 2., 0., 0., pe / 2.],
80                            [0., 1. - pe, 0., 0.],
81                            [0., 0., 1. - pe, 0.],
82                            [pe / 2., 0., 0., 1. - pe / 2.]]))
83
84
85def _dep_choi(pe):
86    """
87    Returns the choi matrix corresponding to qubit depolarization for a
88    given parameter pe.
89
90    TODO: if this is going into production (hopefully it isn't) then check
91    CPTP, expand to arbitrary dimensional systems, etc.
92    """
93    return Qobj(dims=[[[2], [2]], [[2], [2]]],
94                inpt=array([[1. - pe / 2., 0., 0., 1. - pe],
95                            [0., pe / 2., 0., 0.],
96                            [0., 0., pe / 2., 0.],
97                            [1. - pe, 0., 0., 1. - pe / 2.]]),
98                superrep='choi')
99
100
101# CHANGE OF BASIS FUNCTIONS ---------------------------------------------------
102# These functions find change of basis matrices, and are useful in converting
103# between (for instance) Choi and chi matrices. At some point, these should
104# probably be moved out to another module.
105
106_SINGLE_QUBIT_PAULI_BASIS = (identity(2), sigmax(), sigmay(), sigmaz())
107
108
109def _pauli_basis(nq=1):
110    # NOTE: This is slow as can be.
111    # TODO: Make this sparse. CSR format was causing problems for the [idx, :]
112    #       slicing below.
113    B = zeros((4 ** nq, 4 ** nq), dtype=complex)
114    dims = [[[2] * nq] * 2] * 2
115
116    for idx, op in enumerate(starmap(tensor,
117                                     product(_SINGLE_QUBIT_PAULI_BASIS,
118                                             repeat=nq))):
119        B[:, idx] = operator_to_vector(op).dag().full()
120
121    return Qobj(B, dims=dims)
122
123
124# PRIVATE CONVERSION FUNCTIONS ------------------------------------------------
125# These functions handle the main work of converting between representations,
126# and are exposed below by other functions that add postconditions about types.
127#
128# TODO: handle type='kraus' as a three-index Qobj, rather than as a list?
129
130def _super_tofrom_choi(q_oper):
131    """
132    We exploit that the basis transformation between Choi and supermatrix
133    representations squares to the identity, so that if we munge Qobj.type,
134    we can use the same function.
135
136    Since this function doesn't respect :attr:`Qobj.type`, we mark it as
137    private; only those functions which wrap this in a way so as to preserve
138    type should be called externally.
139    """
140    data = q_oper.data.toarray()
141    dims = q_oper.dims
142    new_dims = [[dims[1][1], dims[0][1]], [dims[1][0], dims[0][0]]]
143    d0 = np.prod(np.ravel(new_dims[0]))
144    d1 = np.prod(np.ravel(new_dims[1]))
145    s0 = np.prod(dims[0][0])
146    s1 = np.prod(dims[1][1])
147    return Qobj(dims=new_dims,
148                inpt=data.reshape([s0, s1, s0, s1]).
149                transpose(3, 1, 2, 0).reshape((d0, d1)))
150
151
152def _isqubitdims(dims):
153    """Checks whether all entries in a dims list are integer powers of 2.
154
155    Parameters
156    ----------
157    dims : nested list of ints
158        Dimensions to be checked.
159
160    Returns
161    -------
162    isqubitdims : bool
163        True if and only if every member of the flattened dims
164        list is an integer power of 2.
165    """
166    return all([
167        2**floor(log2(dim)) == dim
168        for dim in flatten(dims)
169    ])
170
171
172def _super_to_superpauli(q_oper):
173    """
174    Converts a superoperator in the column-stacking basis to
175    the Pauli basis (assuming qubit dimensions).
176
177    This is an internal function, as QuTiP does not currently have
178    a way to mark that superoperators are represented in the Pauli
179    basis as opposed to the column-stacking basis; a Pauli-basis
180    ``type='super'`` would thus break other conversion functions.
181    """
182    # Ensure we start with a column-stacking-basis superoperator.
183    sqobj = to_super(q_oper)
184    if not _isqubitdims(sqobj.dims):
185        raise ValueError("Pauli basis is only defined for qubits.")
186    nq = int(log2(sqobj.shape[0]) / 2)
187    B = _pauli_basis(nq) / sqrt(2**nq)
188    # To do this, we have to hack a bit and force the dims to match,
189    # since the _pauli_basis function makes different assumptions
190    # about indices than we need here.
191    B.dims = sqobj.dims
192    return (B.dag() * sqobj * B)
193
194
195def super_to_choi(q_oper):
196    # TODO: deprecate and make private in favor of to_choi,
197    # which looks at Qobj.type to determine the right conversion function.
198    """
199    Takes a superoperator to a Choi matrix
200    TODO: Sanitize input, incorporate as method on Qobj if type=='super'
201    """
202    q_oper = _super_tofrom_choi(q_oper)
203    q_oper.superrep = 'choi'
204    return q_oper
205
206
207def choi_to_super(q_oper):
208    # TODO: deprecate and make private in favor of to_super,
209    # which looks at Qobj.type to determine the right conversion function.
210    """
211    Takes a Choi matrix to a superoperator
212    TODO: Sanitize input, Abstract-ify application of channels to states
213    """
214    q_oper = super_to_choi(q_oper)
215    q_oper.superrep = 'super'
216    return q_oper
217
218
219def choi_to_kraus(q_oper, tol=1e-9):
220    """
221    Takes a Choi matrix and returns a list of Kraus operators.
222    TODO: Create a new class structure for quantum channels, perhaps as a
223    strict sub-class of Qobj.
224    """
225    vals, vecs = eig(q_oper.full())
226    vecs = [array(_) for _ in zip(*vecs)]
227    shape = [np.prod(q_oper.dims[0][i]) for i in range(2)][::-1]
228    return [Qobj(inpt=sqrt(val)*vec2mat(vec, shape=shape),
229            dims=q_oper.dims[0][::-1])
230            for val, vec in zip(vals, vecs) if abs(val) >= tol]
231
232
233def kraus_to_choi(kraus_list):
234    """
235    Takes a list of Kraus operators and returns the Choi matrix for the channel
236    represented by the Kraus operators in `kraus_list`
237    """
238    kraus_mat_list = list(map(lambda x: x.data.toarray(), kraus_list))
239    op_rng = range(kraus_mat_list[0].shape[0])
240    choi_blocks = array([[sum([
241                              np.outer(op[:, c_ix],
242                                       np.transpose(np.conjugate(op))[r_ix, :])
243                              for op in kraus_mat_list])
244                          for r_ix in op_rng]
245                         for c_ix in op_rng])
246    return Qobj(inpt=hstack(hstack(choi_blocks)),
247                dims=[kraus_list[0].dims[::-1], kraus_list[0].dims[::-1]],
248                type='super', superrep='choi')
249
250
251def kraus_to_super(kraus_list):
252    """
253    Converts a list of Kraus operators and returns a super operator.
254    """
255    return choi_to_super(kraus_to_choi(kraus_list))
256
257
258def _nq(dims):
259    dim = np.product(dims[0][0])
260    nq = int(log2(dim))
261    if 2 ** nq != dim:
262        raise ValueError("{} is not an integer power of 2.".format(dim))
263    return nq
264
265
266def choi_to_chi(q_oper):
267    """
268    Converts a Choi matrix to a Chi matrix in the Pauli basis.
269
270    NOTE: this is only supported for qubits right now. Need to extend to
271    Heisenberg-Weyl for other subsystem dimensions.
272    """
273    nq = _nq(q_oper.dims)
274    B = _pauli_basis(nq)
275    # Force the basis change to match the dimensions of
276    # the input.
277    B.dims = q_oper.dims
278    B.superrep = 'choi'
279
280    return Qobj(B.dag() * q_oper * B, superrep='chi')
281
282
283def chi_to_choi(q_oper):
284    """
285    Converts a Choi matrix to a Chi matrix in the Pauli basis.
286
287    NOTE: this is only supported for qubits right now. Need to extend to
288    Heisenberg-Weyl for other subsystem dimensions.
289    """
290    nq = _nq(q_oper.dims)
291    B = _pauli_basis(nq)
292    # Force the basis change to match the dimensions of
293    # the input.
294    B.dims = q_oper.dims
295
296    # We normally should not multiply objects of different
297    # superreps, so Qobj warns about that. Here, however, we're actively
298    # converting between, so the superrep of B is irrelevant.
299    # To suppress warnings, we pretend that B is also a chi.
300    B.superrep = 'chi'
301
302    # The Chi matrix has tr(chi) == d², so we need to divide out
303    # by that to get back to the Choi form.
304    return Qobj((B * q_oper * B.dag()) / q_oper.shape[0], superrep='choi')
305
306def _svd_u_to_kraus(U, S, d, dK, indims, outdims):
307    """
308    Given a partial isometry U and a vector of square-roots of singular values S
309    obtained from an SVD, produces the Kraus operators represented by U.
310
311    Returns
312    -------
313    Ks : list of Qobj
314        Quantum objects represnting each of the Kraus operators.
315    """
316    # We use U * S since S is 1-index, such that this is equivalent to
317    # U . diag(S), but easier to write down.
318    Ks = list(map(Qobj, array(U * S).reshape((d, d, dK), order='F').transpose((2, 0, 1))))
319    for K in Ks:
320        K.dims = [outdims, indims]
321    return Ks
322
323
324def _generalized_kraus(q_oper, thresh=1e-10):
325    # TODO: document!
326    # TODO: use this to generalize to_kraus to the case where U != V.
327    #       This is critical for non-CP maps, as appear in (for example)
328    #       diamond norm differences between two CP maps.
329    if q_oper.type != "super" or q_oper.superrep != "choi":
330        raise ValueError("Expected a Choi matrix, got a {} (superrep {}).".format(q_oper.type, q_oper.superrep))
331
332    # Remember the shape of the underlying space,
333    # as we'll need this to make Kraus operators later.
334    dL, dR = map(int, map(sqrt, q_oper.shape))
335    # Also remember the dims breakout.
336    out_dims, in_dims = q_oper.dims
337    out_left, out_right = out_dims
338    in_left, in_right = in_dims
339
340    # Find the SVD.
341    U, S, V = svd(q_oper.full())
342
343    # Truncate away the zero singular values, up to a threshold.
344    nonzero_idxs = S > thresh
345    dK = nonzero_idxs.sum()
346    U = U[:, nonzero_idxs]
347    S = sqrt(S[nonzero_idxs])
348    # Since NumPy returns V and not V+, we need to take the dagger
349    # to get back to quantum info notation for Stinespring pairs.
350    V = V.conj().T[:, nonzero_idxs]
351
352    # Next, we convert each of U and V into Kraus operators.
353    # Finally, we want the Kraus index to be left-most so that we
354    # can map over it when making Qobjs.
355    # FIXME: does not preserve dims!
356    kU = _svd_u_to_kraus(U, S, dL, dK, out_right, out_left)
357    kV = _svd_u_to_kraus(V, S, dL, dK, in_right, in_left)
358
359    return kU, kV
360
361
362def choi_to_stinespring(q_oper, thresh=1e-10):
363    # TODO: document!
364    kU, kV = _generalized_kraus(q_oper, thresh=thresh)
365
366    assert(len(kU) == len(kV))
367    dK = len(kU)
368    dL = kU[0].shape[0]
369    dR = kV[0].shape[1]
370    # Also remember the dims breakout.
371    out_dims, in_dims = q_oper.dims
372    out_left, out_right = out_dims
373    in_left, in_right = in_dims
374
375    A = Qobj(zeros((dK * dL, dL)), dims=[out_left + [dK], out_right + [1]])
376    B = Qobj(zeros((dK * dR, dR)), dims=[in_left + [dK], in_right + [1]])
377
378    for idx_kraus, (KL, KR) in enumerate(zip(kU, kV)):
379        A += tensor(KL, basis(dK, idx_kraus))
380        B += tensor(KR, basis(dK, idx_kraus))
381
382    # There is no input (right) Kraus index, so strip that off.
383    del A.dims[1][-1]
384    del B.dims[1][-1]
385
386    return A, B
387
388# PUBLIC CONVERSION FUNCTIONS -------------------------------------------------
389# These functions handle superoperator conversions in a way that preserves the
390# correctness of Qobj.type, and in a way that automatically branches based on
391# the input Qobj.type.
392
393def to_choi(q_oper):
394    """
395    Converts a Qobj representing a quantum map to the Choi representation,
396    such that the trace of the returned operator is equal to the dimension
397    of the system.
398
399    Parameters
400    ----------
401    q_oper : Qobj
402        Superoperator to be converted to Choi representation. If
403        ``q_oper`` is ``type="oper"``, then it is taken to act by conjugation,
404        such that ``to_choi(A) == to_choi(sprepost(A, A.dag()))``.
405
406    Returns
407    -------
408    choi : Qobj
409        A quantum object representing the same map as ``q_oper``, such that
410        ``choi.superrep == "choi"``.
411
412    Raises
413    ------
414    TypeError: if the given quantum object is not a map, or cannot be converted
415        to Choi representation.
416    """
417    if q_oper.type == 'super':
418        if q_oper.superrep == 'choi':
419            return q_oper
420        if q_oper.superrep == 'super':
421            return super_to_choi(q_oper)
422        if q_oper.superrep == 'chi':
423            return chi_to_choi(q_oper)
424        else:
425            raise TypeError(q_oper.superrep)
426    elif q_oper.type == 'oper':
427        return super_to_choi(sprepost(q_oper, q_oper.dag()))
428    else:
429        raise TypeError(
430            "Conversion of Qobj with type = {0.type} "
431            "and superrep = {0.choi} to Choi not supported.".format(q_oper)
432        )
433
434
435def to_chi(q_oper):
436    """
437    Converts a Qobj representing a quantum map to a representation as a chi
438    (process) matrix in the Pauli basis, such that the trace of the returned
439    operator is equal to the dimension of the system.
440
441    Parameters
442    ----------
443    q_oper : Qobj
444        Superoperator to be converted to Chi representation. If
445        ``q_oper`` is ``type="oper"``, then it is taken to act by conjugation,
446        such that ``to_chi(A) == to_chi(sprepost(A, A.dag()))``.
447
448    Returns
449    -------
450    chi : Qobj
451        A quantum object representing the same map as ``q_oper``, such that
452        ``chi.superrep == "chi"``.
453
454    Raises
455    ------
456    TypeError: if the given quantum object is not a map, or cannot be converted
457        to Chi representation.
458    """
459    if q_oper.type == 'super':
460        # Case 1: Already done.
461        if q_oper.superrep == 'chi':
462            return q_oper
463        # Case 2: Can directly convert.
464        elif q_oper.superrep == 'choi':
465            return choi_to_chi(q_oper)
466        # Case 3: Need to go through Choi.
467        elif q_oper.superrep == 'super':
468            return to_chi(to_choi(q_oper))
469        else:
470            raise TypeError(q_oper.superrep)
471    elif q_oper.type == 'oper':
472        return to_chi(sprepost(q_oper, q_oper.dag()))
473    else:
474        raise TypeError(
475            "Conversion of Qobj with type = {0.type} "
476            "and superrep = {0.choi} to Choi not supported.".format(q_oper)
477        )
478
479
480def to_super(q_oper):
481    """
482    Converts a Qobj representing a quantum map to the supermatrix (Liouville)
483    representation.
484
485    Parameters
486    ----------
487    q_oper : Qobj
488        Superoperator to be converted to supermatrix representation. If
489        ``q_oper`` is ``type="oper"``, then it is taken to act by conjugation,
490        such that ``to_super(A) == sprepost(A, A.dag())``.
491
492    Returns
493    -------
494    superop : Qobj
495        A quantum object representing the same map as ``q_oper``, such that
496        ``superop.superrep == "super"``.
497
498    Raises
499    ------
500    TypeError
501        If the given quantum object is not a map, or cannot be converted
502        to supermatrix representation.
503    """
504    if q_oper.type == 'super':
505        # Case 1: Already done.
506        if q_oper.superrep == "super":
507            return q_oper
508        # Case 2: Can directly convert.
509        elif q_oper.superrep == 'choi':
510            return choi_to_super(q_oper)
511        # Case 3: Need to go through Choi.
512        elif q_oper.superrep == 'chi':
513            return to_super(to_choi(q_oper))
514        # Case 4: Something went wrong.
515        else:
516            raise ValueError(
517                "Unrecognized superrep '{}'.".format(q_oper.superrep))
518    elif q_oper.type == 'oper':  # Assume unitary
519        return sprepost(q_oper, q_oper.dag())
520    else:
521        raise TypeError(
522            "Conversion of Qobj with type = {0.type} "
523            "and superrep = {0.superrep} to supermatrix not "
524            "supported.".format(q_oper)
525        )
526
527
528def to_kraus(q_oper, tol=1e-9):
529    """
530    Converts a Qobj representing a quantum map to a list of quantum objects,
531    each representing an operator in the Kraus decomposition of the given map.
532
533    Parameters
534    ----------
535    q_oper : Qobj
536        Superoperator to be converted to Kraus representation. If
537        ``q_oper`` is ``type="oper"``, then it is taken to act by conjugation,
538        such that ``to_kraus(A) == to_kraus(sprepost(A, A.dag())) == [A]``.
539
540    tol : Float
541        Optional threshold parameter for eigenvalues/Kraus ops to be discarded.
542        The default is to=1e-9.
543
544    Returns
545    -------
546    kraus_ops : list of Qobj
547        A list of quantum objects, each representing a Kraus operator in the
548        decomposition of ``q_oper``.
549
550    Raises
551    ------
552    TypeError: if the given quantum object is not a map, or cannot be
553        decomposed into Kraus operators.
554    """
555    if q_oper.type == 'super':
556        if q_oper.superrep in ("super", "chi"):
557            return to_kraus(to_choi(q_oper), tol)
558        elif q_oper.superrep == 'choi':
559            return choi_to_kraus(q_oper, tol)
560    elif q_oper.type == 'oper':  # Assume unitary
561        return [q_oper]
562    else:
563        raise TypeError(
564            "Conversion of Qobj with type = {0.type} "
565            "and superrep = {0.superrep} to Kraus decomposition not "
566            "supported.".format(q_oper)
567        )
568
569def to_stinespring(q_oper):
570    r"""
571    Converts a Qobj representing a quantum map $\Lambda$ to a pair of partial isometries
572    $A$ and $B$ such that $\Lambda(X) = \Tr_2(A X B^\dagger)$ for all inputs $X$, where
573    the partial trace is taken over a a new index on the output dimensions of $A$ and $B$.
574
575    For completely positive inputs, $A$ will always equal $B$ up to precision errors.
576
577    Parameters
578    ----------
579    q_oper : Qobj
580        Superoperator to be converted to a Stinespring pair.
581
582    Returns
583    -------
584    A, B : Qobj
585        Quantum objects representing each of the Stinespring matrices for the input Qobj.
586    """
587    return choi_to_stinespring(to_choi(q_oper))
588