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

টুরিং মেশিন — ফরমাল ডেফিনিশন

Turing machine — formal definition
৯ মিনিট পড়া উচ্চ · Advanced Python কোডসহ সম্পূর্ণ বাংলায়

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

  • টুরিং মেশিনের সম্পূর্ণ ফরমাল সাত-টাপল সংজ্ঞা এবং প্রতিটি উপাদানের অর্থ
  • কনফিগারেশন এবং ট্রানজিশন ফাংশন $\delta$ কীভাবে একটি একক গণনা-ধাপ সংজ্ঞায়িত করে
  • accept, reject, এবং "চিরকাল চলতে থাকা" — TM-এর তিনটি সম্ভাব্য হল্টিং ফলাফল, ও কেন এই তৃতীয়টি একেবারে নতুন
  • একটি সত্যিকারের ডিকশনারি-ভিত্তিক টেপ-সহ TuringMachine সিমুলেটর, এবং $\{0^n1^n\}$-এর জন্য ক্রসিং-অফ কৌশল

১ · টুরিং মেশিন কেন সবচেয়ে শক্তিশালী মডেল

L30-এ দেখা Type-0 আনরেস্ট্রিক্টেড গ্রামারকোনো রেস্ট্রিকশন ছাড়া $\alpha \to \beta$ আকারের প্রোডাকশন — চমস্কি হায়ারার্কির সবচেয়ে সাধারণ স্তর-এর স্বীকৃতিদাতা মেশিন হলো টুরিং মেশিন — এই কোর্সের সবচেয়ে শক্তিশালী কম্পিউটেশন মডেল, যেখানে এই পুরো কোর্সের DFA (M2) → PDA (M5) → LBA (M7) সিঁড়ি এসে থামে। L29-এর LBA-র টেপ ইনপুটের দৈর্ঘ্যের মধ্যেই বাউন্ডেড ছিল — TM সেই সীমাবদ্ধতা তুলে নেয়: টেপ কনসেপচুয়ালি অসীম (conceptually infinite) — মেশিন যত খুশি টেপ ব্যবহার করতে পারে, গণনার সময় প্রয়োজনমতো বাড়তে থাকে।

২ · ফরমাল সংজ্ঞা — সাত-টাপল

একটি টুরিং মেশিন ফরমালি একটি সাত-টাপল —

$$M = (Q, \Sigma, \Gamma, \delta, q_0, q_{accept}, q_{reject})$$

  • $Q$ — স্টেটের ফাইনাইট সেট।
  • $\Sigma$ — ইনপুট আলফাবেটযে সিম্বলগুলো দিয়ে ইনপুট স্ট্রিং লেখা হয়।
  • $\Gamma$ — টেপ আলফাবেট$\Sigma \subseteq \Gamma$, এবং $\Gamma$-তে একটি বিশেষ ব্ল্যাংক সিম্বল ␣ থাকে যা $\Sigma$-এ নেই, যেখানে $\Sigma \subseteq \Gamma$ এবং $\Gamma$-তে একটি বিশেষ ব্ল্যাংক সিম্বল ␣ থাকে, ␣ $\notin \Sigma$।
  • $\delta: Q \times \Gamma \to Q \times \Gamma \times \{L, R\}$ — ট্রানজিশন ফাংশনহেডের নিচের সিম্বল পড়ে, একটি (হয়তো ভিন্ন) সিম্বল লেখে, হেড বামে বা ডানে সরায়, ও স্টেট বদলায় — বর্তমান স্টেট ও হেডের নিচের সিম্বল পড়ে, একটি নতুন সিম্বল লেখে, হেড Left বা Right সরায়, এবং নতুন স্টেটে যায়।
  • $q_0 \in Q$ — স্টার্ট স্টেট।
  • $q_{accept} \in Q$ — accept স্টেট।
  • $q_{reject} \in Q$ — reject স্টেট ($q_{reject} \neq q_{accept}$)।
ইনপুট $w \in \Sigma^*$ টেপে বাম-প্রান্ত থেকে লেখা $\delta: Q\times\Gamma\to Q\times\Gamma\times\{L,R\}$ পড়ো · লেখো · সরাও · স্টেট বদলাও টেপ (conceptually অসীম, dict-ভিত্তিক) $q_{accept}$ accept — হল্ট করে $q_{reject}$ reject — হল্ট করে লুপ ∞ কখনো হল্ট করে না DFA/LBA-তে "লুপ" সম্ভব নয় — TM-এর একেবারে নতুন আচরণ (M9-এর ভিত্তি)
ইনপুট, টেপ ও ট্রানজিশন ফাংশন একসাথে গণনার প্রতিটি ধাপ চালায় — এবং সেই গণনা তিনটির মধ্যে যেকোনো একটি পরিণতিতে পৌঁছাতে পারে।

