簡體   English   中英

示例通道約束ECLiPSe

[英]Example channelling constraints ECLiPSe

有人可以提供一個簡單的渠道約束示例嗎?

通道約束用於組合約束問題的視點。 約束編程手冊很好地解釋了它是如何工作的以及為什么它有用:

搜索變量可以是其中一個視點的變量,比如X1(這將在下面進一步討論)。 隨着搜索的進行,傳播約束C1從X1中的變量的域中移除值。 然后,信道約束可以允許從X2中的變量的域中移除值。 使用第二模型C2的約束來傳播這些值刪除可以從這些變量中移除更多值,並且這些移除可以通過信道約束再次轉換回第一視點。 最終結果可能是在視點V1內移除的值多於僅由約束C1移除的值,導致搜索減少。

我不明白這是如何實現的。 究竟是什么限制,它們在真正的問題中看起來如何? 一個簡單的例子非常有用。

當在模型中,問題的各個方面以多種方式表示時,使用信道約束。 然后,它們必須同步這些多個表示,即使它們本身並不模擬問題的某個方面。

通常,在使用約束對問題建模時,您可以通過多種方式選擇變量。 例如,在調度問題中,您可以選擇擁有

  • 每個作業的整數變量(指示哪個機器完成作業)
  • 每台機器的整數變量(表示它執行的作業)
  • 布爾矩陣(表示哪個作業在哪台機器上運行)
  • 或更具異國情調的東西

在一個足夠簡單的問題中,您可以選擇最簡單的表示形式來解決問題的約束。 然而,在具有許多異構約束的現實生活問題中,通常不可能找到這樣的單一最佳表示:一些約束最好用一種類型的變量表示,而另一些約束用另一種變量表示。

在這種情況下,您可以使用多組變量,並在最方便的變量集上制定每個單獨的問題約束。 當然,您最終會遇到多個獨立的子問題,單獨解決這些問題並不能為您提供整個問題的解決方案。 但是通過添加通道約束,可以同步變量集,從而重新連接子問題。 結果是整個問題的有效模型。

正如手冊中引用的那樣,在這樣的表述中,只需對一個變量集(“視點”)進行搜索就足夠了,因為其他值的值是由信道約束所暗示的。

在兩個表示之間進行通道的一些常見示例是:

整數變量布爾數組 :考慮一個整數變量T表示發生事件時的時隙1..N ,以及一個布爾值Bs[N]數組,如果事件發生在時間上,則Bs[T] = 1T 在ECLiPSe中:

    T #:: 1..N,
    dim(Bs, [N]), Bs #:: 0..1,

