পাঠ ২৬ · ৫৬-এর মধ্যে · মডিউল ৬

কনটেক্সট-ফ্রি ল্যাঙ্গুয়েজের জন্য পাম্পিং লেমা

Pumping lemma for context-free languages
৯ মিনিট পড়া মাঝারি · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • CFL পাম্পিং লেমার সঠিক, ফরমাল বিবৃতি — পাঁচ-অংশ বিভাজন ও তিনটি শর্ত
  • এর পেছনের pigeonhole যুক্তি — পার্স ট্রি-এর উচ্চতা ও ভ্যারিয়েবল-পুনরাবৃত্তি
  • $a^nb^nc^n$ কনটেক্সট-ফ্রি নয় তার সম্পূর্ণ contradiction-ভিত্তিক প্রমাণ
  • Python-এ একটি exhaustive split-checker যা প্রতিটি সম্ভাব্য বিভাজনের বিরুদ্ধে প্রমাণ verify করে

১ · CFL পাম্পিং লেমার বিবৃতি

M3/L13 রেগুলার ল্যাঙ্গুয়েজের জন্য একটি পাম্পিং লেমা দিয়েছিল — এখন M4-M5-এ CFL নিয়ে কাজ করার পর, একই ধরনের কিন্তু আরও জটিল একটি প্রপার্টি CFL-এর জন্যও প্রমাণ করা যায়:

যদি $L$ কনটেক্সট-ফ্রি হয়, তাহলে একটি পাম্পিং লেংথPumping lengthএকটি নির্দিষ্ট সংখ্যা $p$, ভাষা $L$-এর উপর নির্ভরশীল, যার চেয়ে বড় দৈর্ঘ্যের প্রতিটি স্ট্রিং পাম্পিং প্রপার্টি মেনে চলতে বাধ্য $p \geq 1$ আছে যেন প্রতিটি স্ট্রিং $s \in L$ যেখানে $|s| \geq p$, সেটিকে পাঁচ ভাগে ভাগ করা যায় $s = uvxyz$, যা নিচের শর্তগুলো মেনে চলে:

  • (১) $|vy| > 0$ (দুটো পাম্পড অংশের অন্তত একটি খালি নয়)
  • (২) $|vxy| \leq p$ (মাঝের তিন অংশ একসাথে $p$-এর মধ্যে সীমাবদ্ধ)
  • (৩) সব $i \geq 0$-এর জন্য, $uv^ixy^iz \in L$ ($v$ আর $y$ — দুটোকেই একইসাথে, একই সংখ্যকবার — "পাম্প" করলেও ফলাফল $L$-এই থেকে যায়)

$$\forall s \in L, |s| \geq p \implies \exists\, u,v,x,y,z: s=uvxyz \land |vy|>0 \land |vxy|\leq p \land \forall i\geq 0, uv^ixy^iz \in L$$

২ · কেন এটি সত্য — পার্স ট্রি-এর উচ্চতা ও Pigeonhole

L13-এ যুক্তিটি ছিল DFA-এর ফিক্সড সংখ্যক স্টেট নিয়ে — লম্বা স্ট্রিং মানেই কোনো স্টেট পুনরায় ভিজিট হবে (pigeonhole)। CFL-এর জন্য যুক্তিটি একইরকম, কিন্তু এবার পার্স ট্রি-এর উচ্চতা নিয়ে কাজ করে:

যথেষ্ট লম্বা স্ট্রিং-এর জন্য, তার CFG পার্স ট্রি (CNF-আকারে, L20 — যা নিশ্চিত করে প্রতিটি ইন্টারনাল নোডের ঠিক দুটো চাইল্ড আছে) root থেকে leaf পর্যন্ত এমন একটি পাথ ধারণ করবে যার দৈর্ঘ্য গ্রামারের মোট ভ্যারিয়েবল সংখ্যার চেয়ে বেশি — pigeonhole অনুযায়ী, সেই পাথে কোনো ভ্যারিয়েবল অবশ্যই পুনরাবৃত্তি হবে। সেই পুনরাবৃত্ত ভ্যারিয়েবলের দুটো occurrence-এর মাঝের subtree-টি "পাম্পযোগ্য" — একটি occurrence-এর subtree অন্যটির জায়গায় বসিয়ে দিলে (duplicate বা remove করে) একটি নতুন বৈধ পার্স ট্রি পাওয়া যায়। এই subtree-এর দ্বারা উৎপন্ন অংশই $v$ (বামে) আর $y$ (ডানে), আর তার ভেতরের অংশ $x$।

