চার্চ-টুরিং থিসিস ও ইউনিভার্সাল টুরিং মেশিন
এই পাঠে যা শিখবেন
- চার্চ-টুরিং থিসিস কী বলে, এবং কেন এটি একটি প্রমাণযোগ্য থিওরেম না হয়ে একটি থিসিস
- ইউনিভার্সাল টুরিং মেশিন (UTM)-এর ধারণা এবং এর গভীর তাত্ত্বিক ও ব্যবহারিক তাৎপর্য
- কীভাবে একটি টুরিং মেশিনকে একটি স্ট্রিং হিসেবে এনকোড করা যায়, এবং সেই এনকোডিং থেকে আবার ডিকোড করে সিমুলেট করা যায়
- M9-এর ডিসাইডেবিলিটি প্রশ্নগুলো (যেমন হল্টিং প্রবলেম) কেন UTM-এর ধারণা ছাড়া অর্থপূর্ণভাবে বলাই সম্ভব নয়
- Python দিয়ে একটি real, working
UniversalSimulator— যা একটি এনকোডেড TM ডিকোড করে জেনেরিকভাবে সিমুলেট করে, verified against সরাসরি চালানো একই TM
১ · চার্চ-টুরিং থিসিস — একটি থিওরেম নয়, একটি থিসিস
চার্চ-টুরিং থিসিসChurch-Turing Thesisযেকোনো reasonable মডেল অফ কম্পিউটেশন টুরিং মেশিনের সমতুল্য শক্তিশালী — এটি প্রমাণিত নয়, প্রবল প্রমাণ-সমর্থিত একটি অনুমান বলে — "অ্যালগরিদম" বা "effective procedure" (একজন মানুষ, বা যেকোনো reasonable মডেল অফ কম্পিউটেশন, যান্ত্রিকভাবে যা সম্পাদন করতে পারবে) ধারণাটি ঠিক ততটাই শক্তিশালী যতটা একটি টুরিং মেশিন গণনা করতে পারে।
এখানে একটি গুরুত্বপূর্ণ, প্রায়ই ভুল বোঝা যায় এমন সূক্ষ্ম বিষয় সরাসরি বলা দরকার — এটি একটি গাণিতিকভাবে প্রমাণযোগ্য থিওরেম নয়। কারণ, "effective procedure" নিজেই একটি অনানুষ্ঠানিক, স্বজ্ঞামূলক (intuitive) ধারণা — এটি নিজেই ফরমালি সংজ্ঞায়িত নয়। যেহেতু সমীকরণের একপাশ (informal notion) নিজেই ফরমাল নয়, তাই এই সমতাকে কখনোই গাণিতিকভাবে প্রমাণ করা সম্ভব নয় — এটি একটি থিসিস (একটি প্রস্তাবিত সত্য), যা overwhelming প্রমাণ দ্বারা সমর্থিত, কিন্তু প্রমাণিত নয়।
সেই প্রমাণটি ঠিক কী? প্রতিটি বিকল্প কম্পিউটেশন মডেল যা এখন পর্যন্ত প্রস্তাব করা হয়েছে — ল্যাম্বডা ক্যালকুলাসঅ্যালোনজো চার্চের ফাংশন-ভিত্তিক গণনার মডেল, রিকার্সিভ ফাংশন, বাস্তব-জগতের প্রোগ্রামিং ল্যাঙ্গুয়েজ, এমনকি M35-M36-এর মাল্টি-টেপ ও নন-ডিটারমিনিস্টিক TM — প্রতিটিই প্রমাণিতভাবে সমতুল্য শক্তির সিঙ্গেল-টেপ ডিটারমিনিস্টিক টুরিং মেশিনের সাথে (M35, M36-এর ইকুইভ্যালেন্স থিওরেমগুলো দেখুন) — কিন্তু এই প্রতিটি সমতা নিজে নিজে একটি প্রমাণযোগ্য থিওরেম, কারণ দুটো পাশই ফরমাল। "TM = সব সম্ভাব্য effective procedure" — এই দাবিটাই একমাত্র যেটা প্রমাণের বাইরে থেকে যায়, কারণ ডানপাশটা ফরমাল নয়।
M35-এর মাল্টি-টেপ/সিঙ্গেল-টেপ ইকুইভ্যালেন্স, বা M36-এর NTM/DTM ইকুইভ্যালেন্স — এগুলো প্রতিটি একটি প্রমাণিত থিওরেম, কারণ দুটো মডেলই ফরমালি সংজ্ঞায়িত। কিন্তু "টুরিং মেশিনই সব সম্ভাব্য কম্পিউটেশনের সীমা" — এই বৃহত্তর দাবিটা একটি থিসিস, কারণ "সম্ভাব্য কম্পিউটেশন" (মানুষের স্বজ্ঞায়) কখনোই সম্পূর্ণরূপে ফরমালাইজড হয়নি — এবং নীতিগতভাবে কখনো হতেও পারবে না, যেহেতু এটি একটি প্রাক-ফরমাল, স্বজ্ঞামূলক ধারণা।
২ · ইউনিভার্সাল টুরিং মেশিন (UTM)
ইউনিভার্সাল টুরিং মেশিনUniversal Turing Machine (UTM)একটি নির্দিষ্ট TM যা অন্য যেকোনো TM-এর এনকোডেড বর্ণনা + ইনপুট নিয়ে সেই TM-কে সিমুলেট করে (UTM) হলো একটি নির্দিষ্ট, একক টুরিং মেশিন $U$ যা ইনপুট হিসেবে নেয় — অন্য যেকোনো টুরিং মেশিন $M$-এর একটি এনকোডিং (একটি স্ট্রিং হিসেবে — L05-এর "যেকোনো বস্তুকে স্ট্রিং হিসেবে এনকোড করা যায়" ধারণার সরাসরি পুনরাবৃত্তি, এবার TM নিজেই সেই বস্তু) প্লাস একটি ইনপুট স্ট্রিং $w$ — এবং $M$-কে $w$-এর উপর চালিয়ে যা ফলাফল হতো, ঠিক সেই ফলাফলই প্রোডিউস করে।
এর তাৎপর্য সত্যিই গভীর — একটি একক, ফিক্সড মেশিন, সঠিক "প্রোগ্রাম" (অন্য TM-এর এনকোডিং) ইনপুট
হিসেবে দিলে, অন্য যেকোনো TM যা গণনা করতে পারত, তা-ই গণনা করতে পারে। এটাই আধুনিক
স্টোরড-প্রোগ্রাম কম্পিউটারএকটি ফিক্সড হার্ডওয়্যার, যেখানে প্রোগ্রাম নিজে মেমোরিতে ডেটা হিসেবে সংরক্ষিত থাকে ও প্রসেস করা হয়-এর
তাত্ত্বিক পূর্বপুরুষ (সরাসরি cross-ref: ../computer-architecture/-এর von Neumann আর্কিটেকচার, যেখানে
প্রোগ্রাম নিছক মেমোরিতে থাকা ডেটা, একটিমাত্র ফিক্সড হার্ডওয়্যার দিয়ে প্রসেস করা হয় — ঠিক UTM-এর ধারণা)।
৩ · M9-এর জন্য কেন এটি অপরিহার্য গোড়াপত্তন
এই পাঠের সবচেয়ে গুরুত্বপূর্ণ সেতুবন্ধন সরাসরি বলা দরকার — UTM-এর অস্তিত্বই একমাত্র কারণ, যা "M কি ইনপুট w-এর উপর হল্ট করে?" — এই প্রশ্নটাকে একটি সুনির্দিষ্ট (well-defined) কম্পিউটেশনাল প্রশ্ন হিসেবে জিজ্ঞাসা করা সম্ভব করে তোলে। কারণ প্রশ্নটা জিজ্ঞাসা করতেই একটি TM-এর বর্ণনাকে ইনপুট হিসেবে আরেকটি মেশিনকে দেওয়ার দরকার পড়ে — আর সেটাই এই পাঠের UTM-এর ঠিক কাজ। M9/L39-এর হল্টিং প্রবলেম সরাসরি এই ধারণার উপর দাঁড়িয়ে।
৪ · TM এনকোডিং ও একটি real UniversalSimulator
নিচের কোড সেলে L32-এর $\{0^n1^n : n \geq 0\}$ টুরিং মেশিনটি (leftmost অচিহ্নিত 0 আর rightmost অচিহ্নিত 1
ক্রস-অফ করার কৌশলে পুনর্গঠিত) একটি একক স্ট্রিং হিসেবে এনকোড করা হচ্ছে (TMEncoder), এবং তারপর একটি
সত্যিকারের UniversalSimulator সেই এনকোডেড স্ট্রিং ডিকোড করে জেনেরিকভাবে সিমুলেট করছে — কোনো
হার্ডকোডেড 0^n1^n-নির্দিষ্ট লজিক ছাড়াই, শুধু ডিকোড করা ট্রানজিশন টেবিল অনুসরণ করে। যাচাই করা
হচ্ছে — সরাসরি TM চালানো আর UniversalSimulator দিয়ে চালানো, উভয়ই প্রতিটি টেস্ট স্ট্রিং-এ অভিন্ন ফলাফল দেয় কি না।
BLANK = '_' # টেপের ব্ল্যাংক সিম্বল
class TuringMachine:
"""L32-এর ধাঁচের একটি জেনেরিক টুরিং মেশিন -- (state, symbol) থেকে
(new_state, write, direction)-এর ট্রানজিশন টেবিল দিয়ে চালিত।"""
def __init__(self, states, input_alphabet, tape_alphabet, transitions,
start, accept, reject, blank=BLANK):
self.states = states
self.transitions = transitions
self.start = start
self.accept = accept
self.reject = reject
self.blank = blank
def run(self, input_string, max_steps=3000):
tape = {i: c for i, c in enumerate(input_string)}
head, state, steps = 0, self.start, 0
while state != self.accept and state != self.reject and steps < max_steps:
symbol = tape.get(head, self.blank)
key = (state, symbol)
if key not in self.transitions:
return 'reject', steps # ট্রানজিশন নেই -- ইমপ্লিসিট reject
new_state, write_symbol, direction = self.transitions[key]
tape[head] = write_symbol
head += 1 if direction == 'R' else -1
state = new_state
steps += 1
if state == self.accept:
return 'accept', steps
elif state == self.reject:
return 'reject', steps
else:
return 'timeout', steps
# L = {0^n 1^n : n >= 0} -- leftmost অচিহ্নিত 0 ও rightmost অচিহ্নিত 1 জোড়ায় জোড়ায় ক্রস-অফ
zn_states = {'q_find0', 'q_goright', 'q_findlast', 'q_gohome', 'q_accept', 'q_reject'}
zn_transitions = {
('q_find0', 'X'): ('q_find0', 'X', 'R'), # আগে চিহ্নিত 0 টপকে যাও
('q_find0', 'Y'): ('q_find0', 'Y', 'R'), # আগে চিহ্নিত 1 টপকে যাও
('q_find0', '0'): ('q_goright', 'X', 'R'), # leftmost 0 -- X দিয়ে চিহ্নিত করো
('q_find0', '1'): ('q_reject', '1', 'R'), # 0 আশা করেছিলাম, 1 পেলাম -- reject
('q_find0', BLANK): ('q_accept', BLANK, 'R'), # কিছুই বাকি নেই -- accept
('q_goright', '0'): ('q_goright', '0', 'R'),
('q_goright', '1'): ('q_goright', '1', 'R'),
('q_goright', 'X'): ('q_goright', 'X', 'R'),
('q_goright', 'Y'): ('q_goright', 'Y', 'R'),
('q_goright', BLANK): ('q_findlast', BLANK, 'L'), # ডানপ্রান্তে পৌঁছে এক ঘর পিছিয়ে আসি
('q_findlast', 'X'): ('q_findlast', 'X', 'L'),
('q_findlast', 'Y'): ('q_findlast', 'Y', 'L'),
('q_findlast', '1'): ('q_gohome', 'Y', 'L'), # rightmost 1 -- Y দিয়ে চিহ্নিত করো
('q_findlast', '0'): ('q_reject', '0', 'L'), # 1 আশা করেছিলাম, 0 পেলাম -- reject
('q_findlast', BLANK): ('q_reject', BLANK, 'L'), # অচিহ্নিত 1 নেই -- reject
('q_gohome', '0'): ('q_gohome', '0', 'L'),
('q_gohome', '1'): ('q_gohome', '1', 'L'),
('q_gohome', 'X'): ('q_gohome', 'X', 'L'),
('q_gohome', 'Y'): ('q_gohome', 'Y', 'L'),
('q_gohome', BLANK): ('q_find0', BLANK, 'R'), # টেপের শুরুতে ফিরে আবার শুরু করো
}
zn_tm = TuringMachine(zn_states, {'0', '1'}, {'0', '1', 'X', 'Y', BLANK},
zn_transitions, 'q_find0', 'q_accept', 'q_reject')
class TMEncoder:
"""একটি TM-কে (states, transitions, start, accept, reject) একটিমাত্র স্ট্রিং-এ এনকোড করে।"""
SEP_FIELD, SEP_RULE, SEP_PARTS, SEP_ARROW = ';;', '|', ',', '>'
@staticmethod
def encode(tm):
parts = [
'STATES=' + TMEncoder.SEP_PARTS.join(sorted(tm.states)),
'START=' + tm.start, 'ACCEPT=' + tm.accept, 'REJECT=' + tm.reject,
'BLANK=' + tm.blank,
]
rules = []
for (state, symbol), (ns, ws, d) in tm.transitions.items():
lhs = TMEncoder.SEP_PARTS.join([state, symbol])
rhs = TMEncoder.SEP_PARTS.join([ns, ws, d])
rules.append(lhs + TMEncoder.SEP_ARROW + rhs)
parts.append('TRANS=' + TMEncoder.SEP_RULE.join(rules))
return TMEncoder.SEP_FIELD.join(parts)
@staticmethod
def decode(encoded):
fields = dict(f.split('=', 1) for f in encoded.split(TMEncoder.SEP_FIELD))
states = set(fields['STATES'].split(TMEncoder.SEP_PARTS))
transitions = {}
if fields['TRANS']:
for rule in fields['TRANS'].split(TMEncoder.SEP_RULE):
lhs, rhs = rule.split(TMEncoder.SEP_ARROW)
state, symbol = lhs.split(TMEncoder.SEP_PARTS)
ns, ws, d = rhs.split(TMEncoder.SEP_PARTS)
transitions[(state, symbol)] = (ns, ws, d)
return (states, transitions, fields['START'], fields['ACCEPT'],
fields['REJECT'], fields['BLANK'])
class UniversalSimulator:
"""<M> (এনকোডেড TM) + w (ইনপুট) নিয়ে M-কে ডিকোড করে জেনেরিকভাবে সিমুলেট করে --
এটাই একটি ইউনিভার্সাল টুরিং মেশিনের মূল ধারণা।"""
def __init__(self, encoded_tm):
(self.states, self.transitions, self.start,
self.accept, self.reject, self.blank) = TMEncoder.decode(encoded_tm)
def run(self, input_string, max_steps=3000):
tape = {i: c for i, c in enumerate(input_string)}
head, state, steps = 0, self.start, 0
while state != self.accept and state != self.reject and steps < max_steps:
symbol = tape.get(head, self.blank)
key = (state, symbol)
if key not in self.transitions:
return 'reject', steps
ns, ws, d = self.transitions[key]
tape[head] = ws
head += 1 if d == 'R' else -1
state = ns
steps += 1
if state == self.accept:
return 'accept', steps
elif state == self.reject:
return 'reject', steps
else:
return 'timeout', steps
# --- verification: encode-decode-সিমুলেট চক্র, বনাম সরাসরি চালানো -- ফলাফল অভিন্ন হতে হবে ---
encoded_M = TMEncoder.encode(zn_tm)
print(f"এনকোডেড ⟨M⟩-এর দৈর্ঘ্য: {len(encoded_M)} ক্যারেক্টার\n")
universal = UniversalSimulator(encoded_M)
tests = ["", "0", "1", "01", "10", "0011", "000111", "0101", "011", "0001"]
print(f"{'w':12s} | {'সরাসরি M(w)':13s} | {'U(<M>,w)':13s} | মিলছে?")
print("-" * 52)
all_match = True
for w in tests:
direct, _ = zn_tm.run(w)
via_utm, _ = universal.run(w)
match = (direct == via_utm)
all_match = all_match and match
print(f"{w!r:12s} | {direct:13s} | {via_utm:13s} | {'হ্যাঁ' if match else 'না'}")
print(f"\nসব টেস্ট স্ট্রিং-এ ফলাফল অভিন্ন: {all_match}")
UniversalSimulator-এর ভেতরে কোথাও 0^n1^n-নির্দিষ্ট কোনো লজিক নেই — এটি
শুধু TMEncoder.decode-এর দেওয়া ট্রানজিশন টেবিল অনুসরণ করে, ঠিক TuringMachine.run-এর
মতোই একই জেনেরিক লুপ চালায়। এটাই একটি সত্যিকারের ইউনিভার্সাল সিমুলেটরের সংজ্ঞাগত বৈশিষ্ট্য — একই কোড, যেকোনো
এনকোডেড TM-এর জন্য কাজ করে, শুধু ইনপুট হিসেবে ভিন্ন এনকোডিং দিলেই।
চার্চ-টুরিং থিসিস আমাদের বলে টুরিং মেশিনই কম্পিউটেশনের সীমা নির্ধারণ করে (একটি থিসিস হিসেবে, প্রমাণ হিসেবে নয়) — আর ইউনিভার্সাল টুরিং মেশিন দেখায় একটিমাত্র ফিক্সড মেশিনই যথেষ্ট, যদি তাকে সঠিক "প্রোগ্রাম" (এনকোডেড TM) দেওয়া যায়। এই দুটো ধারণা একসাথে M9-এর পুরো ভিত্তি তৈরি করে — যেখানে আমরা প্রশ্ন করব, এই সর্বশক্তিমান মডেল দিয়েও কি সব প্রশ্নের উত্তর অ্যালগরিদমিকভাবে দেওয়া সম্ভব?
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ চার্চ-টুরিং থিসিস কেন একটি "থিওরেম" না হয়ে একটি "থিসিস"?
কারণ এই দাবির একপাশে আছে "effective procedure" বা "অ্যালগরিদম"-এর অনানুষ্ঠানিক, স্বজ্ঞামূলক ধারণা — যা কখনোই ফরমালি সংজ্ঞায়িত হয়নি (এবং নীতিগতভাবে হতেও পারে না, কারণ এটি "মানুষ যা করতে পারবে" এমন একটি প্রাক-গাণিতিক ধারণা)। একটি গাণিতিক প্রমাণের জন্য উভয় পাশই ফরমাল হতে হয় — যেহেতু এখানে একপাশ ফরমাল নয়, তাই এই সমতাকে প্রমাণ করা অসম্ভব। এটি শুধু overwhelming পরোক্ষ প্রমাণ (প্রতিটি বিকল্প মডেল TM-এর সমতুল্য প্রমাণিত) দ্বারা সমর্থিত একটি অনুমান।
প্র ০২ ইউনিভার্সাল টুরিং মেশিনের সাথে আধুনিক কম্পিউটারের সম্পর্ক কী?
UTM-ই স্টোরড-প্রোগ্রাম কম্পিউটারের তাত্ত্বিক পূর্বপুরুষ — ../computer-architecture/-এর von
Neumann আর্কিটেকচারে একটি ফিক্সড হার্ডওয়্যার (CPU) মেমোরিতে সংরক্ষিত প্রোগ্রামকে ডেটা হিসেবে পড়ে ও চালায়।
UTM ঠিক এই ধারণারই একটি বিমূর্ত, গাণিতিক সংস্করণ — একটি ফিক্সড মেশিন $U$, যাকে ভিন্ন ভিন্ন "প্রোগ্রাম"
(এনকোডেড TM) দিলে, ভিন্ন ভিন্ন গণনা করতে পারে — নতুন হার্ডওয়্যার তৈরি না করেই।
প্র ০৩
কোড সেলে কীভাবে যাচাই করা হলো যে UniversalSimulator সত্যিই সঠিকভাবে কাজ করছে?
একই টুরিং মেশিন zn_tm দুইভাবে চালানো হয়েছে — একবার সরাসরি (zn_tm.run(w)), আর
একবার এনকোড-ডিকোড-সিমুলেট চক্রের মধ্য দিয়ে (universal.run(w))। ১০টি ভিন্ন টেস্ট স্ট্রিং-এর
প্রতিটির জন্য দুটো ফলাফল তুলনা করে all_match ট্র্যাক করা হয়েছে — যদি এনকোডিং বা ডিকোডিং-এ কোনো
বাগ থাকত, অন্তত একটি স্ট্রিং-এ অমিল ধরা পড়ত। সব মিলে যাওয়া মানেই — UniversalSimulator একটি
জেনেরিক ইন্টারপ্রেটার হিসেবে সত্যিই কাজ করছে, কোনো হার্ডকোডেড শর্টকাট ছাড়াই।
অনুশীলন
-
চিন্তা করুন: যদি ভবিষ্যতে কোনো নতুন কম্পিউটেশন মডেল আবিষ্কৃত হয় যা প্রমাণিতভাবে টুরিং মেশিনের
চেয়ে বেশি শক্তিশালী (অর্থাৎ এমন কিছু গণনা করতে পারে যা কোনো TM কখনো পারে না) — এটি চার্চ-টুরিং থিসিসের জন্য
কী প্রভাব ফেলবে? এটা কি থিসিসটাকে "ভুল প্রমাণ" করবে?
হ্যাঁ, এটি চার্চ-টুরিং থিসিসকে ভুল প্রমাণ করবে (refute করবে) — কারণ থিসিসটা দাবি করে TM-ই সর্বোচ্চ সীমা। তবে লক্ষ্য করুন — এটি সম্ভব, কারণ থিসিসটা প্রমাণিত নয়, শুধু অনুমান। বাস্তবে, আজ পর্যন্ত কোনো এমন মডেল পাওয়া যায়নি (কোয়ান্টাম কম্পিউটিং-সহ — কোয়ান্টাম কম্পিউটার দ্রুততর হতে পারে কিছু সমস্যায়, কিন্তু কোনো নতুন ভাষা recognize করতে পারে না যা TM পারে না) — এই ধারাবাহিক ব্যর্থতাই থিসিসের প্রমাণ-সমর্থন আরও শক্তিশালী করে, যদিও তা কখনোই একটি চূড়ান্ত প্রমাণে পরিণত হয় না।
-
পরীক্ষা করুন: কোড সেলে
testsলিস্টে আপনার নিজের একটি নতুন টেস্ট স্ট্রিং (যেমন"00011100") যোগ করে Run চেপে দেখুন — সরাসরি TM আর UniversalSimulator একই ফলাফল দেয় কি না, এবং আপনার হাতে-গোনা প্রত্যাশার সাথে মেলে কি না।"00011100"-এ তিনটি 0, তিনটি 1, তারপর আরও দুটো 0 আছে — এটি0*1*ফরম্যাটেও নেই (1-এর পরে আবার 0 এসেছে), তাই এটি $0^n1^n$-এ নেই — TM-এর এটিrejectকরার কথা, এবং দুটো সিমুলেশনেই একই ফলাফল আসার কথা, যেহেতুUniversalSimulatorঠিক একই ট্রানজিশন টেবিল অনুসরণ করে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পাঠ ৩৮ · ডিসাইডেবল বনাম টুরিং-রিকগনাইজেবল ল্যাঙ্গুয়েজ পরের পাঠ এই পাঠের UTM ধারণা ব্যবহার করেই M9 শুরু হচ্ছে — decidable ও recognizable ভাষার মধ্যে সেই গুরুত্বপূর্ণ পার্থক্য ফরমালি বোঝা।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ DFA থেকে টুরিং মেশিন, ডিসাইডেবিলিটি ও P বনাম NP পর্যন্ত — সম্পূর্ণ পাঠক্রম।
- Computer Architecture কোর্স তাত্ত্বিক সংযোগ von Neumann আর্কিটেকচার ও স্টোরড-প্রোগ্রাম ধারণা — UTM-এর বাস্তব-জগতের প্রতিরূপ সেই কোর্সে দেখুন।