仓库源文站点原文

前些日子有些关于MD模拟可重复性的讨论, 这里随记一下我的观点想法以及看到的资料, 供参考.

无论讨论什么问题, 首先要做的就是给出问题的定义和范围, 界定一些概念的含义, 达到共识. 如果定义范围不明确, 苹果对桔子, 没有什么讨论的必要. 有些问题之所以无法深入探讨, 其原因就在于无法明确定义, 各以自己的定义为是, 鸡同鸭讲, 自然谁也说服不了谁, 最后只能不了了之. 这种情况在社会科学中尤甚, 自然科学中情况好些, 数学中则做得最好, 所以各种学科都要向数学学习.

哲学和自然科学中一直以来就有决定论(或称确定论)的观点. 广为人知的是拉普拉斯妖的说法. 这种基于经典力学的决定论, 后来受到了很大的质疑, 其中有些来自混沌理论, 有些源自量子力学, 有些则与热力学和统计力学有关.

对于混沌系统, 需要明确的是, 根据定义, 系统是确定性的, 但由于初值敏感性, 所以并不能在现实中表现出确定性; 对于量子力学, 一般认为其本质上是非确定性的, 因为只存在概率描述, 但同时也存在确定性说法, 相关翻译中测不准原理和不确定原理两种说法, 就是这两种观点的反映. 从微观角度而言, 世界本质上是确定性的, 只是表现出随机性, 抑或相反, 这是一个很深刻的问题. 再深入下去就进入了哲学的范畴, 不是科学所能解决的了, 暂且只能存而不论.

让我们不要讨论那么宽泛的问题, 关注具体而微的问题, 也就是计算机程序的确定性问题. 在这里我们暂且假定确定性包含了可重复性: 一份程序具有确定性, 那么在初始条件相同的情况下, 运行任意多次总会给出相同的运行结果, 从而具有可重复性. 反之, 则未必成立. 数学上可说确定性为可重复性的充分条件.

从计算机理论科学的角度而言, 在图灵机上运行的计算机程序和数学理论一样, 毫无疑问地具有确定性(只考虑实际的算法). 但我们毕竟没有生活在理论的完美世界(类似哲学上柏拉图的理念世界)中, 而是生活在现实世界中(虽然我们无法确定现实世界是否只是完美世界的模仿). 理论中作为计算机模型的图灵机具有无限精度, 无限存储, 完全不受外界影响, 是个孤立体系, 但现实中实际运行的计算机只是对图灵机的一种近似, 既没有无限精度, 也没有无限存储, 还要与外界交互. 从这一点上来看, 如果承认现实世界本质上具有非确定性, 那么现实世界中的一切都具有本质上的非确定性, 计算机也不例外, 它的确定性只是表象, 我们无法证明它所具有的确定性在任何时候都成立. 计算机在多年来的发展中一直向着更高的确定性逼近, 但从理论上而言, 只要处于现实中, 永远也不可能达到完美的确定性.

可以设想, 如果我们需要现实的计算机运行具有确定性, 那基本不可能. 在程序运行结束后, 如果查看内存中的数据, 它们是不是确定性的? 这很难, 因为在程序执行过程中, CPU等硬件会有很多操作, 每次操作具体如何实现, 涉及哪些部分都不是我们完全能控制的. 那好, 我们不使用高级语言, 而是使用汇编语言直接操控内存和寄存器等, 这下结果应该具有确定性了吧. 很遗憾, 只要涉及与现实交互, 总有你无法控制的地方. 你能控制对内存, 寄存器的操作, 可你能控制电压电流么? 电压电流不同, 对结果的影响如何? 你能控制电压电流, 那你能控制电子么? 电子在芯片线路中的运动, 是经典的还是量子的, 会随着环境温度的不同而不同么? 即便你能让计算机运行在绝对零度, 那你能控制太阳么? 太阳黑子爆发也会对电子运行产生影响. 你能控制太阳系, 那你能控制宇宙射线么? 高能宇宙射线中的粒子可能会导致内存中的bit位发生翻转. 如果这样细究下去, 最终你需要控制整个宇宙才能保证计算机运行的确定性, 而你所用的计算机也就一步一步趋近于作为理论模型的图灵机. 所以我们只能说, 完全的确定性只是理论上的, 现实中并不存在. 即便你一生写过的程序都具有确定性, 那相对于无限的时空, 其概率也只是零, 不能用以证明确定性的问题.

这样看来, 我们需要降低标准, 要求程序的运行结果具有确定性, 其输出文件具有二进制的可重复性, 虽然将每次运行的结果输出到文件, 会涉及计算机的基本输入输出系统, 过程中也存在很多不可控的因素, 我们暂且忽略它们.

