এই পর্বে যা শিখবেন
- ISBN-10, EAN-13 আর Luhn-এর check digit হাতে বের করা, আর প্রতিটি পদ্ধতি কোন ভুল ধরে ও কোনটি ধরে না তার প্রমাণ
- k mod m hashing-এ বালতিতে key কীভাবে ছড়ায়, আর m বাছাইয়ের নিয়ম (gcd দিয়ে প্রমাণসহ)
- Linear congruential generator-এর period, Hull–Dobell-এর শর্ত আর primitive root-এর সম্পর্ক
- কেন LCG নিরাপত্তার কাজে অচল, আর RANDU-র বিখ্যাত ত্রুটির প্রমাণ
- CRC আর round-robin সময়সূচির পেছনের mod-এর হিসাব
১ · একটি হাতিয়ার, তিনটি কাজ — বাজারঘরের তিন সমস্যা
মডিউল ৪-এ আমরা সংখ্যাতত্ত্বের মূল যন্ত্রপাতি তৈরি করেছি: বিভাজ্যতা আর মৌলিক সংখ্যা (পর্ব ২০), gcd আর extended Euclid (পর্ব ২১), modular arithmetic (পর্ব ২২), inverse আর CRT (পর্ব ২৩), Fermat আর Euler (পর্ব ২৪), আর মৌলিকতার পরীক্ষা (পর্ব ২৫ “Primality test আর factoring — Miller–Rabin থেকে কঠিনতার ধারণা”). এই শেষ পর্বে সেই যন্ত্রপাতি দিয়ে তিনটি দৈনন্দিন কাজ করা হবে, প্রায় কোনো নতুন যন্ত্র ছাড়াই।
কাল্পনিক অনলাইন দোকান বাজারঘরের তিনটি সমস্যা দিয়ে শুরু করা যাক।
- টাইপের ভুল: গুদামের কর্মী ফোনে শুনে ইনভয়েস নম্বর টাইপ করেন। একটি অঙ্ক ভুল হলে বা পাশাপাশি দুটি অঙ্ক উল্টে গেলে ভুল পার্সেল ভুল ঠিকানায় যায়। নম্বরের শেষে একটি অতিরিক্ত অঙ্ক বসিয়ে কি এমন ভুল সঙ্গে সঙ্গে ধরা যায়?
- বোঝা ভাগ করা: কয়েক লক্ষ অর্ডার আইডিকে 64টি ডেটাবেস ভাগে (বালতিতে) রাখতে হবে, যাতে কোনো ভাগে চাপ বেশি না পড়ে। “আইডি mod 64” কি যথেষ্ট?
- এলোমেলো বাছাই: লটারিতে কুপন পাবেন কোন গ্রাহক? কম্পিউটার “এলোমেলো” সংখ্যা বানায় কীভাবে, আর কেউ কি তা আগে থেকে অনুমান করতে পারে?
তিনটির উত্তরই একটি প্রশ্নে নামে: x ↦ ax + b (mod m) ফাংশনটি কখন সব ভাগশেষ ছুঁয়ে যায়, আর কখন কিছু ভাগশেষ বাদ পড়ে? উত্তর দেয় gcd(a, m). gcd = 1 হলে ফাংশনটি একটি bijection, কারণ a-এর inverse আছে (পর্ব ২৩); gcd = g > 1 হলে সে শুধু m/g-টি ভাগশেষে পৌঁছায়। এই এক তথ্য থেকে check digit-এর ভুল ধরা, hashing-এর ছড়িয়ে পড়া আর random-এর period, তিনটিই আসবে। মাঝে মাঝে Euclid-এর lemma (পর্ব ২১) আর order ও primitive root (পর্ব ২৪) লাগবে।
২ · Check digit-এর গণিত — ISBN-10 আর mod 11
Check digit হলো একটি সংখ্যার শেষে বসানো অতিরিক্ত অঙ্ক, যা বাকি অঙ্কগুলো থেকে হিসাব করে বের করা হয়। এটি কোনো নতুন তথ্য বহন করে না; তার কাজ শুধু পাহারা দেওয়া। কেউ নম্বরটি ভুল টাইপ করলে হিসাব আর মেলে না, আর কম্পিউটার সঙ্গে সঙ্গে বলে “নম্বরটি সঠিক নয়”। পরীক্ষার খাতায় রোল নম্বরের পাশে কথায় লেখা রোল নম্বরও একই কাজ করে: দুটো না মিললে কোথাও ভুল হয়েছে।
হাতে নম্বর লেখায় সবচেয়ে সাধারণ দুই ধরনের ভুল (ডাচ গণিতবিদ J. Verhoeff-এর ১৯৬৯ সালের পরিচিত পর্যবেক্ষণ অনুযায়ী): একক ভুল (একটি অঙ্কের জায়গায় অন্য অঙ্ক, যেমন 4 → 7) আর পাশাপাশি অদলবদল বা transposition error (যেমন …58… → …85…). একটি ভালো পদ্ধতিকে অন্তত এই দুই ধরনের ভুল ধরতে হয়।
একটি modulus m আর ওজন w1, …, wn ঠিক করা হয়। অঙ্কের ক্রম d1 d2 ⋯ dn বৈধ, যদি
w1d1 + w2d2 + ⋯ + wndn ≡ 0 (mod m)
প্রথম n − 1টি অঙ্ক (payload) দেওয়া থাকলে শেষ অঙ্ক dn এমনভাবে বাছা হয়, যাতে শর্তটি সত্য হয়।
শেষ অঙ্কটি সবসময় বাছা যায় কি না, তা পর্ব ২৩-এর linear congruence-এর প্রশ্ন: wndn ≡ −(বাকি যোগফল) (mod m). gcd(wn, m) = 1 হলে ঠিক একটি সমাধান mod m আছে। সব পদ্ধতির ভুল-ধরার ক্ষমতা নিচের এক lemma থেকে আসে।
একটি বৈধ নম্বরে (ক) i-তম অঙ্ক di বদলে di′ হলে ভুলটি ধরা পড়ে না ⇔ m ∣ wi(di′ − di). (খ) i আর j অবস্থানের অঙ্ক a আর b অদলবদল হলে ভুলটি ধরা পড়ে না ⇔ m ∣ (wi − wj)(a − b).
প্রমাণ:
- (ক) পুরোনো যোগফল S ≡ 0. নতুন যোগফল S′ = S + wi(di′ − di), কারণ শুধু একটি পদ বদলেছে। — যোগফলের পার্থক্য
- ভুল ধরা পড়ে না মানে নতুন নম্বরও বৈধ, অর্থাৎ S′ ≡ 0 ⇔ m ∣ S′ − S = wi(di′ − di). — congruence-এর সংজ্ঞা
- (খ) আগে i আর j অবস্থানে wia + wjb, পরে wib + wja. পার্থক্য wi(b − a) + wj(a − b) = −(wi − wj)(a − b). ধাপ ২-এর যুক্তিতে ভুল ধরা পড়ে না ⇔ m এই পার্থক্যকে ভাগ করে। ∎ — বীজগণিত
এখন যেকোনো পদ্ধতি পরীক্ষা করা সহজ: শুধু দেখতে হবে কোন গুণফল m দিয়ে বিভাজ্য হতে পারে। এর জন্য সবচেয়ে সুবিধাজনক m হলো একটি মৌলিক সংখ্যা, কারণ তখন Euclid-এর lemma খাটে: মৌলিক p যদি দুটি সংখ্যার গুণফলকে ভাগ করে, তবে অন্তত একটিকে ভাগ করে।
ISBN-10: ওজন 10 থেকে 1, mod 11
পুরোনো বইয়ের ১০ অঙ্কের ISBN-এ ওজন 10, 9, 8, …, 2, 1 আর modulus 11. Check digit 0 থেকে 10 পর্যন্ত যেকোনো মান নিতে পারে; 10 একটি অঙ্কে লেখা যায় না বলে তার জন্য রোমান সংখ্যা “X” ব্যবহৃত হয়। নিচের stepper একটি কাল্পনিক নম্বরের (π-এর অঙ্ক দিয়ে বানানো) check digit বের করে।
1অঙ্ক: 3 1 4 1 5 9 2 6 52ওজন: 10 9 8 7 6 5 4 3 2330 + 9 + 32 = 7147 + 30 + 45 = 82 → 15358 + 18 + 10 = 36 → 1896189 ≡ 2, তাই d₁₀ ≡ −2 ≡ 9
| এই ধাপের গুণ | |
| চলতি যোগফল | |
| mod 11 |
- ওজন বসানো। নয়টি payload অঙ্ক 3 1 4 1 5 9 2 6 5, আর তাদের ওজন বাঁ থেকে 10, 9, …, 2. দশম অঙ্ক (check digit) পাবে ওজন 1.
- প্রথম তিনটি। 10 · 3 = 30, 9 · 1 = 9, 8 · 4 = 32; যোগফল 71.
- পরের তিনটি। 7 · 1 = 7, 6 · 5 = 30, 5 · 9 = 45; এদের যোগ 82, চলতি যোগফল 153.
- শেষ তিনটি। 4 · 2 = 8, 3 · 6 = 18, 2 · 5 = 10; যোগ 36, মোট 189.
- Check digit. 189 = 11 · 17 + 2, তাই 189 ≡ 2 (mod 11). দরকার 189 + 1 · d10 ≡ 0, অর্থাৎ d10 ≡ −2 ≡ 9. যাচাই: 189 + 9 = 198 = 11 · 18 ✓.
ISBN-10 পদ্ধতি প্রতিটি একক ভুল ধরে, আর যেকোনো দুটি অবস্থানের (শুধু পাশাপাশি নয়) দুটি ভিন্ন অঙ্কের অদলবদলও ধরে।
প্রমাণ: ওজন wi = 11 − i, তাই প্রতিটি ওজন 1 থেকে 10-এর মধ্যে।
- একক ভুলে Lemma ২.২(ক) অনুযায়ী দেখতে হবে 11 ∣ wi(di′ − di) সম্ভব কি না। 11 মৌলিক, তাই Euclid-এর lemma-য় তাহলে 11 ∣ wi বা 11 ∣ (di′ − di). — Euclid-এর lemma
- কিন্তু 1 ≤ wi ≤ 10, আর di′ ≠ di হওয়ায় 1 ≤ |di′ − di| ≤ 10 (X = 10 ধরেও)। 1 থেকে 10-এর কোনো সংখ্যা 11 দিয়ে বিভাজ্য নয়, তাই ভুলটি ধরা পড়ে। — আকারের সীমা
- অদলবদলে Lemma ২.২(খ): wi − wj = j − i, আর i ≠ j বলে 1 ≤ |j − i| ≤ 9. a ≠ b বলে 1 ≤ |a − b| ≤ 10. ধাপ ১–২-এর একই যুক্তিতে 11 ∤ (j − i)(a − b). ∎ — Euclid-এর lemma আবার
প্রমাণের প্রতিটি ধাপ m = 11 মৌলিক হওয়ার ওপর দাঁড়িয়ে; এর দাম হলো check digit-এ অদ্ভুত “X” অক্ষর। বইয়ে প্রায়ই সমতুল্য রূপ দেখা যায়, ওজন 1, 2, …, 10, কারণ ∑ (11 − i)di = 11∑di − ∑ i·di. এই রূপে check digit সরাসরি ∑i=19 i·di mod 11 (উপরের নম্বরে 207 mod 11 = 9, একই উত্তর)।
ইতিহাস: ৯ অঙ্কের Standard Book Numbering (SBN) ১৯৬০-এর দশকের শেষে যুক্তরাজ্যে চালু হয়, আর ১০ অঙ্কের ISBN ১৯৭০ সালে আন্তর্জাতিক মান ISO 2108 হিসেবে গৃহীত হয় বলে সাধারণত উল্লেখ করা হয়। ১ জানুয়ারি ২০০৭ থেকে নতুন বইয়ে ১৩ অঙ্কের ISBN-13 ব্যবহৃত হয়, যা দোকানের বারকোডের EAN-13 পদ্ধতির সঙ্গে মেলে। পরের অংশে দেখবেন, এই বদলে ভুল-ধরার ক্ষমতা একটু কমেছে।
৩ · EAN-13 আর Luhn — mod 10-এর আপস
অনেক সিস্টেম শুধু 0–9 অঙ্ক চায়, তাই বারকোড আর ব্যাংক কার্ড mod 10 ব্যবহার করে। কিন্তু 10 = 2 · 5 মৌলিক নয়, আর Euclid-এর lemma খাটে না: 10 ∣ 2 · 5, অথচ 10 ∤ 2 আর 10 ∤ 5. কোন ভুল ফাঁক গলে যায়, তা Lemma ২.২ নির্ভুলভাবে বলে।
EAN-13 আর ISBN-13: ওজন 1, 3, 1, 3, …
দোকানের পণ্যের ১৩ অঙ্কের EAN-13 নম্বরে (আর ISBN-13-এ) বাঁ থেকে ওজন পালাক্রমে 1, 3, 1, 3, …, 1, আর modulus 10. বারকোডের গঠন Computer Fundamentals কোর্সের পর্ব ১৮-এ আছে।
(ক) EAN-13 প্রতিটি একক ভুল ধরে। (খ) পাশাপাশি দুটি ভিন্ন অঙ্ক a, b অদলবদল হলে ভুলটি ধরা পড়ে না ⇔ |a − b| = 5.
প্রমাণ:
- (ক) ওজন 1 হলে পার্থক্য Δ = d′ − d, আর 1 ≤ |Δ| ≤ 9, তাই 10 ∤ Δ. ওজন 3 হলে পার্থক্য 3Δ; gcd(3, 10) = 1, তাই 10 ∣ 3Δ হলে 3-এর inverse (3 · 7 = 21 ≡ 1) দিয়ে গুণ করে 10 ∣ Δ, যা অসম্ভব। — Lemma ২.২(ক), inverse mod 10
- (খ) পাশাপাশি দুটি অবস্থানের ওজন একটি 1, অন্যটি 3, তাই wi − wj = ±2, আর পার্থক্য ±2(a − b). — Lemma ২.২(খ)
- 10 ∣ 2(a − b) ⇔ 5 ∣ (a − b). a, b অঙ্ক আর a ≠ b, তাই এটি ঘটে ঠিক তখন, যখন |a − b| = 5: জোড়াগুলো {0, 5}, {1, 6}, {2, 7}, {3, 8}, {4, 9}. ∎ — 2(a − b)-কে 10 ভাগ করে ⇔ 5 ভাগ করে a − b-কে
অর্থাৎ …38… থেকে …83… হলে বারকোড পদ্ধতি ভুলটি দেখবে না, অথচ ISBN-10 দেখত। ২০০৭ সালের বদলের দাম এই ছোট দুর্বলতা।
Luhn-এর পদ্ধতি: পালাক্রমে দ্বিগুণ
ব্যাংক কার্ড আর অনেক পরিচয় নম্বরে Luhn algorithm ব্যবহৃত হয়। নিয়ম: ডান দিক থেকে check digit-কে ১ নম্বর ধরে প্রতিটি দ্বিতীয় অঙ্ক (২, ৪, ৬, …তম) দ্বিগুণ করুন; দ্বিগুণ করে 9-এর বেশি হলে তার দুই অঙ্ক যোগ করুন (বা 9 বিয়োগ করুন, একই ফল); তারপর সব যোগ করুন। মোট ≡ 0 (mod 10) হলে নম্বরটি বৈধ। এটি পুরোপুরি “ওজনযুক্ত যোগফল” নয়, কারণ দ্বিগুণ অঙ্কে একটি ফাংশন বসে: f(d) = 2d (d ≤ 4) আর f(d) = 2d − 9 (d ≥ 5).
13 8 5 2 0 0 7 1 ?2ডান থেকে দ্বিগুণ: 1, 0, 2, 831→2, 0→0, 2→4, 8→16416 → 1 + 6 = 75(2+0+4+7) + (7+0+5+3) = 28628 + ? ≡ 0 ⇒ ? = 2
| দ্বিগুণের অঙ্ক | |
| বাকি অঙ্ক | |
| যোগফল |
- অবস্থান গোনা। Check digit ? ডান দিক থেকে ১ নম্বরে বসবে, তাই দ্বিগুণ হবে ২, ৪, ৬, ৮ নম্বর অবস্থানের অঙ্ক: ডান থেকে 1, 0, 2, 8.
- দ্বিগুণ। 1 → 2, 0 → 0, 2 → 4, 8 → 16.
- দুই অঙ্কের যোগ। 16 দুই অঙ্কের, তাই 1 + 6 = 7 (বা 16 − 9 = 7). বাকিগুলো এক অঙ্কের, যেমন আছে তেমন থাকে।
- সব যোগ। দ্বিগুণ অংশ 2 + 0 + 4 + 7 = 13, বাকি অঙ্ক 7 + 0 + 5 + 3 = 15. মোট 28 ≡ 8 (mod 10).
- Check digit. 28 + ? ≡ 0 (mod 10) হতে হলে ? = 2. পূর্ণ নম্বর 385200712, আর যাচাইয়ে মোট 30 ≡ 0 ✓.
(ক) Luhn পদ্ধতি প্রতিটি একক ভুল ধরে। (খ) পাশাপাশি দুটি ভিন্ন অঙ্কের অদলবদল ধরা পড়ে না ঠিক তখন, যখন জোড়াটি {0, 9}, অর্থাৎ 09 ↔ 90.
প্রমাণ: f(0), …, f(9)-এর মান যথাক্রমে 0, 2, 4, 6, 8, 1, 3, 5, 7, 9.
- (ক) দ্বিগুণ না হওয়া অবস্থানে পার্থক্য d′ − d, যা 10-এর গুণিতক নয়। দ্বিগুণ অবস্থানে পার্থক্য f(d′) − f(d); তালিকায় f-এর দশটি মান সব আলাদা অঙ্ক, অর্থাৎ f হলো {0, …, 9}-এর একটি permutation, তাই f(d′) ≠ f(d) আর পার্থক্য 1 থেকে 9-এর মধ্যে, 10 দিয়ে বিভাজ্য নয়। — f একটি bijection
- (খ) পাশাপাশি a, b-এর একটি দ্বিগুণ হয়, অন্যটি হয় না। আগে f(a) + b, পরে a + f(b). ভুল ধরা পড়ে না ⇔ f(a) − a ≡ f(b) − b (mod 10). — যোগফলের পার্থক্য
- g(d) = f(d) − d mod 10 হিসাব করুন: d = 0, …, 9-এ g = 0, 1, 2, 3, 4, 6, 7, 8, 9, 0. দুটি ভিন্ন অঙ্কে g সমান শুধু d = 0 আর d = 9-এ। ∎ — দশটি ক্ষেত্র সরাসরি গোনা
Luhn-এর কৌশলটি চতুর: ওজন 2 সরাসরি নিলে (gcd(2, 10) = 2) একক ভুলও ফসকাত, যেমন 0 → 5 (2 · 5 = 10 ≡ 0). “দুই অঙ্ক যোগ” ধাপটি 2d ≥ 10 হলে 1 যোগ করে হারানো তথ্য ফেরত আনে, আর f-কে permutation বানায়। পদ্ধতিটি IBM-এর বিজ্ঞানী Hans Peter Luhn ১৯৫০-এর দশকে প্রস্তাব করেন; তাঁর patent ১৯৬০ সালে মঞ্জুর হয় বলে উল্লেখ করা হয়। C কোর্সের পর্ব ৪৩-এ এর কোড আছে; এখানে আমরা প্রমাণ আর পরীক্ষা নিয়ে কাজ করছি।
এবার পরীক্ষা। নিচের ব্লক প্রতিটি পদ্ধতিতে ৩০০টি এলোমেলো বৈধ নম্বর বানায়, আর প্রতিটিতে সব একক ভুল আর payload-এর পাশাপাশি সব অদলবদল চালিয়ে দেখে কোনটি ফসকায়। প্রথমবার চালালে ব্রাউজারে Python (Pyodide) নামতে কয়েক সেকেন্ড লাগে।
import random
random.seed(26) # fixed seed: same numbers every run
def isbn10_ok(d): # weights 10, 9, ..., 1 (mod 11)
return sum(w * x for w, x in zip(range(10, 0, -1), d)) % 11 == 0
def ean13_ok(d): # weights 1, 3, 1, 3, ... (mod 10)
return sum(x * (3 if i % 2 else 1) for i, x in enumerate(d)) % 10 == 0
def luhn_ok(d): # double every 2nd digit from the right
s = 0
for i, x in enumerate(reversed(d)):
s += (2 * x - 9 if x > 4 else 2 * x) if i % 2 else x
return s % 10 == 0
def experiment(ok, n, top, trials=300):
single = s_miss = swaps = 0; missed = set()
for _ in range(trials):
d = [random.randrange(10) for _ in range(n - 1)]
d += [next(c for c in range(top) if ok(d + [c]))] # add check digit
for i in range(n):
for v in range(top if i == n - 1 else 10):
if v != d[i]:
single += 1; s_miss += ok(d[:i] + [v] + d[i + 1:])
for i in range(n - 2): # swap two neighbouring payload digits
if d[i] != d[i + 1]:
e = d[:]; e[i], e[i + 1] = e[i + 1], e[i]; swaps += 1
if ok(e): missed.add(tuple(sorted(d[i:i + 2])))
print(f" single errors missed {s_miss}/{single}; {swaps} swaps,",
"missed pairs:", sorted(missed) or "none")
for name, ok, n, top in [("ISBN-10", isbn10_ok, 10, 11),
("EAN-13 ", ean13_ok, 13, 10), ("Luhn-16", luhn_ok, 16, 10)]:
print(name); experiment(ok, n, top)
print("stepper numbers valid:", isbn10_ok([3,1,4,1,5,9,2,6,5,9]),
luhn_ok([3,8,5,2,0,0,7,1,2]), ean13_ok([2,0,2,6,1,1,1,0,0,4,2,7,8]))আসল আউটপুট (ব্রাউজারের Python):
ISBN-10
single errors missed 0/27300; 2167 swaps, missed pairs: none
EAN-13
single errors missed 0/35100; 2999 swaps, missed pairs: [(0, 5), (1, 6), (2, 7), (3, 8), (4, 9)]
Luhn-16
single errors missed 0/43200; 3803 swaps, missed pairs: [(0, 9)]
stepper numbers valid: True True True
কোনো পদ্ধতি হাজার হাজার একক ভুলের একটিও ফসকায়নি: তিন উপপাদ্যের (ক) অংশ। ISBN-10 কোনো অদলবদল ফসকায়নি; EAN-13 ঠিক পাঁচটি জোড়া ফসকেছে, প্রতিটির পার্থক্য 5 (উপপাদ্য ৩.১(খ)); Luhn শুধু {0, 9} (উপপাদ্য ৩.২(খ)). প্রমাণ বলে কেন, আর পরীক্ষা বলে প্রমাণের তালিকায় কিছু বাদ পড়েনি। শেষ লাইন দুই stepper-এর নম্বর আর অনুশীলনের EAN নম্বর যাচাই করে।
সাবধান: Check digit শুধু দুর্ঘটনাজনিত ভুল ধরে। নিয়মটি প্রকাশ্য, তাই যে কেউ বানানো নম্বরে সঠিক check digit বসাতে পারে; “Luhn-এ পাস” মানে নম্বরটি আসল নয়, শুধু স্পষ্ট টাইপের ভুল নেই। ইচ্ছাকৃত পরিবর্তন ঠেকাতে গোপন key-যুক্ত MAC লাগে (Cybersecurity কোর্সের পর্ব ০৯).
৪ · Hashing — k mod m, আর কেন m মৌলিক
বাজারঘরের দ্বিতীয় সমস্যা: অনেক key-কে m-টি বালতিতে (bucket) সমানভাবে ভাগ করা। সবচেয়ে সরল hash function হলো h(k) = k mod m: key k যায় k mod m নম্বর বালতিতে। একই বালতিতে দুটি key পড়লে তাকে collision বলে, আর key-এর সংখ্যা বালতির চেয়ে বেশি হলে pigeonhole নীতিতে collision অবশ্যম্ভাবী। লক্ষ্য তাই সমানভাবে ছড়ানো, যাতে প্রতিটি বালতিতে মোটামুটি n/m-টি key থাকে (এই অনুপাত load factor). Hash table-এর গঠন আর কোড DSA কোর্সের পর্ব ৩২-এ; এখানে প্রশ্ন শুধু গাণিতিক: কোন m-এ k mod m ভালো ছড়ায়?
Key সত্যিই এলোমেলো হলে প্রায় যেকোনো m চলে। সমস্যা হয় যখন key-গুলোতে একটি নিয়মিত গঠন থাকে, আর বাস্তবে প্রায় সবসময়ই থাকে। ধরুন বাজারঘরের প্রতিটি গুদাম 8টি করে আইডি আগে থেকে সংরক্ষণ করে, তাই একটি গুদামের অর্ডার আইডি 100000, 100008, 100016, …, অর্থাৎ ধাপ 8. এমন সমান্তর ধারায় (arithmetic progression) k mod m কতটি বালতিতে পড়ে, তা নিচের উপপাদ্য বলে।
m ≥ 1, d আর s পূর্ণসংখ্যা, g = gcd(d, m). তাহলে ধারা s, s + d, s + 2d, … -এর mod m মানগুলো ঠিক m/g-টি ভিন্ন ভাগশেষ: সেই সব r, যেখানে r ≡ s (mod g). ধারাটি m/g ধাপ পরপর পুনরাবৃত্ত হয়, আর এক চক্রে প্রতিটি ভাগশেষ ঠিক একবার আসে।
প্রমাণ:
- s + id ≡ s + jd (mod m) ⇔ m ∣ (i − j)d. দুই পাশকে g দিয়ে ভাগ করলে ⇔ (m/g) ∣ (i − j)(d/g). — congruence-এর সংজ্ঞা
- gcd(m/g, d/g) = 1 (gcd-কে gcd দিয়ে ভাগ করলে সহমৌলিক থাকে, পর্ব ২১). তাই Euclid-এর lemma-র সাধারণ রূপে ⇔ (m/g) ∣ (i − j). — সহমৌলিক উৎপাদক বাদ দেওয়া
- অর্থাৎ i আর j একই ভাগশেষ দেয় ঠিক তখন, যখন i ≡ j (mod m/g). তাই i = 0, 1, …, m/g − 1 দেয় m/g-টি ভিন্ন মান, আর তারপর ধারা পুনরাবৃত্ত হয়। — ধাপ ১–২
- g ∣ d আর g ∣ m, তাই প্রতিটি মান ≡ s (mod g). 0 থেকে m − 1-এর মধ্যে এমন ভাগশেষ ঠিক m/g-টি, আর ধাপ ৩-এ আমরা m/g-টি ভিন্ন পেয়েছি, তাই সবগুলোই আসে। ∎ — গণনা
ধাপ 8-এর আইডি mod 64-এ: g = gcd(8, 64) = 8, তাই মাত্র 64/8 = 8টি বালতি ব্যবহৃত হয়, বাকি 56টি খালি, আর প্রতিটি ব্যবহৃত বালতিতে গড়ের আট গুণ চাপ। m = 61 (মৌলিক) নিলে gcd(8, 61) = 1, সব 61টি বালতি সমান ব্যবহৃত। নিচের ছবিতে ছোট সংখ্যায় একই ঘটনা।
ধাপ 6-এ লাফানো: m = 8 বনাম m = 7
0 থেকে শুরু করে প্রতিবার 6 যোগ করে mod m. বাঁয়ে gcd(6, 8) = 2, তাই শুধু জোড় ঘর; ডানে gcd(6, 7) = 1, তাই সব ঘর।
উপপাদ্য থেকে m বাছাইয়ের দুটি নিয়ম আসে। প্রথমত, m মৌলিক হলে যেকোনো ধাপ d (যেখানে m ∤ d) সব m-টি বালতি ব্যবহার করে, কারণ gcd(d, m) = 1. দ্বিতীয়ত, m = 2r হলে k mod m মানে শুধু k-এর বাইনারির শেষ r-টি bit; key-গুলো যদি শুধু উঁচু bit-এ আলাদা হয় (যেমন সবগুলো 1024-এর গুণিতক থেকে 7 বেশি), তবে সবগুলো একই বালতিতে পড়ে। এ কারণেই পাঠ্যবইয়ে division-method hashing-এ “2-এর ঘাতের কাছাকাছি নয় এমন মৌলিক m” বাছার পরামর্শ দেওয়া হয়।
তবে মৌলিক m-ও কোনো জাদু নয়। ধাপ d যদি নিজেই m-এর গুণিতক হয়, বা কেউ ইচ্ছা করে একই বালতির key পাঠায়, তবে যেকোনো স্থির h ভেঙে পড়ে। এর গাণিতিক উত্তর universal hashing: একটি বড় মৌলিক p (p > সব key) নিয়ে প্রোগ্রাম শুরুর সময় এলোমেলোভাবে a ∈ {1, …, p − 1} আর b ∈ {0, …, p − 1} বাছা হয়, আর ha,b(k) = ((ak + b) mod p) mod m. J. Lawrence Carter আর Mark Wegman ১৯৭০-এর দশকের শেষে (১৯৭৭–৭৯) এই ধারণা দেন; যেকোনো দুটি ভিন্ন key-এর collision-এর সম্ভাবনা বড়জোর 1/m, key যেমনই হোক। প্রমাণের মূলে সেই পুরোনো তথ্য: p মৌলিক বলে k ↦ ak + b mod p একটি bijection.
নিচের ব্লক ধাপ-8 আইডি আর ধাপ-1024 key কয়েকটি m-এ ভাগ করে ব্যবহৃত বালতি আর সবচেয়ে ভরা বালতি দেখায়, একটি universal hash চেষ্টা করে, আর উপপাদ্য ৪.১ ছোট সব m, d-তে মেলায়।
import random
from collections import Counter
from math import gcd
def report(label, keys, m, h=None):
load = Counter(h(k) if h else k % m for k in keys)
print(f"{label:<16} m={m:<5} buckets used {len(load):>4}/{m:<5}"
f" max load {max(load.values()):>3} (ideal {len(keys) / m:.1f})")
ids = [100000 + 8 * i for i in range(2000)] # order ids step by 8
for m in (64, 61, 100, 97):
report("ids step 8", ids, m)
keys = [1024 * i + 7 for i in range(2000)] # only the high bits differ
for m in (1024, 1031):
report("keys step 1024", keys, m)
random.seed(26) # one random (a, b): universal family
p = 1_000_003
a, b = random.randrange(1, p), random.randrange(p)
report("universal hash", keys, 1024, lambda k: (a * k + b) % p % 1024)
ok = all(len({(s + i * d) % m for i in range(m)}) == m // gcd(d, m)
for m in range(1, 60) for d in range(1, 60) for s in range(3))
print("stride theorem holds for all m, d < 60:", ok)
print("p = 1000003 is prime:", all(p % q for q in range(2, int(p ** 0.5) + 1)))আসল আউটপুট (ব্রাউজারের Python):
ids step 8 m=64 buckets used 8/64 max load 250 (ideal 31.2)
ids step 8 m=61 buckets used 61/61 max load 33 (ideal 32.8)
ids step 8 m=100 buckets used 25/100 max load 80 (ideal 20.0)
ids step 8 m=97 buckets used 97/97 max load 21 (ideal 20.6)
keys step 1024 m=1024 buckets used 1/1024 max load 2000 (ideal 2.0)
keys step 1024 m=1031 buckets used 1031/1031 max load 2 (ideal 1.9)
universal hash m=1024 buckets used 1020/1024 max load 3 (ideal 2.0)
stride theorem holds for all m, d < 60: True
p = 1000003 is prime: True
m = 64-এ ২,০০০ আইডি মাত্র 8টি বালতিতে, প্রতিটিতে 250টি; উপপাদ্যের 64/gcd(8, 64) = 8. m = 100-এ gcd(8, 100) = 4, তাই 25টি বালতি। দুটি মৌলিক m (61, 97) সব বালতি ব্যবহার করে, আর সবচেয়ে ভরা বালতি গড়ের প্রায় সমান। ধাপ-1024 key-এ m = 1024 সবচেয়ে খারাপ ফল দেয়: সব ২,০০০ key একটি বালতিতে, কারণ শেষের 10টি bit সবার এক (…0000000111). মৌলিক 1031 সেখানেও প্রতিটি বালতিতে বড়জোর 2টি রাখে। m = 1024 রেখেও universal hash ভালো ছড়ায়, কারণ আগে mod p-তে উঁচু bit-গুলো মিশে যায়। শেষ দুই লাইন উপপাদ্য ৪.১-কে ৩ × ৫৯ × ৫৯টি ক্ষেত্রে মেলায় আর p-এর মৌলিকতা নিশ্চিত করে।
একই উপপাদ্য “বোঝা ছড়ানো”-র অন্য জায়গাতেও খাটে। Double hashing-এ সংঘর্ষ হলে ঘর খোঁজা হয় h, h + d, h + 2d, … mod m ধরে; gcd(d, m) > 1 হলে কিছু খালি ঘর কখনো দেখাই হয় না। আর অর্ডার k-কে ডেলিভারি ভ্যান (k · s) mod v-তে পাঠালে gcd(s, v) = 1 হলেই পরপর v-টি অর্ডার v-টি ভ্যানে একটি করে যায়।
৫ · Pseudorandom সংখ্যা — linear congruential generator
কম্পিউটার একটি নির্দিষ্ট নিয়মে চলে, তাই সে সত্যিকারের এলোমেলো সংখ্যা বানাতে পারে না। সে যা বানায়, তা pseudorandom: একটি সূত্র থেকে আসা সংখ্যার ধারা, যা দেখতে এলোমেলো, কিন্তু শুরুর মান (seed) এক হলে হুবহু একই। সবচেয়ে পুরোনো আর সরল সূত্রটি এই মডিউলের ভাষাতেই লেখা।
modulus m ≥ 2, গুণক a, বৃদ্ধি c (0 ≤ a, c < m) আর seed x0 দিয়ে
xn+1 = (a xn + c) mod m
0 থেকে 1-এর মধ্যে সংখ্যা চাইলে un = xn/m নেওয়া হয়। c = 0 হলে একে multiplicative (গুণনমূলক) LCG বলে।
এর প্রথম প্রস্তাব D. H. Lehmer-এর, ১৯৪৯ সালের একটি সম্মেলনে (প্রকাশ ১৯৫১), বলে সাধারণত উল্লেখ করা হয়। প্রথম প্রশ্ন: ধারা কতক্ষণ নতুন সংখ্যা দেয়?
যেকোনো LCG-এর ধারা শেষ পর্যন্ত পর্যায়বৃত্ত, আর তার পর্যায় বা period (চক্রের দৈর্ঘ্য) বড়জোর m. gcd(a, m) = 1 হলে ধারাটি শুরু থেকেই পর্যায়বৃত্ত: x0 নিজেই আবার আসে।
প্রমাণ:
- প্রতিটি xn ∈ {0, …, m − 1}, মাত্র m-টি মান। x0, …, xm এই m + 1টি পদের মধ্যে তাই দুটি সমান: xi = xj, i < j ≤ m. — pigeonhole নীতি
- পরের পদ শুধু আগের পদের ওপর নির্ভর করে, তাই xi+1 = xj+1, xi+2 = xj+2, …: i-তম পদ থেকে ধারা j − i ≤ m দৈর্ঘ্যের চক্রে ঘোরে। — induction
- gcd(a, m) = 1 হলে x ↦ ax + c একটি bijection (inverse: x ↦ a−1(x − c)), তাই প্রতিটি পদের আগের পদ অনন্য। xi = xj থেকে পিছিয়ে xi−1 = xj−1, …, x0 = xj−i. ∎ — inverse mod m, পর্ব ২৩
সবচেয়ে ভালো হয় period ঠিক m হলে (“full period”): তখন এক চক্রে 0 থেকে m − 1-এর প্রতিটি মান ঠিক একবার আসে, seed যা-ই হোক। কখন তা হয়, তার পূর্ণ উত্তর আছে।
c ≠ 0 হলে LCG-এর period প্রতিটি seed-এ ঠিক m ⇔ তিনটি শর্তই সত্য:
- gcd(c, m) = 1;
- m-এর প্রতিটি মৌলিক উৎপাদক p, a − 1-কে ভাগ করে;
- 4 ∣ m হলে 4 ∣ a − 1.
T. E. Hull আর A. R. Dobell ১৯৬২ সালে এটি প্রকাশ করেন। প্রমাণ এখানে বাদ; এটি Knuth-এর The Art of Computer Programming, খণ্ড ২-এর LCG অধ্যায়ে আছে। তবে একটি দিক সহজেই দেখা যায়: যদি gcd(c, m) = g > 1 আর seed 0 হয়, তবে প্রতিটি পদ g-এর গুণিতক থাকে (a · 0 + c = c, তারপর a · (g-এর গুণিতক) + c), তাই বড়জোর m/g-টি মান আসে, full period অসম্ভব। নিচের stepper শর্তগুলো মেলানো একটি ছোট LCG চালায়।
1m = 16 = 2⁴, a = 5, c = 32gcd(3, 16) = 1; 2 ∣ 4; 4 ∣ 43x₁..x₄ = 6, 1, 8, 114x₅..x₈ = 10, 5, 12, 155x₉..x₁₂ = 14, 9, 0, 36x₁₃..x₁₆ = 2, 13, 4, 7
| শেষ হিসাব | |
| দেখা মান |
- শর্ত মেলানো। m = 16-এর একমাত্র মৌলিক উৎপাদক 2. gcd(3, 16) = 1 ✓; a − 1 = 4, আর 2 ∣ 4 ✓; 4 ∣ 16, তাই 4 ∣ a − 1 লাগে, আর 4 ∣ 4 ✓. Hull–Dobell বলে period হবে 16.
- প্রথম চারটি। 5 · 7 + 3 = 38 ≡ 6, 5 · 6 + 3 = 33 ≡ 1, 5 · 1 + 3 = 8, 5 · 8 + 3 = 43 ≡ 11 (mod 16).
- পরের চারটি। 58 ≡ 10, 53 ≡ 5, 28 ≡ 12, 63 ≡ 15. এখনো কোনো পুনরাবৃত্তি নেই।
- আরও চারটি। 78 ≡ 14, 73 ≡ 9, 48 ≡ 0, 3. লক্ষ করুন বিজোড় আর জোড় পালাক্রমে আসছে; ছোট bit-এর এই নিয়মিততা LCG-এর একটি পরিচিত দুর্বলতা।
- চক্র পূর্ণ। 18 ≡ 2, 13, 68 ≡ 4, আর 5 · 4 + 3 = 23 ≡ 7 = seed. ১৬টি পদে 0 থেকে 15 প্রতিটি ঠিক একবার: full period ∎.
c = 0 হলে (multiplicative LCG) 0 একটি আটকে থাকা মান (0 → 0), তাই period বড়জোর m − 1. m = p মৌলিক আর seed x0 ≠ 0 হলে xn = anx0 mod p, আর xn = x0 ⇔ an ≡ 1, অর্থাৎ period ঠিক ordp(a), পর্ব ২৪-এর order (প্রমাণ অনুশীলন ১-এ)। period সবচেয়ে বড়, p − 1, ঠিক তখন যখন a একটি primitive root. Stephen Park আর Keith Miller ১৯৮৮ সালে “minimal standard” হিসেবে m = 231 − 1 (একটি মৌলিক) আর a = 16807 = 75 প্রস্তাব করেন, যেটি mod 231 − 1-এ একটি primitive root; পরে তাঁরা a = 48271 পছন্দ করেন বলে উল্লেখিত।
নিচের ব্লক stepper-এর ধারা ছাপে, কয়েকটি m-এ সব (a, c) জোড়ার period গুনে Hull–Dobell-এর শর্তের সঙ্গে মেলায়, mod 13-এ multiplicative LCG-এর period দেখায়, আর 16807 সত্যিই primitive root কি না পরীক্ষা করে (231 − 2 = 2 · 32 · 7 · 11 · 31 · 151 · 331, আর পর্ব ২৪-এর নিয়মে প্রতিটি মৌলিক উৎপাদক q-তে g(p−1)/q ≢ 1 দেখতে হয়)।
from math import gcd
def period(a, c, m, seed): # length of the cycle the sequence falls into
seen, x, n = {}, seed, 0
while x not in seen:
seen[x] = n; x = (a * x + c) % m; n += 1
return n - seen[x]
x, seq = 7, []
for _ in range(17):
x = (5 * x + 3) % 16; seq.append(x)
print("x -> (5x + 3) mod 16, seed 7:", seq)
def primes_of(m):
return [p for p in range(2, m + 1) if m % p == 0 and all(p % q for q in range(2, p))]
def hull_dobell(a, c, m):
return (gcd(c, m) == 1 and all((a - 1) % p == 0 for p in primes_of(m))
and (m % 4 != 0 or (a - 1) % 4 == 0))
fulls = {}
for m in (16, 10, 12, 30, 81):
full = [(a, c) for a in range(m) for c in range(m) if period(a, c, m, 0) == m]
same = all((period(a, c, m, 0) == m) == hull_dobell(a, c, m)
for a in range(m) for c in range(m))
fulls[m] = full
print(f"m={m:<3} full-period (a, c) pairs: {len(full):>4} Hull-Dobell agrees: {same}")
print("m = 10, all full-period (a, c):", fulls[10])
print("c = 0, m = 13, seed 1, period for a = 1..12:",
[period(a, 0, 13, 1) for a in range(1, 13)])
M = 2**31 - 1 # Park-Miller: a = 16807 = 7^5
print("16807 primitive root mod 2^31-1:",
all(pow(16807, (M - 1) // q, M) != 1 for q in (2, 3, 7, 11, 31, 151, 331)))আসল আউটপুট (ব্রাউজারের Python):
x -> (5x + 3) mod 16, seed 7: [6, 1, 8, 11, 10, 5, 12, 15, 14, 9, 0, 3, 2, 13, 4, 7, 6]
m=16 full-period (a, c) pairs: 32 Hull-Dobell agrees: True
m=10 full-period (a, c) pairs: 4 Hull-Dobell agrees: True
m=12 full-period (a, c) pairs: 4 Hull-Dobell agrees: True
m=30 full-period (a, c) pairs: 8 Hull-Dobell agrees: True
m=81 full-period (a, c) pairs: 1458 Hull-Dobell agrees: True
m = 10, all full-period (a, c): [(1, 1), (1, 3), (1, 7), (1, 9)]
c = 0, m = 13, seed 1, period for a = 1..12: [1, 12, 3, 6, 4, 12, 12, 4, 3, 6, 12, 2]
16807 primitive root mod 2^31-1: True
প্রথম লাইন stepper-এর ১৬টি মান, আর ১৭তম মান আবার 6: চক্র শুরু থেকে পুনরাবৃত্ত। পাঁচটি m-এর প্রতিটিতে সব m2টি (a, c) জোড়ায় period গোনা হয়েছে, আর “full period” ঠিক সেখানেই, যেখানে Hull–Dobell-এর তিন শর্ত মেলে। সংখ্যাগুলোও শর্ত থেকে আসে: m = 16-এ a ≡ 1 (mod 4) চারভাবে আর c বিজোড় আটভাবে, মোট 32. m = 81-এ a ≡ 1 (mod 3) 27ভাবে আর c-এর φ(81) = 54টি বিকল্প, মোট 1458. কিন্তু m = 10, 12 বা 30-এ শর্তগুলো a = 1 জোর করে, আর x ↦ x + c শুধু গোনা, এলোমেলো কিছু নয়। তাই ভালো LCG-এর m সাধারণত 2-এর বড় ঘাত, অথবা একটি বড় মৌলিক যেখানে c = 0 আর a একটি primitive root. mod 13-এর period-গুলো পর্ব ২৪-এর order-এর তালিকা, আর শেষ লাইন Park–Miller-এর গুণকটি যাচাই করে।
৬ · খারাপ generator আর নিরাপত্তা — RANDU আর অনুমানযোগ্যতা
Full period যথেষ্ট নয়: পরপর মানগুলোর মধ্যে সহজ সম্পর্ক থাকলে generator খারাপ। সবচেয়ে বিখ্যাত উদাহরণ RANDU: m = 231, a = 65539 = 216 + 3, c = 0 (seed বিজোড়)। এটি ১৯৬০-এর দশকে IBM-এর বৈজ্ঞানিক লাইব্রেরিতে বহুল ব্যবহৃত হয়েছিল বলে উল্লেখ করা হয়।
RANDU-র প্রতিটি পরপর তিন মান মানে: xk+2 ≡ 6xk+1 − 9xk (mod 231).
প্রমাণ: M = 231 আর a = 216 + 3.
- a2 = 232 + 6 · 216 + 9. 232 = 2 · M ≡ 0, তাই a2 ≡ 6 · 216 + 9 (mod M). — বর্গের সূত্র
- 6 · 216 = 6(a − 3) = 6a − 18, তাই a2 ≡ 6a − 18 + 9 = 6a − 9 (mod M). — বদলে বসানো
- xk+2 ≡ a2xk ≡ (6a − 9)xk = 6(a xk) − 9xk ≡ 6xk+1 − 9xk (mod M). ∎ — গুণ congruence মানে
এর জ্যামিতিক অর্থ ভয়ংকর। u = xk/M, v = xk+1/M, w = xk+2/M নিলে তিনটিই 0 থেকে 1-এর মধ্যে, আর উপপাদ্য বলে 9u − 6v + w একটি পূর্ণসংখ্যা। এর মান −6 < 9u − 6v + w < 10, তাই সম্ভাব্য পূর্ণসংখ্যা মাত্র 15টি (−5 থেকে 9). অর্থাৎ (u, v, w) বিন্দুগুলো একটি ঘনকের ভেতরে ছড়িয়ে না থেকে মাত্র 15টি সমান্তরাল সমতলে পড়ে থাকে। ত্রিমাত্রিক সিমুলেশনে (যেমন একটি বাক্সে কণার অবস্থান) ঘনকের বেশিরভাগ জায়গায় কখনো বিন্দু পড়ে না। George Marsaglia ১৯৬৮ সালে “Random numbers fall mainly in the planes” প্রবন্ধে দেখান, প্রতিটি multiplicative LCG-এর পরপর বিন্দু কিছু সমতলে পড়ে; RANDU-তে সংখ্যাটি অস্বাভাবিক কম। এই সমতলগুলোর দূরত্ব মাপার পরীক্ষাকে spectral test বলে।
অনুমানযোগ্যতা: তিনটি সংখ্যাই যথেষ্ট
দ্বিতীয় সমস্যাটি নিরাপত্তার। ধরুন বাজারঘর লটারির বিজয়ী বাছে একটি LCG দিয়ে, যার m প্রকাশ্য (ধরুন একটি মৌলিক p), কিন্তু a আর c গোপন। কেউ পরপর তিনটি আউটপুট x0, x1, x2 দেখলেন। তাহলে
x2 − x1 ≡ a(x1 − x0) (mod p)
কারণ c বিয়োগে বাদ যায়। x1 ≠ x0 হলে x1 − x0-এর inverse mod p আছে, তাই a = (x2 − x1)(x1 − x0)−1 mod p, আর তারপর c = x1 − a x0 mod p. এরপর পরের সব “এলোমেলো” সংখ্যা জানা। m অজানা হলেও কয়েকটি বেশি আউটপুট আর gcd দিয়ে তা বের করার পরিচিত কৌশল আছে।
নিচের ব্লক RANDU-তে উপপাদ্য ৬.১ আর সমতলগুলো পরীক্ষা করে, তারপর তিনটি আউটপুট থেকে একটি LCG-এর গোপন a, c বের করে।
from collections import Counter
M = 2**31 # RANDU: x -> 65539 x mod 2^31
xs, x = [], 1
for _ in range(30000):
x = 65539 * x % M; xs.append(x)
print("RANDU from seed 1:", xs[:5])
bad = sum((xs[k + 2] - 6 * xs[k + 1] + 9 * xs[k]) % M != 0 for k in range(len(xs) - 2))
print("triples with x[k+2] != 6x[k+1] - 9x[k] (mod 2^31):", bad)
# scaled to u, v, w in [0, 1): 9u - 6v + w is always a whole number k
planes = Counter((9 * xs[k] - 6 * xs[k + 1] + xs[k + 2]) // M
for k in range(0, len(xs) - 2, 3))
print("planes 9u - 6v + w = k used by 10000 triples:", sorted(planes))
print("smallest / largest plane count:", min(planes.values()), max(planes.values()))
# predicting an LCG: m is public, a and c are secret
m = 2**31 - 1 # a prime modulus
a, c = 48271, 12345 # the secret parameters
out = [2026]
for _ in range(8):
out.append((a * out[-1] + c) % m)
x0, x1, x2 = out[:3] # an attacker sees only these three
a_hat = (x2 - x1) * pow(x1 - x0, -1, m) % m
c_hat = (x1 - a_hat * x0) % m
pred, y = [], x2
for _ in range(6):
y = (a_hat * y + c_hat) % m; pred.append(y)
print("recovered a, c:", a_hat, c_hat)
print("next six outputs predicted correctly:", pred == out[3:])আসল আউটপুট (ব্রাউজারের Python):
RANDU from seed 1: [65539, 393225, 1769499, 7077969, 26542323]
triples with x[k+2] != 6x[k+1] - 9x[k] (mod 2^31): 0
planes 9u - 6v + w = k used by 10000 triples: [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
smallest / largest plane count: 96 1152
recovered a, c: 48271 12345
next six outputs predicted correctly: True
প্রায় ৩০,০০০টি পরপর তিন মানের একটিও উপপাদ্য ৬.১ ভাঙেনি। ১০,০০০টি বিন্দু ঠিক 15টি সমতলে (k = −5 থেকে 9) পড়েছে, যেমন প্রমাণ বলেছিল। মাঝের সমতলগুলোতে বেশি বিন্দু, কারণ ঘনকের সঙ্গে তাদের ছেদ বড়। শেষ দুই লাইন অনুমানের আক্রমণ: তিনটি সংখ্যা দেখেই গোপন a = 48271 আর c = 12345 পুরোপুরি ফেরত এসেছে, আর পরের ছয়টি “এলোমেলো” মান নির্ভুলভাবে অনুমান করা গেছে। সূত্রটির পুরো কাজ করেছে একটি modular inverse, pow(x1 - x0, -1, m).
নিরাপত্তার নিয়ম: OTP, পাসওয়ার্ড রিসেট টোকেন, কুপন কোড বা key-এর মতো যেকোনো গোপন মানে LCG বা সাধারণ PRNG চলে না। Python-এর random module LCG নয়, Mersenne Twister (১৯৯৭–৯৮ সালে Matsumoto আর Nishimura-র নকশা); এটিও যথেষ্ট আউটপুট দেখলে অনুমান করা যায়, আর Python-এর নিজের documentation একে নিরাপত্তার কাজে ব্যবহার করতে নিষেধ করে। গোপন মানের জন্য CSPRNG লাগে, যেমন Python-এর secrets module, যা অপারেটিং সিস্টেমের নিরাপদ উৎস ব্যবহার করে। বিস্তারিত Cybersecurity কোর্সের পর্ব ০৭-এ।
৭ · আরও দুটি প্রয়োগ — CRC আর round-robin সময়সূচি
CRC: mod 2-এ polynomial-এর ভাগশেষ
নেটওয়ার্কে বা ডিস্কে ভুল প্রায়ই আসে bit-এর গুচ্ছে (burst), যেমন বৈদ্যুতিক গোলযোগে টানা কয়েকটি bit উল্টে যাওয়া। সেখানে ব্যবহৃত হয় CRC (cyclic redundancy check), যার গণিত হলো ভাগশেষ, তবে সংখ্যার নয়, polynomial-এর। bit-এর ক্রম 1101-কে পড়া হয় x3 + x2 + 1 হিসেবে, আর সহগগুলো mod 2-এ চলে: 1 + 1 = 0, অর্থাৎ যোগ আর বিয়োগ দুটোই XOR.
প্রেরক ও প্রাপক একটি generator polynomial আগে থেকে ঠিক করেন, ধরুন G(x) = x3 + x + 1 (bit-এ 1011). বার্তা M = 1101-এর পেছনে deg G = 3টি শূন্য বসিয়ে 1101000 বানানো হয়, আর একে 1011 দিয়ে mod 2-এ লম্বা ভাগ করা হয়: প্রতি ধাপে প্রথম 1-এর নিচে 1011 বসিয়ে XOR. ধাপগুলো হলো 1101000 → 0110000 → 0011100 → 0001010 → 0000001, অর্থাৎ ভাগশেষ 001. পাঠানো হয় 1101 001. প্রাপক পুরো 1101001-কে 1011 দিয়ে ভাগ করেন; ভাগশেষ 000 হলে ধরে নেওয়া হয় ভুল নেই (আমাদের scratch হিসাবেও ঠিক 000). যুক্তিটি পূর্ণসংখ্যার check digit-এর মতোই: পাঠানো polynomial-টি G-এর গুণিতক, আর ভুল E যোগ হলে ধরা পড়ে না ঠিক তখন, যখন G ∣ E. ভালো G বাছলে সব একক bit-এর ভুল আর deg G-এর সমান বা ছোট দৈর্ঘ্যের সব burst ধরা পড়ে।
CRC-এর ধারণা W. Wesley Peterson ১৯৬১ সালে প্রকাশ করেন বলে উল্লেখ করা হয়। Ethernet frame-এর CRC-32-সহ বাস্তব প্রয়োগ Computer Networks কোর্সের পর্ব ০৯-এ আছে, আর ভুল শুধু ধরা নয়, সারিয়ে তোলার গণিত পর্ব ৭২ “Error-correcting code — parity, Hamming code আর Hamming distance”-এ আসবে।
Round-robin: সবার সঙ্গে সবার একবার দেখা
বাজারঘরের কর্মীদের ৮টি দল নিয়ে একটি ক্রিকেট টুর্নামেন্ট হবে। প্রতিটি দল অন্য প্রতিটির সঙ্গে ঠিক একবার খেলবে, আর প্রতি রাউন্ডে প্রতিটি দল ঠিক একটি ম্যাচ খেলবে। মোট ম্যাচ C(8, 2) = 28, প্রতি রাউন্ডে 4টি, তাই 7টি রাউন্ড লাগবে। সাজানো যায় mod 7-এর যোগ দিয়ে, এক ধরনের round-robin নিয়মে।
দলগুলোর নাম 0, 1, …, 6 আর ∞. রাউন্ড r-এ (r = 0, …, 6) দুটি ভিন্ন দল i, j ∈ {0, …, 6} খেলে, যদি i + j ≡ r (mod 7); আর যে দল i-তে 2i ≡ r (mod 7), সে খেলে ∞-এর সঙ্গে। তাহলে প্রতি রাউন্ডে প্রতিটি দল ঠিক একটি ম্যাচ খেলে, আর প্রতিটি জোড়া ঠিক একবার মুখোমুখি হয়।
প্রমাণ:
- রাউন্ড r-এ দল i-এর প্রতিপক্ষ j ≡ r − i (mod 7), যা অনন্য। j = i হয় ঠিক তখন, যখন 2i ≡ r. 2-এর inverse mod 7 হলো 4 (2 · 4 = 8 ≡ 1), তাই এমন i ঠিক একটি: i ≡ 4r. সে দল ∞-এর সঙ্গে খেলে, বাকি ছয়টি দল তিন জোড়ায় ভাগ হয়। — inverse mod 7
- দুটি দল i ≠ j (দুটিই ∞ নয়) খেলে শুধু সেই রাউন্ডে, যেখানে r ≡ i + j (mod 7): ঠিক একটি রাউন্ড। — r অনন্যভাবে নির্ধারিত
- দল i আর ∞ খেলে সেই রাউন্ডে, যেখানে r ≡ 2i (mod 7): আবার ঠিক একটি। মোট 7 · 4 = 28 = C(8, 2) ম্যাচ, সব জোড়া একবার করে। ∎ — গণনা
| r | রাউন্ড r-এর চারটি ম্যাচ |
|---|---|
| 0 | 0–∞, 1–6, 2–5, 3–4 |
| 1 | 4–∞, 0–1, 2–6, 3–5 |
| 2 | 1–∞, 0–2, 3–6, 4–5 |
| 3 | 5–∞, 0–3, 1–2, 4–6 |
| 4 | 2–∞, 0–4, 1–3, 5–6 |
| 5 | 6–∞, 0–5, 1–4, 2–3 |
| 6 | 3–∞, 0–6, 1–5, 2–4 |
যেকোনো জোড় n দলে একই নিয়ম mod n − 1-এ চলে (n − 1 বিজোড় বলে 2-এর inverse থাকে); বিজোড় n হলে একটি কাল্পনিক দল যোগ করে তার প্রতিপক্ষকে বিশ্রাম দেওয়া হয়। CPU scheduling-এর round robin (Operating Systems কোর্সের পর্ব ১৮) একই পালাক্রম: সময়-খণ্ড t-তে চলে প্রক্রিয়া t mod n. আর প্রতি 7 দিনে ও প্রতি 11 দিনে আসা দুটি ডেলিভারি একই দিনে পড়ে প্রতি lcm(7, 11) = 77 দিনে, কোন দিনে তা CRT বলে দেয় (পর্ব ২২ ও ২৩)।
৮ · পরীক্ষার অঙ্ক আর একটি ভুল প্রমাণ
বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর লিখিত পরীক্ষায় এই বিষয়ের যে ধাঁচের অঙ্ক আসে, তার তিনটি পূর্ণ সমাধান। প্রতিটি সংখ্যা scratch-এ Python দিয়ে মেলানো।
অঙ্ক ১: ISBN-10 নম্বর 2-71?-28182-0-এর মুছে যাওয়া অঙ্কটি কী?
- ওজন 10, 9, …, 1. অজানা অঙ্ক x চতুর্থ অবস্থানে, তার ওজন 7. বাকি নয়টি অবস্থানের গুণফল 10·2, 9·7, 8·1, 6·2, 5·8, 4·1, 3·8, 2·2, 1·0 = 20, 63, 8, 12, 40, 4, 24, 4, 0; যোগ 175 = 11 · 15 + 10, অর্থাৎ 175 ≡ 10 (mod 11).
- শর্ত: 175 + 7x ≡ 0, অর্থাৎ 7x ≡ −10 ≡ 1 (mod 11). তাই x হলো 7-এর inverse mod 11.
- 7 · 8 = 56 = 5 · 11 + 1, তাই x = 8. পূর্ণ নম্বর 2-718-28182-0, আর যাচাইয়ে মোট 175 + 56 = 231 = 11 · 21 ✓.
অঙ্ক ২: x → (13x + 7) mod 36-এর period কত?
- c = 7 ≠ 0, তাই Hull–Dobell. 36 = 22 · 32, মৌলিক উৎপাদক 2 আর 3.
- gcd(7, 36) = 1 ✓. a − 1 = 12, আর 2 ∣ 12, 3 ∣ 12 ✓. 4 ∣ 36, আর 4 ∣ 12 ✓.
- তিন শর্তই মেলে, তাই period 36 (প্রতিটি seed-এ)। তুলনায় x → (11x + 5) mod 100-এ a − 1 = 10, আর 4 ∣ 100 কিন্তু 4 ∤ 10, তাই full period নয় (seed অনুযায়ী period 10 বা 2).
অঙ্ক ৩: key 5, 17, 29, 41, … (ধাপ 12) mod 18-এ কয়টি বালতিতে পড়ে, কোনগুলোতে?
- g = gcd(12, 18) = 6. উপপাদ্য ৪.১-এ বালতির সংখ্যা 18/6 = 3.
- বালতিগুলো সেই r, যেখানে r ≡ 5 (mod 6): 5, 11, 17. (5, 17, 29 ≡ 11, 41 ≡ 5, …)
- m = 19 (মৌলিক) নিলে gcd(12, 19) = 1, সব 19টি বালতি ব্যবহৃত হতো।
প্রমাণের ভুল ধরুন: “gcd(c, m) = 1 হলেই full period”
একজন লিখলেন: “দাবি: gcd(a, m) = 1 আর gcd(c, m) = 1 হলে x → (ax + c) mod m-এর period m. প্রমাণ: gcd(a, m) = 1, তাই ফাংশনটি {0, …, m − 1}-এর একটি bijection. Bijection কোনো মান বাদ দেয় না, তাই ধারা সব m-টি মান ছুঁয়ে যায়, আর period m. ∎” পড়ে ভুলটি নিজে খুঁজুন।
ভুল কোথায়: “bijection কোনো মান বাদ দেয় না” কথাটি ফাংশনের প্রতিচ্ছবি সম্পর্কে সত্য, কিন্তু একটি নির্দিষ্ট seed থেকে বারবার লাগালে যে ধারা আসে, তা সম্পর্কে নয়। একটি সসীম সেটের bijection সেটটিকে কয়েকটি আলাদা চক্রে ভাগ করে (উপপাদ্য ৫.২), আর ধারা শুধু seed-এর নিজের চক্রে ঘোরে। একটি counterexample: m = 8, a = 3, c = 1. gcd(3, 8) = gcd(1, 8) = 1, তবু seed 0 থেকে ধারা 0, 1, 4, 5, 0, …, period 4. Hull–Dobell-এর শর্ত ৩ ভেঙেছে: 4 ∣ 8 কিন্তু 4 ∤ a − 1 = 2. সঠিক বক্তব্য Hull–Dobell-এর তিন শর্ত।
আরও গভীরে: Verhoeff ১৯৬৯ সালে ১০ অঙ্কের এমন একটি check digit পদ্ধতি দেন, যা “X” ছাড়াই সব একক ভুল আর সব পাশাপাশি অদলবদল ধরে; তার মূলে mod 10-এর যোগ নয়, পাঁচ-কোণী আকৃতির symmetry-র group (dihedral group D5), যা পর্ব ৭০ “Group — symmetry, cyclic group আর Lagrange-এর উপপাদ্য”-এর বিষয়। H. Michael Damm ২০০৪ সালে একটি quasigroup-ভিত্তিক বিকল্প দেন। আধুনিক pseudorandom generator-এর মধ্যে PCG (Melissa O'Neill, ২০১৪) একটি LCG-এর আউটপুটকে একটি permutation দিয়ে লুকিয়ে ছোট bit-এর নিয়মিততা দূর করে, আর এগুলোকে পরীক্ষা করা হয় TestU01-এর মতো পরিসংখ্যানিক পরীক্ষার বড় সংগ্রহ দিয়ে।
পরীক্ষা ও ইন্টারভিউতে: বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এই ধরনের প্রশ্ন প্রায়ই আসে:
- ISBN-10 কেন mod 11? 11 মৌলিক, তাই Euclid-এর lemma-য় সব একক ভুল আর সব অদলবদল ধরা পড়ে; দাম হলো check digit-এ “X” অক্ষর।
- EAN-13 কোন অদলবদল ধরে না? পাশাপাশি a ↔ b, যেখানে |a − b| = 5, কারণ 10 ∣ 2(a − b).
- Hash table-এর আকার মৌলিক কেন? ধাপ d-এর key ব্যবহার করে m/gcd(d, m)টি বালতি; মৌলিক m-এ সবগুলো।
- LCG full period-এর শর্ত? gcd(c, m) = 1, m-এর প্রতিটি মৌলিক উৎপাদক a − 1-কে ভাগ করে, আর 4 ∣ m হলে 4 ∣ a − 1.
- LCG দিয়ে OTP বানানো যায় কি? না; তিনটি আউটপুট থেকে linear congruence সমাধান করে a আর c বের হয়। CSPRNG লাগে।
৯ · AI-এর যুগে — check digit, hash আর random-এর দাবি যাচাই
AI সহকারী এই বিষয়ে চটপটে উত্তর দেয়, কিন্তু কয়েকটি নির্দিষ্ট জায়গায় নিয়মিত ভুল করে। এগুলো চেনা থাকলে ধরতে এক মিনিট লাগে।
| AI-এর ভুল | কীভাবে ধরবেন |
|---|---|
| Luhn-এ ভুল অবস্থান দ্বিগুণ করা (বাঁ দিক থেকে গোনা, বা check digit-কেও দ্বিগুণ করা) | দেওয়া check digit-সহ পূর্ণ নম্বর নিজের ছোট ফাংশনে যাচাই করুন; ৯ অঙ্কের মতো বিজোড় দৈর্ঘ্যে বিশেষভাবে |
| “EAN/Luhn সব অদলবদল ধরে” বা “পার্থক্য শূন্য নয়, তাই ধরা পড়ে”-র মতো প্রমাণ, যেখানে “শূন্য নয়” আর “m দিয়ে বিভাজ্য নয়” গুলিয়ে ফেলা | Lemma ২.২-এর শর্ত m ∣ (ওজনের পার্থক্য)(a − b) নিজে লিখুন; ১০টি অঙ্কের সব জোড়া brute force করুন |
| শর্ত ছাড়া Hull–Dobell বলা (শর্ত ৩ বাদ দেওয়া), বা c = 0-তেও “period m” দাবি করা | ছোট m-এ সব (a, c)-এর period গুনে মিলিয়ে নিন (ব্লক ৩-এর মতো) |
| “মৌলিক m সবসময় নিখুঁত hash দেয়” বা “2-এর ঘাত কখনো চলে না”-র মতো অতিসরল দাবি | কাঠামোযুক্ত key-এ (ধাপ d) বালতি গুনে দেখুন; m ∣ d হলে মৌলিক m-ও ব্যর্থ |
নিরাপত্তার কাজে random বা LCG সুপারিশ করা | গোপন মানের জন্য secrets/CSPRNG চাওয়া হয়েছে কি না দেখুন; LCG-এর অনুমান-আক্রমণ মনে রাখুন |
| বানানো ইতিহাস আর মান: ভুল সাল, ভুল আবিষ্কারক, বানানো “অফিসিয়াল” ধ্রুবক | মূল প্রবন্ধ বা মানপত্রে মিলিয়ে নিন; নিশ্চিত না হলে দাবিটি বাদ দিন |
AI-এর কাছে এই ধরনের হিসাব বা প্রমাণ চাইলে এমন কাঠামো দিন, যাতে প্রতিটি দাবি আলাদা করে পরীক্ষা করা যায়:
AI-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট
এই পর্বের প্রায় প্রতিটি দাবি দশটি অঙ্ক বা ছোট m-এ এক সেকেন্ডে brute force করা যায়; AI-এর “সবসময়” বা “কখনো না” দাবি পেলে আগে ছোট ক্ষেত্রে counterexample খুঁজুন।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১বইয়ের নম্বর ISBN-10 থেকে ISBN-13-এ গেল, আর ভুল ধরার ক্ষমতা একটু কমল। এমন সিদ্ধান্ত কেন নেওয়া হলো, আর এখানে প্রকৌশলের কোন শিক্ষা আছে?
ISBN-10 গাণিতিকভাবে চমৎকার: মৌলিক 11-এর কারণে সব একক ভুল আর যেকোনো দুটি অঙ্কের অদলবদল ধরা পড়ে (উপপাদ্য ২.৩). কিন্তু ১৯৭০ সালের মানটি দুটি বাস্তব চাপে পড়ে। প্রথমত, সংখ্যার ভান্ডার: প্রথম কয়েকটি অঙ্ক দেশ বা ভাষা-গোষ্ঠী আর প্রকাশককে দেওয়া হয়, তাই অনেক প্রকাশকের ভাগে ফাঁকা নম্বর দ্রুত কমে আসছিল। দ্বিতীয়ত, দোকান: বই বিক্রি হয় বারকোড স্ক্যানারে, আর সেখানে আগে থেকেই EAN-13 চলত। পুরোনো ISBN-কে বারকোডে বসাতে সামনে 978 জুড়ে নতুন check digit বানাতে হতো, অর্থাৎ একটি বইয়ের দুটি আলাদা নম্বর।
১ জানুয়ারি ২০০৭ থেকে ISBN-13 চালু হলে এই দ্বৈততা মিটে যায়: একটি নম্বর, যা বারকোডও। পুরোনো ISBN-10-কে সামনে 978 বসিয়ে আর check digit নতুন করে হিসাব করে ISBN-13 বানানো যায়, আর 979 দিয়ে নতুন ভান্ডার খোলে। দাম হলো উপপাদ্য ৩.১-এর দুর্বলতা: পাশাপাশি দুটি অঙ্কের পার্থক্য 5 হলে অদলবদল ধরা পড়ে না, আর দূরের অদলবদল তো নয়ই। “X”-এর ঝামেলাও শেষ, কারণ mod 10-এর check digit সবসময় 0–9.
প্রকৌশলের শিক্ষাটি হলো, একটি মান (standard) শুধু গণিতের সৌন্দর্যে টেকে না; বিদ্যমান যন্ত্রপাতি, ডেটাবেসের ঘর, মানুষের অভ্যাস আর অন্য মানের সঙ্গে মেলানো, সব একসঙ্গে বিবেচনা করতে হয়। তবু গণিত থেকেই জানা যায় ঠিক কী হারানো হলো: “কিছুটা দুর্বল” নয়, নির্দিষ্টভাবে পাঁচটি অঙ্ক-জোড়ার পাশাপাশি অদলবদল। এই সুনির্দিষ্টতাই সিদ্ধান্তকে সচেতন করে, আর বাস্তবে স্ক্যানারের যুগে হাতে টাইপ কমে যাওয়ায় দামটি ছোট বলে ধরা হয়েছিল বলে মনে করা যায়।
মূল উপলব্ধি: একটি পদ্ধতি কী ধরে আর কী ধরে না, তার প্রমাণ থাকলে আপসটি চোখ খোলা রেখে করা যায়; সামঞ্জস্যের জন্য সামান্য দুর্বলতা মেনে নেওয়া প্রায়ই সঠিক প্রকৌশল।
প্র ০২Luhn পদ্ধতি সত্তর বছর ধরে কার্ড নম্বরে টিকে আছে, যদিও Verhoeff-এর পদ্ধতি সব পাশাপাশি অদলবদল ধরে। সরলতা কেন জেতে?
Hans Peter Luhn ১৯৫০-এর দশকে যে যন্ত্রের জন্য পদ্ধতিটি নকশা করেন, তা ছিল যান্ত্রিক, চাকা আর দাঁতের হিসাবযন্ত্র; তাঁর patent ১৯৬০ সালে মঞ্জুর হয় বলে উল্লেখ করা হয়। নিয়ম এমন হতে হতো যা কয়েকটি স্থির টেবিল-দেখা আর যোগে চলে: “দ্বিগুণ করে দুই অঙ্ক যোগ” আসলে একটি দশ ঘরের টেবিল (0, 2, 4, 6, 8, 1, 3, 5, 7, 9), আর বাকি সব শুধু যোগ mod 10. মানুষও মাথায় করতে পারেন, কোনো ভাগ নেই, কোনো “X” নেই।
শুধু mod 10-এর ওজনযুক্ত যোগে সব একক ভুল আর সব পাশাপাশি অদলবদল একসঙ্গে ধরা অসম্ভব, আর Lemma ২.২ দিয়ে তা দুই লাইনে প্রমাণ হয়। সব একক ভুল ধরতে প্রতিটি ওজন 10-এর সঙ্গে সহমৌলিক হতে হয়, অর্থাৎ বিজোড় আর 5-এর গুণিতক নয়। কিন্তু তাহলে পাশাপাশি দুটি ওজনের পার্থক্য দুটি বিজোড় সংখ্যার পার্থক্য, অর্থাৎ জোড়, আর তখন পার্থক্য 5-এর অঙ্ক-জোড়ার অদলবদলে পরিবর্তন 10-এর গুণিতক। Jacobus Verhoeff ১৯৬৯ সালে এই বাধা পেরোনোর পথ দেখান: যোগের জায়গায় dihedral group D5-এর গুণ, যা পাঁচ-কোণী আকৃতির ঘোরানো আর উল্টানো, আর তা commutative নয়। ক্রম বদলালে ফল বদলায়, আর এটিই অদলবদল ধরে। ফলাফল নিখুঁত, কিন্তু একটি 10 × 10 গুণের টেবিল আর একটি permutation টেবিল লাগে।
Luhn টিকে আছে কয়েক কারণে। প্রথমত, যা ফসকায় তা খুব সংকীর্ণ: শুধু 09 ↔ 90, অর্থাৎ ৯০টি সম্ভাব্য পাশাপাশি অদলবদলের মধ্যে ২টি। দ্বিতীয়ত, একবার কোটি কোটি কার্ড, টার্মিনাল আর সফটওয়্যারে একটি নিয়ম বসে গেলে তা বদলানোর খরচ বিশাল, আর লাভ সামান্য। তৃতীয়ত, কার্ড নম্বর আজ বেশিরভাগ স্ক্যান বা কপি করে ঢোকে, হাতে টাইপের ভুল কম। মনে রাখুন, কোনোটিই প্রতারণা ঠেকায় না; সেটি check digit-এর কাজই নয়।
মূল উপলব্ধি: নিখুঁত পদ্ধতি সবসময় জেতে না; প্রয়োগের খরচ, বিদ্যমান ব্যবস্থার জড়তা আর বাকি ঝুঁকির আকার মিলিয়েই একটি “যথেষ্ট ভালো” নিয়ম দশকের পর দশক টিকে থাকে।
প্র ০৩RANDU-র মতো খারাপ generator বহু বছর কেন ব্যবহৃত হলো, আর একটি random generator “ভালো” কিনা কীভাবে বিচার করা হয়?
RANDU-র গুণক 65539 = 216 + 3 বাছা হয়েছিল গতির জন্য: 2-এর ঘাত দিয়ে গুণ মানে শুধু bit সরানো, আর mod 231 মানে ওপরের bit ফেলে দেওয়া, যা সেই সময়ের যন্ত্রে খুব সস্তা ছিল। একক সংখ্যা দেখলে আউটপুট এলোমেলোই মনে হয়, আর এক-মাত্রিক পরিসংখ্যানিক পরীক্ষা (গড়, বিভাজন) সহজে পাস করে। ত্রুটিটি দেখা যায় শুধু তিনটি পরপর সংখ্যা একসঙ্গে দেখলে, আর উপপাদ্য ৬.১-এর এক লাইনের প্রমাণ বলে কেন: a2 ≡ 6a − 9.
George Marsaglia ১৯৬৮ সালে “Random numbers fall mainly in the planes” প্রবন্ধে দেখান, যেকোনো multiplicative LCG-এর পরপর d-টি মান d-মাত্রিক জায়গায় কিছু সমান্তরাল সমতলে (lattice) পড়ে; প্রশ্ন শুধু কতগুলো আর কত ঘন। RANDU-তে ত্রিমাত্রায় মাত্র 15টি, যেখানে একই m-এর ভালো গুণকে অনেক বেশি সমতল সম্ভব। Donald Knuth তাঁর বইয়ে RANDU-কে কঠোর ভাষায় সমালোচনা করেন বলে প্রায়ই উদ্ধৃত হয়। সমস্যাটি দীর্ঘস্থায়ী হয়েছিল কারণ লাইব্রেরির কোড কপি হয়ে নানা জায়গায় ছড়িয়ে পড়ে, আর বহু ব্যবহারকারী generator নিয়ে প্রশ্নই তোলেননি।
আজ একটি generator বিচার হয় কয়েক স্তরে। গাণিতিক বিশ্লেষণ: period (Hull–Dobell, primitive root), আর LCG-এর জন্য spectral test, যা সমতলগুলোর সর্বোচ্চ দূরত্ব মাপে। পরিসংখ্যানিক পরীক্ষার বড় সংগ্রহ: Marsaglia-র Diehard আর পরে L'Ecuyer ও Simard-এর TestU01 (২০০৭), যার কঠিন স্তরে বহু পুরোনো generator ফেল করে। আর ব্যবহারিক বিবেচনা: গতি, অবস্থা (state) কত বড়, আর সমান্তরালে চালানো যায় কি না। Python-এর Mersenne Twister (১৯৯৭–৯৮) সিমুলেশনের জন্য বহু বছর আদর্শ হিসেবে চলেছে; নতুন লাইব্রেরিগুলো অনেক সময় PCG বা xoshiro-পরিবার বেছে নেয়।
মূল উপলব্ধি: “দেখতে এলোমেলো” আর “পরীক্ষায় এলোমেলো” আলাদা; একটি গাণিতিক কাঠামো (এখানে a2 ≡ 6a − 9) থাকলে উপযুক্ত পরীক্ষা তা ধরে ফেলে, তাই generator বাছতে হয় বিশ্লেষণ আর পরীক্ষিত লাইব্রেরি দেখে।
প্র ০৪সিমুলেশনের জন্য ভালো generator আর নিরাপত্তার জন্য ভালো generator আলাদা কেন? দুর্বল randomness-এর একটি ঐতিহাসিক উদাহরণ দিন।
সিমুলেশনের generator-এর কাছে চাওয়া হয় পরিসংখ্যানিক গুণ: সমান বিভাজন, পরপর মানে সম্পর্ক না থাকা, লম্বা period আর গতি। এখানে কোনো প্রতিপক্ষ নেই; কেউ ইচ্ছা করে পরের মান অনুমান করতে চায় না। নিরাপত্তায় প্রশ্নটি একেবারে আলাদা: আগের সব আউটপুট দেখেও পরের মান অনুমান করা কি সম্ভব? §৬-এর আক্রমণ দেখায়, LCG-এর ক্ষেত্রে উত্তর “হ্যাঁ, তিনটি মান দেখেই”, কারণ সম্পর্কটি linear আর modular inverse তা সমাধান করে দেয়। Mersenne Twister-এর অভ্যন্তরীণ অবস্থাও linear (mod 2), আর 624টি পরপর 32-bit আউটপুট থেকে পুরো অবস্থা পুনর্গঠন করা যায় বলে সুপরিচিত।
ঐতিহাসিক উদাহরণ: ১৯৯৫–৯৬ সালে গবেষক Ian Goldberg আর David Wagner দেখান যে তখনকার একটি জনপ্রিয় ওয়েব ব্রাউজারের SSL বাস্তবায়নে key-এর random seed বানানো হতো দিনের সময় আর process id-এর মতো সহজে অনুমানযোগ্য মান থেকে, ফলে সম্ভাব্য key-এর সংখ্যা এত কম ছিল যে সবগুলো চেষ্টা করে দেখা যেত। গাণিতিক encryption ঠিক ছিল; দুর্বল ছিল randomness. পরে আরও কয়েকটি ঘটনায় একই শিক্ষা পুনরাবৃত্ত হয়েছে: key যত লম্বাই হোক, তা অনুমানযোগ্য উৎস থেকে এলে নিরাপত্তা নেই।
CSPRNG-এর নকশা তাই আলাদা নীতিতে: অপারেটিং সিস্টেম হার্ডওয়্যার আর ঘটনার সময় থেকে entropy সংগ্রহ করে, আর তা একটি cryptographic নির্মাণে (hash, block cipher বা stream cipher-ভিত্তিক) চালায়, যাতে আউটপুট থেকে অবস্থা উল্টে বের করা কঠিন বলে বিশ্বাস করা হয়। Python 3.6 থেকে secrets module এই উৎস সহজে ব্যবহার করতে দেয়। নিয়মটি সহজ: সিমুলেশন, গেম আর পরীক্ষার ডেটায় random (seed সহ, পুনরুৎপাদনের জন্য), আর কুপন কোড, টোকেন, পাসওয়ার্ড বা key-এ শুধু CSPRNG. বিস্তারিত Cybersecurity কোর্সের পর্ব ০৭-এ।
মূল উপলব্ধি: randomness-এর মান নির্ভর করে প্রতিপক্ষ আছে কি না তার ওপর; পরিসংখ্যানে নিখুঁত একটি linear generator নিরাপত্তায় সম্পূর্ণ অচল হতে পারে।
প্র ০৫পাঠ্যবই বলে hash table-এর আকার মৌলিক রাখুন, অথচ অনেক বাস্তব hash table-এর আকার 2-এর ঘাত। এই দ্বন্দ্ব কীভাবে মেটে, আর “hash flooding” আক্রমণ এখানে কী শেখায়?
উপপাদ্য ৪.১ বলে, h(k) = k mod m-এ m = 2r শুধু শেষের r-টি bit দেখে, তাই কাঠামোযুক্ত key-এ বিপজ্জনক। কিন্তু 2-এর ঘাতের সুবিধাও বড়: k mod 2r মানে একটি bitwise AND, যা ভাগের চেয়ে অনেক দ্রুত, আর টেবিল দ্বিগুণ করা সহজ। বাস্তব সমাধান হলো দুই স্তর: প্রথমে key-কে একটি ভালো “মেশানো” hash function দিয়ে এমন সংখ্যায় পাঠানো, যার সব bit সব ইনপুট-bit-এর ওপর নির্ভর করে, তারপর শেষের r-টি bit নেওয়া। যেমন Java-র HashMap টেবিলের আকার 2-এর ঘাত রাখে আর hash-এর উঁচু bit-গুলো নিচে XOR করে মেশায়; Python-এর dict-ও 2-এর ঘাতের আকার ব্যবহার করে আর সংঘর্ষে probing-এ উঁচু bit ধীরে ধীরে কাজে লাগায়। অর্থাৎ পাঠ্যবইয়ের “মৌলিক m” আর বাস্তবের “ভালো মেশানো + 2-এর ঘাত” একই সমস্যার দুটি সমাধান।
কিন্তু যেকোনো স্থির hash function-এর একটি দুর্বলতা আছে: কেউ যদি function-টি জানে, সে ইচ্ছা করে এমন হাজার হাজার key বানাতে পারে যারা একই বালতিতে পড়ে, আর তখন প্রতিটি খোঁজ ধীর হয়ে সার্ভার থমকে যায়। ২০১১ সালের শেষে Alexander Klink আর Julian Wälde একটি নিরাপত্তা সম্মেলনে দেখান, অনেক জনপ্রিয় ওয়েব প্ল্যাটফর্ম এভাবে একটি মাত্র বড় অনুরোধে আটকে ফেলা যায়। এর পরে বহু ভাষা hash-এ একটি গোপন এলোমেলো seed যোগ করে; Python 3.3 থেকে string hash randomization ডিফল্টভাবে চালু, আর Python 3.4-এ SipHash গ্রহণ করা হয় (PEP 456).
এটি ঠিক Carter আর Wegman-এর universal hashing-এর ধারণা: function-টি প্রোগ্রাম শুরুতে একটি পরিবার থেকে এলোমেলোভাবে বাছা হয়, তাই প্রতিপক্ষ জানে না কোন key কোথায় যাবে, আর যেকোনো দুটি key-এর collision-এর সম্ভাবনা কম থাকে। গণিতে প্রমাণিত একটি গড়-ক্ষেত্রের নিশ্চয়তা এভাবে বাস্তবের আক্রমণ ঠেকানোর অস্ত্র হয়ে উঠেছে। DSA কোর্সের পর্ব ৩২-এ hash table-এর এই দিকগুলো কোডসহ আছে।
মূল উপলব্ধি: m বাছাইয়ের নিয়মের পেছনের আসল কথা হলো “key-এর কাঠামো যেন বালতিতে না পৌঁছায়”; তা মৌলিক m দিয়ে, ভালো মেশানো দিয়ে, বা প্রতিপক্ষ থাকলে এলোমেলো বাছা function দিয়ে নিশ্চিত করা যায়।
অনুশীলন
আগে খাতায় করুন, তারপর উত্তর খুলে মেলান।
-
প্রমাণ: p মৌলিক, 1 ≤ a ≤ p − 1, আর multiplicative LCG xn+1 = a xn mod p-এর seed x0 ≠ 0 (mod p). প্রমাণ করুন, ধারাটির period ঠিক ordp(a).
- Induction-এ xn ≡ anx0 (mod p): n = 0-তে সত্য, আর xn+1 ≡ a · anx0. — induction
- p ∤ a, তাই gcd(a, p) = 1, আর উপপাদ্য ৫.২-এ ধারাটি শুরু থেকেই পর্যায়বৃত্ত; period হলো সবচেয়ে ছোট n ≥ 1, যাতে xn = x0. — উপপাদ্য ৫.২
- xn = x0 ⇔ anx0 ≡ x0 ⇔ an ≡ 1 (mod p), কারণ p ∤ x0 বলে x0 কেটে দেওয়া যায়। — সহমৌলিক উৎপাদক কাটা
- an ≡ 1 হওয়ার সবচেয়ে ছোট n ≥ 1-ই সংজ্ঞায় ordp(a). তাই period = ordp(a). ∎
যাচাই: ব্লক ৩-এ mod 13-এর period-গুলো 1, 12, 3, 6, 4, 12, 12, 4, 3, 6, 12, 2, যা পর্ব ২৪-এর order-এর তালিকার সঙ্গে হুবহু মেলে।
-
হিসাব: (ক) EAN-13-এর প্রথম ১২ অঙ্ক 2 0 2 6 1 1 1 0 0 4 2 7 (কাল্পনিক) হলে check digit কত? (খ) কাল্পনিক নম্বর 741029-এর পেছনে Luhn check digit কত? (গ) key 3, 13, 23, 33, … (ধাপ 10) mod 25-এ কয়টি বালতিতে পড়ে? (ঘ) x → (2x + 1) mod 9-এর period কি 9?
(ক) অবস্থান ১–১২-এর অঙ্ক 2, 0, 2, 6, 1, 1, 1, 0, 0, 4, 2, 7. বিজোড় অবস্থানগুলো (১, ৩, ৫, ৭, ৯, ১১) ওজন 1 পায়: 2 + 2 + 1 + 1 + 0 + 2 = 8. জোড় অবস্থানগুলো (২, ৪, ৬, ৮, ১০, ১২) ওজন 3 পায়: 0 + 6 + 1 + 0 + 4 + 7 = 18, তিনগুণ 54. মোট 62 ≡ 2 (mod 10), তাই check digit 10 − 2 = 8, আর পূর্ণ নম্বর 2026111004278. সাধারণ ভুল: অবস্থান গুনতে গিয়ে জোড় আর বিজোড় গুলিয়ে ফেলা; তাই অঙ্কগুলো আগে অবস্থানসহ লিখে নিন।
(খ) ডান থেকে payload-এর অঙ্ক 9, 2, 0, 1, 4, 7; দ্বিগুণ হবে ১ম, ৩য়, ৫ম: 9 → 18 → 9, 0 → 0, 4 → 8. যোগ 9 + 2 + 0 + 1 + 8 + 7 = 27, তাই check digit 3, আর নম্বর 7410293.
(গ) gcd(10, 25) = 5, তাই 25/5 = 5টি বালতি: 3, 8, 13, 18, 23 (সবগুলো ≡ 3 mod 5).
(ঘ) না। 9-এর মৌলিক উৎপাদক 3, কিন্তু a − 1 = 1, আর 3 ∤ 1, তাই Hull–Dobell-এর শর্ত ২ ভাঙে। seed 0 থেকে ধারা 0, 1, 3, 7, 6, 4, 0: period 6. সব সংখ্যা scratch-এ Python দিয়ে যাচাই করা।
-
আউটপুট অনুমান: কোড না চালিয়ে বলুন কী ছাপা হবে। তারপর যেকোনো Python-এ চালিয়ে মেলান।
print([(5 * x + 3) % 8 for x in range(8)]) x, seen = 1, [] for _ in range(6): x = 3 * x % 7; seen.append(x) print(seen, sorted({8 * i % 12 for i in range(12)}))[3, 0, 5, 2, 7, 4, 1, 6] [3, 2, 6, 4, 5, 1] [0, 4, 8]প্রথম লাইন: gcd(5, 8) = 1, তাই x ↦ 5x + 3 একটি bijection mod 8, আর তালিকায় 0–7 প্রতিটি ঠিক একবার। দ্বিতীয় লাইনের প্রথম অংশ: 3 একটি primitive root mod 7, তাই ছয় ধাপে 1–6 সব আসে আর শেষে 1. দ্বিতীয় অংশ উপপাদ্য ৪.১: gcd(8, 12) = 4, তাই 12/4 = 3টি মান, সবগুলো 4-এর গুণিতক।
-
প্রমাণের ভুল ধরুন: একজন লিখলেন: “দাবি: ১০ অঙ্কের নম্বরে ওজন 1, 2, …, 10 নিয়ে mod 10-এ check digit বানালে প্রতিটি অদলবদল ধরা পড়ে। প্রমাণ: i, j অবস্থানে a ≠ b অদলবদল হলে যোগফল বদলায় (j − i)(a − b) পরিমাণে। i ≠ j আর a ≠ b, তাই দুটি উৎপাদকই শূন্য নয়, গুণফলও শূন্য নয়, আর ভুল ধরা পড়ে। ∎” ভুল কোথায়, আর একটি counterexample দিন।
- ভুল: ধরা পড়তে হলে গুণফল শূন্য নয়, 10 দিয়ে বিভাজ্য নয় হতে হয় (Lemma ২.২). 10 মৌলিক নয়, তাই দুটি শূন্য-নয় সংখ্যার গুণফল 10-এর গুণিতক হতে পারে; ISBN-10-এর প্রমাণ কাজ করেছিল কারণ 11 মৌলিক।
- Counterexample: অবস্থান ১ আর ৩ (j − i = 2), অঙ্ক 0 আর 5 (a − b = −5): পরিবর্তন 2 · (−5) = −10 ≡ 0 (mod 10), ধরা পড়ে না। আরও সহজ, যেকোনো অবস্থানে ওজন 10 ≡ 0, তাই দশম অঙ্কের একক ভুলও কখনো ধরা পড়ে না।
- সংশোধন: modulus একটি মৌলিক সংখ্যা নিন যা সব ওজন আর অঙ্কের পার্থক্যের চেয়ে বড়, যেমন 11, আর ওজনগুলো তার চেয়ে ছোট আলাদা সংখ্যা রাখুন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি payload-এর Luhn check digit বের করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- ডান দিক থেকে payload-এর ১ম, ৩য়, ৫ম, … অঙ্ক চিহ্নিত করা
- চিহ্নিত প্রতিটি অঙ্ক দ্বিগুণ করা
- দ্বিগুণ করে 9-এর বেশি হলে 9 বিয়োগ করা
- সব অঙ্ক যোগ করে যোগফল mod 10 নেওয়া
- যোগফলকে 10-এর গুণিতকে পৌঁছাতে যত লাগে, সেটিই check digit হিসেবে বসানো
ISBN-10-এর প্রথম নয় অঙ্ক 1 2 3 4 5 6 7 8 9 (ওজন 10, 9, …, 2, check digit-এর ওজন 1, mod 11) হলে check digit কী?
- X
- 0
- 1
- 9
EAN-13 (ওজন পালাক্রমে 1 আর 3, mod 10) পদ্ধতিতে পাশাপাশি দুটি অঙ্কের কোন অদলবদল ধরা পড়ে না?
- …12… → …21…
- …16… → …61…
- …09… → …90…
- …47… → …74…
0, 6, 12, 18, 24, … (ধাপ 6) key-গুলো h(k) = k mod 15-এ কয়টি ভিন্ন বালতিতে পড়ে?
- 15
- 3
- 5
- 6
নিচের কোন LCG x → (ax + c) mod m-এর period প্রতিটি seed-এ ঠিক m?
- x → (3x + 1) mod 8
- x → (2x + 1) mod 9
- x → (5x + 5) mod 10
- x → (5x + 3) mod 16
মৌলিক modulus p প্রকাশ্য, কিন্তু a আর c গোপন, এমন একটি LCG-এর পরপর তিনটি আউটপুট x0, x1, x2 (x1 ≠ x0) জানা। কী বলা যায়?
- a আর c বের করে পরের সব আউটপুট অনুমান করা যায়
- p বড় হলে a বের করা অসম্ভব
- শুধু a বের হয়, c কখনো নয়
- পুরো period না দেখে কিছু বলা যায় না
RANDU-তে (m = 231, a = 65539 = 216 + 3, c = 0) পরপর তিন মানের মধ্যে কোন সম্পর্ক সবসময় সত্য?
- xk+2 ≡ 9xk (mod 231)
- xk+2 ≡ 6xk+1 + 9xk (mod 231)
- xk+2 ≡ 6xk+1 − 9xk (mod 231)
- xk+2 ≡ xk+12 (mod 231)
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Check digit: ∑ widi ≡ 0 (mod m). একক ভুল ফসকায় ⇔ m ∣ wiΔ; অদলবদল ফসকায় ⇔ m ∣ (wi − wj)(a − b).
- ISBN-10 বনাম mod 10: মৌলিক 11 সব একক ভুল আর সব অদলবদল ধরে, দাম “X” অক্ষর। EAN-13 পার্থক্য 5-এর পাশাপাশি অদলবদল ফসকায়; Luhn শুধু 09 ↔ 90.
- Hashing: ধাপ d-এর key mod m-এ ঠিক m/gcd(d, m)টি বালতিতে। মৌলিক m সব বালতি ব্যবহার করে; 2-এর ঘাত শুধু শেষের bit দেখে। প্রতিপক্ষ থাকলে universal hashing.
- LCG: period ≤ m; Hull–Dobell: gcd(c, m) = 1, প্রতিটি মৌলিক p ∣ m-এ p ∣ a − 1, আর 4 ∣ m হলে 4 ∣ a − 1. c = 0 আর m = p মৌলিক হলে period = ordp(a).
- নিরাপত্তা: RANDU-তে xk+2 ≡ 6xk+1 − 9xk; যেকোনো LCG তিনটি আউটপুটে ভেঙে পড়ে। গোপন মানে শুধু CSPRNG. CRC = mod 2-এ polynomial ভাগ।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ২৭ · Classical cipher — shift, affine আর frequency analysisপরবর্তী পর্বগোপন বার্তার প্রাচীন পদ্ধতি আর তা ভাঙার গণিত।
- পর্ব ২৩ · Linear congruence আর Chinese Remainder TheoremপুনরালোচনাModular inverse আর linear congruence, যা check digit, round-robin আর LCG-আক্রমণের ভিত্তি।
- ডিসক্রিট ম্যাথমেটিক্স · কোর্স হোম৮৩টি পর্বপুরো কোর্সের মানচিত্র আর ১৪টি মডিউল।
- DSA · পর্ব ৩২ · Hash table — hash function, collision আর load factorকোডে প্রয়োগChaining, open addressing, rehashing আর বাস্তব hash table-এর কোড।
- Cybersecurity · পর্ব ০৭ · Cryptography-র ভিত্তিবাস্তব ব্যবহারCSPRNG বনাম সাধারণ PRNG, key-এর আকার আর Kerckhoffs-এর নীতি।
- Computer Networks · পর্ব ০৯ · Error detection আর correctionনেটওয়ার্কেParity, checksum, CRC আর Hamming code, frame-এর ভেতরে।