按整数ID引用图形节点

时间:2017-09-14 14:19:11

标签: graph chapel

作为一个学习项目,我正在努力用Chapel实现替换perl中有点慢的程序。我已经算法了,但我正在努力寻找在Chapel中引用数据的最佳方法。我可以直接翻译,但似乎我错过了一个更好的方法。

现有计划的详情:

  • 我有一个大约32000个节点和~2.1M边缘的图形。国家被保存在 数据文件,但它作为守护程序运行,将数据保存在内存中。
  • 每个节点都有一个数字ID(由另一个系统分配)并且有各种各样的 由字符串,整数和布尔值定义的其他属性。
  • 边缘是方向性的,有两个布尔值 归功于他们。
  • 我有一个与此守护进程交互的外部系统,我无法更改。它发出请求,例如“使用这些属性添加节点(int)”,“从节点(int)到节点(int)找到最短路径”,或“从节点(int)添加边到节点(int, int,int)“

在Perl中,程序使用具有常用整数ID的散列作为节点和边缘属性。我当然可以在Chapel中使用关联数组来复制它。

有没有更好的方法将这些捆绑在一起?我一直试图围绕着定义每个项目的不透明节点和边缘的方法,但是如何使用整数ID以简单的方式引用它们一直在努力。

如果某人能够提供理想的方式来完成以下任务,那么它将为我提供所需的推动力。

  • 创建两个节点,其中xx属性由整数ID标识。
  • 使用xx attribues
  • 在两者之间创建边缘
  • 回应请求“显示node(int)的xx属性”

干杯,谢谢。

1 个答案:

答案 0 :(得分:4)

正如您所料,在Chapel中有很多方法可以解决这个问题,但我认为考虑到您的历史方法和外部系统的界面,关联域和数组绝对是一种合适的方式。具体来说,鉴于您希望通过整数ID引用节点,因此关联域/数组是一种自然匹配。

对于Chapel新手:关联域本质上是任意值的集合,在这种情况下就像整数节点ID的集合。关联数组是从关联域的索引到给定类型的元素(变量)的映射。本质上,域表示键,数组表示键值存储或哈希表中的值。

为了表示节点和边缘本身,我将采用Chapel记录的方法。这是我对节点的记录:

record node {
  var id: int;

  var str: string,
      i: int,
      flag: bool;

  var edges: [1..0] edge;
}

如您所见,它将id存储为各种类型的整数,任意属性字段(字符串str,整数i和布尔{{1}你可以为你的程序提供更好的名字),以及我将在一秒钟内返回的一系列边缘。请注意,每个节点可能存在也可能不需要存储其ID ...也许在您拥有节点的任何环境中,您已经知道其ID,在这种情况下存储它可能是多余的。在这里我存储它只是为了表明你可以,而不是因为你必须。

回到边缘:在你的问题中,它听起来好像边缘可能有自己的整数ID并存储在与节点相同的池中,但在这里我采取了不同的方法:根据我的经验,给定一个节点,我通常希望从它的边缘引出一组边,所以我让每个节点存储一个它的传出边的数组。在这里,我使用了一个最初为空的密集一维边缘阵列(flag是Chapel中的一个空范围,因为1..0)。如果要为每个边提供唯一ID,也可以使用关联的边数组。或者您可以完全删除节点数据结构中的边缘并将它们全局存储。如果您不喜欢不同的方法,请随时提出后续问题。

这是我代表优势的记录:

1 > 0

前两个字段(record edge { var from, to: int, flag1, flag2: bool; } from)表示边连接的节点。与上面的节点ID一样,to字段可能是冗余/不必要的,但为了完整性,我在此处将其包括在内。两个标志字段用于表示您与边缘关联的数据属性。

接下来,我将创建关联域和数组来表示节点ID和节点本身的集合:

from

这里,var NodeIDs: domain(int), Nodes: [NodeIDs] node; 是表示节点的整数ID的关联域(集合)。 NodeIDs是一个关联数组,它将这些整数映射到类型Nodes的值(我们在上面定义的记录)。

现在,转向您的三个操作:

  

创建两个节点,其中xx属性由整数ID标识。

以下声明使用Chapel为不能定义自己的记录的默认记录构造函数/初始化程序创建一个名为node的节点变量,其中包含一些任意属性:

n1

然后我可以将它插入节点数组中,如下所示:

var n1 = new node(id=1, "node 1", 42, flag=true);

此分配有效地将Nodes[n1.id] = n1; 添加到n1.id域,并将NodeIDs复制到n1中的相应数组元素中。这是一个创建第二个匿名节点并将其添加到集合的赋值:

Nodes

请注意,在上面的代码中,我假设您要明确选择每个节点的ID(例如,您的数据文件可能是否建立了节点ID?)。另一种方法(这里没有显示)可能是自动确定它们,因为节点是使用全局计数器创建的(如果您并行创建它们,可能是原子计数器)。

填充完我们的节点之后,我们可以串行或并行地迭代它们(这里我并行执行;将Nodes[2] = new node(id=2, "node 2", i=133); 替换为forall会使它们串行):

for

这些循环打印ID和节点的顺序是任意的,原因有两个:(1)它们是并行循环; (2)关联域和数组以任意顺序存储它们的元素。

  

使用xx attribues

在两者之间创建边缘

由于我将边缘与节点相关联,因此我采用了在writeln("Printing all node IDs (in an arbitrary order):"); forall nid in NodeIDs do writeln("I have a node with ID ", nid); writeln("Printing all nodes (in an arbitrary order):"); forall n in Nodes do writeln(n); 类型上创建方法的方法,该方法将为其添加边缘:

node

此过程获取目标节点ID,并将属性作为其参数,使用该信息创建边缘(并将原始节点的ID作为proc node.addEdge(to: int, flag1: bool, flag2: bool) { edges.push_back(new edge(id, to, flag1, flag2)); } 字段提供),并使用{{ 1}}矩形数组上的方法将其添加到边列表中。

然后我调用这个例程三次为节点2创建一些边(包括冗余和自边缘,因为到目前为止我只有两个节点):

from

此时,我可以循环遍历给定节点的所有边缘,如下所示:

push_back()

这里,任意打印顺序仅仅是由于使用了并行循环。如果我使用了序列Nodes[2].addEdge(n1.id, true, false); Nodes[2].addEdge(n1.id, false, true); Nodes[2].addEdge(2, false, false); 循环,我会按照添加顺序遍历边缘,因为使用了一维数组来表示它们。

  

回应请求"显示node(int)"

的xx属性

你现在可能已经得到了这个,但我可以通过索引到writeln("Printing all edges for node 2: (in an arbitrary order):"); forall e in Nodes[2].edges do writeln(e); 数组来获得节点的任意属性。例如,表达式:

for

会给我节点2的字符串属性。这里是我编写的一个小帮助程序来获取(并打印)一些不同的属性):

Nodes

以下是对此的一些要求:

...Nodes[2].str...
  

我正在努力用Chapel实现替换perl中有点慢的程序

鉴于速度是查看Chapel的原因之一,一旦程序正确,请使用proc showAttributes(id: int) { if (!NodeIDs.member(id)) { writeln("No such node ID: ", id); return; } writeln("Printing the complete attributes for node ", id); writeln(Nodes[id]); writeln("Printing its string field only:"); writeln(Nodes[id].str); } 标志重新编译它以使其快速运行。