给定两个矩阵A和B,每个矩阵有4个索引,A[i,j,n_i,m],B[i,j,n_i,m],我需要对前两个索引执行它们的Tensor积,同时保持剩余的两个。对于这个任务,我使用einsum。此外,我需要对最后一个索引的结果求平均值,我只是使用。mean()到特定轴。
C=np.einsum('ij...,kl...->ikjl...',A[:,:,:,:],B[:,:,:,:]).reshape(1024,1024,len(t_list),Mmax).mean(axis=3)
虽然这是可行的,但我想知道是否有任何最佳的方法来做到这一点,因为现在这是相当缓慢的。恐怕我的瓶颈是矩阵的大小:虽然原始的A和B矩阵的形状为(32,32,1000,10000),但得到的矩阵为C=(1024,1024,1000),这是相当昂贵的。
然而,使用einsum然后平均使用。mean(axis=3)仍然比遍历Mmax、创建数组然后取平均值快得多。
我应该说明,我真正追求的是约化矩阵,或这个矩阵的部分迹i。e.
from qutip import *
dim=2*np.ones(2*5)
for n_i,n in enumerate(t_list):
C_s=C[:,:,n_i]
Qobj(C_s,dims=[dim,dim]).ptrace([0,1])
如果可能的话,我想在不需要直接分配C的情况下完成这个操作,我认为这是导致整个过程缓慢的原因。
1条答案
按热度按时间bz4sfanl1#
正如注解中提到的,您复制了不必要的输入数组。此外,如您所建议的,可以直接使用
np.einsum
计算外积的部分迹线,而无需分配C
。但这对代码的可读性没有帮助。..看看
ptrace
的实现和你传递给它的参数(也许下次尝试将这样的外部函数提炼成它的Numpy等价物-通过一些上下文可能更容易理解),看起来你最终会调用_ptrace_dense
(as found here),特别是该函数的else部分:正如你所看到的,这做了一些复杂的整形/换位,然后取结果的
np.trace
。您可以利用外积的踪迹实际上是内积的事实,这两者正是np.einsum
的目的。这消除了大量的乘法运算,并且需要少得多的存储器。您还可以对
np.einsum
中的最终尺寸求和,然后将输出除以最终尺寸的长度,以消除.mean(axis=3)
。为了在一个
np.einsum
步骤中完成所有操作,您必须首先重塑一个输入数组。最后可以归结为:X
现在保存for
循环的堆栈结果,即:e.所有ptrace
结果在最终轴上。这种转变不是特别直观,至少对我来说是这样。然而,到达这里的步骤相当容易,如果冗长。
我将通过我在特定情况下所做的工作:你有两个形状为(32,32)的输入数组,想要前两个子集/量子位/任何东西的部分轨迹。在你的其他维度上传播这个操作应该是显而易见的(如果不是,请阅读here)。
总结一下你要做的事情,它可以归结为:
使用
axis1
和axis2
可以显著简化这一点(不知道为什么Qutip库不这样做):更换
np.trace
:两个稍微不直观的东西可能有助于解释接下来的几个步骤:
(虽然不是很重要):
第一点的意思是:
这很有帮助,因为为了合并两个
np.einsum
,你需要能够将A
和B
(好吧,也许不是B
,稍后会详细介绍)拆分为32 x32的第一个np.einsum
和4x 256的第二个,如前所示。因此,A
和B
的形状必须是(4,8,4,8),因为4*8=32
满足第一个要求,而8*4*8=256
满足第二个要求。这就把以前的问题简化为:现在你要做的就是合并两个
np.einsum
。这很容易,因为第一个输出的ijmnklop
直接Map到第二个输入的ijmnkjmn
。因此:这里应该很明显,
B
实际上不需要4维,只需要2维。最后得出:显然,与我一样使用
np.random
,不如将数据重塑为正确的形状。这不是一个特别令人满意的解释,因为输出代码似乎与输入代码完全无关。但是,我希望这些步骤可以帮助您理解如何处理它,并将其推广到其他
np.einsum
案例。关于泛化到其他子集的具体问题,您可以将
A
重塑为2x 2x。..x2并从中选择任意索引。考虑到前面的(意想不到的)简化,您不必修改B
。例如,如果您想要0和3而不是0和1:请注意,要“保留”的所有尺寸标注都使用唯一的字母命名,并在输出中重复,而要跟踪的所有尺寸标注都在输入中重复,不会出现在输出中。
B
被简单地跟踪,而不管选择哪个维度。最后,你可以写一个函数来自动生成下标:
有趣的问题!