我在3D空间中有一个连续的粒子阵列用于流体模拟,我需要对它进行大量的邻居搜索。我发现将搜索空间划分为立方体单元格并按照它们所处的单元格就地对粒子进行排序对我的问题很有效。对于任何给定的单元格,它的粒子处于连续的跨度中,因此如果您知道开始和结束索引,则可以轻松地迭代它们。例如,单元格N可能占用用于存储粒子的数组的[N_begin,N_end]。
然而,无论你如何划分空间,粒子可能不仅在其自己的细胞中而且在每个相邻细胞中都具有邻居(想象一个几乎触及细胞之间边界的粒子以理解为什么)。因此,邻居搜索需要返回同一单元格中的所有粒子以及3D空间中的所有粒子,当不在模拟空间的边缘时,总共最多27个单元格。没有将单元排序到阵列中(其本质上是1D)可以使所有27个跨距对于任何请求的单元是相邻的。为了解决这个问题,我会跟踪每个单元格在粒子数组中的开始和结束位置,并且我有一个函数可以确定哪些单元格容纳了潜在的邻居。要表示多个指数范围,它必须返回最多27对指数,表示这些范围的begin
和end
。
std::vector<std::pair<int, int>> get_neighbor_indices(const Vec3f &position);
索引在某些时候实际上是必需的,因此它比一对迭代器或其他一些抽象更好地工作。问题是它迫使我使用与实现相关的循环结构。我希望像下面这样的东西,使用一些伪代码和遗漏来简化。
for(int i = 0; i < num_of_particles; ++i) {
auto neighbor_indices = get_neighbor_indices(particle[i].position);
for (int j : neighbor_indices) {
// do stuff with particle[i] and particle[j]
}
}
只有当neighbor_indices
是所有索引的完整列表时,这才有效,但这是大量易于计算的索引,因此会浪费大量内存。因此,我可以在不影响代码性能的情况下获得最佳效果。
for(int i = 0; i < num_of_particles; ++i) {
auto neighbor_indices = get_neighbor_indices(particle[i].position);
for (const auto& indices_pair : neighbor_indices) {
for (int j = indices_pair.first; j < indices_pair.second; ++j) {
// do stuff with particle[i] and particle[j]
}
}
}
失去通用性是我项目的一个挫折,因为我必须经常测试和测量性能,并在遇到性能问题时进行调整。特定于实现的代码会显着减慢此过程。
我正在寻找类似迭代器的东西,除了它将返回索引而不是引用。它允许我按如下方式使用它。
for(int i = 0; i < num_of_particles; ++i) {
auto neighbor_indices = get_neighbor_indices(particle[i].position);
for (int j : neighbor_indices) {
// do stuff with particle[i] and particle[j]
}
}
这种迭代器方法的唯一问题是增加它是很麻烦的。我必须手动跟踪我所处的范围,并不断检查我何时结束切换到下一个。只需要摆脱那条打破迭代循环通用性的那一行就有很多代码。所以我正在寻找一种更简洁的方法来实现“迭代器”,或者只是一种更好的方法来迭代多个范围,就好像它们是一样。
请记住,这是一个瓶颈计算循环,因此抽象必须为零或可忽略不计的成本。