এই পর্বে যা শিখবেন
- Pure literal নিয়মসহ DPLL হাতে চালানো, আর নিয়মটি কেন satisfiability রাখে তার প্রমাণ
- Graph coloring আর শিফট-বণ্টনকে CNF-এ লেখা, encoding-এর সঠিকতার প্রমাণ আর clause গোনা
- CDCL-এর clause শেখার যুক্তি, pigeonhole সূত্র কেন কঠিন, আর random 3-SAT-এর phase transition
- 2-SAT-এর implication graph, SCC দিয়ে সিদ্ধান্তের উপপাদ্য আর তার প্রমাণ
- SMT-এর theory, আর Z3 দিয়ে bit-trick, overflow-check আর একটি state machine-এর bounded model checking
১ · SAT আবার — একটি প্রশ্নে হাজারো সমস্যা
বাজারের এক দোকানি দিনের হিসাব মেলাতে বসেছেন। খাতা মেলানো কঠিন, কিন্তু কেউ যদি বলে “এই তিনটি রসিদ বাদ দিলে মিলে যায়”, তা যাচাই করা এক মিনিটের কাজ। SAT ঠিক এমন: উত্তর খোঁজা কঠিন হতে পারে, প্রস্তাবিত উত্তর যাচাই সহজ। আর আশ্চর্যের কথা, এই একটি প্রশ্নের ভেতরে অগণিত বাস্তব সমস্যা লুকিয়ে রাখা যায়।
পর্ব ০২ “Propositional logic”-এ proposition আর truth table, পর্ব ০৩ “Logical equivalence”-এ CNF আর satisfiability, আর পর্ব ০৭ “কাজে যুক্তি”-এ DPLL, Sudoku-র encoding আর Z3-এর প্রথম ব্যবহার দেখেছেন। মনে করিয়ে দিই: একটি clause কয়েকটি literal-এর ∨, আর CNF সূত্র কয়েকটি clause-এর ∧. সূত্রটি satisfiable, যদি এমন একটি মান-বসানো (model) থাকে যাতে প্রতিটি clause-এ অন্তত একটি literal সত্য।
ইনপুট: n-টি variable-এর একটি CNF সূত্র F. প্রশ্ন: F কি satisfiable? “হ্যাঁ” হলে একটি model দেখানোই প্রমাণ; “না” হলে (UNSAT) প্রমাণ অনেক বড় হতে পারে।
এই অসমতাই SAT-কে বিশেষ করে। প্রস্তাবিত model দিলে প্রতিটি clause একবার দেখে polynomial সময়ে যাচাই করা যায়, তাই SAT NP শ্রেণিতে। আর Cook (১৯৭১) আর স্বাধীনভাবে Levin-এর উপপাদ্য বলে, NP-এর প্রতিটি সমস্যা polynomial সময়ে SAT-এ রূপান্তর করা যায় (Cook–Levin theorem; বক্তব্য পর্ব ০৭-এ, প্রমাণ এখানে বাদ)। তাই SAT NP-complete. Turing machine দিয়ে NP-এর নির্ভুল সংজ্ঞা আসছে পর্ব ৭৫ “Turing machine-এর পরিচয় — computability আর halting problem”-এ; reduction-এর পূর্ণ আলোচনা DSA কোর্সের পর্ব ৬৪-এ। গণনার মডেলের পূর্ণ তত্ত্ব পরিকল্পিত Theory of Computation কোর্সের বিষয়; এখানে আমরা discrete math-এর দিক থেকে দেখি: encoding-এর প্রমাণ, গণনা আর graph.
এই পর্বের পথ: প্রথমে solver কীভাবে খোঁজে (§২, §৪), তারপর সমস্যাকে কীভাবে সূত্রে লিখতে হয় (§৩), কোন ধরন সহজ (§৫), আর শেষে সংখ্যা ও bit-এর জগতে SMT দিয়ে কোড যাচাই (§৬–§৮)।
২ · DPLL-এ pure literal আর একটি পূর্ণ trace
পর্ব ০৭-এ DPLL-এর দুটি নিয়ম প্রমাণসহ দেখেছেন: unit propagation (একটি literal-এর clause থাকলে তার মান বাধ্য) আর শাখা করে backtracking. সেখানে তৃতীয় নিয়মটি বাদ ছিল। F|ℓ মানে ℓ সত্য ধরে সূত্র ছোট করা: ℓ-ওয়ালা clause বাদ, অন্য clause থেকে ¬ℓ মুছে ফেলা।
F-এ literal ℓ আছে কিন্তু ¬ℓ কোথাও নেই; এমন ℓ-কে “pure” বলে। তাহলে F satisfiable ⇔ F|ℓ satisfiable.
- (⇒) F-এর একটি model M নিন, আর ℓ-এর variable-এর মান বদলে ℓ সত্য করুন; নাম দিন M′. — নির্মাণ
- যে clause-এ ℓ আছে, M′-এ তা সত্য। যে clause-এ ℓ নেই, তাতে ¬ℓ-ও নেই (ℓ pure), অর্থাৎ ওই variable-ই নেই; তাই তার মান M-এর মতোই সত্য। M′ F-এর model, আর F|ℓ-এর clause-গুলো F-এর clause-এরই অংশ যেগুলো M′-এ সত্য। — ক্ষেত্র ভাগ
- (⇐) F|ℓ-এর model-এ ℓ = সত্য যোগ করলে ℓ-ওয়ালা clause সত্য হয়, আর বাকি clause F|ℓ-এ অপরিবর্তিত ছিল (মোছার মতো ¬ℓ ছিল না)। ∎ — সংজ্ঞা
সাবধান: unit নিয়ম কোনো model হারায় না, কিন্তু pure নিয়ম হারাতে পারে (যে model-এ ℓ মিথ্যা, সেগুলো)। এটি শুধু satisfiability রাখে; দুটি সূত্রের এমন সম্পর্ককে equisatisfiable বলে। “সব model গোনা” কাজে pure নিয়ম চলবে না।
এবার তিনটি নিয়ম একসঙ্গে, পর্ব ০৭-এর চেয়ে আলাদা একটি সূত্রে। ক্রম: আগে unit, না থাকলে pure (সবচেয়ে ছোট variable), না থাকলে প্রথম clause-এর প্রথম variable-এ অনুমান।
(a ∨ ¬b) ∧ (¬a ∨ ¬c) ∧ (¬a ∨ d) ∧ (¬a ∨ ¬d) ∧ (¬d ∨ c ∨ b)
C1a ∨ ¬bC2¬a ∨ ¬cC3¬a ∨ dC4¬a ∨ ¬dC5¬d ∨ c ∨ b
| a b c d | |
| C1 | |
| C2 | |
| C3 | |
| C4 | |
| C5 |
- শুরু। প্রতিটি clause-এ দুই বা তিনটি literal, তাই unit নেই। Pure-ও নেই: a আর ¬a দুটোই আছে, b আর ¬b, c আর ¬c, d আর ¬d-ও। অনুমান লাগবে; প্রথম clause-এর প্রথম variable a.
- অনুমান a = T. C1 সত্য। C2, C3, C4 থেকে ¬a মুছে যায়: থাকে ¬c, d আর ¬d, তিনটিই unit.
- Unit: c = F. প্রথম unit C2 (¬c). C2 সত্য হলো; C5 থেকে c মুছে থাকে ¬d ∨ b.
- Unit: d = T, বিরোধ। C3 থেকে d = T বাধ্য। কিন্তু C4-এ বাকি ছিল শুধু ¬d, সেটি এখন মিথ্যা: C4 হয়ে গেল empty clause, অর্থাৎ খালি। a = T শাখায় কোনো model নেই।
- Backtrack: a = F. a = T থেকে আসা সব মান মুছে ফেলি। ¬a সত্য, তাই C2, C3, C4 সত্য। C1 থেকে a মুছে থাকে ¬b: unit.
- Unit: b = F. C1 সত্য। C5 থেকে b মুছে থাকে ¬d ∨ c. এখানে কোনো unit নেই।
- Pure literal: c = T. বাকি একমাত্র clause-এ c শুধু ধনাত্মক, d শুধু ঋণাত্মক; দুটোই pure. ছোট variable c বেছে c = T: শেষ clause সত্য, সূত্র SAT. d-এর মান যেকোনো।
- যাচাই আর শিক্ষা। a = F, b = F, c = T-তে পাঁচটি clause-ই সত্য (brute force-এ এমন model ঠিক ৩টি, তিনটিতেই a আর b মিথ্যা)। বিরোধের দোষী C3 আর C4: d-এর ওপর resolution করলে পাই (¬a). §৪-এর CDCL ঠিক এই clause শিখে রাখে।
DPLL-এর সঠিকতার প্রমাণ পর্ব ০৭-এর উপপাদ্য ৪.২ (খোলা variable-এর সংখ্যার ওপর strong induction); Lemma ২.১ সেই প্রমাণে আরেকটি ক্ষেত্র যোগ করে মাত্র, কারণ pure ধাপেও একটি variable কমে আর satisfiability বদলায় না। নিচের ব্লক এই DPLL লেখে, stepper-এর সূত্র মেলায়, তারপর n = 10 variable-এর এলোমেলো 3-CNF-এ উত্তরটি brute force-এর সঙ্গে মেলায়। Brute force-এ 210 = 1024টি সারি একসঙ্গে একটি বড় পূর্ণসংখ্যার bit হিসেবে রাখা হয়েছে: variable v-এর “column” হলো যেসব সারিতে v সত্য তাদের bitmask, আর clause মানে mask-এর OR, সূত্র মানে AND. প্রথমবার চালালে ব্রাউজারে Python (Pyodide) নামতে কয়েক সেকেন্ড লাগে।
import random
def simplify(cnf, lit): # lit is now true
return [[l for l in c if l != -lit] for c in cnf if lit not in c]
def dpll(cnf, trail, stats):
while True:
if [] in cnf: return None # empty clause: conflict
lits = {l for c in cnf for l in c}
unit = next((c[0] for c in cnf if len(c) == 1), None)
pure = min((l for l in lits if -l not in lits), key=abs, default=None)
lit = unit if unit is not None else pure
if lit is None: break
trail, cnf = trail + [lit], simplify(cnf, lit)
if not cnf: return trail # every clause satisfied
stats[0] += 1; x = abs(cnf[0][0]) # one decision (branch) on x
return (dpll(simplify(cnf, x), trail + [x], stats)
or dpll(simplify(cnf, -x), trail + [-x], stats))
def brute(cnf, n): # all 2^n rows at once, as bit masks
N = 1 << n; full = rows = (1 << N) - 1 # bit a of rows: row a still OK
col = [sum(1 << a for a in range(N) if a >> v & 1) for v in range(n)]
for c in cnf:
cm = 0
for l in c: cm |= col[l - 1] if l > 0 else full ^ col[-l - 1]
rows &= cm
return rows != 0
print("stepper formula:", dpll([[1, -2], [-1, -3], [-1, 4], [-1, -4], [-4, 3, 2]], [], [0]))
random.seed(77); n, T = 10, 40
print(" m/n SAT share DPLL = brute avg decisions")
for ratio in (2, 3, 4, 4.3, 5, 6):
sat = agree = dec = 0
for _ in range(T):
F = [[v * random.choice((1, -1)) for v in random.sample(range(1, n + 1), 3)]
for _ in range(round(ratio * n))]
st = [0]; r = dpll(F, [], st) is not None
sat += r; agree += r == brute(F, n); dec += st[0]
print(f"{ratio:4} {sat / T:9.2f} {agree:7}/{T} {dec / T:13.1f}")আসল আউটপুট (ব্রাউজারের Python):
stepper formula: [-1, -2, 3]
m/n SAT share DPLL = brute avg decisions
2 1.00 40/40 2.2
3 1.00 40/40 3.8
4 0.80 40/40 4.5
4.3 0.82 40/40 4.5
5 0.42 40/40 5.1
6 0.20 40/40 4.8
প্রথম লাইন stepper হুবহু: [−1, −2, 3] মানে a = F, b = F, c = T. সারণির প্রতিটি সারিতে ৪০টি এলোমেলো সূত্র, প্রতিটিতে m = ratio × 10টি clause. ২৪০টির প্রতিটিতে DPLL আর brute force একই উত্তর দিয়েছে। “SAT share” কলামটি দেখুন: অনুপাত ২ বা ৩-এ সব সূত্র satisfiable, ৬-এ মাত্র ২০%. মাঝখানে কোথাও পতন; §৪-এ এর নাম phase transition. n = 10 খুব ছোট, তাই পতনটি ঝাপসা, আর decision-এর গড়ও ছোট; বড় n-এ পতন তীক্ষ্ণ হয় আর কঠিন এলাকার খরচ দ্রুত বাড়ে।
৩ · সমস্যাকে CNF-এ লেখা — graph coloring আর শিফট
Solver শুধু clause বোঝে; অর্থ থাকে encoding-এ। পর্ব ০৭-এ Sudoku-র “ঠিক একটি” নিয়ম দেখেছেন। এখানে পর্ব ৬৩-এর graph coloring: বাজারঘরের ডেলিভারি-এলাকাগুলোকে k-টি দিনে ভাগ করতে হবে, আর যে দুটি এলাকা একই রাস্তা ভাগ করে (graph-এ edge), তারা একই দিনে নয়। Sudoku নিজেও একটি নির্দিষ্ট graph-এর 9-coloring, যা পর্ব ৬৩-এ দেখেছেন।
Graph G = (V, E), |V| = n, |E| = m. প্রতিটি v ∈ V আর রং c ∈ {1, …, k}-এর জন্য variable xv,c = “v-এর রং c”. Clause তিন ধরনের: (ক) প্রতিটি v-এ xv,1 ∨ ⋯ ∨ xv,k; (খ) প্রতিটি v আর c < d-তে ¬xv,c ∨ ¬xv,d; (গ) প্রতিটি edge uw আর রং c-তে ¬xu,c ∨ ¬xw,c. মোট nk-টি variable আর n + n·C(k, 2) + m·kটি clause.
G-এর proper k-coloring আছে ⇔ নির্মাণ ৩.১-এর সূত্র satisfiable. আরও বেশি: proper coloring আর model-এর মধ্যে একটি bijection আছে।
- (⇒) Proper coloring f: V → {1, …, k} দিলে xv,c = T ঠিক তখন, যখন f(v) = c. প্রতিটি v-এ ঠিক একটি xv,c সত্য, তাই (ক) আর (খ) সত্য। Edge uw-তে f(u) ≠ f(w), তাই কোনো c-তে xu,c আর xw,c দুটোই সত্য নয়: (গ) সত্য। — নির্মাণ
- (⇐) একটি model নিন। (ক) বলে প্রতিটি v-এ অন্তত একটি রং, (খ) বলে বড়জোর একটি (পর্ব ০৭-এর উপপাদ্য ৩.১), তাই f(v) = সেই একমাত্র c একটি ফাংশন। (গ) বলে edge-এর দুই প্রান্তে একই c সত্য নয়, তাই f proper. — “ঠিক একটি”
- দুই দিকের রূপান্তর একে অপরের উল্টো, তাই bijection: model-এর সংখ্যা = proper k-coloring-এর সংখ্যা। ∎ — পর্ব ১৬-এর inverse
ধাপ ৩ দেখায় কেন (খ) রাখা হয়েছে: শুধু “সম্ভব কি না” জানতে (খ) বাদ দিলেও চলে (একাধিক রং পেলে যেকোনো একটি নিলেই হয়), কিন্তু তখন model-এর সংখ্যা আর coloring-এর সংখ্যা সমান থাকে না। নিচে সবচেয়ে ছোট উদাহরণ, ত্রিভুজ K3.
1vars: u1 u2 u3 v1 … w32u1 ∨ u2 ∨ u3 (×3)3¬u1 ∨ ¬u2 … (×9)4¬u1 ∨ ¬v1 … (×9)5model: u1 v2 w36k = 2: 12, UNSAT
| মোট clause | |
| এই ধাপে |
- Variable. Vertex u, v, w, রং 1, 2, 3. u1 মানে xu,1: “u-এর রং 1”. 3 × 3 = 9টি variable.
- অন্তত একটি রং। প্রতিটি vertex-এ একটি clause: u1 ∨ u2 ∨ u3, v1 ∨ v2 ∨ v3, w1 ∨ w2 ∨ w3. মোট 3.
- বড়জোর একটি রং। প্রতিটি vertex-এ C(3, 2) = 3টি জোড়া: ¬u1 ∨ ¬u2, ¬u1 ∨ ¬u3, ¬u2 ∨ ¬u3. তিন vertex-এ 9; মোট 12.
- প্রতিবেশী আলাদা। ত্রিভুজে edge uv, vw, wu; প্রতিটিতে প্রতিটি রঙের জন্য একটি clause, যেমন ¬u1 ∨ ¬v1. 3 × 3 = 9; মোট 21 = 3 + 3·3 + 3·3.
- একটি model. u1, v2, w3 সত্য, বাকি ছয়টি মিথ্যা। প্রতিটি “অন্তত” clause-এ একটি সত্য, প্রতিটি “বড়জোর” আর edge clause-এ অন্তত একটি ¬ সত্য। উপপাদ্য ৩.২ অনুযায়ী model আছে মোট 3! = ৬টি, ত্রিভুজের ৬টি proper coloring-এর সমান।
- দুই রঙে। k = 2-এ 6টি variable, clause 3 + 3·1 + 3·2 = 12. ত্রিভুজ একটি বিজোড় cycle, তাই ২ রঙে রং করা যায় না; সূত্রটি UNSAT. নিচের কোড ঠিক এটাই পায়।
নিচের ব্লক এই encoder লেখে আর আগের ব্লকের dpll দিয়ে সমাধান করে (দুই ব্লক একই namespace ভাগ করে, তাই আগেরটি আগে চলে)। পরীক্ষার graph: ত্রিভুজ, বাজারঘরের সাতটি এলাকা A–G, আর wheel W5 (একটি কেন্দ্র H যুক্ত পাঁচ-cycle P, Q, R, S, T-এর সব vertex-এর সঙ্গে)। Model থেকে রং পড়ে আলাদা করে মেলানো হয় যে coloring সত্যিই proper.
from itertools import combinations
def colour_cnf(V, E, k): # variable x(v,c): "vertex v gets colour c"
var = {}
for v in V:
for c in range(k):
var[v, c] = len(var) + 1
cnf = [[var[v, c] for c in range(k)] for v in V] # at least one
cnf += [[-var[v, c], -var[v, d]] for v in V
for c, d in combinations(range(k), 2)] # at most one
cnf += [[-var[u, c], -var[w, c]] for u, w in E for c in range(k)] # ends differ
return var, cnf
def solve(name, V, E, k):
var, cnf = colour_cnf(V, E, k)
model = dpll(cnf, [], [0])
head = f"{name}, k={k}: {len(var)} vars, {len(cnf)} clauses ->"
if model is None:
print(head, "UNSAT"); return
col = {v: c for (v, c), i in var.items() if i in model}
print(head, "".join(str(col[v]) for v in V),
"proper:", all(col[u] != col[w] for u, w in E))
K3 = ["abc", ["ab", "bc", "ca"]]
solve("triangle", *K3, 2)
solve("triangle", *K3, 3)
zones = ["ABCDEFG", ["AB", "AC", "BC", "BD", "CD", "CE", "DF", "EF", "EG", "FG"]]
solve("zones", *zones, 3)
W5 = ["HPQRST", ["HP", "HQ", "HR", "HS", "HT", "PQ", "QR", "RS", "ST", "TP"]]
solve("wheel W5", *W5, 3)
solve("wheel W5", *W5, 4)
n, m = len(zones[0]), len(zones[1])
print("formula n + n*C(k,2) + m*k for zones, k=3:", n + n * 3 + m * 3)আসল আউটপুট (ব্রাউজারের Python):
triangle, k=2: 6 vars, 12 clauses -> UNSAT
triangle, k=3: 9 vars, 21 clauses -> 012 proper: True
zones, k=3: 21 vars, 58 clauses -> 0120012 proper: True
wheel W5, k=3: 18 vars, 54 clauses -> UNSAT
wheel W5, k=4: 24 vars, 82 clauses -> 012123 proper: True
formula n + n*C(k,2) + m*k for zones, k=3: 58
ত্রিভুজের দুই লাইন stepper-এর 12 আর 21 মেলায়। এলাকার graph-এ ABC একটি ত্রিভুজ, তাই অন্তত ৩ দিন লাগে, আর solver ৩ দিনের একটি বণ্টন দিয়েছে (A, D, E প্রথম দিন, …)। W5-এ ৩ রং UNSAT: পাঁচ-cycle-এ নিজেই ৩ রং লাগে, আর কেন্দ্র H সবার প্রতিবেশী, তাই চতুর্থ রং লাগে; k = 4-এ SAT. UNSAT উত্তরটি গুরুত্বপূর্ণ: এটি একটি প্রমাণ যে χ(W5) > 3, শুধু “খুঁজে পাইনি” নয় (DPLL সম্পূর্ণ, পর্ব ০৭-এর উপপাদ্য ৪.২)। শেষ লাইনে সূত্র n + n·C(k, 2) + m·k = 7 + 21 + 30 = 58 কোডের গণনার সঙ্গে মিলেছে।
শিফটের শর্ত — দুই literal-এর clause
বাজারঘরের তিন রাইডার a, b, c. প্রত্যেকের একটি সত্য-মিথ্যা variable: সত্য মানে সকালের শিফট, মিথ্যা মানে বিকেলের। ম্যানেজারের শর্তগুলো প্রায়ই দুটি রাইডার নিয়ে: “a বা b অন্তত একজন সকালে” = (a ∨ b); “a সকালে হলে b-ও সকালে (একই ভ্যান)” = (¬a ∨ b); “b আর c একসঙ্গে সকালে নয় (একটিই বাইক)” = (¬b ∨ ¬c). প্রতিটি clause-এ বড়জোর দুটি literal: এটি 2-SAT, যা §৫-এ polynomial সময়ে মেলানো যাবে। কিন্তু “তিনজনের অন্তত একজন” লিখলে তিন literal-এর clause আসে, আর সমস্যা আবার সাধারণ SAT-এর মতো কঠিন হতে পারে।
৪ · CDCL আর কঠিন সূত্র — শেখা clause, pigeonhole আর phase transition
পর্ব ০৭-এ দেখেছেন, আধুনিক solver CDCL: বিরোধ হলে কারণ খুঁজে একটি নতুন clause শেখে। §২-এর stepper-এ বিরোধ ঘটিয়েছিল C3 = (¬a ∨ d) আর C4 = (¬a ∨ ¬d). পর্ব ০৬ “অনুমানের নিয়ম”-এর resolution নিয়মে d বাদ দিলে পাওয়া যায় (¬a): শেখা clause. বাস্তব solver-এ বিরোধের পেছনে কয়েক ডজন অনুমান থাকতে পারে, তবু শেখা clause-এ সাধারণত অল্প কয়েকটি literal থাকে।
F থেকে resolution-এ পাওয়া যেকোনো clause L-এর জন্য F আর F ∧ L-এর model একই।
প্রমাণ: F ∧ L-এর প্রতিটি model F-এরও model. উল্টো দিকে, resolution sound (পর্ব ০৬): দুটি clause (x ∨ A) আর (¬x ∨ B) সত্য হলে x যা-ই হোক A ∨ B সত্য। তাই F-এর প্রতিটি model-এ resolution-এর প্রতিটি ধাপের ফল, আর শেষে L, সত্য। ∎ তাই solver যত খুশি clause শিখতে পারে, উত্তর বদলায় না; শুধু একই ভুল দ্বিতীয়বার হয় না।
এর একটি গভীর পরিণাম আছে। কোনো সূত্রে CDCL “UNSAT” বললে তার শেখা clause-এর ইতিহাস আসলে একটি resolution প্রমাণ, যার শেষে empty clause. তাই resolution-এর জন্য যে সূত্র কঠিন, CDCL-এর জন্যও তা কঠিন। বিখ্যাত উদাহরণ পর্ব ৩২-এর pigeonhole নীতি: n + 1টি অর্ডার, n-টি স্লট, কোনো স্লটে দুটি অর্ডার নয়। Pigeonhole সূত্র PHPn+1n-এ variable pi,j = “অর্ডার i স্লট j-তে”: প্রতিটি অর্ডারের জন্য pi,1 ∨ ⋯ ∨ pi,n, আর প্রতিটি স্লট j ও অর্ডার-জোড়া i < i′-এর জন্য ¬pi,j ∨ ¬pi′,j. মোট n(n + 1)টি variable আর (n + 1) + n·C(n + 1, 2)টি clause. পর্ব ৩২ এক লাইনে প্রমাণ করেছে যে এটি UNSAT; কিন্তু Armin Haken ১৯৮৫ সালে প্রমাণ করেন, এর যেকোনো resolution প্রমাণের আকার n-এর exponential. মানুষের যুক্তি গোনে (“n + 1 > n”), resolution গুনতে পারে না।
সত্যিকারের run ১: pigeonhole সূত্রে Z3
নিচের পরীক্ষাগুলো এই কোর্সের লেখার সময় একটি সাধারণ Windows ডেস্কটপে সত্যিকারভাবে চালানো হয়েছে: Python 3.12, z3-solver 5.4.1 (Microsoft Research-এ তৈরি SMT solver, MIT লাইসেন্স), uv দিয়ে বানানো একটি ছোট প্রজেক্টে। নিজের কম্পিউটারে চালাতে চাইলে:
cd ~/bazarghar-dm
uv add z3-solver
uv run python php_sat.pyimport time
from itertools import combinations
from z3 import Bool, Or, Not, SolverFor
# PHP(n+1, n): n+1 pigeons (orders), n holes (delivery slots), no sharing
def php(n):
p = [[Bool(f"p{i}_{j}") for j in range(n)] for i in range(n + 1)]
cls = [Or(row) for row in p] # every pigeon gets a hole
for j in range(n):
cls += [Or(Not(p[a][j]), Not(p[b][j])) for a, b in combinations(range(n + 1), 2)]
return cls
for n in range(6, 12):
cls = php(n)
s = SolverFor("QF_FD"); s.set("timeout", 60000) # pure Boolean, 60 s cap
s.add(cls)
t0 = time.perf_counter(); r = s.check(); dt = time.perf_counter() - t0
st = s.statistics()
conf = st.get_key_value("sat conflicts") if "sat conflicts" in st.keys() else "?"
print(f"n={n:2}: vars {n*(n+1):3}, clauses {len(cls):4}, {r}, {dt:7.2f} s, conflicts {conf}")$ uv run python php_sat.py
n= 6: vars 42, clauses 133, unsat, 0.00 s, conflicts 605
n= 7: vars 56, clauses 204, unsat, 0.01 s, conflicts 1956
n= 8: vars 72, clauses 297, unsat, 0.09 s, conflicts 5832
n= 9: vars 90, clauses 415, unsat, 1.40 s, conflicts 37821
n=10: vars 110, clauses 561, unsat, 5.62 s, conflicts 110598
n=11: vars 132, clauses 738, unsat, 57.98 s, conflicts 958941
Clause-এর সংখ্যা সূত্র মেলায়: n = 6-এ 7 + 6 · 21 = 133. সূত্রের আকার মাত্র কয়েক শো clause, তবু বিরোধের সংখ্যা প্রতি ধাপে মোটামুটি ৩ থেকে ৯ গুণ বেড়েছে, আর n = 11-এ সময় প্রায় এক মিনিট (৬০ সেকেন্ডের সীমার একটু নিচে)। §৩-এর ৫৮ clause-এর সূত্র মুহূর্তে মিলেছিল; আকার নয়, গঠনই কঠিনতা ঠিক করে। কিছু solver এই কারণে গোনার যুক্তি (cardinality reasoning) বা symmetry ভাঙার কৌশল যোগ করে। সময়ের সংখ্যা যন্ত্রভেদে বদলাবে।
এলোমেলো 3-SAT-এর phase transition
§২-এর ব্লকে দেখেছেন, clause/variable অনুপাত বাড়লে satisfiable সূত্রের ভাগ হঠাৎ পড়ে যায়। বড় n-এ এই পতন প্রায় একটি ধাপের মতো তীক্ষ্ণ হয়, আর পরীক্ষা ও পরিসংখ্যান-পদার্থবিদ্যার হিসাবে এর অবস্থান অনুপাত প্রায় ৪.২৭; একে বলে phase transition বা দশা-বদল। সবচেয়ে কঠিন সূত্রগুলো ঠিক এই এলাকায়: কম অনুপাতে model প্রচুর, সহজে মেলে; বেশি অনুপাতে বিরোধ দ্রুত আসে। Friedgut ১৯৯৯ সালে দেখান যে তীক্ষ্ণ পতন ঘটে, আর বড় k-এর জন্য সঠিক সীমা পরে প্রমাণিত হয়েছে বলে জানা; k = 3-এর সঠিক মান এখনো প্রমাণিত নয় বলেই সাধারণভাবে উল্লেখ করা হয়। এখানে সংখ্যাটি একটি পর্যবেক্ষণ, উপপাদ্য নয়।
৫ · 2-SAT — implication graph আর SCC-তে polynomial সময়
দুই literal-এর clause (x ∨ y) আসলে দুটি শর্ত: x মিথ্যা হলে y সত্য, আর y মিথ্যা হলে x সত্য, অর্থাৎ ¬x → y আর ¬y → x. এই implication-গুলো দিয়ে একটি directed graph বানানো যায়।
2-CNF সূত্র F-এর n-টি variable থেকে 2n-টি vertex (প্রতিটি literal x আর ¬x). প্রতিটি clause (α ∨ β) থেকে দুটি edge: ¬α → β আর ¬β → α. u থেকে v-তে directed path থাকলে লিখি u ⇝ v.
দুটি সরল পর্যবেক্ষণ। (১) প্রতিটি edge একটি সত্য implication: F-এর যেকোনো model-এ ¬α সত্য হলে clause (α ∨ β)-এর জন্য β সত্য। Path-এ এটি শিকলের মতো চলে, তাই model-এ u সত্য আর u ⇝ v হলে v-ও সত্য (path-এর দৈর্ঘ্যের ওপর induction দিয়ে)। (২) Skew symmetry: u ⇝ v ⇔ ¬v ⇝ ¬u, কারণ edge জোড়ায় জোড়ায় আসে: ¬α → β-এর জোড়া ¬β → α, অর্থাৎ উল্টো দিকে নেগেশন (প্রমাণ অনুশীলন ১-এ)।
2-CNF সূত্র F unsatisfiable ⇔ কোনো variable x-এর জন্য x আর ¬x একই strongly connected component-এ, অর্থাৎ x ⇝ ¬x আর ¬x ⇝ x দুটোই।
- (⇐) ধরুন দুটি path-ই আছে, আর F-এর একটি model আছে। x সত্য হলে x ⇝ ¬x পর্যবেক্ষণ (১) অনুযায়ী ¬x-কে সত্য করে, বিরোধ। x মিথ্যা হলে ¬x সত্য, আর ¬x ⇝ x দিয়ে x সত্য, বিরোধ। তাই model নেই। — পর্যবেক্ষণ ১
- (⇒) contrapositive: কোনো x-এ দুই path একসঙ্গে নেই ধরে একটি model বানাই। একটি মান-না-পাওয়া variable নিন; তার একটি literal ℓ বাছুন যাতে ℓ ⇝ ¬ℓ নেই (x ⇝ ¬x থাকলে ℓ = ¬x নিন; অনুমান অনুযায়ী তখন ¬x ⇝ x নেই)। ℓ থেকে পৌঁছানো যায় এমন সব literal সত্য করুন। — নির্মাণ
- এতে বিরোধ নেই: ℓ ⇝ u আর ℓ ⇝ ¬u দুটোই হলে skew symmetry-তে u ⇝ ¬ℓ, তাই ℓ ⇝ u ⇝ ¬ℓ, যা ℓ বাছাইয়ের বিরোধী। আগের ধাপে মিথ্যা হওয়া কোনো u-তেও পৌঁছায় না: আগে বাছা ℓ′ ⇝ ¬u হলে skew symmetry-তে u ⇝ ¬ℓ′, তাই ℓ ⇝ ¬ℓ′, আবার skew symmetry-তে ℓ′ ⇝ ¬ℓ; তাহলে ¬ℓ আগেই সত্য হয়ে ℓ-এর variable মান পেয়ে যেত। — skew symmetry
- প্রতিটি clause সত্য: (α ∨ β)-এ α মিথ্যা হলে ¬α সত্য, অর্থাৎ কোনো বাছা ℓ থেকে ¬α-তে পৌঁছানো যায়; edge ¬α → β থাকায় β-তেও, তাই β সত্য। সব variable মান না পাওয়া পর্যন্ত ধাপ ২–৩ চালালে একটি model পাওয়া যায়। ∎ — edge-এর সংজ্ঞা
SCC বের করা যায় O(n + m) সময়ে (Tarjan বা Kosaraju-র পদ্ধতি; graph-এর দিক পর্ব ৫৯-এ, আর C++/Python বাস্তবায়ন DSA কোর্সের পর্ব ৪৬-এ)। তাই 2-SAT রৈখিক সময়ে মেলে; এই রৈখিক-সময়ের পদ্ধতি সাধারণত Aspvall, Plass আর Tarjan-এর ১৯৭৯ সালের প্রবন্ধের নামে পরিচিত। “x ⇝ ¬x আছে কি” প্রশ্নটি আসলে পর্ব ৫৪-এর transitive closure-এর প্রশ্ন। §৩-এর তিন রাইডারের সঙ্গে চতুর্থ শর্ত যোগ করুন: “b সকালে হলে c-ও সকালে” = (¬b ∨ c).
(a ∨ b) ∧ (¬a ∨ b) ∧ (¬b ∨ c) ∧ (¬b ∨ ¬c)
চার clause-এর implication graph
ওপরে literal a, b, c; নিচে তাদের নেগেশন। প্রতিটি clause দুটি তীর দেয়, মোট আটটি। লাল তীরগুলো একটি cycle: b → c → ¬b → a → b, তাই b আর ¬b একই SCC-তে।
C1a ∨ b : ¬a→b, ¬b→aC2¬a ∨ b : a→b, ¬b→¬aC3¬b ∨ c : b→c, ¬c→¬bC4¬b ∨ ¬c : b→¬c, c→¬b
| edge | |
| path |
- Vertex. তিন variable, তাই ছয়টি literal: a, b, c, ¬a, ¬b, ¬c. এখনো কোনো edge নেই।
- C1 = a ∨ b. a মিথ্যা হলে b সত্য: ¬a → b; b মিথ্যা হলে a সত্য: ¬b → a.
- C2 = ¬a ∨ b. এখানে α = ¬a, তাই ¬α = a: a → b; আর ¬b → ¬a. মোট ৪টি edge.
- C3 = ¬b ∨ c. b → c আর ¬c → ¬b. মোট ৬টি।
- C4 = ¬b ∨ ¬c. b → ¬c আর c → ¬b. মোট ৮টি; ছবিটি এখন সম্পূর্ণ।
- b ⇝ ¬b. b → c (C3), তারপর c → ¬b (C4). অর্থাৎ b সত্য হলে c সত্য, আর তখন C4 b-কে মিথ্যা হতে বাধ্য করে। তাই কোনো model-এ b সত্য নয়।
- ¬b ⇝ b. ¬b → a (C1), তারপর a → b (C2). b মিথ্যাও হতে পারে না। b আর ¬b একই SCC-তে, উপপাদ্য ৫.২ অনুযায়ী UNSAT. ম্যানেজারকে বলা যায়: চতুর্থ শর্তটি C4-এর সঙ্গে মেলে না।
নিচের ব্লক উপপাদ্য ৫.২-এর প্রমাণটিকেই কোডে লেখে: প্রতিটি literal থেকে পৌঁছানো যায় এমন literal-এর সেট (ছোট n-এ এটুকুই যথেষ্ট; বড় n-এ SCC লাগে), x আর ¬x পরস্পর পৌঁছায় কি না পরীক্ষা, আর ধাপ ২–৩-এর নির্মাণে model. তারপর ৫১০টি এলোমেলো 2-CNF-এ brute force-এর সঙ্গে মেলায়, আর পাওয়া model সত্যিই সব clause মানে কি না দেখে।
import random
from itertools import product
def reach(G, s): # every literal reachable from s
seen, todo = {s}, [s]
while todo:
for w in G[todo.pop()]:
if w not in seen: seen.add(w); todo.append(w)
return seen
def two_sat(clauses, n):
G = {l: [] for v in range(1, n + 1) for l in (v, -v)}
for a, b in clauses: # (a or b) gives -a -> b and -b -> a
G[-a].append(b); G[-b].append(a)
R = {l: reach(G, l) for l in G}
if any(-v in R[v] and v in R[-v] for v in range(1, n + 1)):
return None # v and -v in one SCC
val = {}
for v in range(1, n + 1):
if v in val: continue
l = -v if -v in R[v] else v # never pick l with l ~> -l
for u in R[l]: val[u], val[-u] = True, False
return {v: val[v] for v in range(1, n + 1)}
def brute(clauses, n):
return any(all(row[abs(a) - 1] == (a > 0) or row[abs(b) - 1] == (b > 0)
for a, b in clauses) for row in product((False, True), repeat=n))
print("stepper:", two_sat([(1, 2), (-1, 2), (-2, 3), (-2, -3)], 3))
print("drop last clause:", two_sat([(1, 2), (-1, 2), (-2, 3)], 3))
random.seed(7); n = 8; tests = agree = sat = 0
for m in range(4, 21):
for _ in range(30):
C = [tuple(v * random.choice((1, -1)) for v in random.sample(range(1, n + 1), 2))
for _ in range(m)]
val = two_sat(C, n); ok = val is not None
good = not ok or all(val[abs(a)] == (a > 0) or val[abs(b)] == (b > 0) for a, b in C)
tests += 1; sat += ok; agree += (ok == brute(C, n)) and good
print(f"{tests} random 2-CNFs (n=8, m=4..20): satisfiable {sat}, agree with brute force {agree}")আসল আউটপুট (ব্রাউজারের Python):
stepper: None
drop last clause: {1: True, 2: True, 3: True}
510 random 2-CNFs (n=8, m=4..20): satisfiable 395, agree with brute force 510
Stepper-এর সূত্রে None: UNSAT. চতুর্থ clause বাদ দিলে তিনজনই সকালে, যা বাকি তিন শর্ত মানে (brute force-এ এমন model দুটি: b = c = T আর a যেকোনো)। ৫১০টি সূত্রের ৩৯৫টি satisfiable, আর সবকটিতে উত্তর brute force-এর সঙ্গে মিলেছে, প্রতিটি model-ও বৈধ। তুলনা করুন: 3-SAT-এর জন্য এমন কোনো polynomial পদ্ধতি জানা নেই।
৬ · SMT — theory, DPLL(T) আর bit-vector
কোডের দাবিতে থাকে সংখ্যা: “i < n”, “x + y উপচে পড়ে না”. পর্ব ০৭-এ দেখেছেন, SMT solver এমন সূত্র সরাসরি নেয়। “Modulo theories” মানে প্রতিটি পরমাণু-শর্ত (atom) একটি নির্দিষ্ট theory-র ভেতরে ব্যাখ্যা করা হয়:
| Theory | কী নিয়ে | উদাহরণ atom | সিদ্ধান্ত |
|---|---|---|---|
| Linear integer arithmetic (LIA) | পূর্ণসংখ্যা, যোগ, ধ্রুবক দিয়ে গুণ | 2x + 3y ≤ 7 | decidable (Presburger, ১৯২৯) |
| Bit-vector | নির্দিষ্ট বিটের যন্ত্র-পূর্ণসংখ্যা, mod 2w | x & (x − 1) = 0 | decidable; bit ভেঙে SAT-এ যায় |
| Array | read আর write | read(write(A, i, v), i) = v | decidable (quantifier ছাড়া) |
| অরৈখিক পূর্ণসংখ্যা | x · y-এর মতো গুণ | x · y = 91 | সাধারণভাবে undecidable |
শেষ সারির কারণ Hilbert-এর দশম সমস্যা: Matiyasevich ১৯৭০ সালে (Davis, Putnam আর Robinson-এর আগের কাজের ওপর) প্রমাণ করেন, পূর্ণসংখ্যার polynomial সমীকরণের সমাধান আছে কি না, তা কোনো algorithm সব ক্ষেত্রে বলতে পারে না। তাই solver “unknown”-ও বলতে পারে।
DPLL(T) — SAT আর theory-র কথোপকথন
ধরুন F = (x < 3 ∨ y > 5) ∧ (x > 4) ∧ (y < 2), x, y পূর্ণসংখ্যা। প্রতিটি atom-এর জায়গায় একটি Boolean variable বসালে কাঠামো (p ∨ q) ∧ r ∧ s. SAT solver বলে: p, r, s সত্য। Theory solver পরীক্ষা করে x < 3 ∧ x > 4 ∧ y < 2, যা অসম্ভব, আর ফেরত দেয় শেখা clause (¬p ∨ ¬r). SAT solver এবার q, r, s বেছে নেয়; theory বলে y > 5 ∧ y < 2 অসম্ভব, শেখা clause (¬q ∨ ¬s). এখন কাঠামোটি UNSAT, তাই F unsatisfiable. এই ভাগাভাগির নাম DPLL(T): SAT অংশ যুক্তির কাঠামো খোঁজে, theory অংশ শুধু atom-এর ∧ যাচাই করে। Bit-vector-এ আরেকটি পথ bit-blasting: w-বিটের প্রতিটি অপারেশনকে Boolean circuit-এ (যেমন adder) ভেঙে CNF বানানো, ঠিক পর্ব ৬৯-এর Boolean function-এর মতো। Two’s complement আর adder-এর হার্ডওয়্যার দিক Computer Architecture কোর্সের পর্ব ২০-এ।
সত্যিকারের run ২: bit-trick আর binary search-এর মাঝখান
Programmer-দের প্রিয় কৌশল x & (x - 1): x-এর সবচেয়ে ডানের 1-বিটটি মুছে দেয় (যেমন 40 = 101000 থেকে 32 = 100000 হয়)। আর x & -x শুধু সেই বিটটি রাখে। Python দিয়ে কয়েক হাজার মান পরীক্ষা করা যায়, কিন্তু 32-বিটে মান 232টি, প্রায় ৪৩০ কোটি। Z3 সবগুলোর জন্য একবারে উত্তর দেয়। prove(P) ভেতরে ¬P-এর model খোঁজে: না পেলে “proved”, পেলে সেটিই counterexample (পর্ব ০৭)।
from z3 import BitVec, BitVecVal, ZeroExt, Extract, LShR, ULE, Or, And, Implies, prove
x = BitVec("x", 32) # a 32-bit machine word
# x & (x - 1) clears the lowest 1-bit; x & -x keeps only that bit
print("1 clear lowest bit :", end=" "); prove(x & (x - 1) == x - (x & -x))
# power-of-two tests against the definition "x == 2^k for some k"
is_pow2 = Or([x == BitVecVal(1 << k, 32) for k in range(32)])
print("2 pow2 with x != 0 :", end=" "); prove(And(x != 0, x & (x - 1) == 0) == is_pow2)
print("3 pow2 without it :", end=" "); prove((x & (x - 1) == 0) == is_pow2)
# binary-search midpoint on unsigned 32-bit indices
lo, hi = BitVec("lo", 32), BitVec("hi", 32)
exact = Extract(31, 0, LShR(ZeroExt(1, lo) + ZeroExt(1, hi), 1)) # 33-bit sum: no overflow
print("4 (lo + hi) / 2 :", end=" "); prove(Implies(ULE(lo, hi), LShR(lo + hi, 1) == exact))
print("5 lo + (hi - lo)/2 :", end=" "); prove(Implies(ULE(lo, hi), lo + LShR(hi - lo, 1) == exact))$ uv run python bit_tricks.py
1 clear lowest bit : proved
2 pow2 with x != 0 : proved
3 pow2 without it : counterexample
[x = 0]
4 (lo + hi) / 2 : counterexample
[hi = 4294967295, lo = 1]
5 lo + (hi - lo)/2 : proved
লাইন ১: মোছার কৌশল সব 32-বিট x-এ সঠিক। লাইন ২–৩: “x ≠ 0 আর x & (x − 1) = 0” ঠিক তখন, যখন x 2-এর একটি ঘাত; শর্ত x ≠ 0 বাদ দিলে Z3 সঙ্গে সঙ্গে x = 0 দেখায়, কারণ 0 & (−1) = 0 অথচ 0 কোনো 2k নয়। লাইন ৪ বিখ্যাত bug: binary search-এ (lo + hi) / 2. lo = 1, hi = 4294967295 (= 232 − 1) নিলে যোগফল 232, যা 32 বিটে উপচে 0 হয়, আর “মাঝখান” আসে 0, অথচ আসল মাঝখান 2147483648. লাইন ৫-এর lo + (hi - lo) / 2 সব lo ≤ hi-তে সঠিক। লক্ষ করুন “আসল মাঝখান” লিখতে 33 বিটে বড় করা হয়েছে (ZeroExt), যাতে তুলনাটাই উপচে না পড়ে।
৭ · Program যাচাই — overflow আর bounded model checking
সত্যিকারের run ৩: ৮-বিটের overflow-check
৮-বিটের signed পূর্ণসংখ্যা −128 থেকে 127. যন্ত্রে a + b উপচে পড়লে mod 256-এ ঘুরে যায়: 100 + 50 = 150, যা signed-এ −106 পড়া হয়। কোডে প্রায়ই তিন রকম পরীক্ষা দেখা যায়; Z3-কে জিজ্ঞেস করি কোনটি “সত্যিকারের overflow”-এর সমান, যেখানে সত্যিকারের যোগফল 16 বিটে হিসাব করা হয় (SignExt দিয়ে বড় করে নিয়ে)।
from z3 import BitVec, SignExt, And, Or, prove
a, b = BitVec("a", 8), BitVec("b", 8) # signed 8-bit: -128 .. 127
s = a + b # what the machine computes (wraps mod 256)
wide = SignExt(8, a) + SignExt(8, b) # the true sum, in 16 bits
really_overflows = Or(wide > 127, wide < -128)
naive = And(a > 0, b > 0, s < 0) # only checks one direction
textbook = Or(And(a > 0, b > 0, s < 0), And(a < 0, b < 0, s >= 0))
xor_trick = ((s ^ a) & (s ^ b)) < 0 # sign of result differs from both
for name, check in [("naive", naive), ("textbook", textbook), ("xor trick", xor_trick)]:
print(f"{name:9}:", end=" ")
prove(check == really_overflows)$ uv run python overflow8.py
naive : counterexample
[b = 191, a = 189]
textbook : proved
xor trick: proved
Z3 bit-vector-এর মান unsigned হিসেবে ছাপে: a = 189 মানে signed-এ 189 − 256 = −67, b = 191 মানে −65. আসল যোগফল −132 < −128, অর্থাৎ overflow; যন্ত্রে 380 mod 256 = 124, একটি ধনাত্মক সংখ্যা। “naive” পরীক্ষা শুধু দুটি ধনাত্মক সংখ্যার উপচে পড়া দেখে, ঋণাত্মক দিকটি ভুলে যায়। বাকি দুটি সব 65,536টি জোড়ায় সঠিক প্রমাণিত। XOR কৌশলের যুক্তি: overflow ঘটে ঠিক তখন, যখন a আর b-এর চিহ্ন এক কিন্তু ফলের চিহ্ন আলাদা; তখন s ⊕ a আর s ⊕ b দুটোরই চিহ্ন-বিট 1. 65,536টি জোড়া একটি লুপেও পরীক্ষা করা যায়; কিন্তু 64 বিটে 2128টি জোড়া কোনো লুপে সম্ভব নয়, অথচ Z3-এর প্রশ্ন একই থাকে।
Model checking আর bounded model checking
বাজারঘরের গুদামে একটি পণ্যের স্টক ৮-বিটের unsigned সংখ্যা, শুরুতে 5. প্রতিটি ধাপে 1 থেকে 3 পিসের একটি অর্ডার আসে; স্টক যথেষ্ট হলে তা কাটা হয়। নিরাপত্তার দাবি: স্টক কখনো 5-এর বেশি হবে না (মাল আসে না, তাই বাড়ার কথা নয়)। এমন ব্যবস্থা একটি state machine: অবস্থা s, শুরুর শর্ত I(s), এক ধাপের সম্পর্ক T(s, s′), আর “খারাপ” অবস্থা Bad(s). Model checking মানে যাচাই করা যে শুরু থেকে কোনো খারাপ অবস্থায় পৌঁছানো যায় না; অবস্থার graph-এ এটি একটি reachability প্রশ্ন (পর্ব ৫৪)।
k ধাপের জন্য সূত্র BMCk = I(s0) ∧ T(s0, s1) ∧ ⋯ ∧ T(sk−1, sk) ∧ (Bad(s0) ∨ ⋯ ∨ Bad(sk)). BMCk satisfiable ⇔ k বা তার কম ধাপে কোনো খারাপ অবস্থায় পৌঁছানো যায়; তখন model-টিই bug-এর পূর্ণ ধাপ-তালিকা।
এর প্রমাণ সংজ্ঞা থেকেই: model মানে s0, …, sk-এর এমন মান যারা শুরুর শর্ত আর প্রতিটি ধাপ মানে, আর অন্তত একটি খারাপ, অর্থাৎ ঠিক একটি দৈর্ঘ্য ≤ k-এর খারাপ পথ। কিন্তু UNSAT শুধু বলে “k ধাপের মধ্যে নেই”. সব ধাপের জন্য লাগে পর্ব ১৩-এর loop invariant-এর মতো একটি inductive invariant J: (১) I(s) → J(s), (২) J(s) ∧ T(s, s′) → J(s′), (৩) J(s) → ¬Bad(s). তাহলে induction-এ প্রতিটি পৌঁছানো অবস্থায় J সত্য, তাই কোনোটি খারাপ নয়।
সত্যিকারের run ৪: স্টকের লুপে bug খোঁজা
দুটি সংস্করণ: ভুলটিতে শর্ত stock - q >= 0, যা unsigned সংখ্যায় সবসময় সত্য (unsigned বিয়োগের ফল কখনো ঋণাত্মক নয়, বরং ঘুরে যায়); ঠিকটিতে q <= stock.
from z3 import BitVec, BitVecVal, If, ULE, UGT, UGE, And, Or, Not, Solver, sat, unsat
# stock (unsigned 8-bit) starts at 5; each step an order of q in 1..3 arrives
def step(stock, q, buggy):
ok = UGE(stock - q, 0) if buggy else ULE(q, stock) # unsigned: first is always true
return If(ok, stock - q, stock)
def bmc(buggy, k_max):
for k in range(1, k_max + 1):
st = [BitVecVal(5, 8)]
qs = [BitVec(f"q{i}", 8) for i in range(k)]
s = Solver()
for q in qs:
s.add(UGT(q, 0), ULE(q, 3))
st.append(step(st[-1], q, buggy))
s.add(Or([UGT(x, 5) for x in st])) # bad state: stock above 5
if s.check() == sat:
m = s.model()
trace = [m.eval(x).as_long() for x in st]
return f"bug at depth {k}: orders {[m[q].as_long() for q in qs]}, stock {trace}"
return f"no bug up to depth {k_max}"
print("buggy guard :", bmc(True, 10))
print("fixed guard :", bmc(False, 10))
# induction: invariant stock <= 5 holds initially and survives one step
x, q = BitVec("x", 8), BitVec("q", 8)
s = Solver()
s.add(ULE(x, 5), UGT(q, 0), ULE(q, 3), Not(ULE(step(x, q, False), 5)))
print("fixed guard, all depths:", "invariant proved" if s.check() == unsat else s.model())$ uv run python bmc_stock.py
buggy guard : bug at depth 2: orders [3, 3], stock [5, 2, 255]
fixed guard : no bug up to depth 10
fixed guard, all depths: invariant proved
ভুল সংস্করণে BMC দ্বিতীয় ধাপেই bug পেল: 3 পিস কাটলে স্টক 2, তারপর আবার 3 পিসের অর্ডার, শর্ত সত্য, আর 2 − 3 ঘুরে 255. হঠাৎ গুদামে ২৫৫টি “পণ্য”! ঠিক সংস্করণে ১০ ধাপ পর্যন্ত কিছু পাওয়া যায়নি, যা নিজে প্রমাণ নয়। শেষ লাইন J = (stock ≤ 5) inductive কি না পরীক্ষা করে: J সত্য এমন যেকোনো অবস্থা থেকে এক ধাপ পরেও J সত্য (UNSAT মানে কোনো counterexample নেই)। শুরুতে 5 ≤ 5, তাই শর্ত (১)-ও সত্য, আর J নিজেই দাবিটি; তিন শর্ত মিলে সব ধাপের প্রমাণ। ধাপ k বাড়িয়ে এই induction-কে শক্তিশালী করার পদ্ধতিকে k-induction বলে।
৮ · বাস্তবে SAT আর SMT — আর তার সীমা
Model checking-এর ধারণার জন্য Edmund Clarke, E. Allen Emerson আর Joseph Sifakis ২০০৭ সালের Turing Award পান। SAT দিয়ে bounded model checking প্রস্তাব করেন Biere, Cimatti, Clarke আর Zhu, ১৯৯৯ সালের একটি প্রবন্ধে বলে উল্লেখ করা হয়। কয়েকটি ব্যবহার, যতটা প্রকাশ্যে নথিভুক্ত:
- হার্ডওয়্যার: processor-এর নকশায় equivalence checking (দুটি circuit সব ইনপুটে একই ফল দেয় কি না) আর BMC বহুল প্রচলিত বলে বর্ণিত; ১৯৯৪ সালের একটি বহুল আলোচিত processor-এর ভাগের bug-এর পর শিল্পে formal যাচাইয়ের আগ্রহ বাড়ে বলে প্রায়ই বলা হয়।
- Package-এর নির্ভরতা: “এই package-গুলো একসঙ্গে বসানো যায় কি, সংস্করণের শর্ত মেনে?” প্রশ্নটি সাধারণভাবে NP-complete, আর কিছু Linux distribution-এর package manager ও কিছু development tool এর জন্য SAT solver বা SAT-ভিত্তিক পদ্ধতি ব্যবহার করে বলে তাদের নথিতে বলা আছে।
- সফটওয়্যার আর cloud: Z3 নিজে Microsoft Research-এ তৈরি, আর তার পরীক্ষা-তৈরির (test generation) ও driver যাচাইয়ের গবেষণায় ব্যবহারের কথা প্রকাশিত। Amazon Web Services-এর গবেষকরা access-policy বিশ্লেষণে SMT ব্যবহারের পদ্ধতি নিয়ে প্রবন্ধ প্রকাশ করেছেন।
সীমাগুলোও স্পষ্ট বলা দরকার। (১) Undecidability: “এই প্রোগ্রাম কখনো খারাপ অবস্থায় যাবে কি না” সাধারণভাবে অনির্ণেয় (পর্ব ৭৫-এর halting problem আর Rice-এর উপপাদ্য)। BMC এড়িয়ে যায় ধাপ সীমিত করে, invariant এড়িয়ে যায় মানুষের অন্তর্দৃষ্টি চেয়ে। (২) Specification: solver শুধু লেখা দাবি প্রমাণ করে; দাবি ভুল হলে প্রমাণ অর্থহীন। (৩) মডেল আর বাস্তব: ℤ না 32-বিট, compiler-এর আচরণ, সমান্তরাল thread: মডেলে যা নেই, প্রমাণেও তা নেই। (৪) খরচ: §৪ দেখিয়েছে ছোট সূত্রও কঠিন হতে পারে, তাই timeout আর “unknown” বাস্তবের অংশ।
আরও গভীরে: SAT solver এখন খোলা গাণিতিক প্রশ্নেরও উত্তর দেয়: ২০১৬ সালে Heule, Kullmann আর Marek “Boolean Pythagorean triples” সমস্যা মেটান, যার UNSAT প্রমাণ-ফাইল প্রায় ২০০ terabyte বলে বর্ণিত; পরে Schur number five-ও একইভাবে মেটে। এমন দাবি বিশ্বাসযোগ্য করতে solver DRAT-এর মতো ফরম্যাটে প্রমাণ লেখে আর আলাদা, ছোট checker তা যাচাই করে। Proof complexity প্রশ্ন করে: কোন প্রমাণ-পদ্ধতিতে কোন সূত্রের ছোট প্রমাণ আছে; pigeonhole সূত্র resolution-এ কঠিন, কিন্তু cutting planes-এর মতো শক্তিশালী পদ্ধতিতে polynomial.
৯ · পরীক্ষার অঙ্ক আর একটি ভুল প্রমাণ
অঙ্ক ১: encoding-এর গণনা
(ক) Petersen graph (১০টি vertex, ১৫টি edge)-এর 3-coloring-এর CNF-এ কয়টি variable আর clause? (খ) PHP1110-এ কয়টি?
- (ক) variable nk = 10 · 3 = 30; clause n + n·C(3, 2) + m·k = 10 + 30 + 45 = 85.
- (খ) variable 10 · 11 = 110; clause 11 + 10 · C(11, 2) = 11 + 550 = 561, §৪-এর সত্যিকারের run-এর সঙ্গে মেলে। (দুটিই scratch-এ মেলানো।)
অঙ্ক ২: DPLL শুধু unit দিয়ে
(p) ∧ (¬p ∨ q) ∧ (¬q ∨ r) ∧ (¬r ∨ ¬p ∨ s)-এ DPLL চালান।
- (p) unit: p = T. দ্বিতীয় clause থেকে ¬p মুছে (q): q = T. তৃতীয় থেকে (r): r = T.
- চতুর্থ থেকে ¬r আর ¬p মুছে (s): s = T. সব clause সত্য, কোনো অনুমান লাগেনি। Brute force-এ ১৬টি সারির মধ্যে model ঠিক একটি, p = q = r = s = T.
অঙ্ক ৩: 2-SAT হাতে
(x ∨ y) ∧ (¬x ∨ y) ∧ (x ∨ ¬y) ∧ (¬x ∨ ¬y) কি satisfiable?
- Edge: ¬x→y, ¬y→x; x→y, ¬y→¬x; ¬x→¬y, y→x; x→¬y, y→¬x.
- x → y → ¬x আর ¬x → y → x: x আর ¬x একই SCC-তে, তাই UNSAT (চারটি clause মিলে দুই variable-এর চারটি সারির প্রতিটিকে বাদ দেয়; brute force-এ মেলানো)।
প্রমাণের ভুল ধরুন: “3-SAT-ও সহজ”
একজন লিখলেন: “প্রতিটি 3-clause (a ∨ b ∨ c)-কে (a ∨ b) ∧ (b ∨ c)-এ ভাঙুন। ফল একটি 2-CNF, যা §৫ অনুযায়ী রৈখিক সময়ে মেলে। তাই 3-SAT polynomial, আর P = NP. ∎”
ভুল কোথায়: রূপান্তরটি সমতুল্য নয়, এমনকি equisatisfiable-ও নয়। a = F, b = F, c = T-তে (a ∨ b ∨ c) সত্য, অথচ (a ∨ b) মিথ্যা: একটি satisfiable 3-CNF ভাঙা রূপে UNSAT হয়ে যেতে পারে। Reduction-এর প্রমাণে সবসময় দুই দিক দেখাতে হয় (উপপাদ্য ৩.২-এর মতো)। সঠিক তথ্য: সহায়ক variable দিয়ে যেকোনো CNF-কে 3-CNF-এ নেওয়া যায়, কিন্তু 2-CNF-এ নয়, যদি না P = NP.
পরীক্ষা ও ইন্টারভিউতে: বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এই ধরনের প্রশ্ন প্রায়ই আসে:
- Pure literal কী, আর তাকে সত্য ধরা নিরাপদ কেন? যে literal-এর নেগেশন সূত্রে নেই; তাকে সত্য করলে কোনো clause মিথ্যা হয় না, তাই satisfiability বদলায় না।
- 2-SAT কখন UNSAT? Implication graph-এ কোনো x আর ¬x একই SCC-তে থাকলে।
- n vertex, m edge-এর k-coloring-এ কয়টি clause? n + n·C(k, 2) + m·k.
- BMC-তে k ধাপে bug না পেলে কী জানা গেল? শুধু k ধাপের মধ্যে bug নেই; সব ধাপের জন্য inductive invariant লাগে।
- x & (x − 1) কী করে? সবচেয়ে ডানের 1-বিট মোছে; ফল 0 আর x ≠ 0 হলে x 2-এর ঘাত।
১০ · AI-এর যুগে — encoding আর solver-এর উত্তর যাচাই
AI সহকারী Z3 বা SAT-এর কোড খুব দ্রুত লেখে। বিপদটা সেখানেই: ভুল encoding-এ solver নির্ভুলভাবে ভুল প্রশ্নের উত্তর দেয়, আর “proved” দেখে সবাই নিশ্চিন্ত হয়। গণিতের প্রমাণে AI-এর সাধারণ ভুলগুলোও এখানে আসে: আত্মবিশ্বাসী কিন্তু ভুল প্রমাণ, এক দিক দেখিয়ে দুই দিক দাবি, শর্ত বাদ দেওয়া উপপাদ্য, বানানো reference.
| AI-এর ভুল | কীভাবে ধরবেন |
|---|---|
| “ঠিক একটি”-র জায়গায় শুধু “অন্তত একটি” লেখা, বা edge-এর clause বাদ | ছোট graph-এ model গুনে proper coloring-এর সংখ্যার সঙ্গে মেলান (উপপাদ্য ৩.২) |
| Python-এর অসীম int ধরে প্রমাণ, যেখানে কোড চলে 32-বিটে | Int নয়, BitVec ব্যবহার হয়েছে কি না দেখুন; উপচে পড়ার মান (231 − 1, −231) আলাদা পরীক্ষা করুন |
| signed আর unsigned তুলনা গুলিয়ে ফেলা (Z3-এ < signed, ULT unsigned) | প্রতিটি তুলনার ধরন লিখে নিন; counterexample-এর মান signed-এ রূপান্তর করে পড়ুন |
| BMC-র “no bug up to depth k”-কে প্রমাণ বলা | Inductive invariant-এর তিন শর্ত আলাদা করে পরীক্ষা করুন |
| “unknown” বা timeout-কে “সঠিক” পড়া, বা SAT/UNSAT উল্টো পড়া | prove(P) মানে ¬P-এর UNSAT; ফলাফলের অর্থ নিজে লিখে নিন |
| Reduction-এর এক দিক দেখিয়ে সমতুল্যতা দাবি | ছোট উদাহরণে brute force দিয়ে দুই দিকের উত্তর মেলান |
AI-কে SAT বা SMT-এর কাজ দিলে এমন কাঠামো চান, যাতে প্রতিটি দাবি আলাদা করে যাচাই করা যায়:
AI-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১Binary search-এর একটি লাইন প্রায় এক দশক লাইব্রেরিতে ভুল থেকে গেল কীভাবে?
২০০৬ সালে Joshua Bloch একটি বহুল পঠিত লেখায় জানান, Java-র standard library-র binary search (আর merge sort) একটি লাইনে ভুল: mid = (low + high) / 2. প্রায় নয় বছর ধরে এটি সেখানে ছিল বলে তিনি লেখেন। ভুলটি §৬-এর run-এ Z3 যা দেখাল সেটাই: low আর high দুটোই বড় হলে যোগফল signed 32-বিটে উপচে ঋণাত্মক হয়, আর array-র বাইরের index আসে। Bloch আরও লেখেন, Jon Bentley-র “Programming Pearls” বইয়ে প্রমাণসহ দেওয়া binary search-এও একই ভুল ছিল।
প্রমাণ থাকা সত্ত্বেও ভুল কেন? কারণ প্রমাণটি ছিল গণিতের পূর্ণসংখ্যা ℤ-এর জন্য, যেখানে low + high কখনো উপচে পড়ে না। কোড চলে যন্ত্রের 32-বিটে। প্রমাণ নিজে নির্ভুল, কিন্তু মডেল আর বাস্তবের মধ্যে ফাঁক ছিল। আর testing ধরতে পারেনি, কারণ ভুলটি দেখা দেয় শুধু প্রায় একশো কোটি (230) বা তার বেশি উপাদানের array-তে, যা তখন বিরল ছিল।
Edsger Dijkstra-র বহুল উদ্ধৃত কথা: testing bug-এর উপস্থিতি দেখাতে পারে, অনুপস্থিতি নয়। Bit-vector theory-তে SMT এই ফাঁক বন্ধ করে: 264টি (low, high) জোড়ার কোনোটি পরীক্ষা না করেও সবগুলোর জন্য উত্তর দেয়, কারণ solver প্রশ্নটিকে প্রতীক দিয়ে, bit ভেঙে, যুক্তির সমস্যা হিসেবে মেটায়। সমাধান দুই রকম: low + (high - low) / 2, বা unsigned shift দিয়ে (low + high) >>> 1, যা Java-তে signed যোগের উপচে পড়া বিট ঠিকভাবে ফিরিয়ে আনে।
শিক্ষাটি এই পর্বের মূল সুর: প্রমাণের আগে ঠিক করুন কোন জগতে প্রমাণ করছেন। পর্ব ১৩-এর Hoare logic, পর্ব ০৭-এর Z3 আর এখানের BitVec, সবখানে প্রশ্ন একই: specification আর মডেল কি বাস্তব যন্ত্রকে ঠিকঠাক ধরেছে?
মূল উপলব্ধি: নির্ভুল প্রমাণ ভুল মডেলে কোনো নিশ্চয়তা দেয় না; যন্ত্র-পূর্ণসংখ্যার দাবি যন্ত্র-পূর্ণসংখ্যার theory-তেই প্রমাণ করতে হয়।
প্র ০২Solver “UNSAT” বললে বিশ্বাস করবেন কেন? Solver-এরও তো bug থাকতে পারে।
SAT উত্তর সহজে যাচাইযোগ্য: model-টি নিয়ে প্রতিটি clause একবার দেখলেই হয়, solver-কে বিশ্বাস করার দরকার নেই। UNSAT আলাদা। “কোনো model নেই” দাবির সংক্ষিপ্ত সাক্ষী সাধারণভাবে জানা নেই; থাকলে NP = coNP হতো, যা বেশিরভাগ গবেষক অসত্য মনে করেন। আধুনিক solver লক্ষ লক্ষ লাইনের অপ্টিমাইজ করা কোড, তাতে bug থাকা অস্বাভাবিক নয়, আর SAT প্রতিযোগিতায় ভুল উত্তর দেওয়া solver ধরা পড়ার ঘটনাও বর্ণিত আছে।
সমাধান হলো প্রমাণ-সার্টিফিকেট। §৪-এর Lemma ৪.১ দেখিয়েছে, CDCL-এর প্রতিটি শেখা clause resolution-এ পাওয়া যায়। তাই solver চাইলে প্রতিটি শেখা clause-এর একটি তালিকা লিখে রাখতে পারে, যার শেষে empty clause. একটি ছোট, আলাদা প্রোগ্রাম (checker) সেই তালিকার প্রতিটি clause সত্যিই আগেরগুলো থেকে আসে কি না যাচাই করে। এই কাজের জন্য বহুল প্রচলিত ফরম্যাট DRAT, আর বড় SAT প্রতিযোগিতায় UNSAT উত্তরের সঙ্গে এমন প্রমাণ দাবি করা হয় বলে জানা যায়। Checker এত ছোট যে তাকে নিজেই proof assistant-এ formally যাচাই করা হয়েছে, এমন checker-ও আছে বলে বর্ণিত।
এই পদ্ধতির সবচেয়ে নাটকীয় ব্যবহার ২০১৬ সালের Boolean Pythagorean triples সমস্যা: 1 থেকে n পর্যন্ত সংখ্যাকে দুই রঙে রং করা যায় কি, যাতে a2 + b2 = c2-এর কোনো ত্রয়ী এক রঙের না হয়? Heule, Kullmann আর Marek দেখান n = 7824 পর্যন্ত যায়, 7825-এ যায় না; UNSAT অংশের প্রমাণ-ফাইল প্রায় ২০০ terabyte বলে প্রকাশিত। এটি পর্ব ৭৬ “Ramsey theory আর extremal combinatorics-এর পরিচয়”-এর ধাঁচের প্রশ্ন, উত্তর এসেছে SAT থেকে।
দার্শনিক প্রশ্ন থেকেই যায়: কোনো মানুষ যে প্রমাণ পড়তে পারে না, তা কি “প্রমাণ”? চার রঙের উপপাদ্যের কম্পিউটার-প্রমাণ (পর্ব ৬৩) নিয়েও একই বিতর্ক হয়েছিল। আজকের উত্তর মোটামুটি এই: বিশ্বাস রাখা হয় ছোট, যাচাইযোগ্য checker-এর ওপর, বিশাল solver-এর ওপর নয়।
মূল উপলব্ধি: SAT-এর সাক্ষী ছোট, UNSAT-এর সাক্ষী হতে পারে বিশাল; তাই solver resolution-ভিত্তিক প্রমাণ লেখে, আর বিশ্বাস সরে যায় একটি ছোট checker-এর দিকে।
প্র ০৩Pigeonhole মানুষের কাছে এক লাইনের যুক্তি, অথচ solver-এর কাছে এত কঠিন কেন?
মানুষ বলে: “n + 1টি অর্ডার, n-টি স্লট; প্রতিটি অর্ডারের একটি স্লট লাগবে আর কোনো স্লটে দুটি নয়, তাই অন্তত n + 1টি স্লট লাগবে। বিরোধ।” এই যুক্তি গোনে: সে সংখ্যার তুলনা করে। কিন্তু resolution-এর ভাষায় সংখ্যা নেই, আছে শুধু clause: (x ∨ A) আর (¬x ∨ B) থেকে (A ∨ B) পাওয়া। গোনার কাজটি clause দিয়ে করতে গেলে অনেক মধ্যবর্তী তথ্য লিখে রাখতে হয়, যেমন “এই অর্ডারগুলো এই স্লটগুলোতে যেতে পারে না”, আর এমন সমন্বয়ের সংখ্যা বিশাল।
Armin Haken ১৯৮৫ সালে প্রমাণ করেন, PHPn+1n-এর যেকোনো resolution প্রমাণে exponential সংখ্যক clause লাগে। প্রমাণের মূল ধারণা: যেকোনো ছোট প্রমাণে কোনো না কোনো clause-কে অনেক “তথ্য” একসঙ্গে বহন করতে হবে, আর একটি bottleneck যুক্তি দেখায় এমন বড় clause অনেকগুলো লাগে। পরে অনেক গবেষক এই ফল সরল ও শক্তিশালী করেছেন, আর এখান থেকেই proof complexity একটি পূর্ণ গবেষণা-ক্ষেত্র হয়ে ওঠে।
যেহেতু CDCL-এর UNSAT ইতিহাস একটি resolution প্রমাণ (Lemma ৪.১-এর আলোচনা), solver-এর সময়ও exponential হতে বাধ্য। §৪-এর run-এ n = 11-এ প্রায় দশ লক্ষ বিরোধ আর এক মিনিট লেগেছে, মাত্র ৭৩৮টি clause-এ। অথচ শক্তিশালী প্রমাণ-পদ্ধতি আছে: cutting planes রৈখিক অসমতা দিয়ে যুক্তি করে (সব “অন্তত একটি” যোগ করলে ≥ n + 1, সব “বড়জোর একটি” যোগ করলে ≤ n), আর সেখানে polynomial আকারের প্রমাণ আছে। Extended resolution-এও (নতুন variable-এর সংজ্ঞা যোগ করার অনুমতি দিলে) polynomial প্রমাণ জানা।
ব্যবহারিক শিক্ষা: সমস্যায় গোনার গঠন থাকলে শুধু CNF-এ ছেড়ে না দিয়ে cardinality constraint বা pseudo-Boolean solver ব্যবহার করুন, বা symmetry ভাঙার clause যোগ করুন (যেমন “অর্ডার ১ স্লট ১-এ”)। একটি সমস্যা কোন প্রমাণ-পদ্ধতিতে সহজ, তা জানা মানে সঠিক যন্ত্র বেছে নেওয়া।
মূল উপলব্ধি: Solver-এর শক্তি তার প্রমাণ-পদ্ধতির শক্তির সমান; resolution গুনতে পারে না, তাই pigeonhole-এর মতো গোনার যুক্তি তার কাছে exponential কঠিন।
প্র ০৪2-SAT সহজ আর 3-SAT কঠিন: মাত্র একটি literal-এর পার্থক্যে এত বড় ফারাক কেন?
দুই literal-এর clause-এর একটি বিশেষ ধর্ম আছে: একটি literal মিথ্যা হলেই অন্যটি বাধ্য হয়। তাই প্রতিটি clause একটি implication, আর পুরো সূত্র একটি directed graph. একটি মান বসালে তার সব ফল graph-এ path ধরে ছড়ায়, কোনো অনুমান লাগে না, আর বিরোধ মানেই একটি বিশেষ cycle (x ⇝ ¬x ⇝ x), যা একই SCC-তে x আর ¬x রাখে। Graph-এর reachability আর SCC রৈখিক সময়ের প্রশ্ন, তাই 2-SAT-ও। Krom ১৯৬৭ সালে 2-SAT-এর এই কাঠামো নিয়ে কাজ করেছিলেন বলে উল্লেখ করা হয়, আর রৈখিক-সময়ের পরিচিত পদ্ধতিটি Aspvall, Plass আর Tarjan-এর (১৯৭৯)।
তিন literal-এর clause (a ∨ b ∨ c)-এ একটি literal মিথ্যা হলে কিছুই বাধ্য হয় না: বাকি দুটির যেকোনোটি সত্য হলেই চলে। এখানেই অনুমান আর backtracking-এর জন্ম। একটি সিদ্ধান্তের ফল আর একটি path নয়, একটি শাখা-গাছ। আর Cook–Levin অনুযায়ী 3-SAT NP-complete, অর্থাৎ এর polynomial পদ্ধতি পাওয়া মানে P = NP.
একই প্যাটার্ন discrete math-এ বারবার দেখা যায়। 2-coloring (bipartite কি না) BFS দিয়ে রৈখিক সময়ে মেলে, কারণ একটি vertex-এর রং তার প্রতিবেশীর রং বাধ্য করে; 3-coloring NP-complete (পর্ব ৬৩)। Euler path সহজ, Hamilton path কঠিন (পর্ব ৬০)। Bipartite matching সহজ (পর্ব ৬৪), তিন-মাত্রিক matching কঠিন। প্রতিবার সহজ সংস্করণে একটি সিদ্ধান্ত পরেরটিকে পুরোপুরি ঠিক করে দেয়, কঠিন সংস্করণে বিকল্প খোলা থাকে।
বাস্তবে এর অর্থ: সমস্যা encode করার সময় দেখুন সব শর্ত দুই-literal-এ লেখা যায় কি না। §৩-এর রাইডারের শিফট ঠিক এমন ছিল। আর Horn clause-এর মতো আরও কিছু বিশেষ শ্রেণিও polynomial; Schaefer-এর ১৯৭৮ সালের dichotomy উপপাদ্য Boolean constraint-এর কোন ধরন সহজ আর কোনটি NP-complete, তার পূর্ণ তালিকা দেয় বলে পরিচিত।
মূল উপলব্ধি: দুই literal-এ প্রতিটি মিথ্যা literal অন্যটিকে বাধ্য করে, তাই যুক্তি graph-এর path হয়ে যায়; তিন literal-এ বিকল্প খোলা থাকে, আর সেখানেই কঠিনতা।
প্র ০৫Bounded model checking এত সফল কেন, যদি তা সব ধাপের প্রমাণ দেয় না?
১৯৮০-এর দশকে Clarke ও Emerson এবং আলাদাভাবে Queille ও Sifakis model checking প্রস্তাব করেন: একটি সসীম অবস্থা-ব্যবস্থায় সব পৌঁছানো অবস্থা ঘুরে দেখে temporal logic-এর একটি দাবি যাচাই। সমস্যা ছিল state explosion: 100টি বিটের অবস্থা মানে 2100টি অবস্থা। ১৯৯০-এর দশকে BDD (binary decision diagram) দিয়ে অবস্থার সেটকে সংক্ষেপে রাখার কৌশল বড় অগ্রগতি আনে, কিন্তু কিছু circuit-এ BDD-ও বিশাল হয়ে যেত।
১৯৯৯ সালে Biere, Cimatti, Clarke আর Zhu-র প্রস্তাবিত bounded model checking ভিন্ন পথ নেয়: অবস্থার সেট নয়, শুধু k দৈর্ঘ্যের পথ খোঁজা, একটি SAT সূত্রে (সংজ্ঞা ৭.১)। ঠিক তখনই CDCL solver দ্রুত শক্তিশালী হচ্ছিল, তাই BMC সেই অগ্রগতির পুরো সুবিধা পেল। আর বাস্তবে বেশিরভাগ bug অগভীর: শুরু থেকে অল্প কয়েক ধাপে পৌঁছানো যায়, যেমন §৭-এর স্টকের bug দুই ধাপে। BMC এমন bug দ্রুত খোঁজে, আর সবচেয়ে বড় উপহার দেয়: একটি নির্দিষ্ট, পুনরায় চালানো যায় এমন ধাপ-তালিকা (counterexample trace), যা প্রকৌশলীকে সরাসরি দেখায় কী ঘটছে।
পূর্ণ প্রমাণের জন্য BMC-কে অন্য কৌশলের সঙ্গে মেলানো হয়। k-induction (§৭-এর শেষ লাইনের সাধারণ রূপ) দেখায় যে k ধাপ ভালো থাকলে পরের ধাপও ভালো। Craig interpolation আর IC3/PDR-এর মতো পদ্ধতি স্বয়ংক্রিয়ভাবে inductive invariant খোঁজে; পর্ব ১৩-এ যে invariant মানুষ খুঁজেছিল, এরা যন্ত্র দিয়ে তা খুঁজতে চায়। সফটওয়্যারে CBMC-এর মতো টুল C প্রোগ্রামের লুপকে k বার খুলে (unrolling) BMC চালায় বলে বর্ণিত।
সীমাটি সৎভাবে বলতে হয়: “k = 20 পর্যন্ত কিছু পাইনি” একটি তথ্য, প্রমাণ নয়। একটি কাউন্টার যা 21 ধাপে উপচে পড়ে, k = 20-এর BMC তা দেখবে না। তাই রিপোর্টে সীমাটি লিখে রাখা পেশাদার সততার অংশ।
মূল উপলব্ধি: BMC সম্পূর্ণতার বিনিময়ে গতি আর পরিষ্কার counterexample পায়, আর অগভীর bug-এর জগতে তা অত্যন্ত কার্যকর; সব ধাপের নিশ্চয়তার জন্য inductive invariant লাগে।
অনুশীলন
আগে খাতায় করুন, তারপর উত্তর খুলে মেলান।
-
প্রমাণ: 2-CNF-এর implication graph-এ প্রমাণ করুন: u ⇝ v ⇔ ¬v ⇝ ¬u, অর্থাৎ skew symmetry সত্য।
- প্রথমে একটি edge: u → v আছে মানে কোনো clause (α ∨ β)-এ u = ¬α, v = β (বা উল্টো ভূমিকায়)। একই clause দ্বিতীয় edge দেয় ¬β → α, অর্থাৎ ¬v → ¬u. — সংজ্ঞা ৫.১
- (⇒) path-এর দৈর্ঘ্য L-এ induction. L = 0-এ u = v, তাই ¬v = ¬u. ধরুন L দৈর্ঘ্যে সত্য; L + 1 দৈর্ঘ্যের path u ⇝ w → v-তে hypothesis দেয় ¬w ⇝ ¬u, আর ধাপ ১ দেয় ¬v → ¬w; জুড়লে ¬v ⇝ ¬u. — induction
- (⇐) ¬v ⇝ ¬u-তে (⇒) প্রয়োগ করলে ¬¬u ⇝ ¬¬v, অর্থাৎ u ⇝ v. ∎ — double negation
-
গণনা: (ক) n = 5 vertex আর m = 6 edge-এর graph-এর 4-coloring-এর CNF-এ কয়টি variable আর clause? (খ) PHP65-এ কয়টি? (গ) n-টি variable-এর 2-CNF-এর implication graph-এ কয়টি vertex, আর m-টি clause থেকে কয়টি edge?
(ক) variable 5 · 4 = 20; clause 5 + 5 · C(4, 2) + 6 · 4 = 5 + 30 + 24 = 59. (খ) variable 5 · 6 = 30; clause 6 + 5 · C(6, 2) = 6 + 75 = 81. (গ) 2nটি vertex আর বড়জোর 2mটি edge (দুটি clause একই edge দিলে কম)। সব সংখ্যা scratch-এ মেলানো।
-
আউটপুট অনুমান: কোড না চালিয়ে বলুন কী ছাপা হবে। এরপর কোন নিয়মে (unit না pure) DPLL শেষ ধাপটি নেবে? তারপর যেকোনো Python-এ চালিয়ে মেলান।
def simplify(cnf, lit): return [[l for l in c if l != -lit] for c in cnf if lit not in c] F = [[1, 2], [-1, 3], [-3, -2], [2, 3]] print(simplify(F, 1)) print(simplify(simplify(F, 1), 3))[[3], [-3, -2], [2, 3]] [[-2]]literal 1 সত্য: [1, 2] বাদ, [−1, 3] থেকে −1 মুছে [3], বাকি দুটি অপরিবর্তিত। তারপর 3 সত্য: [3] আর [2, 3] বাদ, [−3, −2] থেকে −3 মুছে [−2]. শেষে একটি unit clause, তাই নিয়মটি unit: 2 = F, আর সূত্র SAT (1 = T, 2 = F, 3 = T হলে সব clause সত্য)। CPython 3.12-এ চালিয়ে মেলানো।
-
যুক্তির ভুল ধরুন: “একটি কাউন্টার c শুরুতে 0; প্রতি ধাপে c < 1000 হলে c এক বাড়ে, আর c = 1000 হলে 0 হয়। দাবি: c কখনো 20 ছাড়ায় না। BMC k = 1 থেকে 10 পর্যন্ত কোনো counterexample পায়নি। অতএব দাবি সত্য। ∎” ভুল কোথায়?
- ভুল ধাপ: BMCk-এর UNSAT শুধু বলে k ধাপের মধ্যে খারাপ অবস্থা নেই (সংজ্ঞা ৭.১)। দশ ধাপে c বড়জোর 10, তাই কিছু পাওয়ার কথাই ছিল না।
- পাল্টা-উদাহরণ: ২১ ধাপ পরে c = 21 > 20. BMC k = 21 দিলে সঙ্গে সঙ্গে এই পথটি দেখাত।
- সঠিক পদ্ধতি: সব ধাপের দাবির জন্য inductive invariant লাগে। এখানে c ≤ 20 inductive নয় (c = 20 থেকে এক ধাপে 21); আর দাবিটি নিজেই মিথ্যা।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি state machine-এ bounded model checking চালানোর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- শুরুর অবস্থার শর্ত I(s0) লেখা
- এক ধাপের সম্পর্ক T-কে k বার জুড়ে s0 থেকে sk পর্যন্ত খোলা
- Bad(s0) ∨ ⋯ ∨ Bad(sk) যোগ করা
- পুরো সূত্রটি SAT বা SMT solver-কে দেওয়া
- SAT হলে model থেকে bug-এর ধাপ-তালিকা পড়া, UNSAT হলে বলা “k ধাপ পর্যন্ত নিরাপদ”
৫টি vertex আর ৬টি edge-এর একটি graph-এর 3-coloring-কে প্রচলিত CNF-এ লেখা হলো (প্রতিটি vertex-এ অন্তত একটি ও বড়জোর একটি রং, আর প্রতিটি edge ও রঙে দুই প্রান্ত আলাদা)। মোট কয়টি clause?
- 23
- 26
- 38
- 15
একটি 2-CNF সূত্রের implication graph-এ কোন অবস্থায় সূত্রটি unsatisfiable?
- Graph-এ কোনো directed cycle থাকলে
- কোনো variable x আর ¬x একই strongly connected component-এ থাকলে
- কোনো x-এর জন্য x থেকে ¬x-এ path থাকলেই
- Edge-এর সংখ্যা vertex-এর সংখ্যার চেয়ে বেশি হলে
একটি CNF সূত্রে literal p আছে, কিন্তু ¬p কোথাও নেই। DPLL p = সত্য বসালে নিচের কোনটি নিশ্চিত?
- সব model রক্ষা পায়
- একটি বিরোধ তৈরি হতে পারে
- p একটি unit clause হতে হবে
- Satisfiable কি না, সেই উত্তর বদলায় না
CDCL solver দুটি clause (¬a ∨ d) আর (¬a ∨ ¬d)-এর বিরোধ থেকে resolution দিয়ে কোন clause শেখে?
- (¬a)
- (d ∨ ¬d)
- (a)
- Empty clause
32-বিটের unsigned x = 40 হলে x & (x − 1)-এর মান কত?
- 39
- 32
- 8
- 0
একটি সফটওয়্যারের bounded model checking-এ k = 1 থেকে 15 পর্যন্ত প্রতিটি সূত্র UNSAT এলো। কী বলা যায়?
- দাবিটি সব ধাপে সত্য
- ১৫ ধাপের মধ্যে অন্তত একটি bug আছে
- শুরু থেকে ১৫ বা তার কম ধাপে কোনো খারাপ অবস্থায় পৌঁছানো যায় না
- কিছুই বলা যায় না
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- DPLL: unit (কোনো model হারায় না), pure literal (শুধু satisfiability রাখে), তারপর অনুমান আর backtrack; CDCL বিরোধ থেকে resolution-এ clause শেখে, যা model বদলায় না।
- Encoding: k-coloring-এ nk variable আর n + n·C(k, 2) + m·k clause; সঠিকতা প্রমাণ করতে দুই দিক দেখান, model আর সমাধানের bijection সবচেয়ে ভালো।
- কঠিনতা: PHPn+1n resolution-এ exponential (Haken ১৯৮৫); এলোমেলো 3-SAT সবচেয়ে কঠিন অনুপাত ≈ ৪.২৭-এর কাছে (পর্যবেক্ষণ)।
- 2-SAT: (α ∨ β) থেকে ¬α → β আর ¬β → α; UNSAT ⇔ কোনো x আর ¬x একই SCC-তে; রৈখিক সময়।
- SMT আর যাচাই: prove(P) = ¬P-এর UNSAT; ℤ না BitVec, signed না unsigned স্পষ্ট রাখুন; BMC k ধাপ পর্যন্ত, inductive invariant সব ধাপে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৭৮ · ক্যাপস্টোন — বাজারঘরের সমস্যায় discrete mathপরবর্তী পর্বপুরো কোর্সের ধারণা একটি বড় প্রজেক্টে।
- পর্ব ০৭ · কাজে যুক্তি — specification, ধাঁধা আর SAT solverপুনরালোচনাDPLL-এর প্রথম প্রমাণ, Sudoku আর Z3-এর প্রথম ব্যবহার।
- পর্ব ১৩ · Program-এর সঠিকতাপুনরালোচনাLoop invariant, Hoare logic আর Z3-এ verification condition.
- ডিসক্রিট ম্যাথমেটিক্স · কোর্স হোম৮৩টি পর্বপুরো কোর্সের মানচিত্র আর ১৪টি মডিউল।
- DSA · পর্ব ৬৫ · NP-hard সমস্যা বাস্তবেকোডে প্রয়োগকঠিন সমস্যা চেনা আর মোকাবিলার কৌশল।
- DSA · পর্ব ৪৬ · SCC, bridge আর articulation pointকোডে প্রয়োগSCC-এর বাস্তবায়ন আর 2-SAT কোডে।