代課老師:劉西洋
課件下載:https://github.com/mathematical-logic/mathematical-logic
參考:"好心人"的往年總結
從萊布尼茲之夢到馮-諾依曼計算機
命題邏輯的有序二叉決策圖(OBDD)
題型 1:根據真值表畫出 BDD,或根據公式畫出 BDD。
三步走(不斷利用香農展開的過程):
a) 固定一個變量,畫出此變量的節點及 0 1 分支。
b) 看是否有分支可以合并,如果可以則合并,否則再選取另一個變量轉到步驟 a)。
c) 直到分支節點處變為 0 或 1,則結束。
題型 2:化簡圖
這里就使用 PPT 里面給的那個 Reduction 操作。這里把流程 走一遍。
題型 3:給出兩個 f,對其進行<op>操作。
這題需要兩步, 第一步是 Apply 操作,將兩個圖合并成一個圖,第二步是 Reduction 操作,就 是上面的化簡。
a) f1 的根結點 v1,f2 的跟結點 v2,操作為<op>,
整個過程就是在 f1 中取一個結點,在 f2 中取一個結點,然后比較
b) 如果 v1 和 v2 均為終止結點,那么目標圖上畫一個終止結點,值為 v1<op>v2
c) 如果 v1 和 v2 都不是終止結點,那就比較他們的索引值。
1. 如果 index(v1) = index(v2) = i,目標圖畫一個非終止結點,對應 index = i;
然后在 low(v1)和 low(v2)遞歸地應用該算法,深度優先,完成之后,在 high(v1)和 high(v2)遞歸地應用該算法。
2. 如果 index(v1) = i,index(v2) > i,目標圖畫一個非終止結點,對應 index = i;然后,在子樹中遞歸調用該算法。
d) 如果 v1 和 v2 一個是非終止結點,另一個是終止結點,終止結點的值是x
1. 如果 x<op>v2 的值由 x 決定,目標圖上畫一個終止結點,值為 x。
2. 如果 x<op>v2 的值不由 x 決定,目標圖上畫一個非終止結點,index = index(v2)
命題邏輯基于SAT Solver的DPLL可滿足性判定算法
DPLL Framework
DPLL(Davis-Putnam-Logemann-Loveland)算法,是一種完備的、以回溯
為基礎的算法,用于解決在合取范式(CNF)
中命題邏輯的布爾可滿足性問題
;也就是解決CNF-SAT問題。DPLL是一種高效的程序,并且經過40多年還是最有效的SAT解法,以及很多一階邏輯
的自動定理證明
的基礎。
Some conception:
1. 文字:一個變量或者它的補。
2. 子句:合取范式中兩個合取符號間的部分叫子句。
3. 單元子句、單元文字:子句中其他變量的邏輯值都為 0,只剩一個文字未被賦值,
則該文字是單元文字,對應的子句是單元子句。
4. 矛盾子句:子句中所有文字的邏輯值都為 0。
5. 可滿足問題:一個合取范式,存在一組賦值使所有子句的邏輯值都為 1。
6. SAT 求解算法:1. 局部搜索算法(不完備算法);2. DPLL 算法(完備算法)
1、如果文字a(正文字或者負文字)在子句c(clause)中出現并且在其它子句中未出現!a,那么刪除該子句。
`刪除后所得公式與原公式具有相同的可滿足性!`
2、`任意選擇`一個文字b,并且對b賦值“真” (V = {b}) ,刪除包含b的子句,并且把所有其余子句中的!b刪除。
3、對`只剩余一個文字的子句賦值`,使該文字為真(擴大the partial valuation V= {b,!C} )。
4、如果the partial valuation有矛盾賦值(contradictory valuation),例如 V = {b, !C, C} ,
`退回原來的選擇`(backtrack to the privous choice),重新對文字b賦值 V= {!b} .
5、如果已經沒有辦法回退,則該公式不可滿足(unsatisfiable)
- Recursive description
DPLL(formula, assignment){
necessary = deduction(formula, assignment); # 找出必要的變量賦值
new_asgnmnt = union(necessary, assignment); # 得到新的一組賦值
if (is_satisfied(formula, new_asgnmnt)) # 判斷是否可滿足
return SATISFIABLE;
else if (is_conflicting(formula, new_asgnmnt)) # 判斷是否出現沖突
return CONFLICT;
var = choose_free_variable(formula, new_asgnmnt); # 挑選一個未賦值變量
asgn1 = union(new_asgnmnt, assign(var, 1)); # 給未賦值變量賦 1,得到新的賦值
if (DPLL(formula, asgn1)==SATISFIABLE) # 遞歸判斷
return SATISFIABLE;
else {
asgn2 = union (new_asgnmnt, assign(var, 0)); # 給未賦值變量賦 0
return DPLL(formula, asgn2); # 遞歸判斷
}
}
- Iterative Description
status = preprocess(); # 預處理
if (status!=UNKNOWN) return status;
while(true) {
decide_next_branch(); # 變量決策
while (true) {
status = deduce(); # 推理(BCP)
if (status == CONFLICT) {
blevel = analyze_conflict(); # 沖突分析
if (blevel == 0) # 如果沖突層次為頂層,則邏輯式直接不可滿足
return UNSATISFIABLE;
else backtrack(blevel); # 智能回溯
}
else if (status == SATISFIABLE)
return SATISFIABLE;
else break;
}
}
迭代比遞歸的優勢:
- 遞歸速度慢且容易發生溢出,相對于迭代就有很多自身的劣勢。
- 迭代具有非時間順序回溯(智能回溯)的優勢。
預處理:所謂預處理就是根據邏輯蘊含推理以及冗余子句刪除和添加等值子句等相關技術。
變量決策:如何快速有效地決策出下一個將要賦值的變量。
MOM 方法:
思想:如果一個子句長度很小,那它就很不容易被滿足,所以我們要優先考慮它們,給予它們更高的權重。
方法要點:子句長度短,出現頻率高。-
VSIDS 方法:
- 為每一個變量設定一個 score,這個 score 的初始值就是該變量在所有子句集中出現的次數。
- 每當一個包含該文字的沖突子句被添加進子句庫,該文字的 score 就會加 1。
- 哪個變量的 socre 值最大,就從這個變量開始賦值。
- 為了防止一些變量長時間得不到賦值,經過一定時間的決策后,每一個變量的 score 值都會被 decay,具體方式是把 score 值除以一個常
數(通常為 2-4 左右)。——“周期衰減”
優點:統計數據與變量賦值狀態無關,因此系統資源開銷非常低。可以 顯著提高求解器性能。
BCP:通過推理來減少一些不必要的搜索,加快效率。主要依賴單元子句規則
Counters 方法:每個子句有兩個計數器,一個記錄邏輯值為 true 的文 字數,另一個記錄邏輯值為 false 的文字數。如果一個 CNF 實例有 m 個子句,n 個變量,每個子句平均有 L 個文字,則有一個變量被賦值 的時候,會有平均 mL/n 個計數器需要更新,在回溯的時候,每取消 一個變量賦值,也會平均有 mL/n 個計數器的更新。如果一個子句的false 計數器的值等于該子句的文字數,則說明出現了沖突;如果一 個子句的 false 計數器的值等于該子句的文字數減 1,則該子句是單元子句。
head/tail 方法:每個子句存在一個數組中,并且有兩個指針,一個頭指針,一個尾指針,每個變量 v 有四個附加鏈表,分別裝有句頭是 v 的子句,句頭是非 v 的子句,句尾是 v 的子句,句尾是非 v 的子句。 所以,如果有 m 個子句,四個鏈表中就存放著 2m 個子句。移動指針有四種情況:
1. 第一個遇到的文字為真,說明子句已經滿足,直接忽略該子句。
2. 第一個遇到的文字未賦值且不是句尾,那就將該子句刪除,放到對應文字的鏈表中。
3. 頭尾指針之間只有一個變量未賦值且其他文字都為假,則出現單元子句。
4. 頭尾指針之間所有文字都為假,產生沖突,回溯。
當一個變量被賦值的時候,平均有 m/n 個子句需要更新(m 為子句 數,n 為變量數)。
- 2-literal watching 方法(應用于 zChaff 求解器):與 head/tail 方法相 似,為每個子句關聯兩個指針,但這兩個指針沒有頭和尾之分,位置 任意。它與 head/tail 方法的關鍵區別是回溯時指針的位置無需移動, 取消一個變量的賦值時間的開銷是常量,但壞處是只有遍歷完所有 文字才能找到單元文字。兩個指針隨時監視子句中任意兩個未被賦 值為 0 的文字。每個變量 v 有兩個附加鏈表,對應變量的正負形態, 存放的是對應的子句。設初始時變量 v 賦值為 1,則搜索將在一個含 有文字-v 的子句中進行(因為 v 已滿足),并且此時一個監視指針指 向文字-v,繼續搜索,該過程中有四種情況:
1. 如果存在文字 L 不是該子句的另外一個被監視文字,則刪除指向文字-v 的指針,
并添加指向文字 L 的指針,相當于指針移動。
2. 如果唯一符合條件的文字 L 是另外一個被監視文字,且它沒有被賦值,
則該被監視文字是單元文字,該子句是單元子句。
3. 如果唯一符合條件的文字 L 是另外一個被監視文字,且它已經被賦值為 1,
說明該子句已經滿足,不需要進行任何處理。
4. 如果所有文字都已經被賦值為 0,那么出現沖突,回溯。
沖突分析
- Conflict Analysis and Learning 尋找沖突的原因并學習
analyze_conflict(){
cl = find_conflicting_clause();
while (!stop_criterion_met(cl)) {
lit = choose_literal(cl);
var = variable_of_literal( lit );
ante = antecedent( var );
cl = resolve(cl, ante, var);
}
add_clause_to_database(cl);
back_dl = clause_asserting_level(cl);
return back_dl;
}
智能回溯
- 時序回溯:直接返回上一層,將變量取值翻轉。
- 非時序回溯:結合沖突學習,智能分析,跳回多個決策層,并且會將導
致沖突的子句加入到子句集中。
子句數據結構
- 數組方式:數組采用連續空間存儲,內存利用率和存儲效率更高,局部訪問能力更強。連續存儲能提高 cache 命中率,從而增加計算速度。但不靈活。
- 鏈表方式:便于對子句進行增加和刪除操作,存儲效率低。因為缺少局部訪問能力,往往會造成緩存丟失。
注:head/tail 和 2-literal watching 都被稱為“膨脹數據結構”,它們都采用數組存儲子句,最具競爭力。
例題:設計一個基于數組的數據結構存儲子句
方案 1:head/tail
用數組存儲子句的文字,對于每個數組設置兩個指針,分別為頭指針 與尾指針,然后對于每個變量 v 設置 4 個鏈表,分別裝有句頭是 v 的子句, 句頭是非 v 的子句,句尾是 v 的子句,句尾是非 v 的子句,這樣存儲的子 句,無論子句順序及其中變量順序的改變都不會影響所有變量的四個鏈表中 的子句數量。取所有變量 v 是句頭的子句與非 v 是句頭的子句或者 v 是句尾 的子句與非 v 是句尾的子句合取可以得到公式。
方案 2:2-literal watching
為每個子句關聯兩個指針,這兩個指針位置任意,隨時監視子句中任
意兩個未被賦值為 0 的文字。每個變量 v 有兩個附加鏈表,對應變量的正負 形態,存放的是含有對應文字的子句。該種結構的好處是回溯時指針的位置 無需移動,取消一個變量的賦值時間的開銷是常量。此外,重新分配最近分 配和未分配的變量將比第一次分配的變量更快,并且可以減少內存訪問總數, 提高緩存命中率。
FDS
一個程序 D 由五部分組成?? = < ??,??,??,??,??,?? >
??:有限狀態集合。包括程序中的變量和語句.
??:可觀測狀態集合。定義??屬于??。一般沒有說明的話,默認所有狀態都可觀測,因此??等于??。這個不重要,應該不考.
??:初始條件.
??:轉換關系。考察的重點.
??:justice 集合,弱公平的.
??:compassion 集合,強公平的.