def tensor_contract(qobj, *pairs): """Contracts a qobj along one or more index pairs. Note that this uses dense representations and thus should *not* be used for very large Qobjs. Parameters ---------- pairs : tuple One or more tuples ``(i, j)`` indicating that the ``i`` and ``j`` dimensions of the original qobj should be contracted. Returns ------- cqobj : Qobj The original Qobj with all named index pairs contracted away. """ # Record and label the original dims. dims = qobj.dims dims_idxs = enumerate_flat(dims) tensor_dims = dims_to_tensor_shape(dims) # Convert to dense first, since sparse won't support the reshaping we need. qtens = qobj.data.toarray() # Reshape by the flattened dims. qtens = qtens.reshape(tensor_dims) # Contract out the indices from the flattened object. # Note that we need to feed pairs through dims_idxs_to_tensor_idxs # to ensure that we are contracting the right indices. qtens = _tensor_contract_dense(qtens, *dims_idxs_to_tensor_idxs(dims, pairs)) # Remove the contracted indexes from dims so we know how to # reshape back. # This concerns dims, and not the tensor indices, so we need # to make sure to use the original dims indices and not the ones # generated by dims_to_* functions. contracted_idxs = deep_remove(dims_idxs, *flatten(list(map(list, pairs)))) contracted_dims = unflatten(flatten(dims), contracted_idxs) # We don't need to check for tensor idxs versus dims idxs here, # as column- versus row-stacking will never move an index for the # vectorized operator spaces all the way from the left to the right. l_mtx_dims, r_mtx_dims = map(np.product, map(flatten, contracted_dims)) # Reshape back into a 2D matrix. qmtx = qtens.reshape((l_mtx_dims, r_mtx_dims)) # Return back as a qobj. return Qobj(qmtx, dims=contracted_dims, superrep=qobj.superrep)
def test_dims_to_tensor_shape(): # Dims for a superoperator: # L(L(C⁰ × C¹, C² × C³), L(C³ × C⁴, C⁵ × C⁶)), # where L(X, Y) is a linear operator from X to Y (dims [Y, X]). in_dims = [[2, 3], [0, 1]] out_dims = [[3, 4], [5, 6]] dims = [out_dims, in_dims] # To make the expected shape, we want the left and right spaces to each # be flipped, then the whole thing flattened. shape = (5, 6, 3, 4, 0, 1, 2, 3) assert_equal(dims_to_tensor_shape(dims), shape)
def test_dims_to_tensor_shape(): # Dims for a superoperator: # L(L(C⁰ × C¹, C² × C³), L(C³ × C⁴, C⁵ × C⁶)), # where L(X, Y) is a linear operator from X to Y (dims [Y, X]). in_dims = [[2, 3], [0, 1]] out_dims = [[3, 4], [5, 6]] dims = [out_dims, in_dims] # To make the expected shape, we want the left and right spaces to each # be flipped, then the whole thing flattened. shape = (5, 6, 3, 4, 0, 1, 2, 3) assert_equal( dims_to_tensor_shape(dims), shape )
def tensor_swap(q_oper, *pairs): """Transposes one or more pairs of indices of a Qobj. Note that this uses dense representations and thus should *not* be used for very large Qobjs. Parameters ---------- pairs : tuple One or more tuples ``(i, j)`` indicating that the ``i`` and ``j`` dimensions of the original qobj should be swapped. Returns ------- sqobj : Qobj The original Qobj with all named index pairs swapped with each other """ dims = q_oper.dims tensor_pairs = dims_idxs_to_tensor_idxs(dims, pairs) data = q_oper.data.toarray() # Reshape into tensor indices data = data.reshape(dims_to_tensor_shape(dims)) # Now permute the dims list so we know how to get back. flat_dims = flatten(dims) perm = list(range(len(flat_dims))) for i, j in pairs: flat_dims[i], flat_dims[j] = flat_dims[j], flat_dims[i] for i, j in tensor_pairs: perm[i], perm[j] = perm[j], perm[i] dims = unflatten(flat_dims, enumerate_flat(dims)) # Next, permute the actual indices of the dense tensor. data = data.transpose(perm) # Reshape back, using the left and right of dims. data = data.reshape(list(map(np.prod, dims))) return Qobj(inpt=data, dims=dims, superrep=q_oper.superrep)
def test_dims_to_tensor_shape(self, indices): assert dims_to_tensor_shape(indices.base) == indices.shape