9

我开发了一个科学应用程序(模拟在细胞核中移动的染色体)。染色体被分成小片段,这些片段使用 4x4 旋转矩阵围绕随机轴旋转。

问题是模拟执行了数千亿次旋转,因此浮点舍入误差会累积并呈指数增长,因此随着时间的流逝,片段往往会“飘走”并与染色体的其余部分分离。

我在 C++ 中使用双精度。该软件目前在 CPU 上运行,但将移植到 CUDA,并且模拟最多可以持续 1 个月。

我不知道如何以某种方式重新规范化染色体,因为所有片段都链接在一起(您可以将其视为双向链表),但如果可能的话,我认为这将是最好的主意。

你有什么建议吗 ?我觉得有点失落。

非常感谢你,

H。

编辑:添加了简化的示例代码。您可以假设所有矩阵数学都是经典实现。

// Rotate 1000000 times
for (int i = 0; i < 1000000; ++i)
{
    // Pick a random section start
    int istart = rand() % chromosome->length;

    // Pick the end 20 segments further (cyclic)
    int iend = (istart + 20) % chromosome->length;

    // Build rotation axis
    Vector4 axis = chromosome->segments[istart].position - chromosome->segments[iend].position;
    axis.normalize();

    // Build rotation matrix and translation vector
    Matrix4 rotm(axis, rand() / float(RAND_MAX));
    Vector4 oldpos = chromosome->segments[istart].position;

    // Rotate each segment between istart and iend using rotm
    for (int j = (istart + 1) % chromosome->length; j != iend; ++j, j %= chromosome->length)
    {
        chromosome->segments[j].position -= oldpos;
        chromosome->segments[j].position.transform(rotm);
        chromosome->segments[j].position += oldpos;
    }
}
4

7 回答 7

9

您需要为您的系统找到一些约束,并努力将其保持在一些合理的范围内。我做了一堆分子碰撞模拟,在这些系统中总能量是守恒的,所以每一步我都会仔细检查系统的总能量,如果它变化了某个阈值,那么我知道我的时间步长选择不当(太大或太小)我选择一个新的时间步并重新运行它。这样我就可以实时跟踪系统发生的事情。

对于这个模拟,我不知道你有多少守恒量,但如果你有,你可以试着保持这个不变。请记住,使您的时间步长变小并不总能提高准确性,您需要根据您拥有的精度来优化步长。我已经运行了数周的 CPU 时间的数值模拟,并且守恒量始终在 10^8 的 1 分之内,所以有可能,你只需要玩一些。

此外,正如 Tomalak 所说,也许尝试始终将您的系统引用到开始时间而不是上一步。因此,不要总是移动你的染色体,而是将染色体保持在它们的起始位置,并与它们一起存储一个转换矩阵,让你到达当前位置。当您计算新的旋转时,只需修改变换矩阵。这可能看起来很傻,但有时这很有效,因为错误平均为 0。

例如,假设我有一个位于 (x,y) 的粒子,每一步我计算 (dx, dy) 并移动粒子。逐步的方式会做到这一点

t0 (x0,y0)
t1 (x0,y0) + (dx,dy) -> (x1, y1)
t2 (x1,y1) + (dx,dy) -> (x2, y2)
t3 (x2,y2) + (dx,dy) -> (x3, y3)
t4 (x3,30) + (dx,dy) -> (x4, y4)
...

如果你总是参考 t0,你可以这样做

t0 (x0, y0) (0, 0)
t1 (x0, y0) (0, 0) + (dx, dy) -> (x0, y0) (dx1, dy1)
t2 (x0, y0) (dx1, dy1) + (dx, dy) -> (x0, y0) (dx2, dy2)
t3 (x0, y0) (dx2, dy2) + (dx, dy) -> (x0, y0) (dx3, dy3)

所以在任何时候,tn,为了得到你的真实位置,你必须做 (x0, y0) + (dxn, dyn)

现在对于像我的示例这样的简单翻译,您可能不会赢得太多。但是对于轮换来说,这可以挽救生命。只需保留与每个染色体相关的欧拉角矩阵并更新它而不是染色体的实际位置。至少这样他们就不会飘走。

