[英]Solving floating-point rounding issues C++
我開發了一種科學應用(模擬在細胞核中移動的染色體)。 將染色體分成小片段,使用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;
}
}
您需要為系統找到一些約束,並努力將其保持在一定的合理范圍內。 我做了一堆分子碰撞模擬,在那些系統中總能量是守恆的,所以每一步我都要仔細檢查系統的總能量,如果它變化了一些閾值,那么我就知道我的時間步長選擇不當(太大或太小)我選擇一個新的時間步驟並重新運行它。 這樣我就可以實時跟蹤系統發生的情況。
對於這個模擬,我不知道你有多少守恆量,但如果你有一個,你可以嘗試保持這個不變。 請記住,縮短時間步長並不總能提高精度,您需要使用精度來優化步長。 我已經進行了幾周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)
現在像我的例子一樣簡單的翻譯,你可能不會贏得很多。 但對於輪換,這可以節省生命。 只需保留一個與每個染色體相關的歐拉角矩陣,然后更新它而不是染色體的實際位置。 至少這樣他們就不會飄走了。
編寫公式,以便時間步長T
的數據不僅僅來自時間步長T-1
的浮點數據。 盡量確保將浮點錯誤的產生限制在單個時間步長。
沒有更具體的問題需要解決,這里很難說更具體的內容。
問題描述相當模糊,所以這里有一些相當含糊的建議。
選項1:
找到一些約束條件,以便(1)它們應該始終保持,(2)如果它們失敗,但僅僅是,它很容易調整系統以便它們執行,(3)如果它們全部保持,則模擬不是'變得非常瘋狂,(4)當系統開始變得瘋狂時,約束開始失敗但只是輕微。 例如,對於某些d,染色體的相鄰位之間的距離可能最多為d,如果一些距離略大於d,那么您可以(例如)從一端沿着染色體走,修復通過將下一個片段與其前任以及所有后繼片段一起移動而使任何距離過大。 或者其他的東西。
然后經常檢查約束,以確保捕獲時任何違規仍然很小; 當你發現違規行為時,請解決問題。 (你可能應該安排,當你解決問題時,你“不僅僅滿足”約束。)
如果它的價格便宜,檢查約束所有的時間 ,那么你當然可以這樣做。 (這樣做也可能使您能夠更便宜地進行修復,例如,如果這意味着任何違規行為總是很小。)
選項2:
找到一種描述系統狀態的新方法,使問題不可能出現。 例如,也許(我懷疑這一點)你可以為每個相鄰的片段對存儲一個旋轉矩陣,並強制它總是一個正交矩陣,然后讓片段的位置由這些旋轉矩陣隱式確定。
選項3:
而不是將約束視為約束,而是提供一些小的“恢復力”,這樣當某些事情變得不合時,它往往會被拉回原來的狀態。 請注意,當沒有任何問題時,恢復力為零或至少可以忽略不計,這樣它們就不會比原始數值誤差更嚴重地擾亂您的結果。
我想這取決於所需的精度,但您可以使用基於“整數”的浮點數。 使用此方法,您使用整數並為小數位數提供自己的偏移量。
例如,精度為4小數點,你就可以得到
浮點值 - > int值1.0000 - > 10000 1.0001 - > 10001 0.9999 - > 09999
當你進行乘法和除法時必須小心,並且在應用精確偏移時要小心。 另外,你可以很快得到溢出錯誤。
1.0001 * 1.0001變為10001 * 10001/10000
如果我正確讀取了這段代碼,任何兩個相鄰染色體片段之間的距離都不應該改變。 在這種情況下,在主循環之前計算每對相鄰點之間的距離,並且在主循環之后,如果需要,移動每個點以與前一點具有適當的距離。
您可能需要在主循環期間多次強制執行此約束,具體取決於具體情況。
基本上,您需要避免從這些(不精確的)矩陣運算符中累積誤差,並且在大多數應用程序中有兩種主要方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.