Foundationপ্রথম নীতি থেকে
LEVEL 13লেসন ১৪/২৩অ্যাডভান্সড১ ঘণ্টা ৫ মিনিট

Formal Methods — Hoare Logic, Model Checking, TLA+

Formal Methods: Hoare Logic, Model Checking, and TLA+

Testing দেখায় bug আছে; formal methods দেখায় bug নেই — কিন্তু শুধু যা আপনি স্পেসিফাই করেছেন তার জন্য। Hoare logic দিয়ে sequential program-এর deductive proof, model checking দিয়ে exhaustive state-space exploration, আর TLA+ দিয়ে distributed protocol স্পেসিফিকেশন — তিনটা ভিন্ন হাতিয়ার, একই লক্ষ্য।

এই লেসন শেষে আপনি পারবেন

  • Hoare triple {P} C {Q} সঠিকভাবে পড়তে ও লিখতে পারবেন, আর assignment, sequence, if, while — চারটা মূল inference rule প্রয়োগ করতে পারবেন
  • Loop invariant বেছে নিয়ে একটা সম্পূর্ণ program-এর (sum-computing loop) correctness Hoare rules দিয়ে ধাপে ধাপে প্রমাণ করতে পারবেন
  • Partial correctness ও total correctness-এর পার্থক্য এবং variant function দিয়ে termination প্রমাণের পদ্ধতি ব্যাখ্যা করতে পারবেন
  • Model checking-এর exhaustive state-space exploration পদ্ধতি ব্যাখ্যা করে Hoare logic-এর deductive পদ্ধতির সাথে তুলনা করতে পারবেন, state explosion সমস্যাসহ
  • TLA+ কীভাবে concurrent/distributed system-কে state machine + temporal logic দিয়ে স্পেসিফাই করে তা ব্যাখ্যা করতে পারবেন, বাস্তব AWS কেস-স্টাডি সহ

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

একটা payment system-এর গল্প। দুইটা request প্রায় একই মুহূর্তে আসে — একজন ব্যবহারকারী দ্রুত দুইবার “Pay” বোতাম চাপেন, নেটওয়ার্ক সামান্য দেরি করে, আর দুইটা request-ই একই ব্যালেন্স পড়ে ফেলে:

Thread A: balance পড়ে (= 100)
Thread B: balance পড়ে (= 100)
Thread A: balance - 50 লিখে (= 50)
Thread B: balance - 50 লিখে (= 50)    ← হওয়া উচিত ছিল 0

দুইটা টাকা কাটা হলো, কিন্তু balance একবারই কমলো। কোম্পানি ৫০ টাকা হারালো। QA team মাসের পর মাস test চালিয়েছে — কখনো ধরা পড়েনি, কারণ এই bug ধরা পড়তে দুইটা thread-কে ঠিক এই ক্রমে interleave হতে হবে, আর সেটা কোটিতে একবার ঘটে। Load test-এও প্রায়ই মিস হয়ে যায়।

এখানেই টেস্টিং-এর একটা মৌলিক সীমাবদ্ধতা দেখা যায়:

Formal methods এই ফাঁকটা ভরাট করার চেষ্টা — গাণিতিকভাবে প্রমাণ করা যে একটা program বা protocol একটা নির্দিষ্ট property সব পরিস্থিতিতেই মেনে চলে, শুধু যেগুলো test করা হয়েছে সেগুলোতে নয়।

এই লেসনে আমরা তিনটা সম্পূরক হাতিয়ার দেখব:

  1. Hoare logic — একটা sequential program-এর correctness হাতে-কলমে deductive proof দিয়ে প্রমাণ করার নিয়ম।
  2. Model checking — একটা finite state space সম্পূর্ণভাবে exhaustively explore করে একটা temporal property যাচাই করার automated পদ্ধতি।
  3. TLA+ — concurrent ও distributed system-কে state machine + temporal logic দিয়ে স্পেসিফাই করার ভাষা, যা দিয়ে Amazon সত্যিই production-এর আগে DynamoDB আর S3-তে সূক্ষ্ম bug খুঁজে পেয়েছিল।

তিনটাই একই প্রশ্নের ভিন্ন উত্তর: “আমি কীভাবে নিশ্চিত হব যে এই সিস্টেম যা করার কথা ঠিক তাই করবে — সব ক্ষেত্রে?”

মূল ধারণা

Hoare Logic — program সম্পর্কে দাবি করার ভাষা

programming-languages/formal-semantics-এ আমরা Hoare triple-এর প্রাথমিক পরিচয় পেয়েছিলাম axiomatic semantics-এর আলোচনায়। এখানে আমরা সেই নিয়মগুলো সম্পূর্ণভাবে, প্রতিটা inference rule সহ দেখব — যাতে একটা প্রকৃত program-এর সম্পূর্ণ correctness প্রমাণ করা যায়।

নোটেশন

{P} C {Q}\{P\}\ C\ \{Q\}

  • P — precondition: program C চালানোর আগে যা সত্য বলে ধরা হয়
  • C — একটা program বা program-এর টুকরা (statement)
  • Q — postcondition: C সফলভাবে শেষ হওয়ার পরে যা সত্য হবে

অর্থ (partial correctness): “যদি P সত্য থাকা অবস্থায় C চালানো হয়, আর C যদি থেমে যায় (terminate করে), তাহলে থামার মুহূর্তে Q সত্য থাকবে।”

লক্ষ্য করুন শব্দটা — “যদি থামে”। এই triple C-র termination সম্পর্কে কিছু বলে না, শুধু বলে “থামলে ফলাফল সঠিক হবে”। এটাকেই বলে partial correctness। (Termination আলাদাভাবে প্রমাণ করতে হয় — নিচে দেখব।)

চারটা মূল inference rule

১. Assignment axiom — সবচেয়ে মৌলিক, আর সবচেয়ে পাল্টা-স্বজ্ঞাত (counter-intuitive), কারণ এটা পেছনের দিকে কাজ করে:

{Q[E/x]}  x:=E  {Q}\{Q[E/x]\}\ \ x := E\ \ \{Q\}

এখানে Q[E/x] মানে “Q-তে প্রতিটা free occurrence-এর x-কে E দিয়ে প্রতিস্থাপন করা”। উদাহরণ:

{x+1>5}  x:=x+1  {x>5}\{x + 1 > 5\}\ \ x := x + 1\ \ \{x > 5\}

আমরা postcondition x > 5 দিয়ে শুরু করি, তারপর x-এর জায়গায় x + 1 বসিয়ে precondition বের করি: (x+1) > 5, যা সরল করে x > 4। এটাই সঠিক precondition — x-এর মান 4-এর বেশি হলে, x := x + 1-এর পরে x-এর মান 5-এর বেশি হবে।