于 2011-04-11T23:05:55.530 回答
4

编写您的公式,以便 timestep 的数据T不仅仅来自 timestep 中的浮点数据T-1。尽量确保浮点错误的产生仅限于单个时间步长。

如果没有更具体的问题要解决,很难在这里说任何更具体的内容。

于 2011-04-11T22:04:16.577 回答
1

问题描述比较模糊,所以这里有一些比较模糊的建议。

选项1:

找到一些约束条件,例如(1)它们应该始终保持,(2)如果它们失败,但只是,很容易调整系统以便它们这样做,(3)如果它们都保持那么你的模拟是' t 变得非常疯狂,并且 (4) 当系统开始变得疯狂时,约束开始失败,但只是轻微地失败。例如,染色体相邻位之间的距离可能最多为 d,对于某些 d,如果其中一些距离略大于 d,那么您可以(例如)从一端沿着染色体走,固定通过将下一个片段与所有后续片段一起移向其前身,任何太大的距离。或者其他的东西。

然后经常检查约束以确保任何违规行为在被发现时仍然很小;当你发现违规时,解决问题。(您可能应该安排,当您解决问题时,您“不仅仅满足”约束。)

如果一直检查约束很便宜那么您当然可以这样做。(这样做还可以让您更便宜地进行修复,例如,如果这意味着任何违规行为总是很小的。)

选项 2:

找到一种描述系统状态的新方法,使问题不可能出现。例如,也许(我对此表示怀疑)您可以为每对相邻的片段存储一个旋转矩阵,并强制它始终为正交矩阵,然后让片段的位置由这些旋转矩阵隐式确定。

选项 3:

不要将您的约束视为约束,而应提供一些小的“恢复力”,以便当某些事情超出常规时,它往往会被拉回应有的状态。请注意,当没有任何问题时,恢复力为零或至少可以忽略不计,这样它们就不会比原始数字错误更严重地扰乱您的结果。

于 2011-04-11T22:14:18.923 回答
0

我认为这取决于您使用的编译器。

Visual Studio 编译器支持 /fp 开关,它告诉浮点操作的行为

你可以阅读更多关于它的信息。基本上, /fp:strict 是最苛刻的模式

于 2011-04-11T22:06:12.157 回答
0

我想这取决于所需的精度,但您可以使用基于“整数”的浮点数。使用这种方法,您可以使用整数并为小数位数提供自己的偏移量。

例如,如果精度为 4 位小数,您将有

浮点值 -> 整数值 1.0000 -> 10000 1.0001 -> 10001 0.9999 -> 09999

在进行乘法和除法时必须小心,并且在应用精度偏移时要小心。否则,您可以快速得到溢出错误。

1.0001 * 1.0001 变为 10001 * 10001 / 10000

于 2011-04-11T22:09:31.257 回答
0

如果我正确阅读了这段代码,那么任何两个相邻染色体片段之间的距离都不会改变。在这种情况下,在主循环计算每对相邻点之间的距离之前,在主循环之后,如果需要,移动每个点以与前一个点保持适当的距离。

您可能需要在主循环期间多次强制执行此约束,具体取决于具体情况。

于 2011-04-11T23:44:54.120 回答
0

基本上,您需要避免这些(不精确)矩阵运算符的误差累积,并且在大多数应用程序中有两种主要方法可以做到这一点。

  1. 与其将位置写成多次操作的某个初始位置,您可以写出在 N 次操作之后显式地写出操作符是什么。例如,假设您有一个位置 x 并且您正在添加一个值 e(您无法准确表示)。比计算 x += e 好得多;大量时间将是计算 x + EN;其中 EN 是一种更准确的方式来表示 N 次后操作发生的情况。您应该考虑是否有某种方法可以更准确地表示许多旋转的动作。
  2. 稍微人为的是,将您新找到的点从您的旋转中心投射到与预期半径的任何差异。这将保证它不会漂移(但不一定保证旋转角度是准​​确的。)
于 2011-04-12T03:33:46.327 回答