S (root) u ... A v ... A ... y একই A আবার! x ... z দুই A-এর মাঝের subtree = v..x..y এটাই পাম্পযোগ্য
পার্স ট্রি-তে root-to-leaf পাথে ভ্যারিয়েবল $A$ দুইবার দেখা গেলে, ভেতরের subtree ($v$...$x$...$y$) বাইরের occurrence-এর জায়গায় বসিয়ে বা সরিয়ে "পাম্প" করা যায় — CFL পাম্পিং লেমার জ্যামিতিক ভিত্তি।

৩ · প্রমাণ — $a^nb^nc^n$ কনটেক্সট-ফ্রি নয়

L18-এর গ্রামার-ভিত্তিক উদাহরণে $a^nb^nc^n$ দেখা গিয়েছিল "inherently ambiguous" প্রসঙ্গে। এখন CFL পাম্পিং লেমা দিয়ে দেখানো যাক এই ভাষাটি আসলে কনটেক্সট-ফ্রিই নয়:

  • (১) ধরে নিন $L = \{a^nb^nc^n : n \geq 0\}$ কনটেক্সট-ফ্রি — তাহলে একটি পাম্পিং লেংথ $p$ থাকতেই হবে।
  • (২) নির্বাচন করুন $s = a^pb^pc^p \in L$, যার দৈর্ঘ্য $3p \geq p$।
  • (৩) যেকোনো বৈধ বিভাজন $s = uvxyz$ শর্ত $|vxy| \leq p$ মেনে — এর মানে $vxy$ ব্লকটি $a$, $b$, $c$ — তিনটি symbol-block-এর সবগুলোতে একসাথে বিস্তৃত হতে পারবে না (কারণ প্রতিটি ব্লকের দৈর্ঘ্য $p$, আর তিনটি ব্লক মিলিয়ে দৈর্ঘ্য $3p$, কিন্তু $vxy \leq p$ হওয়ায় এটি সর্বোচ্চ দুটি সংলগ্ন ব্লক স্পর্শ করতে পারে)।
  • (৪) পাম্পিং (যেমন $i=2$, দ্বিগুণ করা) তাই হয় কিছু symbol-এর সংখ্যা বাড়িয়ে দেবে অন্যগুলো অপরিবর্তিত রেখে — $a,b,c$-এর সংখ্যার সমতা ভেঙে যাবে — contradiction, যেহেতু $L$ ধরে নেওয়া হয়েছিল $n=n=n$ ভাষা।
  • (৫) সিদ্ধান্ত: $L = \{a^nb^nc^n\}$ কনটেক্সট-ফ্রি নয়।
L22-এর ফোরশ্যাডো এখন বাস্তবায়িত

L22-এ বলা হয়েছিল একটি PDA-এর স্ট্যাক একটিমাত্র সংখ্যা "গুনতে" পারে (যেমন $0^n1^n$-এ 0-এর সংখ্যা)। কিন্তু $a^nb^nc^n$-এ একসাথে দুটো স্বতন্ত্র সমতা ($a$-এর সংখ্যা = $b$-এর সংখ্যা = $c$-এর সংখ্যা) যাচাই করতে হয় — একটি স্ট্যাক দিয়ে এই তিন-মুখী সমতা রক্ষা করা সম্ভব না, ঠিক এই কারণেই এটি CFL-এর সীমার বাইরে চলে যায়। এই ভাষার জন্য M7/L29-এ একটি context-sensitive গ্রামার লাগবে।