那么, 让我们将目光放得更低些, 不讨论具有哲学意味的确定性问题, 而是考虑更具体现实的问题: 实际运行的计算机程序具有一定的确定性, 其结果也具有一定的可重复性, 这些可重复性受哪些可控因素的影响, 如何尽可能地使运行结果具有可重复性? 这是一个实际的问题, 也是在计算机发展初期颇受关注的一个问题, 有很多相应的讨论. 毕竟, 要想使用计算机处理实际问题, 那么首先必须保证它给出的结果具有尽可能高的可重复性, 即便所给结果是错的, 那也要每次都错得完全一样才好, 否则, 你该相信哪次的结果呢? 这就像有一个处于封闭屋子中的人, 你给他一块表, 他知道是什么时间, 如果你再给他一块走时不同的表, 他反而不知道到底是什么时间了.

对现实的计算机而言, 由于精度和存储有限, 在实数计算中采用的表示模型不同, 得到的结果也会不同. 现在大部分实数计算都采用浮点数IEEE 754标准, 这个标准的一个重要方面就是要保证计算结果的可重复性, 尽量减少截断误差和舍入误差的影响.

由于浮点数的表示精度有限, 导致的一个问题就是加法不具有可交换性, 在计算机中a+b=b+a并不总是成立的. 因而, 在进行累加时, 如果无法确切地规定累加顺序, 那很可能会得到不同的结果. 编译程序时, 编译器一般会对各种操作进行优化和向量化, 这常会改变累加顺序, 从而导致结果不同. 如果使用了并行, 就更难保证累加顺序了. 在GPU上, 核心数非常多, 且可以同时运行, 很难保证累加顺序, 因此这个问题更严重.

浮点数精度有限导致的另一个问题是舍入误差, 这个问题现在影响不大了, 但也很难说完全解决了.

如果想更多地了解浮点数计算以及可重复性方面的问题, 可以看看几篇资料:

关于程序的可重复性, 游戏领域的相关讨论更多些, 因为具有现实意义. 大型的在线游戏会有很多用户在玩, 每个用户都可能会随时退出, 过段时间再重新上线. 要记录每个用户退出时的精确状态是很困难的, 会涉及大量的参数. 那么当用户退出时, 能不能只记录几个重要的初始参数, 等他重新登录时, 先虚拟运行一遍, 得到他退出时的状态以便继续呢? 这就要求精确地还原状态, 否则可能会导致完全不同的结果. 举个极端的例子, 射出来一颗子弹, 在未击中敌人之前, 敌人下线了, 你认为击中了, 对方认为没击中, 这两种不同的结果可能会导致十分不同的后果, 差之毫厘, 谬以千里, 就像西方那首歌谣中说的:

少了一枚铁钉, 掉了一只马掌; 掉了一只马掌, 瘸了一匹战马; 瘸了一匹战马, 败了一次战役; 败了一次战役, 丢了一个国家.

目前看来, 这种课重复性还是可以做到的, 虽然代价不小. 具体可以参考一下程序员的经验.

如果你同意上面的一些说法, 那就容易理解为什么MD程序无法保证可重复性了. 毕竟, 比起计算速度, 可重复性在MD模拟方面并没有那么重要.

那么, 不可重复就一定是错的么? 倒也不能这么说, 因为可重复性并不是可靠性的唯一保证. 毕竟, 实际的实验结果也无法做到每次都得到完全相同的数据. 与其相比, 计算机程序的可重复性还要高不少. 我们需要关注的是结果的不确定性是不是可控的, 能否满足我们的要求. 但实际上, 对于处于混沌的系统而言, 这也是无法做到的, 为此, 我们只能希望自己的体系没有处于混沌区域.

关于MD可重复性的另外一个观点是影子理论, 我没有去查证它的具体来源和说法了, 大致而言, 有点像是柏拉图的理念世界和洞穴假象. 我们认为体系实际存在真实的演化轨迹, 具有确定性, 但这条轨迹处于理念世界(相空间)中,无法被我们直接感知, 我们每次的模拟结果只是它在现实世界的投影, 我们只能根据这些投影推断理念轨迹的性质. 我们得到的每条轨迹可能无法确定是否真实, 但我们仍然认为它模仿了真实轨迹(假定存在真实轨迹的话), 是真实轨迹的影子, 我们无法区分真实和影子, 但认为它们是存在对应关系的. 实际上, 我倒认为这是科学, 唯物主义必须承认的前提, 像是康德所谓的物自体, 存在是第一位的, 如果连其存在都未必承认, 像佛家那样, 那就没有必要继续探究下去了. 到这里已经触碰到了科学的边界, 再继续下去就是哲学, 神学的领域里, 打住吧.

上面是随便想想, 随口说的, 没什么条理, 姑妄言之, 姑妄听之. 下面我们来具体地做些实际测试.