Package: departed
DEPARTED provides partial trace and partial transpose operations formatrices with a Kronecker-product structure.InstallationAs of release 0.3.0, it is possible to retrieve the latest release of thepackage from the PyPi repository by running 'pip install --user departed'ExamplesSee the examples directory.Documentation- ptrace (matrix, component_dims, component_mask)Computes the partial trace of a matrix with a Kronecker-product structure.: matrixdefines the matrix to be partially traced: component_dimsdefines a list of component dimensions: component_maskdefines a mask specifying whether a component should betraced out (1, True) or kept (0, False).ExampleConsider a density matrix R of a three-qubit quantum state. The partialtrace over the 1st and 3rd qubit is obtained withptrace(R, [ 2, 2, 2 ], [ 1, 0, 1 ]),where the mask [ 1, 0, 1 ] uses 1 for components that will be traced out.- ptranspose (matrix, component_dims, component_mask)Computes the partial transpose of a matrix with a Kronecker-product structure.: matrixdefines the matrix to be partially transposed: component_dimsdefines a list of component dimensions: component_maskdefines a mask specifying whether a component should betransposed (1, True) or left intact (0, False).ExampleConsider a density matrix R of a three-qubit quantum state. The partialtranspose over the 1st and 3rd qubit is obtained withptranspose(R, [ 2, 2, 2 ], [ 1, 0, 1 ]),where the mask [ 1, 0, 1 ] uses 1 for components that will be transposed.- mask_from_component_list (component_list, mask_width, invert = False)Constructs a component_mask from a list of component indices.By default, 1 is set for components listed in index_list and 0 for thoseunlisted. These values can be inverted by setting invert = True.: component_listdefines a list of component indices (starting from 0): mask_widthdefines the total number of components (mask width): invertdefines how component_list is translated into component_maskExampleConsider a system with 5 components. Suppose the partial operation ofchoice is performed over the 2nd and 4th components. Thecorresponding mask [ 0, 1, 0, 1, 0 ] can be constructed withmask_from_component_list([ 1, 3 ], 5),where we note that the indices start from 0.Suppose that the partial operation should NOT affect the 3rd and 5thcomponent. The respective mask [ 1, 1, 0, 1, 0 ] can be constructed withmask_from_component_list([ 2, 4 ], 5, invert = True),where setting the optional parameter (invert = True) inverts the mask.Operating principles of Kronecker product and its tensor representationAn understanding of the internal memory layout of matrices with aKronecker-product structure is crucial for both the partial trace and partialtranspose operations. In both algorithms, the matrix is first reshaped into atensor with the axes of its constituent matrices arranged according to theirdirection, with row axes preceding column axes.In particular, consider a matrix with a Kronecker-product structurecomprising N matrices and its tensor representation. The first N axes of thetensor are the row axes of its matrix constituents and the last N axes aretheir column axes.This ordering appears counter-intuitive, however, it follows from theinternal memory layout of the underlying numpy.ndarray object.ExampleTo illustrate this concept in greater detail, we consider a tensor productof three matrix spaces, where the first matrix space comprises (m x m)matrices, the second one (n x n) matrices, and finally, the thirdone (o x o) matrices. The product space contains (mno x mno) matrices.Let Q be a matrix (with a Kronecker-product structure) from this productspace. Its tensor representation can be obtained throughT = Q.reshape([ m, n, o, m, n, o ]),where the first three axes of the tensor T each correspond to the row axesof matrices from the constituent matrix spaces and the last three axes ofthe tensor each correspond to the column axes of these matrices.For example, should one have three matrices - A, B, and C - and theirKronecker product, kron(kron(A, B), C), the elementT[:, 0, 2, :, 1, 3 ]of its tensor representation equals toA * B[0, 1], * C[2, 3].RemarksSome software libraries use tensors structurally compatible withKronecker-product matrices to represent quantum states and operations.Operating principles of partial transposeConsider an arbitrary bipartite quantum state defined by its density operatorrho = sum(i, j, k, l) R(i, k, j, l) |i><j| |k><l|,where the rank four tensor R(i, k, j, l) determines its coefficients. Theorder of its indices (axes) follows the tensor representation of matriceswith a Kronecker-product structure. Its row indices precede column indices.We define the partial transpose over the second system aspt(rho) = sum(i, j, k, l) R(i, k, j, l) |i><j| |l><k|= sum(i, j, k, l) R(i, l, j, k) |i><j| |k><l|.If the density operator is encoded in a matrix with a Kronecker-productstructure, rather than a rank four tensor, the partial transpose no longeramounts to a simple exchange of axes.It can be realized by determining the affected indices of the matrixand exchanging their values. Not only is this approach cumbersome, but alsoextremely inelegant.A better alternative is to reshape the matrix into a tensor of appropriaterank. The reshaping operation is computationally cheap as it only affects theinterpretation of the underlying numpy.ndarray object.Suppose Q is the matrix representation of the state rho. Suppose m and n arethe dimensions of the individual systems comprising the state. WithT = reshape(Q, [ m, n, m, n ])we obtain a rank four tensor T. Consequently, the partial transposecorresponds to the exchange of the 2nd and the 4th index,pt(T)(i, k, j, l) = T(i, l, j, k).We can reconstruct the Kronecker representation by reshaping the tensor backinto the original matrix shape.This idea can be generalized to an arbitrary number of components. It ispractical to use the standard numpy.transpose procedure and supply thecorrect axial permutation. Our ptranspose does exactly that.Operating principles of partial traceWe build on the same ideas we have explored for partial transpose and thenadd a figurative cherry on top: by reordering the tensor, we can perform onlya single trace operation even if multiple components are to be traced out.Let us begin with a bipartite system again and a density matrixrho = sum(i, j, k, l) R(i, k, j, l) |i><j| |k><l|.We define the partial trace performed over its second component asrho' = sum(u) sum(i, j, k, l) R(i, k, j, l) |i><j| <u|k> <l|u>= sum(i, j, k) R(i, k, j, k) |i><j|= sum(i, j) [ sum(k) R(i, k, j, k) ] |i><j|,where we have presumed that the individual { |k>, |l> } kets are orthonormal.If the density operator is encoded in a matrix Q with a Kronecker-productstructure, the partial trace can be obtained from its tensor representationT = reshape(Q, [ m, n, m, n ]).To obtain the partial trace, we must evaluateM(i, j) = sum(k) T(i, k, j, k),which defines the elements M(i, j) of the marginal tensor. This approach canbe extended for multi-partite systems and further optimized. We can permutethe axes to gather the parts to be traced out into a single block and thencompute the trace in one go.Notes and acknowledgmentsThis project was inspired by QuTiP (https://github.com/qutip/qutip) and theiressentially identical implementation. This project aims to explain theunderlying principles behind these functions.