Python 高效构造有限元/有限体积法矩阵
这是FEM/FVM方程系统的典型用例,因此可能具有更广泛的兴趣。从一个三角形网格开始 我想创建一个Python 高效构造有限元/有限体积法矩阵,python,numpy,matrix,scipy,scientific-computing,Python,Numpy,Matrix,Scipy,Scientific Computing,这是FEM/FVM方程系统的典型用例,因此可能具有更广泛的兴趣。从一个三角形网格开始 我想创建一个scipy.sparse.csr\u矩阵。矩阵行/列表示网格节点处的值。矩阵在主对角线上以及两个节点通过边连接的任何位置都有条目 以下是一个MWE,它首先构建节点->边缘->单元关系,然后构建矩阵: import numpy import meshzoo from scipy import sparse nx = 1600 ny = 1000 verts, cells = meshzoo.rec
scipy.sparse.csr\u矩阵
。矩阵行/列表示网格节点处的值。矩阵在主对角线上以及两个节点通过边连接的任何位置都有条目
以下是一个MWE,它首先构建节点->边缘->单元关系,然后构建矩阵:
import numpy
import meshzoo
from scipy import sparse
nx = 1600
ny = 1000
verts, cells = meshzoo.rectangle(0.0, 1.61, 0.0, 1.0, nx, ny)
n = len(verts)
nds = cells.T
nodes_edge_cells = numpy.stack([nds[[1, 2]], nds[[2, 0]],nds[[0, 1]]], axis=1)
# assign values to each edge (per cell)
alpha = numpy.random.rand(3, len(cells))
vals = numpy.array([
[alpha**2, -alpha],
[-alpha, alpha**2],
])
# Build I, J, V entries for COO matrix
I = []
J = []
V = []
#
V.append(vals[0][0])
V.append(vals[0][1])
V.append(vals[1][0])
V.append(vals[1][1])
#
I.append(nodes_edge_cells[0])
I.append(nodes_edge_cells[0])
I.append(nodes_edge_cells[1])
I.append(nodes_edge_cells[1])
#
J.append(nodes_edge_cells[0])
J.append(nodes_edge_cells[1])
J.append(nodes_edge_cells[0])
J.append(nodes_edge_cells[1])
# Create suitable data for coo_matrix
I = numpy.concatenate(I).flat
J = numpy.concatenate(J).flat
V = numpy.concatenate(V).flat
matrix = sparse.coo_matrix((V, (I, J)), shape=(n, n))
matrix = matrix.tocsr()
与
您可以创建并查看上述配置文件:
方法tocsr()。因此,我正在寻找加快速度的方法
我已经发现:
- 由于数据的结构,矩阵对角线上的值可以提前求和,即:
V.append(vals[0, 0, 0] + vals[1, 1, 2])
I.append(nodes_edge_cells[0, 0]) # == nodes_edge_cells[1, 2]
J.append(nodes_edge_cells[0, 0]) # == nodes_edge_cells[1, 2]
这使得I
,J
,V
更短,从而加快了tocsr
- 现在,边是“每个单元”。我可以使用
numpy.unique
识别彼此相等的边,有效地节省了I
、J
、V
的一半。然而,我发现这也需要一些时间。(这并不奇怪。)
我的另一个想法是我可以用一个简单的numpy.add.at
替换对角线V
,I
,J
,如果有一个类似csr\u矩阵的数据结构,其中主对角线是分开保存的。我知道这存在于其他一些软件包中,但在scipy中找不到。对吗
也许有一种合理的方法可以直接构建CSR?我会尝试直接创建CSR结构,特别是如果您使用np.unique
,因为这会为您提供排序键,这是完成工作的一半
我假设您正处于这样一个点上,即您使用np.unique
的可选inverse
输出上的I,j
按字典顺序排序并重叠v
求和np.add.at
然后,v
和j
已采用csr格式。剩下要做的就是创建indptr
,您只需通过np.searchsorted(i,np.arange(M+1))
即可获得它,其中M
是列长度。您可以将它们直接传递给sparse.csr\u矩阵
构造函数
好的,让代码说:
import numpy as np
from scipy import sparse
from timeit import timeit
def tocsr(I, J, E, N):
n = len(I)
K = np.empty((n,), dtype=np.int64)
K.view(np.int32).reshape(n, 2).T[...] = J, I
S = np.argsort(K)
KS = K[S]
steps = np.flatnonzero(np.r_[1, np.diff(KS)])
ED = np.add.reduceat(E[S], steps)
JD, ID = KS[steps].view(np.int32).reshape(-1, 2).T
ID = np.searchsorted(ID, np.arange(N+1))
return sparse.csr_matrix((ED, np.array(JD, dtype=int), ID), (N, N))
def viacoo(I, J, E, N):
return sparse.coo_matrix((E, (I, J)), (N, N)).tocsr()
#testing and timing
# correctness
N = 1000
A = np.random.random((N, N)) < 0.001
I, J = np.where(A)
E = np.random.random((2, len(I)))
D = np.zeros((2,) + A.shape)
D[:, I, J] = E
D2 = tocsr(np.r_[I, I], np.r_[J, J], E.ravel(), N).A
print('correct:', np.allclose(D.sum(axis=0), D2))
# speed
N = 100000
K = 10
I, J = np.random.randint(0, N, (2, K*N))
E = np.random.random((2 * len(I),))
I, J, E = np.r_[I, I, J, J], np.r_[J, J, I, I], np.r_[E, E]
print('N:', N, ' -- nnz (with duplicates):', len(E))
print('direct: ', timeit('f(a,b,c,d)', number=10, globals={'f': tocsr, 'a': I, 'b': J, 'c': E, 'd': N}), 'secs for 10 iterations')
print('via coo:', timeit('f(a,b,c,d)', number=10, globals={'f': viacoo, 'a': I, 'b': J, 'c': E, 'd': N}), 'secs for 10 iterations')
加速比:5x因此,最终证明这是COO和CSR的sum_duplicates
(就像@hpaulj怀疑的那样)之间的差异。多亏了这里的每个人(特别是@paul panzer)的努力,我们正在进行tocsr
的巨大加速
SciPy的tocsr
对(I,J)
进行了lexsort
,因此它有助于组织索引,使(I,J)
能够得到相当的排序
对于上述示例中的nx=4
,ny=2
,I
和J
是
[1 6 3 5 2 7 5 5 7 4 5 6 0 2 2 0 1 2 1 6 3 5 2 7 5 5 7 4 5 6 0 2 2 0 1 2 5 5 7 4 5 6 0 2 2 0 1 2 1 6 3 5 2 7 5 5 7 4 5 6 0 2 2 0 1 2 1 6 3 5 2 7]
[1 6 3 5 2 7 5 5 7 4 5 6 0 2 2 0 1 2 5 5 7 4 5 6 0 2 2 0 1 2 1 6 3 5 2 7 1 6 3 5 2 7 5 5 7 4 5 6 0 2 2 0 1 2 5 5 7 4 5 6 0 2 2 0 1 2 1 6 3 5 2 7]
首先对单元格的每一行进行排序,然后按第一列对行进行排序,如
cells = numpy.sort(cells, axis=1)
cells = cells[cells[:, 0].argsort()]
产生
[1 4 2 5 3 6 5 5 5 6 7 7 0 0 1 2 2 2 1 4 2 5 3 6 5 5 5 6 7 7 0 0 1 2 2 2 5 5 5 6 7 7 0 0 1 2 2 2 1 4 2 5 3 6 5 5 5 6 7 7 0 0 1 2 2 2 1 4 2 5 3 6]
[1 4 2 5 3 6 5 5 5 6 7 7 0 0 1 2 2 2 5 5 5 6 7 7 0 0 1 2 2 2 1 4 2 5 3 6 1 4 2 5 3 6 5 5 5 6 7 7 0 0 1 2 2 2 5 5 5 6 7 7 0 0 1 2 2 2 1 4 2 5 3 6]
对于原始帖子中的数字,排序将运行时间从大约40秒缩短到8秒
如果首先对节点进行更适当的编号,也许可以实现更好的排序。我在考虑和。我想看看不同步骤的时间安排。tocsr
使用编译代码。我认为,在手之前对coo
输入进行任何按摩都会花费同样长的时间,如果不是更多的话。你确定tocsr
会花费很长时间吗?我对一个10k×10k的矩阵做了一些非常类似的事情,其中I,J,V的长度已经达到了数百万,而且没花那么长的时间。也许5-10秒。谢谢你的评论。我在原来的帖子中添加了个人资料。很有趣。我以为sum\u duplicates
是编译的csr
构造的一部分,但是您的配置文件显示它是coo.py
中的Python方法。它使用lexsort
、np.nonzero
和reduceat
。因此,做你自己的重复计算是有希望的。如果您的coo
具有规范的格式
,tocsr
应该快得多。csr
有自己的sum\u duplicates
方法。它使用编译的sparse.\u sparsetools.csr\u sum\u duplicates
。我实际上希望避免使用unique
,因为它不是一个O(n)操作(或者我认为是这样)。当然,不管怎样,如果to_csr
做到了这一点,我也可以自己做。所有的提示都很好。我似乎记得,berunique
使用了argsort
,所以它是O(n logn)。另一方面,CSR对索引进行了排序,因此除非您的数据以任何形式进行了预排序(是吗?),或者您有一些图论约束(例如,每个节点的边数固定或有界),否则无法避免O(n log n)。我不认为您的数据是按节点分组的还是类似的?网格是否与外观一样规则?因为可以利用这种结构来实现更快的排序。看来加快排序的关键是以一种最小化lexsort
时间的方式生成元素。如果重复项彼此相邻,add.reduceat
可以快速求和。@NicoSchlömer我已经实现了直接tocsr,看起来比通过coo要快得多。很可能大部分的加速是由于我避免使用lexsort
而使用argsort
。你能查一下你的实际数据吗?我很想知道它在现实世界中的效果有多好。@hpaulj似乎lexsort使用mergesort,而argsort在默认情况下使用quicksort。当我传递kind=mergesort
时,argsort生成与lexsort相同的输出。运行速度慢了两倍多。
cells = numpy.sort(cells, axis=1)
cells = cells[cells[:, 0].argsort()]
[1 4 2 5 3 6 5 5 5 6 7 7 0 0 1 2 2 2 1 4 2 5 3 6 5 5 5 6 7 7 0 0 1 2 2 2 5 5 5 6 7 7 0 0 1 2 2 2 1 4 2 5 3 6 5 5 5 6 7 7 0 0 1 2 2 2 1 4 2 5 3 6]
[1 4 2 5 3 6 5 5 5 6 7 7 0 0 1 2 2 2 5 5 5 6 7 7 0 0 1 2 2 2 1 4 2 5 3 6 1 4 2 5 3 6 5 5 5 6 7 7 0 0 1 2 2 2 5 5 5 6 7 7 0 0 1 2 2 2 1 4 2 5 3 6]