২. Sequence rule — দুইটা statement একের পর এক:

{P} C1 {R}{R} C2 {Q}{P}  C1; C2  {Q}\frac{\{P\}\ C_1\ \{R\} \qquad \{R\}\ C_2\ \{Q\}}{\{P\}\ \ C_1;\, C_2\ \ \{Q\}}

C1-এর postcondition আর C2-এর precondition একই R হতে হবে — এটাই দুইটা statement-কে “সেলাই” করে।

৩. If-then-else rule:

{P∧B} C1 {Q}{P∧¬B} C2 {Q}{P}  if B then C1 else C2  {Q}\frac{\{P \wedge B\}\ C_1\ \{Q\} \qquad \{P \wedge \neg B\}\ C_2\ \{Q\}}{\{P\}\ \ \texttt{if}\ B\ \texttt{then}\ C_1\ \texttt{else}\ C_2\ \ \{Q\}}

দুইটা branch-ই একই Q-তে পৌঁছাতে হবে, কিন্তু প্রতিটা branch-এর নিজস্ব অতিরিক্ত অনুমান আছে — then branch জানে B সত্য, else branch জানে B মিথ্যা।

৪. While rule — সবচেয়ে গুরুত্বপূর্ণ, কারণ এখানেই loop invariant আসে:

{I∧B} C {I}{I}  while B do C  {I∧¬B}\frac{\{I \wedge B\}\ C\ \{I\}}{\{I\}\ \ \texttt{while}\ B\ \texttt{do}\ C\ \ \{I \wedge \neg B\}}

I হলো loop invariant — এমন একটা predicate যা প্রতিটা iteration-এর আগে ও পরে সত্য থাকে (loop-এর ভেতরে সাময়িকভাবে মিথ্যা হতে পারে, কিন্তু প্রতিটা সম্পূর্ণ iteration শেষে আবার সত্য)। Loop শেষ হলে (B মিথ্যা হয়ে গেলে), আমরা জানি I তখনও সত্য, প্লাস ¬B — এই দুইটা মিলিয়েই postcondition।

৫. Rule of consequence — precondition শক্তিশালী করা বা postcondition দুর্বল করা সবসময় নিরাপদ:

P⇒P′{P′} C {Q′}Q′⇒Q{P}  C  {Q}\frac{P \Rightarrow P' \qquad \{P'\}\ C\ \{Q'\} \qquad Q' \Rightarrow Q}{\{P\}\ \ C\ \ \{Q\}}

এই rule-টা একটা “adapter” — বাকি চারটা rule দিয়ে পাওয়া triple-কে আপনার আসল দরকারি P আর Q-এর সাথে মেলাতে ব্যবহার হয়।