然后可以設置兩個表示之間的通道

    ( for(I,1,N), param(T,Bs) do Bs[I] #= (T#=I) )

它將在TBs之間傳播信息。 實現此通道的另一種方法是特殊用途bool_channeling / 3約束。

開始/結束整數變量布爾數組 (時間表):我們有整數變量S,E表示活動的開始和結束時間。 另一方面,布爾運算Bs[N]的陣列使得如果活動在時間T發生,則Bs[T] = 1 在ECLiPSe中:

    [S,E] #:: 1..N,
    dim(Bs, [N]), Bs #:: 0..1,

通道可以通過實現

    ( for(I,1,N), param(S,E,Bs) do Bs[I] #= (S#=<I and I#=<E) ).

雙重表示作業/機器整數變量 :這里, Js[J] = M表示作業J在機器M上執行,而雙重公式Ms[M] = J表示機器M執行作業J

    dim(Js, [NJobs]), Js #:: 0..NMach,
    dim(Ms, [NMach]), Ms #:: 1..NJobs,

通過渠道實現渠道化

    ( multifor([J,M],1,[NJobs,NMach]), param(Js,Ms) do
        (Js[J] #= M) #= (Ms[M] #= J)
    ).

設置變量布爾數組 :如果使用可以直接處理集變量的求解器(例如庫(ic_sets) ),這些可以反映到布爾數組中,表示集合中元素的成員資格。 為此目的,庫提供了一個專用約束membership _booleans / 2

正如二元約束滿足問題的雙視點啟發式(PA Geelen)中所述

兩個不同模型的通道約束允許表達兩組變量之間的關系,每個模型之一。

這意味着其中一個視點中的分配可以轉換為另一個視圖中的分配,反之亦然,以及當搜索啟動時,一個模型中的排除值也可以從另一個模型中排除。

讓我舉一個我在編寫數獨求解器時實現的示例。

經典觀點

在這里,我們以與人類相同的方式解釋問題:使用1到9之間的整數以及所有行,列和塊必須包含每個整數一次的定義。

我們可以使用以下內容在ECLiPSe中輕松說明這一點:

% Domain
dim(Sudoku,[N,N]),
Sudoku[1..N,1..N] :: 1..N

% For X = rows, cols, blocks
alldifferent(X)

這足以解決數獨謎題。

二進制布爾觀點

然而,可以選擇通過其二進制布爾數組表示整數(在@jschimpf的答案中顯示)。 如果不清楚這是做什么的,請考慮下面的小例子(這是內置功能!):

?­ ic_global:bool_channeling(Digit, [0,0,0,1,0], 1).
    Digit = 4
    Yes (0.00s cpu)
?­ ic_global:bool_channeling(Digit, [A,B,C,D], 1), C = 1.
    Digit = 3
    A = 0
    B = 0
    C = 1
    D = 0
    Yes (0.00s cpu)

如果我們使用此模型來表示數獨,則每個數字將被其二進制布爾數組替換,並且可以寫入相應的約束。 這個答案很簡單,我不會包含約束的所有代碼,但單個和約束足以解決其二進制布爾表示中的數獨謎題。

現在,將這兩個視點與相應的約束模型相結合,可以在它們之間進行通道,並查看是否進行了任何改進。

由於兩個模型仍然只是一個NxN板,因此表示的維度不存在差異,並且通道變得非常簡單。

讓我先給你一個最后一個例子,說明在我們的兩個模型中填充整數1..9的塊是什么樣的:

% Classic viewpoint
1 2 3
4 5 6
7 8 9

% Binary Boolean Viewpoint
[](1,0,0,0,0,0,0,0,0)  [](0,1,0,0,0,0,0,0,0)  [](0,0,1,0,0,0,0,0,0) 
[](0,0,0,1,0,0,0,0,0)  [](0,0,0,0,1,0,0,0,0)  [](0,0,0,0,0,1,0,0,0) 
[](0,0,0,0,0,0,1,0,0)  [](0,0,0,0,0,0,0,1,0)  [](0,0,0,0,0,0,0,0,1)

我們現在清楚地看到模型之間的聯系,只需編寫代碼來引導我們的決策變量。 使用SudokuBinBools作為我們的板,代碼看起來像:

( multifor([Row,Col],1,N), param(Sudoku,BinBools,N) 
do
  Value is Sudoku[Row,Col], 
  ValueBools is BinBools[Row,Col,1..N], 
  ic_global:bool_channeling(Value,ValueBools,1) 
).

此時,我們有一個通道模型,在搜索過程中,如果在其中一個模型中修剪了值,其影響也會發生在另一個模型中。 這當然可以導致進一步的整體約束傳播。

推理

為了解釋二元布爾模型對數獨謎題的有用性,我們必須首先區分alldifferent/1提供的一些不同的alldifferent/1實現:

推理不同/ 1

這在實踐中意味着如下所示:

?­ [A, B, C] :: [0..1], ic:alldifferent([A, B, C]). 
  A = A{[0, 1]} 
  B = B{[0, 1]} 
  C = C{[0, 1]} 
  There are 3 delayed goals. 
  Yes (0.00s cpu) 

?­ [A, B, C] :: [0..1], ic_global:alldifferent([A, B, C]). 
  No (0.00s cpu)

由於尚未使用正向檢查(ic庫)進行任何分配,因此尚未檢測到查詢的無效性,而Bounds Consistent版本會立即注意到此情況。 在通過高度約束的模型進行搜索和回溯時,此行為可能導致約束傳播存在相當大的差異。

在這兩個庫的頂部有ic_global_gac庫,用於全局約束,其中維護了廣義弧一致性(也稱為超弧一致性或域一致性)。 這種不同的/ 1約束提供了比邊界一致的更多修剪機會, 保留完整域一致性有其成本,並且在高度約束模型中使用該庫通常會導致運行性能的損失。

正因為如此,我發現Sudoku難題嘗試使用不同的邊界一致(ic_global)實現以最大化性能並隨后嘗試通過引用二進制布爾模型來自己接近域一致性。

實驗結果

下面是'platinumblonde'數獨謎題的回溯結果(被稱為最難,最混亂的數獨謎題, 在數獨的混亂中解決,M。ErcseyRavasz和Z.Toroczkai)分別使用前向檢查,邊界一致性,域一致性,獨立的二進制布爾模型,最后是通道模型:

      (ic)   (ic_global)  (ic_global_gac)  (bin_bools)  (channelled)
BT    6 582      43             29             143           30

正如我們所看到的,我們的通道模型(使用邊界一致性(ic_global))仍然需要一個回溯而不是域一致的實現,但它肯定比獨立邊界一致版本更好。

當我們現在看一下運行時間(結果是計算多次執行的平均值的結果,這是為了避免極端情況),不包括前向檢查實現,因為它已被證明不再有趣解決數獨謎題:

          (ic_global)  (ic_global_gac)  (bin_bools)  (channelled)
Time(ms)     180ms          510ms           100ms        220ms

看看這些結果,我認為我們可以成功地完成實驗(這些結果得到了20多個其他數獨謎題實例的證實):

將二進制布爾視點引導到邊界一致的獨立實現會產生比域一致的獨立實現稍微強一些的約束傳播行為,但是運行時間范圍從長到明顯更快。

編輯:嘗試澄清

想象一下,代表Sudoku板上一個單元的某個域變量的剩余域為4..9。 使用邊界一致性,可以保證對於值4和9,可以找到滿足所有約束的其他域值,從而提供一致性。 但是,沒有明確保證域中其他值的一致性(這就是“域一致性”)。

使用二進制布爾模型,我們定義以下兩個求和約束:

  • 每個二進制布爾數組的總和始終等於1
  • 每行/列/塊中每個數組的每個第N個元素的總和始終等於1

額外約束強度由第二個約束強制執行,第二個約束除了約束行,列和塊之外,還隱含地說:“每個單元格只能包含每個數字一次”。 當僅使用bounds一致的alldifferent / 1約束時,不會主動表達此行為!

結論

顯然,引導對於改進獨立約束模型非常有用, 但是如果新模型的約束強度弱於當前模型的約束強度,顯然,不會進行任何改進。 另請注意,擁有更具約束力的模型並不意味着總體上更好的性能! 實際上,添加更多約束將減少解決問題所需的回溯量,但如果必須進行更多約束檢查,則可能還會增加程序的運行時間。

這是一個簡單的例子,適用於SWI-Prolog,但也應該在ECLiPSe Prolog中工作(在后面你必須使用(::)/ 2而不是(in)/ 2):

約束C1:

 ?- Y in 0..100.
 Y in 0..100.

約束C2:

 ?- X in 0..100.
 X in 0..100.

引導約束:

 ?- 2*X #= 3*Y+5.
 2*X#=3*Y+5.

全部一起:

?- Y in 0..100, X in 0..100, 2*X #= 3*Y+5.
Y in 1..65,
2*X#=3*Y+5,
X in 4..100.

因此,信道約束在兩個方向上都起作用,它減少了C1的域以及C2的域。

有些系統使用迭代方法,結果是這個通道可能需要相當長的時間,這里有一個例子,需要大約1分鍾來計算SWI-Prolog:

?- time(([U,V] ins 0..1_000_000_000, 36_641*U-24 #= 394_479_375*V)).
% 9,883,559 inferences, 53.616 CPU in 53.721 seconds 
(100% CPU, 184341 Lips)
U in 346688814..741168189,
36641*U#=394479375*V+24,
V in 32202..68843.

另一方面,ECLiPSe Prolog眨眼間就做了:

[eclipse]: U::0..1000000000, V::0..1000000000, 
              36641*U-24 #= 394479375*V.
U = U{346688814 .. 741168189}
V = V{32202 .. 68843}
Delayed goals:
     -394479375 * V{32202 .. 68843} + 
     36641 * U{346688814 .. 741168189} #= 24
Yes (0.11s cpu)

再見

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM