寫不出 Qhull

舊文。大概寫於 2017 年 9 月。

quick hull 算法很簡單,但是要實現這個算法,而且要保證程序堪作大用,那就很難了。

要將一個凸包外部的點 x 合併到凸包上,首先需要在凸包上搜索 x 的可見域。我們身爲萬物之靈的人類,雖然深懂得眼見爲實的道理,但也常常被 P 過的照片而欺騙。身爲一個 n 維點的 x 也不能倖免,它也無法分辨屬於可見域的那些凸包面片哪一個能真正爲它所見。

將一張紙呈水平狀置於眼前,讓自己只能看到它的邊。此時,看到的是一張紙嗎?理論上,我們也無法確定。不過幸好我們的瞳孔直徑大於紙的厚度,以致於總是能夠隱約看到這張紙的兩面,但即便如此,與我所見的那條邊相對的邊卻不能爲我所見。這種情況下,就不能宣稱我看到的是一張紙,只能宣稱我看到了一張紙的某條邊。要讓點 x 也像我這樣去看一張紙,那麼點 x 需要膨脹爲一個以它爲中心的小球,凸包上的面片需要有一個厚度,並且小球的直徑應當大於面片的厚度。當面片貫穿小球時,點 x 便可以宣稱它看到了一張有厚度的面片的部分邊,並且還有一部分不能爲它所見。這種情況下,點 x 稱自己與該面片共面,並將其歸於不可見域。

看起來,問題也很好解決。然而,點 x 並不知道自己應當膨脹多大。若膨脹得過大,會導致它經常看不到凸包上的面片而誤以爲可見域不存在。若膨脹得過小,又會導致不該看到的又被它看到了,而它又弄不明白那些不過是海市蜃樓。

問題始終都是問題。一個問題解決了,只不過是從原來的問題裏擠壓出來新的問題。這可能是這個世界固有的一些「Bug」決定的。

quick hull 算法的作者爲該算法提供了一個很穩健的實現,即 Qhull 包。他的論文講這個算法只用了不到 2 頁紙,但是 Qhull 的 C 代碼卻有 17000 餘行。人類語言的表現力秒殺程序語言。

Qhull 用了兩種啓發式方法儘量提高點 x 的可見域的合理性,一種是將近似共面的面片合併起來,另一種是輕微搖晃 x 的位置。第一種方法會導致凸包面片不夠單純,譬如三維凸包不能完全由三角形構成,而且也不能完全解決所有的面片維度退化問題。第二種方法會讓點 x 覺得自己不再是自己。

這兩種方法我一個都實現不出來。第一種方法會產生不單純的凸包,我要用這種凸包,需要自己動手去處理那些不單純的凸包面片,然而我也不清楚該如何處理它們。第二種方法很符合我的需要,畢竟我對點 x 在參與凸包構建過程的之後還是不是它原本的面目不太關心,然而我卻很難確定在多大範圍內搖晃點 x,這個問題的難度跟確定點 x 的膨脹直徑差不多。

寫不出來也不丟人。Matlab 與 Mathematica 的開發者們也沒寫出來,或不屑去寫,所以它直接用了 Qhull。最終我實現出來的是一種冷酷無情的 quick hull。凡是與凸包上的某個面片近似共面的點 x,凸包的大門永遠對其關閉。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章