小能豆

高效构建 FEM/FVM 矩阵

py

这是 FEM/FVM 方程系统的典型用例,因此可能具有更广泛的意义。从三角网格

RS6MJ.png

我想创建一个scipy.sparse.csr_matrix。矩阵的行/列表示网格节点的值。矩阵在主对角线上以及两个节点通过边连接的地方都有条目。

这是一个 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()

python -m cProfile -o profile.prof main.py
snakeviz profile.prof

可以创建并查看上述内容的个人资料:

TmNtl.png

该方法tocsr()在这里占用了大部分的运行时间,但在构建更复杂时也是如此alpha。因此,我正在寻找加快速度的方法。

我已经发现了:

  • 由于数据的结构,矩阵对角线上的值可以预先加起来,即

py 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]

这使得IJV短,从而加快tocsr

  • 目前,边缘是“每个单元”的。我可以使用 识别彼此相等的边缘,从而有效地节省了、、numpy.unique的大约一半。但是,我发现这也需要一些时间。(这并不奇怪。)I``J``V

我还有一个想法,如果存在一个类似 的数据结构,其中主对角线被单独保存,那么我可以用一个简单的 来替换对角线,。V我知道这在其他一些软件包中存在,但在 scipy 中找不到它。对吗?I``J``numpy.add.at``csr_matrix

也许有一种合理的方法来直接构建 CSR?


阅读 22

收藏
2024-11-18

共1个答案

小能豆

因此,最终这被证明是 COO 和 CSR 之间的区别sum_duplicates(正如 @hpaulj 所怀疑的那样)。感谢所有参与人员(尤其是 @paul-panzer)的努力,PR正在进行中,以tocsr大大加快速度。

SciPytocsr确实做到了lexsort(I, J)因此它有助于以某种方式组织索引,从而(I, J)使结果公平排序。

对于nx=4ny=2在上面的例子中,IJ

[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,然后按第一列对行进行排序,例如

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 秒。

如果一开始就对节点进行更适当的编号,也许可以实现更好的排序。

2024-11-18