通过 Stackoverflow 上列出的一些技巧以及 Jakob Stark 在他的回答中描述的转换,可以将等式重写为不再遭受灾难性取消的形式。
原来的问题是求一维解,有一个简单的解,但在实践中,我们需要 3D 的公式,然后解就更复杂了。见this notebook for a full derivation。
Python中3D数值稳定计算的示例实现:
import numpy as np
# numerically stable implementation
@np.vectorize
def msq2(px1, py1, pz1, px2, py2, pz2, m1, m2):
p1_sq = px1 ** 2 + py1 ** 2 + pz1 ** 2
p2_sq = px2 ** 2 + py2 ** 2 + pz2 ** 2
m1_sq = m1 ** 2
m2_sq = m2 ** 2
x1 = m1_sq / p1_sq
x2 = m2_sq / p2_sq
x = x1 + x2 + x1 * x2
a = angle(px1, py1, pz1, px2, py2, pz2)
cos_a = np.cos(a)
if cos_a >= 0:
y1 = (x + np.sin(a) ** 2) / (np.sqrt(x + 1) + cos_a)
else:
y1 = -cos_a + np.sqrt(x + 1)
y2 = 2 * np.sqrt(p1_sq * p2_sq)
return m1_sq + m2_sq + y1 * y2
# numerically stable calculation of angle
def angle(x1, y1, z1, x2, y2, z2):
# cross product
cx = y1 * z2 - y2 * z1
cy = x1 * z2 - x2 * z1
cz = x1 * y2 - x2 * y1
# norm of cross product
c = np.sqrt(cx * cx + cy * cy + cz * cz)
# dot product
d = x1 * x2 + y1 * y2 + z1 * z2
return np.arctan2(c, d)
数值稳定的实现永远不会产生负面结果,这是幼稚实现的常见问题,即使是双精度也是如此。
让我们将数值稳定函数与一个简单的实现进行比较。
# naive implementation
def msq1(px1, py1, pz1, px2, py2, pz2, m1, m2):
p1_sq = px1 ** 2 + py1 ** 2 + pz1 ** 2
p2_sq = px2 ** 2 + py2 ** 2 + pz2 ** 2
m1_sq = m1 ** 2
m2_sq = m2 ** 2
# energies of particles 1 and 2
e1 = np.sqrt(p1_sq + m1_sq)
e2 = np.sqrt(p2_sq + m2_sq)
# dangerous cancelation in third term
return m1_sq + m2_sq + 2 * (e1 * e2 - (px1 * px2 + py1 * py2 + pz1 * pz2))
对于下图,动量 p1 和 p2 是从 1 到 1e5 随机选取的,m1 和 m2 的值是从 1e-5 到 1e5 随机选取的。所有实现都以单精度获取输入值。两种情况下的引用都是使用 mpmath 使用带有 100 位小数的朴素公式计算得出的。
简单的实现会丢失某些输入的所有准确性,而数值稳定的实现不会。