Я предполагаю, что это действительный MCVE:
In [139]: f = np.arange(10)
In [140]: A = np.arange(20).reshape(10,2)
In [141]: f.dot(A)
Out[141]: array([570, 615])
In [142]: d = [0,2,5,10]
In [143]: for i,j in zip(d[:-1],d[1:]):
...: print(f[i:j].dot(A[i:j,:]))
...:
[2 3]
[58 67]
[510 545]
, где 570
= 2+58+510
.
In [145]: np.array([f[i:j].dot(A[i:j,:]) for i,j in zip(d[:-1],d[1:])])
Out[145]:
array([[ 2, 3],
[ 58, 67],
[510, 545]])
Учитывая, что срезы i:j
могут различаться по длине, может быть трудно «векторизовать» это в истинном смысле. Мы можем скрыть итерации, но записать их так, чтобы переместить все итерации в скомпилированный код, будет сложно. Накопительные операции, такие как cumsum
, часто являются лучшим выбором. Нам часто приходится отступать назад и смотреть на проблему с другой точки зрения (в отличие от простого удаления цикла).
numba
и cython
часто используются для ускорения итеративных решений, но я не буду вдаваться в них.
Если d
делит массивы на равные части, мы можем использовать изменение формы для вычисления частей:
In [228]: A.shape
Out[228]: (10, 2)
In [229]: f.shape
Out[229]: (10,)
In [230]: f2 = f.reshape(2,5)
In [231]: A2 = A.reshape(2,5,2)
In [233]: np.einsum('ij,ijk->ik',f2,A2)
Out[233]:
array([[ 60, 70],
[510, 545]])
Оператор matmul
также работает, хотя требует некоторого изменения размеров:
In [236]: (f2[:,None,:]@A2)[:,0,:]
Out[236]:
array([[ 60, 70],
[510, 545]])
Если d
делит массивы только на пару размеров, я думаю, мы могли бы сгруппировать общие размеры и выполнить приведенные выше изменения и einsum для каждой группы, но я не проработал детали:
In [238]: d = [0,2,5,7,10]
In [239]: np.array([f[i:j].dot(A[i:j,:]) for i,j in zip(d[:-1],d[1:])])
Out[239]:
array([[ 2, 3],
[ 58, 67],
[122, 133],
[388, 412]])
In [240]: [f[i:j] for i,j in zip(d[:-1],d[1:])]
Out[240]: [array([0, 1]), array([2, 3, 4]), array([5, 6]), array([7, 8, 9])]
Здесь у нас есть 2 группы, одна из которых имеет длину 2, а другая - длину 3.