擬人擬物法求解不等圓Packing問題

NP難度這門課還是比較有意思的,老師佈置了一道作業,寫一個用擬人擬物法求解不等圓Packing問題的小程序。

問題描述:在一個已知的容器中希望能放下N個不同形狀大小的物體,其中界限容器的封閉邊境以及各個物體都是不可入的剛性實體,如果客觀上放不下,我們要求做出放不下的判斷;如果客觀上放得下,則要求給出每個物體的位置和方向。

這就是所謂的Packing問題,我們將問題簡化了,只考慮圓形的容器和物體,給定容器的半徑、物體的數量、物體的半徑,求解是否存在一種佈局,使得容器和物體兩兩之間都不相交。Packing問題是一個NP難度問題,因爲我們要確定不存在滿足條件的佈局,必須窮舉所有的佈局,這是不可能的。

擬物方法是解決Packing問題的比較經典的算法。我們將這N個圓形物體和容器想象成光滑彈性的氣球,容器是固定不動的,當這N個物體放入容器後,就會受到擠壓而在彈力的作用下開始運動,最終有可能各個物體和容器都恢復自己的大小和形狀,問題就得到了解決。因此我們引入以下概念:

  • 任一時刻,稱N個物體的圓心座標x1,y1,x2,y2……,xn,yn爲系統在此刻的格局;
  • 物體間的距離Lij,如果i、j物體相切,Lij=0;如果i、j物體相離,Lij>0;如果i、j物體相交,Lij<0,且|Lij| = Ri + Rj - 圓心距離;
  • 物體間的彈性勢能Uij,當Lij>=0時,Uij=0;當Lij<0時,Uij=Lij^2;
  • 物體和容器所構成系統的總彈性勢能U,等於各個物體彈性勢能之和;
  • 物體移動的步長h,物體向當前時刻所受合力方向移動的距離;
  • 梯度t,h梯度下降的速度。

很顯然,U等於0代表着問題得到了解決。具體的擬物方法:1.初始化階段,爲物體隨機產生一個格局;2.計算當前格局的總彈性勢能,如果U=0退出算法;3.比較當前總勢能和上一格局的總勢能,如果勢能上升了,h=h*t;4.計算每一個物體所受的合力向量,朝其合力方向移動h長度,獲得新的格局;5.返回第二步。第三步的意思是梯度下降,當發現總勢能上升時,說明格局一步跳大了越過了平衡點,這時應減小步長h。

但是純粹的擬物方法有可能掉入局部最小值的陷阱,即各個物體和容器的受力並不爲零,但是合力爲零,物體將不會繼續運動,這時就需要引入擬人方法。想象一下擁擠的公交車,車裏面最想挪位置的是擠得最厲害的人,因此我們將當前擠壓最嚴重的物體(姑且稱其爲“難民”吧)拿出來放到另外一個位置上,走出局部最小值陷阱。先引入以下概念:

  • 任一格局之下,物體i的絕對痛苦指數DPi爲此時自身具有的彈性勢能;
  • 任一格局之下,物體i的相對痛苦指數RDPi爲此時其自身具有的彈性勢能除以其半徑的平方。

當h很小時,物體的移動速度很慢,我們就認爲系統掉入了局部最小值陷阱。此時選出RDP最大的物體,隨機放到五個位置(容器外圍的四角和圓心)中去,如果這次的難民連續兩次中標,就改爲調整DP最小的物體。

算法的擬物部分較死板一些,而擬人部分有不少地方值得思考。包括如何判斷局部最小值陷阱、如何選擇難民以及如何重新放置難民,這幾個點的設計直接會影響到算法的效率。關於如何放置難民,我測試了三種方案。

  1. 隨機取一個座標,這是書上推薦的辦法,但是我的測試卻並不理想,解題的時間很看運氣,往往很長時間找不到最優解。
  2. 只從固定的點中選擇,我選擇的候選點是能包含容器的最小矩形的四個角,然後將難民分配到最遠的角去,但是這種辦法可能導致死循環,難民在兩個點來回奔波,而且如果大量的空白區域被幾個物體圍在中心,會導致其它物體長時間不能進入。
  3. 在四角的基礎上,增加容器中心的位置,然後從五個位置隨機選擇一個點安放難民,試驗表明這種方法是三種方法裏面最好的。

可以看出,隨機性的算法可能很長時間得不到解,而非隨機性的算法就可能進入死循環,這是不是說明了“過於理性的思考可能使人發瘋”呢。關於如何判斷局部最小值陷阱,我覺得是比較難的,試了兩種方法,理論上都有缺陷,但測試都還沒發現問題。

  1. 當h很小時,就判定進入了局部最小值陷阱,這是書上推薦的方法,實現很簡單,但是限制了物體移動步長的最小值,可能恰好只需要一個更小的步長就能到達最優解;
  2. 當h/U很小時,就判定進入局部最小值陷阱,這種方法可以判斷出所有的局部最小值陷阱,同1一樣,也存在將最優解誤判爲陷阱的可能性,但是我認爲概率更小。

在實現的時候需要注意,h應該是和容器半徑成比例的。用於測試的實驗輸入數據:

  1. 容器的半徑爲6,小圓個數是7,它們的半徑都是2;
  2. 容器的半徑爲2.4143,小圓的個數是9,其中四個小圓的半徑是1,五個小圓的半徑是0.41415;
  3. 容器的半徑爲2.4143,小圓的個數是17,其中四個小圓的半徑是1,五個小圓的半徑是0.41415,八個小圓的半徑是0.2。

第一種輸入最簡單,可以用來檢驗程序是否存在大的邏輯問題,程序正確的話應很快就能計算出來。


第二種輸入較複雜些,如果按照書上的算法,當落入局部最小值陷阱時,將難民隨機放置到一個位置,運氣不好可能較長時間才能解出來。所以我對其進行了優化,在圓的四角取四個點,加上圓心,將難民隨機放到這五個位置之一,通常幾秒鐘就能找到答案。


第三種輸入是最難的,在第二種輸入的情況下增加了8個小圓,解題需要非常長的時間。大概跑了20分鐘得到了結果。


程序是用MFC寫的,遇到了在別的機器不能運行的問題,原因是工程選擇了使用MFC的動態鏈接庫,需要在vs裏將工程的properties->General->Use of MFC改爲使用靜態鏈接庫,同時properties->C/C++->Runtime Library也要改成MT。

CirclePacking小程序下載地址

CirclePacking源代碼下載地址

參考文獻

[1] 黃文奇,許如初。近世計算理論導引——NP難度問題的背景、前景以及求解算法研究。 科學出版社。

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