ConstructRuleমূল ধারণা
Assignment{Q[E/x]} x:=E {Q}পেছন থেকে সামনে — postcondition-এ x কে E বসানো
Sequence{P}C1{R}, {R}C2{Q} ⟹ {P}C1;C2{Q}মাঝের শর্ত R দিয়ে জোড়া লাগানো
Ifদুই branch-ই Q-তে পৌঁছায়B জানা যায় branch-এর ভেতরে
While{I∧B}C{I} ⟹ {I}while B do C{I∧¬B}Invariant maintenance = inductive step
ConsequenceP⟹P', {P'}C{Q'}, Q'⟹Q ⟹ {P}C{Q}Precondition আঁটসাঁট, postcondition ঢিলা করা
পাঁচটা Hoare rule — প্রতিটা program construct-এর জন্য একটা।

ভেতরে কী ঘটছে

একটা সম্পূর্ণ correctness প্রমাণ — sum-computing loop

তত্ত্ব যথেষ্ট। এবার একটা বাস্তব program পুরোপুরি প্রমাণ করি।

// precondition: n ≥ 0
i := 1;
s := 0;
while (i ≤ n) do
    s := s + i;
    i := i + 1
// postcondition: s = n(n+1)/2

এই program-টা 1 + 2 + ⋯ + n গণনা করে — mathematics/induction-এর প্রথম উদাহরণের ঠিক সেই সমষ্টি, শুধু এবার আমরা এটাকে একটা program হিসেবে প্রমাণ করব, mathematical induction দিয়ে নয়, Hoare rules দিয়ে।

ধাপ ১ — invariant বেছে নেওয়া

Invariant বাছার সবচেয়ে ভালো উপায়: postcondition-কে “সাধারণীকরণ” করা। Postcondition বলে s = n(n+1)/2 — i = n+1-এর সময়কার একটা বিশেষ ক্ষেত্র। সাধারণ invariant:

I:s=(i−1)⋅i2 ∧ 1≤i≤n+1I:\quad s = \frac{(i-1) \cdot i}{2} \ \wedge\ 1 \le i \le n+1

মানে: “যতগুলো সংখ্যা ইতিমধ্যে যোগ হয়েছে (1 থেকে i-1 পর্যন্ত), s-এ ঠিক তাদের যোগফল আছে।”

ধাপ ২ — Initialization

দেখাতে হবে: {n ≥ 0} i:=1; s:=0 {I}

i:=1; s:=0-এর পরে I-তে i=1, s=0 বসিয়ে যাচাই:

0=(1−1)⋅12=0 ✓1≤1≤n+1  ⟺  n≥0 ✓0 = \frac{(1-1)\cdot 1}{2} = 0 \ \checkmark \qquad\qquad 1 \le 1 \le n+1 \iff n \ge 0\ \checkmark

n ≥ 0 precondition ঠিক এই শর্তটাই দেয়। Assignment axiom দুইবার প্রয়োগ করে (পেছন থেকে) এই একই সিদ্ধান্তে পৌঁছানো যায় — নিজে চেষ্টা করুন Question 1-এ।

ধাপ ৩ — Maintenance (এটাই মূল ধাপ)

দেখাতে হবে: {I ∧ i ≤ n} s:=s+i; i:=i+1 {I}

Assignment axiom পেছন থেকে প্রয়োগ করি। শেষ statement (i := i+1) থেকে শুরু — I-তে i-র জায়গায় i+1 বসাই:

I[i+1/i]  =  s=i(i+1)2 ∧ 0≤i≤nI[i{+}1/i] \;=\; s = \frac{i(i+1)}{2} \ \wedge\ 0 \le i \le n

এখন প্রথম statement (s := s+i) — এই নতুন predicate-এ s-এর জায়গায় s+i বসাই:

(s=i(i+1)2)[s+i/s]  =  s+i=i(i+1)2    ⟺    s=i(i+1)2−i=i(i−1)2\left(s = \frac{i(i+1)}{2}\right)[s{+}i/s] \;=\; s + i = \frac{i(i+1)}{2} \;\iff\; s = \frac{i(i+1)}{2} - i = \frac{i(i-1)}{2}

তাহলে দেখাতে হবে:

I∧(i≤n) ⟹ (s=i(i−1)2 ∧ 0≤i≤n)I \wedge (i \le n) \ \Longrightarrow\ \left(s = \frac{i(i-1)}{2} \ \wedge\ 0 \le i \le n\right)

বাঁ পাশ খুলে লিখি: I মানে s = (i-1)i/2 ∧ 1 ≤ i ≤ n+1, প্লাস i ≤ n। তাহলে s = (i-1)i/2 = i(i-1)/2 — মিলে যায়। আর 1 ≤ i ≤ n+1 প্লাস i ≤ n থেকে 1 ≤ i ≤ n, যা 0 ≤ i ≤ n-কে entail করে। Implication সত্য — rule of consequence প্রযোজ্য। ∎ (maintenance)

ধাপ ৪ — While rule প্রয়োগ

Maintenance প্রমাণিত হওয়ায়, while rule সরাসরি দেয়:

{I}  while (i≤n) do {⋯ }  {I∧¬(i≤n)}\{I\}\ \ \texttt{while}\ (i \le n)\ \texttt{do}\ \{\cdots\}\ \ \{I \wedge \neg(i \le n)\}

অর্থাৎ {I ∧ i > n}। এই শর্তে I-এর 1 ≤ i ≤ n+1 আর i > n একসাথে মানে i = n+1। সেটা I-এর প্রথম অংশে বসিয়ে:

s=(i−1)i2=n(n+1)2s = \frac{(i-1)i}{2} = \frac{n(n+1)}{2}

ঠিক আমাদের postcondition! rule of consequence-এর দরকারই পড়ল না — সরাসরি মিলে গেল। ∎

ধাপকী প্রমাণ করা হলোকোন rule
Initialization{n≥0} i:=1;s:=0 {I}Assignment ×2 + Sequence
Maintenance{I∧i≤n} s:=s+i;i:=i+1 {I}Assignment ×2 + Sequence + Consequence
Loop{I} while(i≤n) do ⋯ {I∧i>n}While rule
উপসংহার{I∧i>n} ⟹ {s=n(n+1)/2}সরাসরি বীজগণিত
প্রমাণের প্রতিটা ধাপ কোন Hoare rule ব্যবহার করেছে।

Partial বনাম total correctness — termination আলাদা

উপরের পুরো প্রমাণ শুধু partial correctness দেখায়: “যদি loop-টা থামে, তাহলে উত্তর সঠিক।” কিন্তু loop-টা যে আদৌ থামবে, সেটা আলাদা প্রমাণ।

Total correctness = partial correctness + termination।

Termination প্রমাণ করতে দরকার একটা variant function (বা “loop measure”) — এমন একটা রাশি যা:

  1. প্রতিটা iteration-এ কঠোরভাবে কমে
  2. নিচে সীমাবদ্ধ (একটা নির্দিষ্ট মানের নিচে যেতে পারে না)

এখানে variant n - i। প্রতিবার loop body চললে i এক বাড়ে, তাই n - i এক কমে। আর যতক্ষণ loop চলছে (i ≤ n), n - i ≥ 0 — নিচে সীমাবদ্ধ। mathematics/induction-এর well-ordering principle অনুযায়ী, অ-ঋণাত্মক পূর্ণসংখ্যার কঠোরভাবে হ্রাসমান কোনো অসীম ক্রম থাকতে পারে না — তাই loop-টা সসীম সংখ্যক ধাপে থামবেই।

উদাহরণ

Model checking — একদম ভিন্ন কৌশল

Hoare logic deductive — আপনি হাতে (বা tool-এর সাহায্যে) একটা proof tree বানান, প্রতিটা ধাপ একটা inference rule দিয়ে justify করা। এটা সসীম আর অসীম, উভয় ধরনের state space-এই কাজ করে (কারণ n একটা variable, নির্দিষ্ট সংখ্যা নয়) — কিন্তু invariant মানুষকে বের করতে হয়, যেটা সৃজনশীল আর কঠিন কাজ।

Model checking সম্পূর্ণ ভিন্ন দর্শন: system-টার state space যদি সসীম হয়, পুরো state space automatically exhaustively explore করে ফেলা — প্রতিটা reachable state, প্রতিটা সম্ভাব্য interleaving — আর যাচাই করা একটা temporal property সবসময় সত্য কি না।

উদাহরণ — আমাদের intro-র race condition, model checking-এর চোখে

মনে করুন দুইটা thread একই কাউন্টার বাড়ায়, প্রতিটা counter += 1 তিনটা atomic sub-step-এ ভাঙে: load, inc, store।

Thread A:  a := counter;  a := a + 1;  counter := a
Thread B:  b := counter;  b := b + 1;  counter := b

State-টা (pc_A, pc_B, counter, a, b) — pc মানে প্রতিটা thread কোন sub-step-এ আছে। দুইটা thread-এর ৩টা করে sub-step, তাই সম্ভাব্য interleaving সংখ্যা \binom{6}{3} = 20।

Model checker সবগুলো ২০টা interleaving চালিয়ে দেখে — safety property counter = 2 (দুইবার increment-এর পরে) শেষে সবসময় সত্য কি না। ফলাফল: বেশিরভাগ interleaving-এ ঠিক, কিন্তু একটাতে ভুল — ঠিক intro-র bug-টা:

A: load counter (0)
B: load counter (0)    ← এখানেই সমস্যার বীজ
A: inc a (1)
A: store counter (1)
B: inc b (1)
B: store counter (1)   ← হওয়া উচিত ছিল 2

Model checker শুধু “bug আছে” বলে না — এটা এই কাউন্টার-উদাহরণ trace-টাও দেয়, ঠিক যে interleaving-এ property ভাঙে। এটাই model checking-এর সবচেয়ে বড় practical সুবিধা: একটা concrete, reproducible bug trace, যেটা Hoare logic-এর “এই invariant বাঁচে না” প্রমাণ থেকে সরাসরি পাওয়া যায় না।

Hoare logic বনাম model checking — দুই দর্শন
  1. কী explore করেHoare: symbolic proof tree (হাতে বা tool-সাহায্যে) — variable-এর মান নির্দিষ্ট নয়
  2. Model checking: concrete reachable state-এর সসীম সেট — প্রতিটা explicit
  3. কে বুদ্ধি খরচ করেHoare: মানুষ — invariant বের করা সৃজনশীল কাজ
  4. Model checking: মেশিন — একবার model লিখে দিলে exploration automatic
  5. ScaleHoare: symbolic, তাই n = 10 বা n = 10¹⁰⁰ একই প্রমাণ
  6. Model checking: state space সসীম হতেই হবে, আর বড় হলে exponential ব্যয়
  7. আউটপুটHoare: "প্রমাণিত" বা "প্রমাণ পাইনি" (invariant খুঁজে না পেলে অনিশ্চিত)
  8. Model checking: "সত্য" অথবা একটা concrete counterexample trace

State space explosion

সমস্যা: reachable state-এর সংখ্যা প্রতিটা নতুন process/variable/thread-এর সাথে exponentially বাড়ে। উপরের ২-thread উদাহরণে 20টা interleaving; 3 thread হলে কয়েক হাজার; 10 thread হলে astronomically বড়। বাস্তব distributed system-এ (যেমন একটা consensus protocol-এর ৫টা node, প্রতিটার নিজস্ব state, নেটওয়ার্ক message loss/duplication/ reorder-সহ) reachable state-এর সংখ্যা কোটি কোটি ছাড়িয়ে যায় — একে বলে state space explosion, model checking-এর মূল সীমাবদ্ধতা।

সমাধানের কৌশল: symbolic model checking (state-কে explicit না রেখে boolean formula/BDD দিয়ে represent করা), bounded model checking (শুধু k ধাপ পর্যন্ত explore করা — bug থাকলে অনেক সময় অল্প ধাপেই পাওয়া যায়), আর abstraction (কম গুরুত্বপূর্ণ detail বাদ দিয়ে state space ছোট করা)।

নিজে চালিয়ে দেখুন

EXPERIMENT

নিজের হাতে exhaustive interleaving-explore করে race condition ধরা

Python 3· ২০ মিনিট
from itertools import permutations

# প্রতিটা thread-এর sub-step: ('load', thread), ('inc', thread), ('store', thread)
def make_steps(thread_id):
    return [('load', thread_id), ('inc', thread_id), ('store', thread_id)]

steps_a = make_steps('A')
steps_b = make_steps('B')
all_steps = steps_a + steps_b

# একটা interleaving-এ A আর B-এর নিজেদের ভেতরের order ঠিক থাকতে হবে,
# শুধু A আর B-এর মধ্যে interleave করা যায়। তাই সব 6-permutation না,
# শুধু সেইগুলো যেগুলোতে relative order রক্ষা পায়।
def valid_interleavings(a, b):
    if not a:
        yield list(b)
        return
    if not b:
        yield list(a)
        return
    for rest in valid_interleavings(a[1:], b):
        yield [a[0]] + rest
    for rest in valid_interleavings(a, b[1:]):
        yield [b[0]] + rest


def simulate(interleaving):
    counter = 0
    reg = {'A': None, 'B': None}
    for op, tid in interleaving:
        if op == 'load':
            reg[tid] = counter
        elif op == 'inc':
            reg[tid] = reg[tid] + 1
        elif op == 'store':
            counter = reg[tid]
    return counter


total = 0
bugs = 0
for interleaving in valid_interleavings(steps_a, steps_b):
    total += 1
    result = simulate(interleaving)
    if result != 2:
        bugs += 1
        if bugs == 1:
            print("প্রথম বাগ-উৎপাদনকারী interleaving পাওয়া গেল:")
            for op, tid in interleaving:
                print(f"  {tid}: {op}")
            print(f"  ফলাফল: counter = {result}  (প্রত্যাশিত: 2)\n")

print(f"মোট interleaving পরীক্ষা করা হলো: {total}")
print(f"যেগুলোতে property ভেঙেছে (counter != 2): {bugs}")
print(f"safety property রক্ষা পেয়েছে: {total - bugs} / {total}")

প্রত্যাশিত আউটপুট (সংক্ষিপ্ত):

প্রথম বাগ-উৎপাদনকারী interleaving পাওয়া গেল:
  A: load
  B: load
  A: inc
  A: store
  B: inc
  B: store
  ফলাফল: counter = 1  (প্রত্যাশিত: 2)

মোট interleaving পরীক্ষা করা হলো: 20
যেগুলোতে property ভেঙেছে (counter != 2): 18
safety property রক্ষা পেয়েছে: 2 / 20

লক্ষ্য করুন সংখ্যাটা — মাত্র 2/20 interleaving-এ property রক্ষা পায় (যেগুলোতে একটা thread সম্পূর্ণ শেষ করে তারপর আরেকটা শুরু হয়)। 18/20 মানে ৯০% সম্ভাব্য schedule-এই bug ঘটে! তাও production-এ এটা “rare” মনে হয়, কারণ বাস্তব thread scheduler প্রায়ই দুইটা consecutive sub-step একসাথে চালায় (কম context switch), তাই bug-এর আসল সম্ভাবনা কম মনে হয় — কিন্তু গাণিতিকভাবে state space-এ এটা সর্বত্র লুকিয়ে আছে, আর load বেশি হলে (বেশি thread, বেশি contention) সেই সম্ভাবনা বেড়ে যায়।

এই ছোট প্রোগ্রামটাই একটা bare-bones model checker — valid_interleavings হলো state-space exploration, simulate হলো transition function, আর result != 2 চেক হলো safety property যাচাই। SPIN, TLC-এর মতো আসল tool ঠিক এই একই কাজ করে, শুধু অনেক বেশি efficient ভাবে (symbolic representation, partial-order reduction — একই ফলাফল দেয় এমন interleaving-গুলো একসাথে গোনা) আর অনেক বড় state space-এর জন্য।

এটা কী প্রমাণ করে

একটা ছোট মডেল checker হাতে লিখলে দেখা যায় কীভাবে সব সম্ভাব্য interleaving enumerate করে একটা safety property ভাঙার exact trace বের করা যায় — model checking আসলে কী করে, তার একটা micro-scale সংস্করণ।

নিজে বানান

BUILD IT

TLA+ style-এ একটা counter protocol স্পেসিফাই করা

Pseudocode / TLA+-অনুপ্রাণিত · ●●●●○
  1. State variable ঘোষণা করুন — system-এর snapshot কী কী variable দিয়ে গঠিত
  2. Init predicate লিখুন — শুরুর state কেমন
  3. Next relation লিখুন — প্রতিটা সম্ভাব্য action, একটা disjunction হিসেবে
  4. Safety property লিখুন — একটা Invariant predicate যা সবসময় সত্য থাকা উচিত
  5. (ধারণাগতভাবে) TLC exhaustively সব reachable state explore করে invariant যাচাই করে

TLA+ (Temporal Logic of Actions) একটা system-কে state machine হিসেবে স্পেসিফাই করে — Init predicate (শুরুর state) + Next relation (কোন state থেকে কোন state-এ যাওয়া যায়) + সব সময় সত্য থাকা উচিত এমন Invariant।

নিচেরটা আসল TLA+ syntax নয় (সেটা শিখতে আলাদা সময় লাগে), কিন্তু এর কাঠামো — variable, Init, Next, Invariant — হুবহু TLA+-এর মতোই। আমাদের race-condition counter-টাকে এভাবে স্পেসিফাই করা যায়:

VARIABLES counter, pc_A, pc_B, reg_A, reg_B

Init ==
    /\ counter = 0
    /\ pc_A = "load"  /\ pc_B = "load"
    /\ reg_A = 0      /\ reg_B = 0

\* প্রতিটা thread-এর একটা action — "/\" মানে AND, "\/" মানে OR
StepA_Load  == /\ pc_A = "load"
               /\ reg_A' = counter
               /\ pc_A'  = "inc"
               /\ UNCHANGED <<counter, pc_B, reg_B>>

StepA_Inc   == /\ pc_A = "inc"
               /\ reg_A' = reg_A + 1
               /\ pc_A'  = "store"
               /\ UNCHANGED <<counter, pc_B, reg_B>>

StepA_Store == /\ pc_A = "store"
               /\ counter' = reg_A
               /\ pc_A'    = "done"
               /\ UNCHANGED <<pc_B, reg_A, reg_B>>

\* StepB_Load, StepB_Inc, StepB_Store — প্রতিসম, B-এর জন্য

Next == StepA_Load \/ StepA_Inc \/ StepA_Store
     \/ StepB_Load \/ StepB_Inc \/ StepB_Store

Spec == Init /\ [][Next]_<<counter, pc_A, pc_B, reg_A, reg_B>>

\* Safety property — দুই thread-ই "done" হলে counter ঠিক 2 হওয়া উচিত
CounterCorrect ==
    (pc_A = "done" /\ pc_B = "done") => (counter = 2)

পড়ার নিয়ম:

  • /\ আর \/ — logical AND/OR, indentation দিয়ে grouping বোঝানো হয়
  • x' (prime) — “পরের state-এ x-এর মান” — একটা action কীভাবে state বদলায় তা বর্ণনা করে
  • UNCHANGED <<...>> — যে variable-গুলো এই action-এ বদলায় না তা স্পষ্ট করে বলা (TLA+-এ বাধ্যতামূলক — নাহলে অনির্দিষ্ট থেকে যায়)
  • [][Next]_vars — “সবসময় (□), হয় Next ঘটবে নয় সব variable অপরিবর্তিত থাকবে” — পুরো execution-এর একটা legal step describe করে
  • Spec — Init দিয়ে শুরু, তারপর সবসময় Next-এর কোনো একটা disjunct অনুসরণ করা — এটাই পুরো protocol-এর সংজ্ঞা

TLC model checker এই স্পেসিফিকেশন নিয়ে সব reachable state exhaustively explore করে, প্রতিটাতে CounterCorrect যাচাই করে। যদি কোথাও ভাঙে (ঠিক আমাদের experiment-এ পাওয়া সেই ১৮টা bad interleaving-এর মতো), TLC একটা concrete counterexample trace প্রিন্ট করে — ঠিক কোন state থেকে কোন state-এ গিয়ে property ভাঙল, ধাপে ধাপে।

নিজে বাড়ান:

  1. একটা Fix action যোগ করুন যা load-কে atomic করে (compare-and-swap-এর মতো) — নতুন Next-এ সেই fix যোগ করলে CounterCorrect সব state-এ সত্য থাকবে, TLC-তে ভাবুন কীভাবে
  2. তৃতীয় thread C যোগ করুন — Init, Next বাড়ান, ভাবুন state space কতগুণ বাড়ল
  3. একটা liveness property লিখুন (ধারণাগতভাবে): “শেষ পর্যন্ত (◇) pc_A = "done"” — safety property “কিছু খারাপ কখনো ঘটে না” থেকে ভিন্ন, liveness বলে “কিছু ভালো শেষ পর্যন্ত ঘটবেই”
  4. আসল TLA+ শিখতে চাইলে TLA+ Toolbox বা VS Code extension দিয়ে এই স্পেসিফিকেশন প্রকৃত syntax-এ লিখে TLC চালিয়ে দেখুন

বাস্তব সিস্টেমে

AWS-এ TLA+ — কাল্পনিক নয়, documented বাস্তবতা

২০১৪ সালে Amazon-এর একটা দল (Chris Newcombe এবং সহকর্মীরা) Communications of the ACM-এ একটা paper প্রকাশ করে: “How Amazon Web Services Uses Formal Methods”। এটা শুধু একাডেমিক আগ্রহের বিষয় নয় — production system-এ formal methods ব্যবহারের সবচেয়ে ভালো-documented বাস্তব কেস স্টাডিগুলোর একটা।

মূল দাবি: DynamoDB, S3, EBS আর অন্যান্য কয়েকটা core AWS service-এ TLA+ দিয়ে এমন সব bug পাওয়া গেছে যেগুলো traditional testing খুঁজে পেত না — কারণ সেগুলো এমন rare interleaving বা failure-mode-এ ঘটত যা reproduce করা প্রায় অসম্ভব ছিল।

paper-এ বর্ণিত কিছু নির্দিষ্ট উদাহরণ:

  • একটা replication protocol-এর bug যা শুধু তখনই প্রকাশ পেত যখন একটা নির্দিষ্ট node ঠিক একটা নির্দিষ্ট মুহূর্তে fail করত — এমন একটা timing window যা বছরের পর বছর production traffic-এও কখনো ঘটেনি, কিন্তু TLA+ model checking কয়েক মিনিটেই সেই exact trace খুঁজে বের করে।
  • একাধিক subtle bug যা শুধু তখনই ঘটত যখন একাধিক rare event একসাথে ঘটত (network partition + node crash + একটা নির্দিষ্ট client retry pattern) — মানুষের পক্ষে সব combination কল্পনা করে test লেখা প্রায় অসম্ভব, কিন্তু exhaustive exploration সব combination স্বয়ংক্রিয়ভাবে ধরে ফেলে।

paper-এর আরেকটা গুরুত্বপূর্ণ পর্যবেক্ষণ: engineer-রা যারা TLA+ শিখে ব্যবহার করেছেন, তারা রিপোর্ট করেছেন যে শুধু স্পেসিফিকেশন লেখার প্রক্রিয়া-ই design-এর ফাঁক প্রকাশ করে দেয় — model checker চালানোর আগেই, কারণ একটা protocol-কে precise, ambiguity-মুক্ত ভাষায় লিখতে বাধ্য হলে “hand-wavy” অংশগুলো তাৎক্ষণিকভাবে ধরা পড়ে।

অন্যান্য জায়গায় formal methods

Raft consensus protocol। distributed-systems/raft-এ আমরা Raft-এর Log Matching Property আর Leader Completeness-এর induction-ভিত্তিক প্রমাণ দেখেছি। Diego Ongaro-র মূল PhD dissertation-এ এই একই guarantee-গুলোর একটা সম্পূর্ণ TLA+ specification আছে, যা TLC দিয়ে model-checked — Raft-কে শুধু “বোঝার জন্য সহজ” না, formally verified বলেও দাবি করা যায়।

Chip design। Intel-এর ঐতিহাসিক Pentium FDIV bug (১৯৯৪, ভুল floating-point division টেবিলের একটা entry-র কারণে ভুল উত্তর)-এর পর, Intel floating-point unit ডিজাইনে formal verification (model checking-এর একটা রূপ) বাধ্যতামূলক করে দেয় — একটা bug-এর কারণে কোম্পানির ৪৭৫ মিলিয়ন ডলার recall খরচ হয়েছিল।

Aviation ও safety-critical software। DO-178C (বিমান software certification standard)-এর উচ্চতর assurance level formal methods-কে explicit ভাবে স্বীকৃতি দেয় testing-এর পরিপূরক হিসেবে।

Database transaction protocol। Two-phase commit, distributed lock manager-এর মতো protocol-এর নতুন optimization প্রায়ই TLA+ দিয়ে প্রথমে verify করা হয় প্রোডাকশনে যাওয়ার আগে — MongoDB, CockroachDB-র মতো কোম্পানি প্রকাশ্যে TLA+ specification প্রকাশ করেছে তাদের consensus/replication layer-এর জন্য।

যে ভুলগুলো সবাই করে

“Formal verification মানে প্রমাণ যে software-এ কোনো bug নেই।”

না — এটা প্রমাণ করে software আপনার লেখা specification মেনে চলে।

যদি specification-টাই অসম্পূর্ণ বা ভুল হয় (যেমন কোনো property লিখতেই ভুলে গেছেন, বা বাস্তব environment-এর একটা প্রয়োজনীয় শর্ত model-এ ধরা পড়েনি), তাহলে proof passing করলেও production bug থাকতে পারে।

“formal verification” মানে trusted base-টা code থেকে specification-এ সরে যায় — কিন্তু সম্পূর্ণ অদৃশ্য হয় না। এটাই কারণ CompCert বা seL4-এর মতো প্রজেক্টেও সতর্কতার সাথে বলা হয়: “আমরা প্রমাণ করেছি implementation specification মেনে চলে — specification নিজে ঠিক বাস্তব প্রয়োজনীয়তা ধরেছে কি না, সেটা আলাদা প্রশ্ন।”

“Model checking মানে সব সম্ভাব্য input চালিয়ে দেখা — এটা আসলে খুব ধীর exhaustive testing।”

সম্পূর্ণ ভিন্ন। Testing একটা নির্দিষ্ট input-এর জন্য program চালিয়ে দেখে ফলাফল ঠিক কি না। Model checking একটা abstract model-এর সব reachable state explore করে একটা property (যা অসীম সংখ্যক execution trace-কে cover করে) যাচাই করে।

একটা concurrent system-এ n input-এর জন্য test করলে হয়তো nটা নির্দিষ্ট schedule-এই চলে; model checking সেই একই input-এর সব সম্ভাব্য schedule (interleaving) দেখে — experiment section-এর 20টা interleaving সবগুলোই একই input নিয়ে, শুধু ভিন্ন timing। Testing এই পার্থক্য কখনো ধরতে পারে না, কারণ একটা মাত্র schedule চলে প্রতিটা রান-এ।

“Hoare logic শুধু toy academic example-এর জন্য — বাস্তব software-এ ব্যবহারযোগ্য নয়।”

আংশিক পুরনো ধারণা। সরাসরি হাতে Hoare-style proof লেখা বড় codebase-এ বিরল, ঠিক — কিন্তু এর নীতি সর্বত্র লুকিয়ে আছে:

  • Dafny, F, Verus* — এই ভাষাগুলো প্রতিটা function-এর জন্য precondition/postcondition/loop invariant লেখাকে ভাষার অংশ করে দিয়েছে, আর compiler-ই automatically Hoare-style verification condition তৈরি করে SMT solver দিয়ে check করে।
  • assert statement — বাস্তব production কোডে যে assertion লেখেন, সেগুলো আসলে informal Hoare precondition/postcondition, runtime-এ checked।
  • Rust-এর borrow checker — টাইপ-স্তরে ownership/lifetime নিয়ম চেক করা, formal reasoning-এরই একটা বিশেষায়িত, automated রূপ।

Hoare logic হাতে না লিখলেও, এর কাঠামোগত চিন্তা (“কী precondition লাগবে, কী invariant maintain হয়”) প্রতিটা careful engineer-এর মাথায় implicitly চলে।

বুঝেছেন কি না দেখুন

1

Assignment axiom সরাসরি প্রয়োগ করে দেখান কেন {n ≥ 0} i:=1; s:=0 {s = (i-1)·i/2 ∧ 1 ≤ i ≤ n+1} সত্য — sequence rule আর assignment axiom ব্যবহার করে, পেছন থেকে সামনে।

প্রয়োগ

Postcondition I: s = (i-1)i/2 ∧ 1 ≤ i ≤ n+1 থেকে শুরু, পেছনের দিকে যাই।

দ্বিতীয় statement (s:=0) -এর জন্য assignment axiom: I-তে s-এর জায়গায় 0 বসাই:

I[0/s]=(0=(i−1)i/2)∧1≤i≤n+1I[0/s] = \big(0 = (i-1)i/2\big) \wedge 1 \le i \le n+1

প্রথম statement (i:=1) -এর জন্য assignment axiom: উপরের predicate-এ i-এর জায়গায় 1 বসাই:

(0=(1−1)⋅1/2)∧1≤1≤n+1\big(0 = (1-1)\cdot 1/2\big) \wedge 1 \le 1 \le n+1 =(0=0)∧(n≥0)=true∧(n≥0)=n≥0= (0 = 0) \wedge (n \ge 0) = \text{true} \wedge (n \ge 0) = n \ge 0

তাহলে sequence rule অনুযায়ী:

{n≥0}  i:=1; s:=0  {I}\{n \ge 0\}\ \ i:=1;\, s:=0\ \ \{I\}

ঠিক যা প্রমাণ করতে চেয়েছিলাম। এই একই ফলাফল আমরা lesson-এর মূল টেক্সটে “সামনে থেকে” (i=1, s=0 বসিয়ে সরাসরি চেক করে) পেয়েছিলাম — দুইটা পথই মেলে, কারণ assignment axiom logically সমতুল্য দুইদিকেই প্রয়োগযোগ্য। Backward application (এই উত্তরে) সবসময় নিরাপদ — mechanically প্রয়োগ করা যায়; forward substitution (“বসিয়ে চেক করা”) প্রায়ই দ্রুত কিন্তু সবসময় সরাসরি rule-এর রূপ নয়, rule of consequence প্রয়োজন হতে পারে।

2

Factorial-এর জন্য এই loop-টার সম্পূর্ণ correctness প্রমাণ করুন Hoare logic দিয়ে (invariant বলুন, initialization ও maintenance দেখান):

// precondition: n ≥ 0
k := 0;
p := 1;
while (k < n) do
    k := k + 1;
    p := p * k
// postcondition: p = n!
প্রয়োগ

Invariant: I: p = k! ∧ 0 ≤ k ≤ n

এটা postcondition (p = n!)-এর সাধারণীকরণ, ঠিক sum-loop-এর মতো — “এখন পর্যন্ত যতগুলো factor গুণ হয়েছে, p-তে তাদের factorial আছে।”

Initialization: {n ≥ 0} k:=0; p:=1 {I}

I-তে k=0, p=1 বসাই: 1 = 0! = 1 ✓, আর 0 ≤ 0 ≤ n ⟺ n ≥ 0 ✓ — ঠিক precondition। Sequence + assignment axiom দিয়ে formally ধাপে ধাপে: I[1/p][0/k] সরল করলে n ≥ 0 আসে (Question 1-এর মতোই কৌশল)।

Maintenance: {I ∧ k < n} k:=k+1; p:=p*k {I}

পেছন থেকে: I-তে p-এর জায়গায় p*k বসাই না — প্রথমে শেষ statement (p := p*k) থেকে শুরু। I-তে p-এর জায়গায় p*k বসাই:

I[p⋅k/p]=(p⋅k=k!)∧0≤k≤nI[p{\cdot}k/p] = \big(p \cdot k = k!\big) \wedge 0 \le k \le n

এবার প্রথম statement (k := k+1) — এই predicate-এ k-এর জায়গায় k+1 বসাই:

(p⋅(k+1)=(k+1)!)∧0≤k+1≤n\big(p \cdot (k{+}1) = (k{+}1)!\big) \wedge 0 \le k+1 \le n

সরলীকরণ: (k+1)! = (k+1)·k!, তাই প্রথম অংশ ⟺ p = k! (যেহেতু k+1 ≠ 0, দুই পাশ ভাগ করা যায়)। দ্বিতীয় অংশ ⟺ -1 ≤ k ≤ n-1।

তাহলে দেখাতে হবে: I ∧ (k < n) ⟹ (p = k! ∧ -1 ≤ k ≤ n-1)

I মানে p = k! (মিলে যায়) আর 0 ≤ k ≤ n, প্লাস k < n মানে 0 ≤ k ≤ n-1 — যা -1 ≤ k ≤ n-1-কে entail করে (দুর্বল করে) ✓ Rule of consequence দিয়ে maintenance প্রমাণিত। ∎

Loop শেষে: I ∧ ¬(k<n) = p=k! ∧ 0≤k≤n ∧ k≥n ⟹ k=n, তাই p = n! — postcondition। ✓

Termination: variant n - k, প্রতি iteration-এ ঠিক ১ কমে, k ≤ n থাকা অবস্থায় অ-ঋণাত্মক — well-ordering দিয়ে সসীম ধাপে থামে।

3

কেন while true do skip-এর জন্য যেকোনো postcondition Q Hoare logic-এ partial-correct প্রমাণ করা যায়? এটা কি formal methods-এর একটা দুর্বলতা?

যুক্তি

While rule দিয়ে I = true নিন। {true ∧ true} skip {true} তুচ্ছভাবে সত্য (skip কিছু বদলায় না, আর true সবসময় সত্য)। While rule দেয়:

{true}  while true do skip  {true∧¬true}\{true\}\ \ \texttt{while}\ true\ \texttt{do skip}\ \ \{true \wedge \neg true\}

Postcondition true ∧ false = false। Logic-এ false থেকে যেকোনো কিছু follow করে (ex falso quodlibet), আর rule of consequence বলে যদি Q' ⟹ Q হয় তাহলে postcondition Q'-কে যেকোনো দুর্বল Q দিয়ে replace করা যায়। যেহেতু false ⟹ Q (যেকোনো Q-এর জন্য vacuously সত্য), তাই আমরা যেকোনো Q বসাতে পারি।

এটা দুর্বলতা নয় — এটা partial correctness-এর সংজ্ঞার সরাসরি পরিণতি। Partial correctness বলে “যদি থামে, তাহলে Q”। এই loop কখনো থামে না, তাই “যদি থামে” শর্তটা কখনো satisfy হয় না — পুরো statement vacuously সত্য, ঠিক যেমন “যদি 2+2=5, তাহলে চাঁদ পনিরের তৈরি” logically সত্য একটা statement (মিথ্যা premise থেকে)।

এই কারণেই lesson-এর মূল টেক্সটে জোর দেওয়া হয়েছে: partial correctness একা কখনো যথেষ্ট নয় — সবসময় আলাদাভাবে termination (variant function দিয়ে) প্রমাণ করতে হবে। যে কোনো serious verification tool (Dafny, F*) এই দুটোকে একসাথে বাধ্যতামূলক করে, ঠিক এই ফাঁকটা বন্ধ করতে।

4

Experiment section-এ আমরা দেখেছি 20টা সম্ভাব্য interleaving-এর মধ্যে 18টা race condition-এ ভোগে। যদি একজন engineer বলেন “আমরা এই কোড হাজার বার production-এ চালিয়েছি, কখনো bug দেখিনি — তাই এটা নিরাপদ”, এই যুক্তির সমস্যা কী, model checking-এর দৃষ্টিতে?

যুক্তি

সমস্যাটা দুই ভাগে।

প্রথমত, “কখনো দেখিনি” মানে “ঘটেনি না” — মানে “দেখা যায়নি”। Race condition-এর ফলাফল যদি silently ভুল হয় (যেমন counter একটু কম হওয়া, কিন্তু crash না করা), তাহলে bug detect না হয়ে চুপচাপ ভুল ডেটা তৈরি হতে পারে। Production-এ “কখনো দেখিনি” প্রায়ই মানে “কখনো লক্ষ্য করিনি” — একটা payment system-এ ৫০ টাকার discrepancy হয়তো মাসের রিপোর্টে চাপা পড়ে যায়, যতক্ষণ না accumulate হয়ে বড় হয়।

দ্বিতীয়ত, “হাজার বার চলেছে” একটা statistical sample, exhaustive proof না। Production-এর thread scheduler বাস্তবে 20টা possible interleaving-এর মধ্যে সব equally likely চালায় না — কিছু interleaving (যেমন একটা thread সম্পূর্ণ শেষ করে আরেকটা শুরু হওয়া) অনেক বেশি সম্ভাব্য, কারণ context switch ব্যয়বহুল আর scheduler কম করে। তাই bug-উৎপাদনকারী বিরল interleaving হয়তো মিলিয়ন রান-এ একবার ঘটে — production-এ “হাজার বার” এখনো সেই মিলিয়নের ধারেকাছেও না।

Model checking-এর মূল সুবিধা ঠিক এখানেই: এটা “সম্ভাবনার” উপর নির্ভর করে না। এটা প্রতিটা সম্ভাব্য interleaving structurally enumerate করে — rare হোক বা common, exhaustive exploration-এ সবগুলোই সমান গুরুত্ব পায়। এটাই AWS-এর case study-তে দেখা bug-গুলোর pattern: এমন rare timing window যা বছরের production traffic-এও ঘটেনি, কিন্তু TLC কয়েক মিনিটেই খুঁজে বের করেছে।

5

আপনি একটা নতুন distributed lock service ডিজাইন করছেন। প্রথমে কোন পদ্ধতিতে (Hoare logic, model checking, বা TLA+) verify করা শুরু করবেন, আর কেন? তিনটার প্রতিটার ভূমিকা কোথায় হতে পারে একই project-এ?

ডিজাইন

তিনটাই আলাদা স্তরে, একসাথে ব্যবহারযোগ্য — কোনোটা “সেরা” না, প্রতিটার নিজস্ব জায়গা।

প্রথমে TLA+ দিয়ে protocol-level design। Lock service-এর মূল ঝুঁকি distributed-systems-এর — network partition, node crash, message reorder/loss, concurrent client request। এগুলো precisely এমন জায়গা যেখানে TLA+ সবচেয়ে শক্তিশালী: পুরো protocol-কে state machine হিসেবে লিখে, safety property (একই সময়ে দুইজন lock ধরতে পারবে না) আর liveness property (lock request শেষ পর্যন্ত granted বা denied হবেই — চিরকাল ঝুলে থাকবে না) যাচাই করা। এটা implementation লেখার আগে করা উচিত — design-এর ভুল তখন সস্তায় ধরা পড়ে, কোড লেখার পরে নয়।

তারপর, নির্দিষ্ট critical algorithm-এর জন্য Hoare logic-স্টাইল reasoning। যেমন lock-এর internal state machine-এর একটা sequential অংশ (একটা single node-এর মধ্যে lease renewal logic) — এখানে explicit precondition/postcondition/invariant লেখা (হয়তো Dafny/F*-এর মতো tool দিয়ে, না হলে code comment + assertion হিসেবে) implementation-এর correctness নিশ্চিত করে, protocol-level design ঠিক থাকলেও।

সবশেষে, model checking implementation-এর কাছাকাছি স্তরে। যদি TLA+ spec-এর কাছাকাছি একটা executable model বানানো যায় (বা production code-এর উপর সরাসরি — যেমন Jepsen-এর মতো টুল, যেটা আসল distributed system চালিয়ে network fault inject করে আর observed behavior একটা formal consistency model-এর বিরুদ্ধে checks করে), সেটা implementation আসলেই spec মেনে চলছে কি না তার একটা empirical সেতু — কারণ TLA+ proof শুধু spec সম্পর্কে বলে, code spec সঠিকভাবে implement করেছে কি না তা আলাদা প্রশ্ন (এই ফাঁকটাই Misconception section-এর প্রথম দাবির মূল কথা)।

সংক্ষেপে: TLA+ → design-এর সবচেয়ে ঝুঁকিপূর্ণ (concurrent/distributed) অংশ প্রথমে যাচাই, Hoare-স্টাইল reasoning → sequential algorithm-এর বিস্তারিত সঠিকতা, model checking/testing tool (Jepsen) → code স্পেসিফিকেশন মেনে চলছে কি না তার empirical যাচাই। তিনটা মিলিয়ে trusted base যতটা সম্ভব ছোট করা যায়, একটাও একা সম্পূর্ণ নিশ্চয়তা দেয় না।

এরপর কী

আমরা দেখলাম কীভাবে program-কে state আর transition-এর মধ্য দিয়ে যুক্তি দেওয়া যায় — Hoare triple precondition/postcondition দিয়ে, model checking reachable state দিয়ে, TLA+ পুরো protocol-কে state machine হিসেবে।

কিন্তু একটা সম্পূর্ণ ভিন্ন দৃষ্টিভঙ্গি আছে computation বোঝার — যেখানে কোনো “state” নেই, কোনো assignment নেই, শুধু function। পরের লেসনে আমরা ফিরে যাব ১৯৩০-এর দশকে, Alonzo Church-এর কাছে, যিনি computation-কে মাত্র তিনটা নিয়মে ধরে ফেলেছিলেন — lambda calculus। এটাই সেই ভাষা যেখান থেকে আজকের প্রতিটা lambda keyword (Python), arrow function (JavaScript), আর সম্পূর্ণ functional programming paradigm-এর জন্ম।

আরও পড়ুন

  • An Axiomatic Basis for Computer Programming — C. A. R. Hoare · 1969 — মূল paper, যেখানে {P} C {Q} নোটেশন আর inference rule-গুলো প্রথম প্রকাশিত হয়
  • The Temporal Logic of Actions — Leslie Lamport · 1994, ACM TOPLAS — TLA+-এর গাণিতিক ভিত্তি
  • How Amazon Web Services Uses Formal Methods — Chris Newcombe, Tim Rath, Fan Zhang, Bogdan Munteanu, Marc Brooker, Michael Deardeuff · Communications of the ACM, 2015 — DynamoDB, S3 আর অন্যান্য AWS system-এ TLA+ দিয়ে production-এর আগে পাওয়া সূক্ষ্ম bug-এর কেস স্টাডি