有限元矩阵稀疏结构的计算


13

问题:有哪些方法可以准确有效地计算有限元矩阵的稀疏结构?

信息:我正在使用Poerson压力方程求解器,使用Galerkin方法和二次Lagrange基础(用C编写),并使用PETSc进行稀疏矩阵存储和KSP例程。为了有效地使用PETSc,我需要为全局刚度矩阵预先分配内存。

目前,我正在做一个模拟程序集,以估计每行非零的数量,如下所示(伪代码)

int nnz[global_dim]
for E=1 to NUM_ELTS
  for i=1 to 6
    gi = global index of i 
    if node gi is free
      for j=1 to 6
        gj = global index of j
        if node gj is free 
          nnz[i]++

但是,这会高估nnz,因为某些节点之间的交互可能会在多个元素中发生。

我已经考虑过尝试跟踪发现的i,j交互,但是我不确定如何在不使用大量内存的情况下执行此操作。我还可以遍历节点,并找到以该节点为中心的基本功能的支持,但是随后我必须搜索每个节点的所有元素,这似乎效率很低。

我发现了这个最近的问题,其中包含一些有用的信息,特别是来自Stefano M的,他写了

我的建议是应用一些图论概念,以python或C语言实现它,即将矩阵中的元素视为图的边并计算邻接矩阵的稀疏结构。列表列表或键字典是常见的选择。

我正在寻找有关此的更多详细信息和资源。我承认我不太了解图论,而且我对所有可能有用的CS技巧都不熟悉(我从数学的角度出发)。

谢谢!

Answers:


5

您的想法是跟踪您发现的i,j交互可以起作用,我认为这就是您和Stefano M所指的“ CS技巧”。这相当于以列表格式构造稀疏矩阵。

不知道您有多少CS,因此,如果您已经知道这一点,我深表歉意:在链接列表数据结构中,每个条目都存储一个指向该条目后面和该条目之前的指针。在其中添加和删除条目很便宜,但在其中查找条目并不那么简单-您可能必须仔细检查所有条目。

因此,对于每个节点i,您都存储一个链表。然后,您遍历所有元素;如果您发现两个节点i和j已连接,则可以查看i的链表。如果j还不存在,则将其添加到列表中,同样将i添加到j的列表中。如果按顺序添加它们是最简单的。

填充列表列表之后,您现在知道矩阵每一行中非零条目的数量:这是该节点列表的长度。这些信息正是您在PETSc的矩阵数据结构中预分配稀疏矩阵所需要的。然后,您可以释放列表列表,因为您不再需要它。

但是,此方法假定您所拥有的只是每个元素包含哪些节点的列表。

一些网格生成程序包(例如Triangle)不仅可以输出元素列表及其包含的节点,还可以输出三角测量中每个边的列表。在那种情况下,您不会冒高估非零输入项数量的风险:对于分段线性元素,每个边正好为您提供2个刚度矩阵输入项。您正在使用分段二次方,因此每个边都计入4个条目,但是您知道了。在这种情况下,您可以使用普通数组通过边缘列表一遍找到每行的非零条目数。

使用这种方法,您必须从硬盘读取一个特大文件,如果您的实际计算量不那么大,那实际上比使用元素列表要慢。尽管如此,我认为它更简单。


谢谢。我确实有可用的边缘列表,所以我现在可能会使用您的第二种方法,但是我可能会回去尝试第一种方法,只是用链接列表之类的东西弄脏了我的手(感谢介绍...我我只上过基础CS课,虽然我不擅长编程,但我对数据结构和算法一无所知)
John Edwardson 2013年

乐意效劳!我从中获得了很多CS知识:books.google.com/books?isbn = 0262032937-为了上帝的爱,请阅读有关摊销分析的信息。用C编写自己的链表或二进制搜索树数据结构是值得的。
Daniel Shapero


2

已投票

我个人不知道这样做有什么便宜的方法,所以我只是高估了数字,即对所有行使用合理的大值。

例如,对于由线性8节点十六进制元素组成的结构完美的网格,对角线块和非对角线块中每行的nnz均为dof * 27。对于大多数完全非结构化的自动生成的六边形网格,数量很少超过dof * 54。对于线性Tet,我从来不需要超越dof * 30。对于某些形状/形状比例非常低的网格物体,可能必须使用稍大的值。

代价是本地(等级)内存消耗介于2x-5x之间,因此您可能需要在群集上使用比平时更多的计算节点。

顺便说一句,我确实尝试过使用可搜索列表,但是确定稀疏结构所花费的时间比组装/解决所花费的时间更多。但是我的实现非常简单,并且没有使用边缘信息。

另一个选择是使用示例中所示的DMMeshCreateExodus之类的例程。


0

您正在寻找所有唯一的(gi,gj)连接,这建议将它们全部放入一个(非重复的)关联容器中,然后计算其基数-在C ++中,这将是一个std :: set <std :: pair <int,int>>。在伪代码中,将“ nnz [i] ++”替换为“ s.insert [pair(gi,gj)]”,然后非零的最终数量为s.size()。它应在O(n-log-n)时间中运行,其中n是非零数。

由于您可能已经知道了可能的gi的范围,因此可以通过gi索引“展开”表以提高性能。这将用std :: vector <std :: set <int>>替换您的集合。您用“ v [gi] .insert(gj)”填充,然后非零的总数来自所有gi的v [gi] .size()的总和。这应该以O(n-log-k)的时间运行,其中k是每个元素的未知数(对于您而言,六个-基本上是大多数pde代码的常数,除非您谈论的是hp方法)。

(注意-希望这是对所选答案的评论,但时间太长-抱歉!)


0

ET×

EijT={1if dof jelement i0elsewhere
A=EETETETE
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.