৩ · কনফিগারেশন

L22-এর PDA কনফিগারেশনের সরাসরি সাধারণীকরণ — একটি টুরিং মেশিনের কনফিগারেশন হলো গণনার একটি সম্পূর্ণ স্ন্যাপশট: বর্তমান স্টেট, টেপের সম্পূর্ণ কনটেন্ট, এবং হেডের অবস্থান। একটি গণনা হলো এমন কনফিগারেশনের একটি সিকোয়েন্স, যেখানে প্রতিটি পরবর্তী কনফিগারেশন $\delta$ প্রয়োগ করে আগেরটি থেকে পাওয়া যায়।

হল্টিং — তিনটি সম্ভাব্য ফলাফল (M9-এর ভিত্তি)

একটি ইনপুটে TM-এর গণনা ঠিক তিনটির মধ্যে একটিতে পৌঁছায় — $q_{accept}$-এ পৌঁছে accept করে, $q_{reject}$-এ পৌঁছে reject করে, অথবা কখনো হল্ট না করে চিরকাল চলতেই থাকে। এই তৃতীয় সম্ভাবনাটি DFA-র (M2, সবসময় ঠিক $|w|$ ধাপে শেষ হয়) বা L29-এর LBA-র (বাউন্ডেড কনফিগারেশন, শেষমেশ হয় হল্ট করে নয়তো একই কনফিগারেশন পুনরাবৃত্তি করে) ক্ষেত্রে ঘটতেই পারে না। ঠিক এই নতুন সম্ভাবনাটিই M9/L39-এর হল্টিং প্রবলেমের কেন্দ্রবিন্দু।

৪ · কোড — একটি জেনুইন TuringMachine সিমুলেটর

নিচে একটি সত্যিকারের TuringMachine ক্লাস — টেপ একটি Python ডিকশনারি (position → symbol), যেখানে না-লেখা প্রতিটি পজিশন ডিফল্টভাবে ব্ল্যাংক — এভাবে কনসেপচুয়ালি অসীম টেপ দক্ষভাবে represent করা যায় (শুধু non-blank সেলগুলো সংরক্ষিত হয়)। $\{0^n1^n : n \geq 0\}$-এর জন্য ক্লাসিক ক্রসিং-অফ কৌশল ব্যবহার করা হয়েছে — বারবার leftmost অচিহ্নিত 0 আর rightmost অচিহ্নিত 1 চিহ্নিত (cross off) করা হয়, যতক্ষণ না সব মিলে যায়।

Python
# একটি সত্যিকারের টুরিং মেশিন সিমুলেটর -- টেপ = position -> symbol ডিকশনারি
# (না-লেখা পজিশন ডিফল্টভাবে ব্ল্যাংক -- conceptually অসীম টেপ দক্ষভাবে represent করে)

class TuringMachine:
    def __init__(self, transitions, start, accept, reject, blank='_'):
        self.transitions = transitions   # dict: (state, symbol) -> (new_state, write, move)
        self.start = start
        self.accept = accept
        self.reject = reject
        self.blank = blank

    def run(self, input_string, max_steps=2000):
        tape = {i: ch for i, ch in enumerate(input_string)}
        head = 0
        state = self.start
        steps = 0
        while steps < max_steps:
            if state == self.accept:
                return "accept", steps
            if state == self.reject:
                return "reject", steps
            symbol = tape.get(head, self.blank)
            key = (state, symbol)
            if key not in self.transitions:
                return "reject", steps          # অসংজ্ঞায়িত ট্রানজিশন -- reject
            new_state, write, move = self.transitions[key]
            tape[head] = write
            head += 1 if move == 'R' else -1
            state = new_state
            steps += 1
        # max_steps শুধু এই স্যান্ডবক্সের ব্যবহারিক নিরাপত্তা-সীমা -- তাত্ত্বিক TM মডেলে
        # কোনো ধাপ-সীমা নেই, এটি অসীম সময় ধরে চলতে পারে
        return "timeout", steps