৪ · Python-এ Exhaustive Split-Checking

নিচের কোড সেলে একটি সত্যিকারের check_cfl_pumping_violation ফাংশন — একটি নির্দিষ্ট $s$ ও claimed $p$-এর জন্য প্রতিটি সম্ভাব্য বৈধ পাঁচ-ভাগ বিভাজন ($|vxy|\leq p$, $|vy|>0$ শর্ত মেনে) enumerate করে, এবং প্রতিটির জন্য $i=2$ দিয়ে পাম্প করে $a^nb^nc^n$-এ membership হারায় কিনা যাচাই করে — এটি হাতে-বাছাই করা একটি বিভাজন নয়, বরং সব সম্ভাব্য বিভাজনের উপর একটি সম্পূর্ণ, exhaustive যাচাই।

Python
# L26 -- CFL পাম্পিং লেমা, a^n b^n c^n কনটেক্সট-ফ্রি নয় তার exhaustive প্রমাণ

def is_anbncn(s):
    """L = {a^n b^n c^n : n >= 0}-এর জন্য মেম্বারশিপ চেকার -- regex ছাড়া, সরাসরি স্ক্যান"""
    i, n = 0, len(s)
    a_count = 0
    while i < n and s[i] == "a":
        a_count += 1; i += 1
    b_count = 0
    while i < n and s[i] == "b":
        b_count += 1; i += 1
    c_count = 0
    while i < n and s[i] == "c":
        c_count += 1; i += 1
    if i != n:          # ক্রম ভুল থাকলে (a*b*c* না হলে)
        return False
    return a_count == b_count == c_count


def all_five_way_splits(s, p):
    """s = uvxyz -- |vxy| <= p এবং |vy| > 0 শর্ত মানা সব সম্ভাব্য বিভাজন yield করে"""
    n = len(s)
    for vxy_start in range(0, n + 1):
        max_vxy_len = min(p, n - vxy_start)
        for vxy_len in range(0, max_vxy_len + 1):
            vxy_end = vxy_start + vxy_len
            u, vxy, z = s[:vxy_start], s[vxy_start:vxy_end], s[vxy_end:]
            for v_len in range(0, vxy_len + 1):
                for y_len in range(0, vxy_len - v_len + 1):
                    x_len = vxy_len - v_len - y_len
                    if v_len + y_len == 0:
                        continue   # শর্ত (1): |vy| > 0
                    v = vxy[:v_len]
                    x = vxy[v_len:v_len + x_len]
                    y = vxy[v_len + x_len:]
                    yield (u, v, x, y, z)


def check_cfl_pumping_violation(s, p, membership_fn, i=2):
    """s-এর প্রতিটি বৈধ পাঁচ-ভাগ বিভাজনে i=2 দিয়ে পাম্প করে membership ভাঙে কিনা চেক করে।
    রিটার্ন করে (সবগুলো বিভাজনই violate করেছে কিনা, মোট কতগুলো বিভাজন, প্রথম ব্যতিক্রম আছে কি)।"""
    total = 0
    non_violating = None
    for (u, v, x, y, z) in all_five_way_splits(s, p):
        total += 1
        pumped = u + v * i + x + y * i + z
        if membership_fn(pumped) and non_violating is None:
            non_violating = (u, v, x, y, z, pumped)   # প্রমাণ ভেঙে যাবে যদি এটি ঘটে
    return (non_violating is None, total, non_violating)


for p in (2, 3, 4):
    s = "a" * p + "b" * p + "c" * p
    all_violate, total, counterexample = check_cfl_pumping_violation(s, p, is_anbncn, i=2)
    print(f"p={p}  s={s!r}  মোট-বিভাজন-চেক-করা={total}  প্রতিটিই-violate-করেছে={all_violate}")
    if counterexample:
        print("   ব্যতিক্রম পাওয়া গেছে (প্রমাণ ভেঙে যেত):", counterexample)

