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