# L = { 0^n 1^n : n >= 0 } -- leftmost 0 ও rightmost 1 বারবার crossing off
B = '_'
trans = {
    ('q0', '0'): ('q1', 'X', 'R'),    # leftmost অচিহ্নিত 0 -> X দিয়ে চিহ্নিত, ডানে matching 1 খোঁজো
    ('q0', 'Y'): ('q3', 'Y', 'R'),    # সব 0 শেষ -- বাকি অংশ সব Y কিনা যাচাই করো
    ('q0', B):   ('qaccept', B, 'R'),
    ('q1', '0'): ('q1', '0', 'R'),
    ('q1', 'Y'): ('q1', 'Y', 'R'),
    ('q1', '1'): ('q2', 'Y', 'L'),    # rightmost অচিহ্নিত 1 -> Y দিয়ে চিহ্নিত, বামে ফিরে যাও
    ('q1', B):   ('qreject', B, 'R'),
    ('q2', '0'): ('q2', '0', 'L'),
    ('q2', 'Y'): ('q2', 'Y', 'L'),
    ('q2', 'X'): ('q0', 'X', 'R'),    # X সীমানায় পৌঁছালে q0 থেকে পরের 0 খোঁজো
    ('q3', 'Y'): ('q3', 'Y', 'R'),
    ('q3', B):   ('qaccept', B, 'R'),
}
tm = TuringMachine(trans, start='q0', accept='qaccept', reject='qreject')

test_strings = ["", "01", "0011", "000111", "0110", "011", "10", "0001111"]
print("স্ট্রিং          | ফলাফল    | ধাপ")
print("-" * 40)
for s in test_strings:
    result, steps = tm.run(s)
    print(f"{s!r:16s} | {result:8s} | {steps}")

    
উপরের কোডটি হাতে-যাচাই করা — সব আটটি টেস্ট স্ট্রিং প্রত্যাশিতভাবেই accept/reject হয়, এবং ২০ জোড়া 0/1 (মোট ৪০ ক্যারেক্টার) দিয়েও মাত্র কয়েকশ ধাপে সঠিকভাবে accept করে। লক্ষ্য করুন X ও Y — এই দুটি অতিরিক্ত টেপ-সিম্বল $\Gamma$-তে আছে কিন্তু $\Sigma = \{0,1\}$-এ নেই; এগুলো ছাড়া মেশিন মনে রাখতে পারত না কোন 0/1 ইতিমধ্যে মিলে গেছে — এটিই $\Sigma \subseteq \Gamma$ শর্তটি ঠিক কেন দরকার তার একটি concrete উদাহরণ।
মূল কথা · Key takeaway

টুরিং মেশিন একটি সাত-টাপল যার টেপ কনসেপচুয়ালি অসীম — এই একটি বৈশিষ্ট্যই এটিকে LBA-র চেয়ে শক্তিশালী করে তোলে, এবং একই সাথে "চিরকাল লুপে আটকে থাকা" নামক একটি সম্পূর্ণ নতুন আচরণের জন্ম দেয়, যা পরবর্তী পাঠ (L33) এবং পুরো M9 মডিউলের কেন্দ্রীয় বিষয়।

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

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

প্র ০১ টেপকে "কনসেপচুয়ালি অসীম" (conceptually infinite) বলা হয় কেন — "physically infinite" নয়?

কারণ কোনো নির্দিষ্ট গণনায় TM কখনোই সত্যিকারের অসীম টেপ ব্যবহার করে না — যেকোনো finite সংখ্যক ধাপের পর টেপের কেবল একটি finite অংশই non-blank থাকতে পারে (উপরের কোডেও টেপ একটি Python dict, যা শুধু আসলে-ব্যবহৃত পজিশনগুলোই সংরক্ষণ করে)। "অসীম" মানে হলো — টেপের কোনো পূর্ব-নির্ধারিত উচ্চ সীমা নেই; মেশিন যত ধাপ চালাতে চায়, তত পজিশন পর্যন্ত টেপ বাড়াতে পারে। এটিই L29-এর LBA-র বাউন্ডেড টেপের সাথে মূল পার্থক্য।

প্র ০২ DFA (M2) বা LBA (M7)-এর জন্য "চিরকাল লুপে আটকে থাকা" কেন সম্ভব নয়, কিন্তু TM-এর জন্য সম্ভব?