print()
print("সিদ্ধান্ত: প্রতিটি পরীক্ষিত p-এর জন্য, a^p b^p c^p-এর প্রতিটি সম্ভাব্য পাঁচ-ভাগ বিভাজন,")
print("i=2 দিয়ে পাম্প করলে a^n b^n c^n ভাষার বাইরে চলে যায় -- অর্থাৎ কোনো পাম্পিং লেংথ")
print("থাকতেই পারে না (contradiction), তাই L = {a^n b^n c^n} কনটেক্সট-ফ্রি নয় (QED)।")

    
লক্ষ্য করুন all_five_way_splits-এ vxy_start স্ট্রিং-এর যেকোনো পজিশন থেকে শুরু হতে পারে (শুধু শুরু থেকে নয়) — এটাই M3/L13-এর রেগুলার-পাম্পিং লেমার শর্ত (2)-এর ($|xy|\leq p$, যা সবসময় স্ট্রিং-এর একেবারে শুরু থেকে গণনা করা হতো) থেকে CFL-ভার্সনের একটি গুরুত্বপূর্ণ পার্থক্য — এখানে $vxy$ ব্লকটি স্ট্রিং-এর যেকোনো জায়গায় থাকতে পারে, শুধু তার নিজের দৈর্ঘ্য $p$-এর মধ্যে সীমাবদ্ধ থাকতে হয়।
মূল কথা · Key takeaway

CFL পাম্পিং লেমা M3/L13-এর রেগুলার-ল্যাঙ্গুয়েজ পাম্পিং লেমার একই যুক্তির (contradiction, pigeonhole) একটি গভীরতর সংস্করণ — এখন পার্স-ট্রি-উচ্চতার উপর ভিত্তি করে, পাঁচ-ভাগ বিভাজন আর দুটো সিমাল্টেনিয়াস পাম্পড অংশ নিয়ে। $a^nb^nc^n$ এর ক্লাসিক উদাহরণ দেখায় — একটি একক স্ট্যাক দিয়ে দুটো স্বতন্ত্র সমতা একসাথে রক্ষা করা যায় না, যা CFL-এর একটি মৌলিক সীমা নির্দেশ করে (L27-এ CFL ক্লোজার প্রপার্টির আলোচনায় এই একই উদাহরণ আবার কাজে লাগবে)।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ CFL পাম্পিং লেমায় কেন দুটো অংশ ($v$ ও $y$) পাম্প করতে হয়, শুধু একটি ($y$, রেগুলার লেমার মতো) নয়?

কারণ CFG-এর পার্স ট্রি-তে একটি ভ্যারিয়েবল পুনরাবৃত্তি হলে, তার subtree-টি স্ট্রিং-এর একটি একক সংলগ্ন অংশ উৎপন্ন করে না — বরং সেই subtree-এর ভেতরের ভ্যারিয়েবল (যা root-এ আবার একই সিম্বল) নিজেও আরও কিছু উৎপন্ন করে, ফলে subtree-টি তার বাম দিকে কিছু টার্মিনাল ($v$) আর ডান দিকে কিছু টার্মিনাল ($y$) উৎপন্ন করে, মাঝখানে $x$ রেখে। DFA-ভিত্তিক রেগুলার প্রমাণে এমন কোনো "ভেতরের গঠন" ছিল না — শুধু একটি লুপ, তাই একটি অংশ ($y$) যথেষ্ট ছিল।

প্র ০২ $a^nb^nc^n$-এর প্রমাণে কেন $s = a^pb^pc^p$ বেছে নেওয়া হলো, অন্য কোনো স্ট্রিং নয়?

কারণ এই নির্দিষ্ট গঠনটাই নিশ্চিত করে যে $vxy$ ব্লক (দৈর্ঘ্য $\leq p$) কখনোই তিনটি symbol-block একসাথে স্পর্শ করতে পারবে না — এটাই প্রমাণের কেন্দ্রীয় কৌশল। যদি স্ট্রিংটি এমনভাবে বাছাই করা হতো যেখানে $vxy$ সহজেই সব ব্লক স্পর্শ করতে পারতো (যেমন খুব ছোট একটি স্ট্রিং), তাহলে হয়তো এমন একটি বিভাজন পাওয়া যেত যা পাম্প করলেও ভাষায়ই থেকে যেত — প্রমাণটি তখন কাজ করতো না। L14-এর মতোই, স্ট্রিং বাছাই এই ধরনের প্রমাণের "শিল্প" (art)।

