在时间积分器中准确计算当前时间

计算科学 浮点
2021-12-24 18:35:00

非线性守恒定律系统时间积分的Runge--Kutta方法

ut+f(u)x=0.

由于系统是非线性的,我们必须dt在每个时间步上重新计算时间步。然后我们必须使用如下代码找到新的当前时间:

cur_time = cur_time + dt

但是,如果我们要积分到非常大的时间,则cur_time时间步长dt会很大,因此浮点运算导致的误差会显着累积。

有没有办法避免这种情况?如果时间步长是恒定的,我可以处理它,但非恒定时间步长怎么办?

更新 2016-04-17。我添加了一个示例来证明浮点运算即使对于少量的 step 和 small ratio 也会引入显着的错误cur_time/dt为简单起见,我dt在这里使用恒定时间步长,但是,非恒定时间步长的问题应该是类似的。代码采用 Python 编程语言:

dt = 0.2
n = 100
cur_time = [0]
for i in range(n):
    cur_time.append(cur_time[-1] + dt)
cur_time[-5:]  # Shows last five time points.

这导致

19.199999999999964,
19.399999999999963,
19.599999999999962,
19.79999999999996,
19.99999999999996

因此,即使积分时间和时间步数很少,时间点也会因浮点运算导致的误差累积而轻微损坏。

3个回答

这不应该发生。如果dt次,则会出现浮点错误但这意味着您已经完成了大约时间步,我认为您不太可能这样做,因为这将花费非常长的时间。1013cur_time1013

在实践中,您执行如此多的时间步以使舍入是错误的可能性非常小。

您更新的问题意味着您需要非常精确地知道当前时间 - 精确到数量级的差异是不可接受的。如果您需要以比几倍双精度舍入误差更好的精度跟踪某些东西,唯一的选择是使用双精度数字以外的东西。您可以使用更高精度的浮点数(请参阅mpmath)或有理算术(请参阅sympy)。 1014

(如果可能,您可能希望在其他地方使用它时将转换为双精度浮点数,以避免减慢整个代码的速度。但是当您的时间变量达到时,您的解决方案也可能被视为的解决方案,其中无论如何都与双精度舍入有关)。ΔtTT^=T+ϵϵ

也不要被计算机显示的精度误导,并记住错误不会以相同的方式叠加,具体取决于您对相同值的计算。

考虑以下:

import numpy as np # Personnal comfort, not relevant here.
dt = 0.2
n  = 100
add_time, mul_time = np.zeros(n), np.zeros(n)
for i in range(n):
    if i == 0:
        add_time[i], mul_time[i] = 0.2
    else:
        add_time[i] = add_time[i-1] + dt
        mul_time[i] = (i-1)*dt
add_time[-5:], mul_time[-5]

这导致以下情况:

(array([ 19.2,  19.4,  19.6,  19.8,  20. ]), array([ 19.2,  19.4,  19.6,  19.8,  20. ]))

但出于演示目的,我打印了以下内容:

print "add_time: {0:18.18f}\n {1:18.18f}\n {2:18.18f}\n {3:18.18f}\n {4:18.18f}\n mul_time: {5:18.18f}\n {6:18.18f}\n {7:18.18f}\n {8:18.18f}\n {9:18.18f}\n".format(add_time[-5], add_time[-4], add_time[-3], add_time[-2], add_time[-1], mul_time[-5], mul_time[-4], mul_time[-3], mul_time[-2], mul_time[-1])
add_time: 19.199999999999963762
19.399999999999963052
19.599999999999962341
19.799999999999961631
19.999999999999960920
mul_time: 19.200000000000002842
19.400000000000002132
19.600000000000001421
19.800000000000000711
20.000000000000000000

我最初的猜测也是这很好用,因为 dt 设置为 0.2 的值,但由于浮点精度,你总是会出现舍入错误。但是,如果您可以通过最少的操作提前存储您的值,就像这个简短示例中的时间步长一样,请执行此操作。操作次数越少,误差越小。

编辑:从打印的数组中减去预期值时,可以注意以下简单精度:

add_time: -0.000000000000035527
0.000000000000035527
0.000000000000039080
0.000000000000039080
0.000000000000039080
mul_time: 0.000000000000003553
0.000000000000003553
0.000000000000000000
0.000000000000000000
0.0000000000000000000000000

我们处于精确设置中,其中的错误甚至与打印无关。因此,我实际上建议在每次更改时间步时设置一个标志,并使用以下内容计算新的时间步,例如:

tn=to+idtn
在哪里to是时间步长改变的时间,dtn新的时间步长和i使用这个新值的迭代次数。