DFA প্রতিটি ইনপুট সিম্বলে ঠিক একটি ধাপ নেয়, তাই $|w|$ দৈর্ঘ্যের ইনপুটে ঠিক $|w|$ ধাপেই থেমে যায় — লুপ করার কোনো উপায় নেই। LBA-র টেপ ইনপুটের দৈর্ঘ্যের সমানুপাতিক বাউন্ডেড, তাই সম্ভাব্য কনফিগারেশনের সংখ্যা finite — pigeonhole নীতি অনুযায়ী, যথেষ্ট লম্বা গণনার পর একই কনফিগারেশন পুনরাবৃত্তি হতে বাধ্য, যা নীতিগতভাবে সনাক্তযোগ্য (তাই LBA-র decision procedures সবসময় হল্ট করানো যায়)। TM-এর টেপ অসীম হতে পারে বলে সম্ভাব্য কনফিগারেশনের সংখ্যাও অসীম — তাই মেশিন কখনো পুনরাবৃত্তি না করেই চিরকাল নতুন নতুন কনফিগারেশনে যেতে পারে।

প্র ০৩ উপরের কোডে X আর Y মার্কার কেন দরকার — সরাসরি মিলে যাওয়া 0/1 মুছে ফেলে (ব্ল্যাংক লিখে) দিলে সমস্যা কী হতো?

যদি মিলে যাওয়া 0/1-কে সরাসরি ব্ল্যাংকে পরিণত করা হতো, তাহলে টেপে একটি "গর্ত" তৈরি হতো — এবং পরের ধাপে "leftmost অচিহ্নিত 0" খোঁজার সময় মেশিন বলতে পারত না সে গর্তের বামে না ডানে আছে, কারণ ব্ল্যাংক সবসময় "টেপের শেষ" নির্দেশ করার কথা। X/Y মার্কার ব্যবহার করে মেশিন স্পষ্টভাবে "এই পজিশনটি ইতিমধ্যে প্রসেস করা হয়েছে" বলতে পারে, অথচ টেপের গঠন (কোনটা 0-এর জায়গা, কোনটা 1-এর জায়গা) অক্ষত থাকে — এটিই টেপকে সঠিকভাবে "স্ক্র্যাচ স্পেস" হিসেবে ব্যবহারের কৌশল, যা L34-এ আরও গভীরভাবে দেখা যাবে।

অনুশীলন

  1. চিন্তা করুন: উপরের $\{0^n1^n\}$ TM-টি কি কখনোই "timeout" রিটার্ন করতে পারে (যদি max_steps যথেষ্ট বড় করা হয়)? এই মেশিনটি কি একটি ডিসাইডার নাকি শুধু একটি রিকগনাইজার (পরের পাঠ L33-এর পার্থক্য মনে করুন)?

    না — এই নির্দিষ্ট মেশিনটি প্রতিটি ইনপুটেই finite সংখ্যক ধাপে হয় accept নয় reject করে, কখনো লুপে আটকায় না (প্রতিটি ধাপে হয় একটি নতুন 0/1 চিহ্নিত হয়, নয়তো মেশিন সরাসরি reject/accept-এ চলে যায় — অসীমভাবে চালানোর কোনো পথ নেই)। তাই max_steps যথেষ্ট বড় হলে এটি কখনো "timeout" রিটার্ন করবে না — এটি একটি প্রকৃত ডিসাইডার, শুধু রিকগনাইজার নয়, যা L33-এ ফরমালি সংজ্ঞায়িত হবে।

  2. পরীক্ষা করুন: test_strings-এ "00011" (তিনটি 0, দুটি 1) যোগ করে Run চাপুন — TM কি সঠিকভাবে reject করে? হাতে ট্রেস করে দেখুন ঠিক কোন ধাপে reject ঘটে।

    হ্যাঁ, reject হয়। প্রথম দুই রাউন্ডে দুটি 0 (X-তে) আর দুটি 1 (Y-তে) মিলে যায়, তারপর q0-এ ফিরে গিয়ে তৃতীয় 0 পেয়ে সেটাকেও X করে ডানে matching 1 খুঁজতে যায় (q1) — কিন্তু আর কোনো 1 নেই, শুধু ব্ল্যাংক পাওয়া যায়, তাই ('q1', '_') ট্রানজিশন অনুযায়ী সরাসরি qreject-এ যায়। এটি ঠিক প্রত্যাশিত — 3 ≠ 2, তাই স্ট্রিংটি $\{0^n1^n\}$-এর সদস্য নয়।

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

আগের পাঠ
চমস্কি হায়ারার্কি — সম্পূর্ণ তুলনা