প্র ০৩ যদি $vxy$ ব্লক শুধু $a$ আর $b$ (দুটো ব্লক, তিনটি নয়) স্পর্শ করে, তাহলে দ্বিগুণ করলে ($i=2$) কেন এখনও ভাষার বাইরে চলে যায়?

যদি $vxy$ শুধু $a$ আর $b$-এর সীমানা স্পর্শ করে (কিন্তু $c$ একেবারেই স্পর্শ করে না), তাহলে দ্বিগুণ করলে $a$-এর সংখ্যা এবং/অথবা $b$-এর সংখ্যা বাড়বে, কিন্তু $c$-এর সংখ্যা ঠিক $p$-ই থেকে যাবে (একদম অপরিবর্তিত)। যেহেতু ভাষায় থাকতে হলে তিনটি সংখ্যাই সমান হতে হয়, আর এখন অন্তত একটি (a অথবা b) বেড়ে গেছে অথচ c অপরিবর্তিত, তাই $a$-সংখ্যা = $b$-সংখ্যা = $c$-সংখ্যা শর্তটি ভেঙে যায় — membership হারায়। এই যুক্তিটি $vxy$-এর সব সম্ভাব্য অবস্থানের জন্যই একইভাবে কাজ করে, যা কোডের exhaustive চেক নিশ্চিত করে দেখিয়েছে।

অনুশীলন

  1. চিন্তা করুন: $L = \{a^nb^n : n \geq 0\}$ (শুধু দুটো ব্লক, তিনটি নয়) কি CFL পাম্পিং লেমা দিয়ে "কনটেক্সট-ফ্রি নয়" প্রমাণ করা যাবে? কেন বা কেন নয়?

    না — $\{a^nb^n\}$ আসলে কনটেক্সট-ফ্রিই (একটি সহজ CFG $S \to aSb \mid \varepsilon$ দিয়ে জেনারেট করা যায়, এবং L22-এর মতো একটি সাধারণ PDA দিয়েও স্বীকৃত হয়)। তাই CFL পাম্পিং লেমা এই ভাষার জন্য প্রযোজ্য এবং সন্তুষ্ট — এটি প্রমাণ করার চেষ্টা করলে দেখা যাবে যেকোনো বিভাজনে পাম্পিং সবসময় ভাষাতেই থেকে যায় (অন্তত একটি বৈধ বিভাজন পাওয়া যাবে যা কাজ করে) — মনে রাখবেন, পাম্পিং লেমা শুধু একটি necessary শর্ত, তাই এটি "প্রমাণ করতে ব্যর্থ হওয়া" মানেই ভাষা নন-কনটেক্সট-ফ্রি নয়।

  2. পরীক্ষা করুন: কোড সেলে p=5 যোগ করে চালান — মোট-বিভাজন-চেক-করা সংখ্যাটি $p$ বাড়ার সাথে সাথে কীভাবে বাড়ে তা লক্ষ্য করুন।

    $p$ বাড়ার সাথে সাথে সম্ভাব্য বিভাজনের সংখ্যা দ্রুত বাড়ে (স্ট্রিং দৈর্ঘ্য $3p$, আর $vxy$-এর সম্ভাব্য অবস্থান ও দৈর্ঘ্য, তার ভেতরে $v$/$x$/$y$-এর সম্ভাব্য বিভাজন — সব মিলিয়ে polynomial বৃদ্ধি) — তবুও, $p=5$-এ প্রতিটিই-violate-করেছে=True থাকা উচিত, প্রমাণের সাধারণতা (generality) আবার নিশ্চিত করে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
L25 · ডিটারমিনিস্টিক বনাম নন-ডিটারমিনিস্টিক PDA