সরাসরি পর্বে যান
পর্ব ৪৩ · ৭৯-এর মধ্যে · মডিউল ৭

Constraint satisfaction — রুটিন, Sudoku আর backtracking

Constraint Satisfaction Problems — Variables, Domains, Constraints, Backtracking, MRV, Forward Checking, AC-3, Min-Conflicts and CP-SAT

Taught in Bangla, this lesson writes problems as variables, domains and constraints, then solves map colouring, an exam timetable, Sudoku and n-queens with backtracking, MRV, LCV, forward checking and AC-3, compares them in real runs, and builds a staff roster with OR-Tools CP-SAT, checking every result independently.

এই পর্বে যা শিখবেন

  • একটি বাস্তব সমস্যাকে variable, domain আর constraint-এ লেখা; unary, binary আর global constraint; constraint graph আঁকা
  • মানচিত্রে রং, বাজারঘরের স্টাফ রুটিন, পরীক্ষার সময়সূচি আর Sudoku-কে CSP হিসেবে লেখা, আর সব মিশ্রণ গুনে দেখা কেন অন্ধ খোঁজ চলে না
  • CSP-র backtracking, MRV, degree আর LCV heuristic, আর forward checking কীভাবে ভবিষ্যতের ব্যর্থতা আগেই দেখে
  • Arc consistency আর AC-3 হাতে trace করা, আর সাধারণ backtracking থেকে AC-3 পর্যন্ত চারটি পদ্ধতির সত্যিকারের তুলনা
  • বড় n-queens-এ min-conflicts local search, আর OR-Tools CP-SAT দিয়ে নরম পছন্দসহ একটি রুটিন সমাধান

মডিউল ৭-এর আগের পর্বগুলোতে (পর্ব ৩৯ থেকে ৪২) সমস্যাকে দেখা হয়েছে পথ খোঁজা হিসেবে: শুরুর state থেকে action নিয়ে নিয়ে লক্ষ্যে পৌঁছানো। এই পর্বে পথের দাম বা ক্রম গুরুত্বপূর্ণ নয়; দরকার শুধু এমন একটি পূর্ণ উত্তর, যা সব নিয়ম একসঙ্গে মানে: কোন পরীক্ষা কোন দিনে, Sudoku-র কোন ঘরে কোন অঙ্ক, কোন কর্মী কোন শিফটে। Russell ও Norvig-এর পাঠ্যবই “Artificial Intelligence: A Modern Approach” (৪র্থ সংস্করণ) একে পুরো একটি অধ্যায় দিয়েছে; এখানের উদাহরণগুলো নিজেদের বানানো।

১ · CSP কী — variable, domain আর constraint

কলেজের পরীক্ষা কমিটির কথা ভাবুন। চারটি পরীক্ষা (AI, Database, Networking, Math), তিনটি দিন। নিয়মগুলো এমন: যে পরীক্ষাগুলোর ছাত্রছাত্রী এক, সেগুলো একই দিনে নয়; Math-এর শিক্ষক প্রথম দিন নেই; Networking-এর ল্যাব শুধু তৃতীয় দিন খালি। কমিটি একটি তালিকা চায়, যেখানে প্রতিটি পরীক্ষার পাশে একটি দিন লেখা, আর কোনো নিয়ম ভাঙেনি।

বিয়েবাড়িতে অতিথি বসানোর কথা ভাবুন। প্রত্যেক অতিথি একটি “variable”, টেবিলগুলো তাঁর সম্ভাব্য “মান”, আর নিয়ম হলো “ঝগড়া করা দুই চাচাকে এক টেবিলে নয়”। কে আগে বসলেন, তাতে কিছু যায় আসে না; শেষে সবাই বসলেন আর কোনো নিয়ম ভাঙল না, এটুকুই লক্ষ্য।

এই কাঠামোর তিনটি অংশ:

  • Variable X1, …, Xn: যার মান ঠিক করতে হবে। এখানে চারটি পরীক্ষা: AI, DB, NET, MATH.
  • Domain Di: প্রতিটি variable-এর সম্ভাব্য মানের সেট। এখানে প্রতিটির জন্য {1, 2, 3}, মানে তিনটি দিন।
  • Constraint: কোন কোন মানের মিশ্রণ অনুমোদিত। যেমন AI ≠ DB (এক ছাত্রছাত্রী), MATH ≠ 1.

প্রতিটি variable-এ একটি করে মান বসানোকে বলে assignment. সব variable-এ মান বসলে তা complete assignment; কোনো constraint না ভাঙলে consistent. CSP-র সমাধান মানে একটি complete আর consistent assignment. কখনো সমাধান থাকে না; তখন ভালো পদ্ধতির কাজ হলো সেটি প্রমাণ করা।

Constraint-এর তিন রকম

Unary

একটিমাত্র variable-এর ওপর নিয়ম: MATH ≠ 1, NET = 3. এগুলো search-এর আগেই domain থেকে বাদ দেওয়া যায়।

unary constraint

Binary

দুটি variable-এর মধ্যে: AI ≠ DB, AI < MATH, পাশের বিভাগের রং আলাদা। এগুলোই constraint graph-এর রেখা।

binary constraint

Global

অনেক variable একসঙ্গে: Sudoku-র একটি সারির ৯টি ঘরের অঙ্ক সব আলাদা। এর নাম all-different.

global constraint

All-different constraint-কে চাইলে জোড়ায় জোড়ায় ≠ দিয়ে ভেঙে লেখা যায়: ৯টি ঘরের জন্য 9C2 = ৩৬টি binary constraint. কিন্তু global রূপে রাখলে solver বেশি বুঝতে পারে। যেমন তিনটি ঘরে যদি শুধু {1, 2} মান বাকি থাকে, জোড়া-জোড়া ≠ দেখে তা ধরা কঠিন, অথচ all-different সরাসরি বলে “তিনজনের জন্য দুটি মান, অসম্ভব”। এ ছাড়া আছে hard constraint (ভাঙা চলবেই না) আর soft constraint বা পছন্দ (ভাঙলে জরিমানা); দ্বিতীয়টি এলে সমস্যাটি optimization-ও হয়ে যায় (অংশ ৯)।

Constraint graph — নিয়মের মানচিত্র

Binary constraint-গুলো একটি graph-এ আঁকা যায়: প্রতিটি variable একটি বিন্দু (node), আর যে দুটি variable-এর মধ্যে constraint আছে, তাদের মধ্যে একটি রেখা (edge). Graph-এর মৌলিক ধারণা (node, edge, adjacency list, degree) DSA কোর্সের graph পর্বে আছে। নিচে এই পর্বের প্রধান উদাহরণ: বাংলাদেশের ৮টি বিভাগ, যেখানে পাশাপাশি দুটি বিভাগে একই রং দেওয়া যাবে না।

বিভাগের constraint graph আর তিন রঙের একটি সমাধান রংপুর রাজশাহী ময়মন. সিলেট ঢাকা খুলনা বরিশাল চট্টগ্রাম degree 6 লাল = R · সবুজ = G · নীল = B
৮টি variable (বিভাগ), ১৪টি binary constraint (রেখা)। রেখাগুলো আনুমানিক: জেলার সীমানার তথ্য দেখে করা, নদী-সীমানার কয়েকটি জায়গায় ভিন্ন ব্যাখ্যা থাকতে পারে। এটি মানচিত্র নয়, শুধু “কে কার পাশে” তার ছবি; ময়মন. = ময়মনসিংহ।

রেখাগুলো ঠিক করতে লেখার সময়ে Wikipedia-র জেলাভিত্তিক পাতায় সীমানার বর্ণনা দেখা হয়েছে: যেমন জামালপুরের (ময়মনসিংহ বিভাগ) পশ্চিমে কুড়িগ্রাম, গাইবান্ধা (রংপুর), বগুড়া আর সিরাজগঞ্জ (রাজশাহী); হবিগঞ্জের (সিলেট) পশ্চিমে কিশোরগঞ্জ (ঢাকা) আর ব্রাহ্মণবাড়িয়া (চট্টগ্রাম); ভোলার (বরিশাল) পূর্বে লক্ষ্মীপুর আর নোয়াখালী (চট্টগ্রাম)। ঢাকার degree (প্রতিবেশীর সংখ্যা) ৬, সবচেয়ে বেশি; অংশ ৬-এ এটি কাজে লাগবে।

DSA-র backtracking থেকে পার্থক্য। DSA কোর্সের backtracking পর্বে n-queens আর subset sum-এর জন্য আলাদা আলাদা recursive কোড লেখা হয়েছে: একটি করে সিদ্ধান্ত, নিয়ম ভাঙলে ফিরে আসা। এখানে আমরা সেই কৌশলকে একটি সাধারণ কাঠামো বানাব: যেকোনো সমস্যাকে variable, domain আর constraint-এ লিখলেই একই solver চলবে, আর heuristic ও inference যোগ হবে সমস্যা-নিরপেক্ষভাবে। এটাই CSP-কে AI-এর একটি “ভাষা” করে তোলে।

২ · চারটি সমস্যা, একই ছাঁচে

একই কাঠামোতে চারটি সমস্যা পাশাপাশি। বাজারঘর (এই কোর্সের কাল্পনিক অনলাইন দোকান)-এর রুটিনের নাম আর নিয়ম সব বানানো।

সমস্যাVariableDomainConstraint
বিভাগে রং (graph colouring)৮টি বিভাগ{লাল, সবুজ, নীল}১৪টি: পাশের বিভাগ ≠
বাজারঘরের সাপ্তাহিক রুটিন৫ জন × ৭ দিন × ২ শিফট = ৭০টি হ্যাঁ/না{0, 1}প্রতি শিফটে ২ জন (শুক্রবার সকালে ১), প্রতি শিফটে অন্তত একজন সিনিয়র, দিনে একটির বেশি শিফট নয়, সপ্তাহে সর্বোচ্চ ৬টি, রাতের শিফটের পরদিন সকালে নয়; সঙ্গে নরম পছন্দ
পরীক্ষার সময়সূচি৪টি পরীক্ষা{1, 2, 3} দিনMATH ≠ 1, NET = 3, AI < MATH, AI ≠ DB, DB ≠ NET, NET ≠ MATH
Sudoku৮১টি ঘর{1, …, 9}; দেওয়া ঘরে একটিই মান২৭টি all-different (৯ সারি, ৯ কলাম, ৯ বাক্স) = ৮১০টি জোড়া ≠

Sudoku-র ৮১০ সংখ্যাটি হাতে বের করা যায়। প্রতিটি ঘরের “প্রতিবেশী” (peer) ২০টি: নিজের সারির আরও ৮টি, কলামের ৮টি, আর বাক্সের যে ৪টি ঘর সারি বা কলামে আগে গোনা হয়নি। ৮১ × ২০ = ১,৬২০, কিন্তু প্রতিটি জোড়া দুবার গোনা হলো, তাই ৮১০টি binary constraint. পরীক্ষার সময়সূচিতে AI < MATH নিয়মটি (Math-এর প্রশ্ন AI পরীক্ষার পরে ছাপা হবে, ধরা যাক) দেখায় যে constraint শুধু ≠ নয়, যেকোনো সম্পর্ক হতে পারে।

মূল ধারণা

CSP-র শক্তি হলো সমস্যার ভেতরটা solver-এর কাছে খোলা থাকে। পর্ব ৩৯–৪১-এর সাধারণ search-এ একটি অবস্থা ছিল কালো বাক্স: শুধু “লক্ষ্যে পৌঁছেছি কি না” জিজ্ঞেস করা যেত। CSP-তে solver দেখতে পায় কোন variable কোন নিয়মে কার সঙ্গে বাঁধা। সেজন্যই সে বুঝতে পারে “এই ঘরে আর মাত্র একটি অঙ্ক বাকি, এটি আগে বসানো উচিত”, যা কালো বাক্সের search পারে না।

৩ · সব মিশ্রণ পরখ করলে কী হয় — generate-and-test-এর বিস্ফোরণ

সবচেয়ে সরল পদ্ধতি হলো সব সম্ভাব্য complete assignment একে একে বানিয়ে প্রতিটি পরীক্ষা করা। একে বলে generate-and-test. n-টি variable, প্রতিটির d-টি মান হলে মোট মিশ্রণ dn. ছোট সমস্যায় চলে, বড়তে নয়। ধরুন একটি কম্পিউটার প্রতি সেকেন্ডে ১০০ কোটি (109) মিশ্রণ পরীক্ষা করতে পারে, যা খুবই উদার অনুমান।

সমস্যামিশ্রণ dn109/সেকেন্ড হারে
৮টি বিভাগ, ৩ রং38 = 6,561চোখের পলকে
8-queens (প্রতি সারিতে একটি রানি, ৮টি কলাম)88 = 16,777,216প্রায় 0.017 সেকেন্ড
বাজারঘরের রুটিন, ৭০টি হ্যাঁ/না270 ≈ 1.18 × 1021প্রায় ৩৭ হাজার বছর
20-queens2020 ≈ 1.05 × 1026প্রায় ৩৩০ কোটি বছর
৩২টি clue-র Sudoku (৪৯টি খালি ঘর)949 ≈ 5.7 × 1046প্রায় 1.8 × 1030 বছর

মহাবিশ্বের বয়স মোটামুটি 1.4 × 1010 বছর। এটাই combinatorial explosion: variable একটি বাড়লে কাজ d গুণ বাড়ে। মূল ভুলটি হলো generate-and-test পুরো assignment বানানোর পরে নিয়ম দেখে। AI-এর উত্তর: আংশিক assignment-এই নিয়ম দেখা। রংপুর লাল আর রাজশাহী লাল হলে বাকি ৬টি বিভাগের 36 = ৭২৯টি মিশ্রণ আর দেখার দরকার নেই; সেই পুরো শাখাটি একবারে কেটে ফেলা যায়।

সাধারণভাবে CSP কঠিন, heuristic জাদু নয়। তিন রঙে একটি graph রং করা যায় কি না, এই প্রশ্নটিই NP-complete (Karp ১৯৭২ সালের বিখ্যাত তালিকায় graph colouring ছিল)। মানে খারাপতম ক্ষেত্রে দ্রুত পদ্ধতি জানা নেই, আর এর অর্থ P বনাম NP প্রশ্নের সঙ্গে জড়িত। এই পর্বের কৌশলগুলো বাস্তবের অনেক সমস্যায় বিশাল পার্থক্য আনে, কিন্তু কিছু সমস্যা সব পদ্ধতিতেই কঠিন থাকে।

৪ · CSP-র backtracking search — একবারে একটি variable

CSP-র backtracking-এর ধাপগুলো সহজ:

  1. এখনো মান পায়নি এমন একটি variable বাছুন।
  2. তার domain থেকে একটি মান নিন, আর যেসব প্রতিবেশী আগেই মান পেয়েছে শুধু তাদের সঙ্গে constraint মিলিয়ে দেখুন।
  3. মিললে মানটি বসিয়ে পরের variable-এ যান (recursion). না মিললে পরের মান।
  4. কোনো মানই না চললে এই variable থেকে ফিরে আসুন (backtrack): আগের variable-এর মান তুলে নিয়ে তার পরের মান চেষ্টা করুন।

দুটি পর্যবেক্ষণ এটিকে generate-and-test থেকে আলাদা করে। এক, CSP-তে variable-এর ক্রম বদলালেও সমাধান বদলায় না (assignment commutative), তাই প্রতিটি স্তরে শুধু একটি variable নিয়ে ভাবলেই চলে; এতে search tree-র পাতা dn-এর বেশি হয় না, n!·dn নয়। দুই, নিয়ম ভাঙলেই পুরো শাখা কাটা পড়ে। এটি আসলে depth-first search, যেখানে গভীরতা = কতগুলো variable মান পেয়েছে।

নিচের কোড বিভাগের মানচিত্রে এই সাধারণ backtracking চালায়। তিনটি সংখ্যা গোনা হয়: tried (কতবার একটি রং চেষ্টা করা হলো), checks (কতবার একটি প্রতিবেশীর রঙের সঙ্গে মেলানো হলো) আর backtracks (একটি বসানো রং কতবার তুলে নিতে হলো)। একই সমস্যা তিনভাবে চালানো হয়েছে: পশ্চিম থেকে পূর্বের একটি নির্দিষ্ট ক্রমে, ঢাকাকে প্রথমে রেখে, আর মাত্র দুটি রং দিয়ে। এই ব্লকে শুধু Python-এর মৌলিক অংশ; প্রথমবার চালালে ব্রাউজারে Python নামতে কয়েক সেকেন্ড লাগতে পারে।

EDGES = """Rangpur-Rajshahi Rangpur-Mymensingh Rajshahi-Mymensingh Rajshahi-Dhaka
Rajshahi-Khulna Khulna-Dhaka Khulna-Barishal Barishal-Dhaka Barishal-Chattogram
Dhaka-Chattogram Dhaka-Sylhet Dhaka-Mymensingh Mymensingh-Sylhet Sylhet-Chattogram"""
nb = {}                                   # constraint graph: neighbours
for e in EDGES.split():
    a, b = e.split("-")
    nb.setdefault(a, set()).add(b)
    nb.setdefault(b, set()).add(a)

def solve(order, colors):
    asg, st = {}, {"tried": 0, "checks": 0, "backtracks": 0}
    def bt(i):
        if i == len(order):
            return True
        var = order[i]
        for c in colors:
            st["tried"] += 1
            used = [asg[n] for n in nb[var] if n in asg]   # assigned neighbours
            st["checks"] += len(used)
            if c in used:
                continue                  # violates a constraint: next colour
            asg[var] = c
            if bt(i + 1):
                return True
            del asg[var]                  # undo = one backtrack
            st["backtracks"] += 1
        return False
    return (dict(asg) if bt(0) else None), st
w2e = ["Rangpur", "Rajshahi", "Khulna", "Barishal",
       "Chattogram", "Sylhet", "Mymensingh", "Dhaka"]
for name, order, k in [("west->east, 3 colours", w2e, 3),
                       ("Dhaka first, 3 colours", ["Dhaka"] + w2e[:-1], 3),
                       ("west->east, 2 colours", w2e, 2)]:
    sol, st = solve(order, ["red", "green", "blue"][:k])
    print(name, st, "\n  ", sol)

আসল আউটপুট:

west->east, 3 colours {'tried': 125, 'checks': 375, 'backtracks': 36}
   {'Rangpur': 'red', 'Rajshahi': 'green', 'Khulna': 'blue', 'Barishal': 'green', 'Chattogram': 'blue', 'Sylhet': 'green', 'Mymensingh': 'blue', 'Dhaka': 'red'}
Dhaka first, 3 colours {'tried': 17, 'checks': 36, 'backtracks': 0}
   {'Dhaka': 'red', 'Rangpur': 'red', 'Rajshahi': 'green', 'Khulna': 'blue', 'Barishal': 'green', 'Chattogram': 'blue', 'Sylhet': 'green', 'Mymensingh': 'blue'}
west->east, 2 colours {'tried': 26, 'checks': 32, 'backtracks': 12}
   None

একই মানচিত্র, একই রং, কিন্তু ক্রম বদলাতেই কাজ সাত গুণের বেশি কমে গেল: পশ্চিম থেকে পূর্বে গেলে ১২৫ বার রং চেষ্টা আর ৩৬টি backtrack, ঢাকা আগে দিলে ১৭ বার আর একটিও না। কারণ প্রথম ক্রমে ঢাকা আসে সবার শেষে; ততক্ষণে তার ছয় প্রতিবেশী প্রায়ই তিনটি রংই নিয়ে ফেলে, আর ভুলটি অনেক আগে (খুলনা বা বরিশালে) হয়েছিল বলে অনেক দূর পিছোতে হয়। দুই রঙে None: সমাধান নেই, আর backtracking ২৬টি চেষ্টায় তা প্রমাণ করেছে (ঢাকা, রাজশাহী আর খুলনা পরস্পরের প্রতিবেশী, একটি ত্রিভুজ)। পরের অংশগুলোর লক্ষ্য এই তিনটি সংখ্যা কমানো।

৫ · Forward checking — ভবিষ্যতের ব্যর্থতা আগে দেখা

সাধারণ backtracking শুধু পেছনে তাকায়: “আগে যারা মান পেয়েছে, তাদের সঙ্গে মিলছে তো?” কিন্তু ঢাকার সমস্যাটি দেখা গেল অনেক পরে। Forward checking সামনে তাকায়: একটি variable-এ মান বসানোর সঙ্গে সঙ্গে তার যেসব প্রতিবেশী এখনো মান পায়নি, তাদের domain থেকে অসঙ্গত মানগুলো মুছে ফেলে। কোনো প্রতিবেশীর domain খালি হয়ে গেলে সঙ্গে সঙ্গে বোঝা যায় এই পথে সমাধান নেই, আর তখনই ফিরে আসা যায়।

সিট-প্ল্যান বানানোর সময় শিক্ষক একজন ছাত্রকে বসিয়েই পাশের চেয়ারের তালিকায় দাগ দেন: “এখানে ওর ঝগড়ার বন্ধুকে নয়”। কোনো চেয়ারের তালিকা খালি হলে তখনই বোঝেন, আগের সিদ্ধান্ত বদলাতে হবে; পুরো ক্লাস বসানো পর্যন্ত অপেক্ষা করেন না।

নিচের stepper পশ্চিম থেকে পূর্বের সেই একই ক্রমে forward checking চালায়। R = লাল, G = সবুজ, B = নীল; “=R” মানে মান বসানো হয়েছে, বাকিগুলো domain-এ যা অবশিষ্ট। প্রতিটি ধাপে রং বেছে নেওয়া হয় domain-এর ক্রমে (R, তারপর G, তারপর B).

1var = next in fixed order2val = next value in dom[var]3remove val from free neighbours4if a domain is empty: undo
domain (Watch)
রংপুর
রাজশাহী
খুলনা
বরিশাল
চট্টগ্রাম
সিলেট
ময়মনসিংহ
ঢাকা
  1. রংপুর = R. রংপুরের দুই প্রতিবেশী রাজশাহী আর ময়মনসিংহের domain থেকে R মুছে গেল: দুজনেরই বাকি G B. বাকিরা রংপুরের প্রতিবেশী নয়, তাদের domain অপরিবর্তিত।
  2. রাজশাহী = G. রাজশাহীর domain-এর প্রথম মান G. প্রতিবেশী খুলনা আর ঢাকা থেকে G মুছে R B থাকল; ময়মনসিংহের G B থেকে G মুছে শুধু B.
  3. খুলনা = R. বরিশালের G B, ঢাকার শুধু B. খেয়াল করুন: ঢাকা আর ময়মনসিংহ পাশাপাশি, দুজনেরই শুধু B. সমাধান এখানেই অসম্ভব, কিন্তু forward checking দুটি খালি variable-এর মধ্যের নিয়ম দেখে না, তাই টের পেল না।
  4. বরিশাল = G. চট্টগ্রামের domain থেকে G মুছে R B. ঢাকায় G আগেই ছিল না।
  5. চট্টগ্রাম = R. সিলেটের domain থেকে R মুছে G B. অথচ ধাপ ৩ থেকেই এই শাখা মৃত।
  6. সিলেট = G. ময়মনসিংহে আর ঢাকায় G ছিল না, কিছু বদলাল না। দুজনেরই এখনো শুধু B.
  7. ময়মনসিংহ = B, ঢাকা খালি। ঢাকার একমাত্র মান B মুছে গেল, domain ∅. Forward checking এখানে ধরল, ময়মনসিংহের B তুলে নেওয়া হলো (undo 1).
  8. পেছনে ফেরার সারি। ময়মনসিংহের আর মান নেই, তাই সিলেটে ফেরা: সিলেট = B দিলে ময়মনসিংহ খালি। তারপর চট্টগ্রাম = B, বরিশাল = B, দুটিতেই ঢাকা খালি। শেষে খুলনার R-ই তুলে নিতে হলো। মোট ১০টি বসানো, ৮টি তুলে নেওয়া; খুলনার বাকি শুধু B.
  9. খুলনা = B. এবার ঢাকার R B থেকে B মুছে শুধু R, বরিশালের R G থাকল। ঢাকা আর ময়মনসিংহ আর একই রঙে আটকে নেই।
  10. শেষ পর্যন্ত সমাধান। বরিশাল = R দিলে ঢাকা খালি (undo), তাই বরিশাল = G; চট্টগ্রাম = R-এ ঢাকা খালি, তাই B; সিলেট = R-এ ঢাকা খালি, তাই G; তারপর ময়মনসিংহ = B, ঢাকা = R. মোট ১৯টি বসানো আর ১১টি undo; সাধারণ backtracking-এর ৩৬টি backtrack-এর চেয়ে অনেক কম।

ধাপ ৩-এর ঘটনাটি মনে রাখুন: forward checking দুটি খালি variable-এর মধ্যের বিরোধ দেখে না। ঢাকা {B} আর ময়মনসিংহ {B} পাশাপাশি মানে নিশ্চিত ব্যর্থতা; এটি ধরে arc consistency (অংশ ৭)। তবু forward checking সস্তা, প্রতিবার শুধু প্রতিবেশীদের একবার দেখা।

৬ · কোন variable আগে, কোন মান আগে — MRV, degree আর LCV

অংশ ৪-এর আউটপুট বলেছে, ক্রম বদলালেই কাজ সাত গুণ কমে। কিন্তু প্রতিটি সমস্যার জন্য হাতে ভালো ক্রম খোঁজা চলে না। তিনটি সাধারণ heuristic (অনুমাননির্ভর নিয়ম) সব CSP-তে কাজে লাগে।

MRV — সবচেয়ে কম বিকল্প আগে

Minimum remaining values: যে variable-এর domain-এ সবচেয়ে কম মান বাকি, তাকে আগে নিন। যার মাত্র একটি মান, তাকে এখনই বসান; যার শূন্য, সেখানে এখনই ব্যর্থতা ধরা পড়বে। একে “fail-first” নীতিও বলে।

variable বাছাই

Degree — সবচেয়ে বেশি বাঁধন আগে

MRV-তে সমান হলে (শুরুতে সবারই ৩টি রং) যে variable সবচেয়ে বেশি খালি প্রতিবেশীর সঙ্গে constraint-এ বাঁধা, তাকে নিন। তার সিদ্ধান্ত সবচেয়ে বেশি জনের বিকল্প ছাঁটবে।

MRV-র tie-break

LCV — সবচেয়ে কম বাধা দেয় এমন মান আগে

Least constraining value: variable ঠিক হলে, তার যে মান প্রতিবেশীদের domain থেকে সবচেয়ে কম মান মোছে, সেটি আগে চেষ্টা করুন। ভবিষ্যতের জন্য সবচেয়ে বেশি পথ খোলা রাখে।

মান সাজানো

একটি আপাত-বিরোধ: variable বাছাইয়ে “সবচেয়ে কঠিনটি আগে”, অথচ মান বাছাইয়ে “সবচেয়ে সহজটি আগে”। কারণ প্রতিটি variable-কে শেষ পর্যন্ত মান পেতেই হবে, তাই কঠিনটির ব্যর্থতা যত আগে ধরা পড়ে তত কম কাজ নষ্ট; কিন্তু মানগুলোর মধ্যে শুধু একটিকে সফল হতে হবে, তাই সম্ভাব্য সফলটি আগে। (সব সমাধান খুঁজতে হলে বা সমাধান না থাকলে মানের ক্রমে লাভ নেই, কারণ সব মানই দেখতে হয়।)

নিচের stepper বিভাগের মানচিত্রে MRV + degree আর forward checking একসঙ্গে চালায়। প্রতিটি ঘরে লেখা “বাকি মানের সংখ্যা · d খালি প্রতিবেশীর সংখ্যা”; সমান হলে তালিকার আগের নাম (রংপুর, রাজশাহী, খুলনা, বরিশাল, চট্টগ্রাম, সিলেট, ময়মনসিংহ, ঢাকা ক্রমে)। ← চিহ্ন দেওয়া variable-টি এই ধাপে বাছা হলো।

1cands = free vars with fewest values2tie: most free neighbours (degree)3assign first value, forward check
বাকি মান · d (Watch)
রংপুর
রাজশাহী
খুলনা
বরিশাল
চট্টগ্রাম
সিলেট
ময়মনসিংহ
ঢাকা
  1. শুরু: সবাই সমান, degree ঠিক করে। সব বিভাগের ৩টি করে রং বাকি, তাই MRV কাউকে আলাদা করতে পারে না। Degree heuristic দেখে কার খালি প্রতিবেশী সবচেয়ে বেশি: ঢাকার ৬টি। ঢাকা = R.
  2. ছয়জনের ২টি করে। ঢাকার ছয় প্রতিবেশীর প্রত্যেকের ২টি রং, রংপুরের ৩টি। ছয়জনের মধ্যে degree সবচেয়ে বেশি রাজশাহী আর ময়মনসিংহের (৩), তালিকায় আগে রাজশাহী। রাজশাহী = G.
  3. দুজনের মাত্র ১টি। খুলনা আর ময়মনসিংহ দুজনেই ঢাকা (R) আর রাজশাহী (G)-র প্রতিবেশী, তাই দুজনের বাকি শুধু B. MRV দুজনকে সামনে আনে; degree-তে ময়মনসিংহ (খালি প্রতিবেশী রংপুর আর সিলেট, ২টি) খুলনাকে (শুধু বরিশাল, ১টি) হারায়। ময়মনসিংহ = B.
  4. তিনজনের ১টি। রংপুর (R), খুলনা (B) আর সিলেট (G), সবার একটি করে মান। রংপুরের কোনো খালি প্রতিবেশী নেই (d0); খুলনা আর সিলেট d1-এ সমান, তালিকায় আগে খুলনা। খুলনা = B.
  5. বরিশাল। খুলনা B হওয়ায় বরিশালের শুধু G. বরিশাল আর সিলেট দুজনেরই ১টি মান আর d1, বরিশাল আগে। বরিশাল = G.
  6. চট্টগ্রাম। চট্টগ্রামের প্রতিবেশী ঢাকা (R) আর বরিশাল (G), বাকি শুধু B. চট্টগ্রাম আর সিলেট d1-এ সমান, চট্টগ্রাম আগে। চট্টগ্রাম = B.
  7. বাকি দুজন, কারও খালি প্রতিবেশী নেই। রংপুর আর সিলেট দুজনেরই ১টি মান আর d0; তালিকার ক্রমে রংপুর। রংপুর = R.
  8. শেষ variable. সিলেট = G. ৮টি variable-এ ঠিক ৮টি মান বসানো, একটিও undo নেই। অংশ ৫-এর নির্দিষ্ট ক্রমে লেগেছিল ১৯টি বসানো আর ১১টি undo.

LCV-এর একটি হাতে হিসাব

পরীক্ষার সময়সূচিতে unary constraint প্রয়োগের পর domain: AI {1, 2, 3}, DB {1, 2, 3}, NET {3}, MATH {2, 3}. ধরুন AI-কে আগে বাছা হলো। AI-এর প্রতিবেশী DB (≠) আর MATH (AI < MATH). প্রতিটি মান প্রতিবেশীদের থেকে কয়টি মান মোছে:

  • AI = 1: MATH-এর {2, 3} দুটিই 1-এর বড়, কিছু মোছে না; DB থেকে 1 মোছে। মোট ১টি।
  • AI = 2: MATH থেকে 2 মোছে (2 < 2 নয়); DB থেকে 2. মোট ২টি।
  • AI = 3: MATH-এর দুটিই মোছে (খালি!), DB থেকে 3. মোট ৩টি।

তাই LCV-এর ক্রম 1, 2, 3, আর অংশ ৭-এ দেখবেন একমাত্র সমাধানে AI = 1. তবে LCV-এর হিসাবেও constraint check লাগে; বড় domain-এ এর খরচ কখনো লাভ ছাড়িয়ে যায়, তাই অনেক solver মান সাজাতে সস্তা নিয়ম বা কিছুটা randomness ব্যবহার করে।

Sudoku-তে MRV + forward checking বনাম সাধারণ backtracking

এবার একই কৌশল Sudoku-তে। নিচের ধাঁধাটি (৩২টি clue) এই পর্বের জন্য seed দেওয়া Python কোড দিয়ে বানানো: প্রথমে একটি পূর্ণ গ্রিড, তারপর এলোমেলো ক্রমে ঘর খালি করা, যতক্ষণ উত্তর একটিমাত্র থাকে। কোনো বই বা ওয়েবসাইট থেকে নেওয়া নয়। ৮১টি অক্ষরের লেখায় প্রতি ৯টি অক্ষর এক সারি, . মানে খালি ঘর। plain সারি ধরে ধরে ঘর ভরে; mrv_fc প্রতিবার সবচেয়ে কম বিকল্পের ঘর বাছে আর বসানোর পর peer-দের domain ছাঁটে।

P = "....4.3.5..2....9....5.98..6.......9..8.941..4..1..23..8...1.53..3.25..1..53.8972"
C = range(81)
peers = [{j for j in C if j != i and (j // 9 == i // 9 or j % 9 == i % 9
          or (j // 27, j % 9 // 3) == (i // 27, i % 9 // 3))} for i in C]
st = {"plain": [0, 0], "mrv+fc": [0, 0]}          # [values placed, backtracks]
def plain(g, i=0):                                 # row by row, fixed order
    if i == 81: return True
    if g[i]: return plain(g, i + 1)
    for v in range(1, 10):
        if all(g[j] != v for j in peers[i]):       # check against filled peers
            st["plain"][0] += 1
            g[i] = v
            if plain(g, i + 1): return True
            g[i] = 0
            st["plain"][1] += 1                    # undo = backtrack
    return False

def mrv_fc(dom, free):
    if not free: return dom
    i = min(sorted(free), key=lambda k: len(dom[k]))   # MRV
    for v in sorted(dom[i]):
        st["mrv+fc"][0] += 1
        new = [d - {v} if j in peers[i] else d.copy() for j, d in enumerate(dom)]
        new[i] = {v}                                   # forward checking above
        if all(new[j] for j in peers[i] & free) and (res := mrv_fc(new, free - {i})):
            return res
        st["mrv+fc"][1] += 1
    return None
g = [int(c) if c != "." else 0 for c in P]
dom = [{g[i]} if g[i] else set(range(1, 10)) - {g[j] for j in peers[i]} for i in C]
res = mrv_fc(dom, {i for i in C if not g[i]})
print("clues:", 81 - P.count("."), "| plain solved:", plain(g))
print("same answer:", [min(d) for d in res] == g, "| row 1:", g[:9])
for k, (placed, back) in st.items():
    print(f"{k:7} placed={placed:5}  backtracks={back:5}")

আসল আউটপুট:

clues: 32 | plain solved: True
same answer: True | row 1: [8, 6, 9, 7, 4, 2, 3, 1, 5]
plain   placed= 7996  backtracks= 7947
mrv+fc  placed=   49  backtracks=    0

দুটি পদ্ধতি একই উত্তর দিয়েছে (same answer: True), যা হওয়ারই কথা, কারণ ধাঁধার উত্তর একটিই। পার্থক্য পরিশ্রমে: সারি ধরে চলা backtracking ৭,৯৯৬বার একটি অঙ্ক বসিয়েছে আর ৭,৯৪৭বার তুলে নিয়েছে; MRV + forward checking ৪৯টি খালি ঘরে ঠিক ৪৯বার বসিয়েছে, একবারও ভুল নয়। আলাদা করে গুনে দেখা গেছে, ৪৯টি ধাপের ৪৬টিতে MRV এমন ঘর পেয়েছে যার মাত্র একটি অঙ্ক বাকি (মানুষ যাকে “naked single” বলেন), আর বাকি ৩টিতে দুটি অঙ্কের প্রথমটিই ঠিক ছিল। তবে এটা এই ধাঁধার সৌভাগ্য, নিয়ম নয়; অংশ ৮-এর কঠিন ধাঁধায় MRV + forward checking-কেও ২০বার ফিরতে হয়েছে। খেয়াল করুন, plain দেওয়া clue-গুলোকে আগেই বসানো ধরে নেয়, আর mrv_fc-এর min প্রতিবার সব খালি ঘর ঘুরে দেখে, যার নিজের খরচ আছে।

৭ · Arc consistency আর AC-3 — নিয়মের ঢেউ ছড়ানো

অংশ ৫-এর ধাপ ৩-এ forward checking যা দেখেনি, তা ধরার জন্য একটি শক্তিশালী ধারণা: arc consistency. একটি binary constraint-কে দুই দিকের দুটি “arc” হিসেবে দেখুন: X → Y আর Y → X. X → Y arc consistent মানে X-এর domain-এর প্রতিটি মানের জন্য Y-এর domain-এ অন্তত একটি মান আছে, যার সঙ্গে নিয়মটি মানে। এমন সঙ্গী (support) না থাকলে X-এর সেই মানটি কোনো সমাধানে আসতে পারে না, তাই মুছে ফেলা নিরাপদ। এই মোছার কাজটিকে বলে revise.

একটি মান মুছলে যারা সেই মানের ওপর নির্ভর করছিল, তাদের আবার দেখতে হয়; এই ঢেউ ছড়ানোকে বলে constraint propagation.

AC-3 অ্যালগরিদম Alan Mackworth-এর ১৯৭৭ সালের প্রবন্ধ “Consistency in Networks of Relations” (Artificial Intelligence জার্নাল) থেকে পরিচিত; নামের “3” মানে তিনি যে কয়েকটি সংস্করণ তুলনা করেছিলেন, তার তৃতীয়টি। এর আগে David Waltz লাইন-ড্রয়িংয়ের ছবি বোঝার কাজে একই ধরনের “filtering” ব্যবহার করেছিলেন, আর Ugo Montanari ১৯৭৪ সালে constraint network-এর কাঠামো দেন বলে সাধারণত উল্লেখ করা হয়। Forward checking আর “fail-first” ধারণাটি প্রায়ই Haralick ও Elliott-এর ১৯৮০ সালের প্রবন্ধের সঙ্গে যুক্ত করা হয়।

AC-3-এর কাজ: সব arc একটি queue-তে রাখা। একটি করে arc (Xi, Xj) বের করে revise. কিছু মুছলে Xi-এর দিকে তাকানো সব arc (Xk, Xi), Xj ছাড়া, আবার queue-তে (আগে থেকে থাকলে আর নয়)। কোনো domain খালি হলে সমস্যাটির সমাধান নেই। queue খালি হলে সব arc consistent. নিচে পরীক্ষার সময়সূচিতে পুরো trace. সংক্ষেপে A = AI, D = DB, N = NET, M = MATH; unary constraint আগেই প্রয়োগ করা।

1queue = all arcs2while queue:3    Xi, Xj = queue.popleft()4    if revise(Xi, Xj):5        if not dom[Xi]: return False6        add (Xk, Xi), Xk != Xj7return True
domain আর queue (Watch)
A (AI)
D (DB)
N (NET)
M (MATH)
queue
revise
  1. শুরু। চারটি binary constraint (A < M, A ≠ D, D ≠ N, N ≠ M), প্রতিটির দুই দিক, মোট ৮টি arc. Unary নিয়ম আগেই বসানো: N = {3}, M থেকে 1 বাদ।
  2. A → M. A-র প্রতিটি মানের জন্য M-এ বড় একটি মান চাই। A = 1 (M = 2 আছে), A = 2 (M = 3 আছে), কিন্তু A = 3-এর কোনো সঙ্গী নেই। A থেকে 3 মুছল। D → A-কে যোগ করার কথা, কিন্তু সেটি queue-তে আগে থেকেই আছে।
  3. M → A, A → D, D → A. M-এর প্রতিটি মানের ছোট A আছে; ≠ নিয়মে অন্য দিকে দুটি মান থাকলে সঙ্গী মেলেই। তিনটি revise, কিছু মুছল না।
  4. D → N. N-এর একমাত্র মান 3, তাই D = 3-এর কোনো সঙ্গী নেই (D ≠ N). D থেকে 3 মুছল, D এখন {1, 2}. D-র অন্য প্রতিবেশী A, তাই A → D আবার queue-র শেষে।
  5. N → D, N → M. N = 3-এর জন্য D-তে 1 বা 2 আছে, M-এ 2 আছে। কিছু মুছল না।
  6. M → N. M = 3 আর N = 3 একই দিনে চলবে না, M থেকে 3 মুছল: M = {2}. M-এর অন্য প্রতিবেশী A, তাই A → M আবার queue-তে। ঢেউটি এবার AI-এর দিকে ফিরছে।
  7. A → D, তারপর A → M. A → D-তে কিছু মোছে না (D-তে দুটি মান)। কিন্তু A → M-এ এবার M শুধু {2}, তাই A = 2 আর চলে না (2 < 2 মিথ্যা)। A = {1}. A-র অন্য প্রতিবেশী D, তাই D → A queue-তে।
  8. D → A. A এখন শুধু 1, তাই D = 1 বাদ (D ≠ A). D = {2}. D-র অন্য প্রতিবেশী N, তাই N → D queue-তে।
  9. N → D, queue খালি। N = 3-এর সঙ্গী D = 2. মোট ১২টি revise-এর পর সব arc consistent, আর প্রতিটি domain-এ একটিই মান: AI দিন ১, DB দিন ২, NET দিন ৩, MATH দিন ২. এই সমস্যায় AC-3 একাই সমাধান দিল, কোনো search ছাড়াই।

সাধারণত AC-3 একা সমাধান দেয় না; domain ছোট করে, তারপর search লাগে। তখন দুটি উপায়: search-এর আগে একবার AC-3 (preprocessing), অথবা প্রতিটি মান বসানোর পর সেই variable থেকে শুরু করে AC-3 চালানো। দ্বিতীয়টির নাম MAC (maintaining arc consistency), আর অংশ ৮-এর তুলনায় “MRV + MAC(AC-3)” মানে এটাই। খরচের হিসাব: e-টি binary constraint আর সর্বোচ্চ d আকারের domain হলে AC-3-এর খারাপতম সময় O(e·d3): প্রতিটি arc বড়জোর d বার queue-তে ফেরে (arc-টি যে variable-এর দিকে নির্দেশ করে, তার domain থেকে অন্তত একটি মান মুছলে তবেই ফেরে), আর একটি revise-এ d × d জোড়া দেখা লাগে।

from collections import deque

def ac3(dom, cons):
    arcs = {}
    for (x, y), f in cons.items():                 # each constraint = two arcs
        arcs[(x, y)] = f
        arcs[(y, x)] = lambda b, a, f=f: f(a, b)
    queue, revisions = deque(arcs), 0
    while queue:
        xi, xj = queue.popleft()
        revisions += 1
        bad = {a for a in dom[xi] if not any(arcs[xi, xj](a, b) for b in dom[xj])}
        if bad:
            dom[xi] -= bad                         # revise: drop unsupported values
            if not dom[xi]:
                return False, revisions
            for (xk, xl) in arcs:                  # re-check arcs pointing at xi
                if xl == xi and xk != xj and (xk, xi) not in queue:
                    queue.append((xk, xi))
    return True, revisions

def exam_csp():
    dom = {"AI": {1, 2, 3}, "DB": {1, 2, 3}, "NET": {3}, "MATH": {2, 3}}  # unary done
    cons = {("AI", "MATH"): lambda a, m: a < m,    # AI exam before Math
            ("AI", "DB"): lambda a, b: a != b,     # shared students
            ("DB", "NET"): lambda a, b: a != b,
            ("NET", "MATH"): lambda a, b: a != b}
    return dom, cons

dom, cons = exam_csp()
print("before:", dom)
print("ok, revisions:", ac3(dom, cons), "\nafter: ", dom)
dom, cons = exam_csp()
cons[("DB", "MATH")] = lambda a, b: a != b         # one more clash rule
print("with DB != MATH:", ac3(dom, cons), dom)

আসল আউটপুট:

before: {'AI': {1, 2, 3}, 'DB': {1, 2, 3}, 'NET': {3}, 'MATH': {2, 3}}
ok, revisions: (True, 12)
after:  {'AI': {1}, 'DB': {2}, 'NET': {3}, 'MATH': {2}}
with DB != MATH: (False, 12) {'AI': set(), 'DB': {1}, 'NET': {3}, 'MATH': {2}}

প্রথম run stepper-এর হাতে trace-এর সঙ্গে হুবহু মেলে: ১২টি revise, শেষে AI = 1, DB = 2, NET = 3, MATH = 2. দ্বিতীয় run-এ একটি নতুন নিয়ম যোগ হলো, DB ≠ MATH (ধরা যাক দুই পরীক্ষার পরিদর্শক একই জন)। তখন AC-3 search ছাড়াই False দেয়: MATH = 2 হলে DB = 2 চলে না, DB = {1}, তখন AI ≠ 1, অথচ AI < MATH = 2 মানে AI = 1 লাগে। AI-এর domain খালি (set()), মানে এই নিয়মগুলো একসঙ্গে কখনো মানা যাবে না। কমিটির জন্য এটি দামি তথ্য: কোনো একটি নিয়ম শিথিল করতে হবে। কোডের lambda b, a, f=f: f(a, b) উল্টো দিকের arc বানায়; f=f না লিখলে Python-এর late binding-এ সব lambda শেষ constraint-টিকেই ধরত।

আরও গভীরে: Arc consistency শুধু জোড়া দেখে; তিনটি variable একসঙ্গে দেখলে path consistency, আর k-টি দেখলে k-consistency, যা আরও শক্তিশালী কিন্তু দামি। all-different-এর জন্য বিশেষ propagator আছে: Régin ১৯৯৪ সালে bipartite matching দিয়ে এর পূর্ণ arc consistency দক্ষভাবে করার উপায় দেখান, যা জোড়া-জোড়া ≠-এর চেয়ে বেশি মোছে। আধুনিক constraint solver-এর ভেতরে মূলত এ ধরনের propagator, search আর ব্যর্থতা থেকে শেখা (clause learning) একসঙ্গে কাজ করে; AC-3-এর পরের সংস্করণ AC-4 (১৯৮৬) আর AC-2001 খারাপতম সময় O(e·d2)-এ নামায়।

৮ · সত্যিকারের তুলনা — Sudoku, n-queens আর min-conflicts

এবার চারটি পদ্ধতি একই কোডের কাঠামোয় পাশাপাশি: (১) সাধারণ backtracking, নির্দিষ্ট ক্রম; (২) তার সঙ্গে MRV; (৩) MRV + forward checking; (৪) MRV + MAC, মানে প্রতিবার AC-3. প্রতিটি সংখ্যা একটি ল্যাপটপে সত্যিকারের run থেকে (লেখার সময়ে Python 3.14.4, শুধু standard library, একটি run). Sudoku-র তিনটি ধাঁধাই একই seed-এর একটি পূর্ণ গ্রিড থেকে ঘর মুছে বানানো, প্রতিটির উত্তর একটিই: সহজ (৪০টি clue, AC-3 একাই সমাধান করে), মাঝারি (৩২টি, AC-3 একা ৮১টির ৪৪টি ঘর ঠিক করে) আর কঠিন (২৫টি, এর কোনো clue মুছলে উত্তর আর একটি থাকে না)। placed = বসানো মান, backtracks = তুলে নেওয়া মান, checks = দুটি মানের মধ্যে constraint যাচাইয়ের সংখ্যা।

সমস্যাপদ্ধতিplacedbacktrackschecksসময়
Sudoku সহজ-৪০plain BT44840725,1340.01s
+ MRV41051,3990.01s
MRV + FC4102,602<0.01s
MRV + MAC41015,3160.01s
Sudoku মাঝারি-৩২plain BT7,9967,947456,4640.09s
+ MRV49067,2830.01s
MRV + FC4903,059<0.01s
MRV + MAC49025,2010.01s
Sudoku কঠিন-২৫plain BT1,609,5371,609,48194,122,59719.58s
+ MRV7620108,4950.02s
MRV + FC76203,670<0.01s
MRV + MAC58235,9450.01s
8-queensplain BT1131052,438<0.01s
+ MRV75676,850<0.01s
MRV + FC7567777<0.01s
MRV + MAC20122,331<0.01s
20-queensplain BT199,635199,61525,428,8424.55s
+ MRV145125134,1930.02s
MRV + FC1451254,144<0.01s
MRV + MAC523239,5400.01s

সারণির কঠিন-২৫ অংশের আসল terminal আউটপুট (সত্যিকারের run, path বদলে নিরপেক্ষ নাম):

~/bazarghar-csp$ python compare.py
Python 3.14.4
…
Sudoku hard-25
method          │     placed │ backtracks │       checks │     time │
plain BT        │  1,609,537 │  1,609,481 │   94,122,597 │   19.58s │ ok
BT + MRV        │         76 │         20 │      108,495 │    0.02s │ ok
MRV + FC        │         76 │         20 │        3,670 │    0.00s │ ok
MRV + MAC(AC-3) │         58 │          2 │       35,945 │    0.01s │ ok
…

সংখ্যাগুলো কী বলে

  • কঠিন ধাঁধায় পার্থক্য বিশাল: ১৬ লাখ backtrack আর প্রায় ২০ সেকেন্ড বনাম ২০টি বা ২টি। সহজ ধাঁধায় সাধারণ backtracking-ও চলেছে; কঠিনতা কয়েকটি clue-তে হঠাৎ লাফ দেয়।
  • MRV আর MRV + FC-র placed আর backtracks একই। কারণ এখানে forward checking ছাড়া MRV-কে নিজেই প্রতিটি খালি ঘরের বৈধ মান গুনতে হয়; সে একই ঘর বাছে, একই ক্রমে। পার্থক্য খরচে: ১,০৮,৪৯৫ বনাম ৩,৬৭০ check, কারণ forward checking domain মনে রাখে, প্রতিবার নতুন করে গোনে না।
  • MAC সবচেয়ে কম ভুল করে, কিন্তু বেশি check করে। কঠিন-২৫-এ মাত্র ২টি backtrack, কিন্তু forward checking-এর প্রায় দশ গুণ check. কোনটি দ্রুত, তা সমস্যা আর implementation-এর ওপর নির্ভর করে; এই ছোট পরীক্ষায় দুটিই এক সেকেন্ডের অনেক নিচে।
  • n-queens-এ MRV একা কম কাজের। 8-queens-এ সাধারণ backtracking-এর ২,৪৩৮ check-এর চেয়ে “+ MRV”-এর ৬,৮৫০ বেশি: শুরুতে সব সারির domain সমান, আর বৈধ মান গোনার খরচ লাভকে ছাড়িয়ে গেছে। 20-queens-এ অবশ্য MRV ১,৯৯,৬১৫টি backtrack-কে ১২৫-এ নামিয়েছে।
  • সতর্কতা: একটি যন্ত্রে, একটি run-এ, এই পর্বের সরল কোডে পাওয়া সংখ্যা, সাধারণ নিয়ম নয়; ধাঁধা বা ক্রম বদলালে সংখ্যা বদলাবে।

Min-conflicts — পুরো বোর্ড বসিয়ে মেরামত

সম্পূর্ণ ভিন্ন একটি পথ আছে। আংশিক assignment থেকে না শুরু করে, শুরুতেই সব variable-এ এলোমেলো মান বসান, তারপর মেরামত করুন: যে variable নিয়ম ভাঙছে, এমন একটি এলোমেলোভাবে বাছুন, আর তাকে এমন মান দিন যাতে সবচেয়ে কম বিরোধ (conflict) হয়। একে বলে min-conflicts, আর এটি local search-এর একটি রূপ, যার পুরো পরিবার (hill climbing, simulated annealing) পর্ব ৪৪-এ। Minton ও সহকর্মীদের ১৯৯০-এর দশকের শুরুর গবেষণায় দেখানো হয়েছিল যে বড় n-queens-এ এটি বিস্ময়করভাবে দ্রুত, আর এই ধারণা Hubble Space Telescope-এর পর্যবেক্ষণ-সূচির কাজের সঙ্গে যুক্ত ছিল বলে উল্লেখ পাওয়া যায়।

নিচের কোড n-queens-এ দুই পথই চালায়: সাধারণ backtracking আর MRV + forward checking (অংশ ৮-এর সারণির মতো একই গণনা), তারপর min-conflicts. প্রতিটি সারি একটি variable, কলাম তার মান।

import random
def safe(r1, c1, r2, c2):
    return c1 != c2 and abs(c1 - c2) != abs(r1 - r2)

def backtrack(n, mrv):            # rows are variables, columns are values
    count = {"placed": 0, "backtracks": 0}
    def go(dom, asg):
        if len(asg) == n: return asg
        free = [r for r in range(n) if r not in asg]
        r = min(free, key=lambda x: len(dom[x])) if mrv else free[0]
        for c in dom[r]:
            if not all(safe(r, c, r2, c2) for r2, c2 in asg.items()): continue
            count["placed"] += 1
            new = {x: [v for v in dom[x] if safe(r, c, x, v)] if x in free else dom[x]
                   for x in range(n)} if mrv else dom     # forward checking
            if all(new[x] for x in free if x != r) and go(new, {**asg, r: c}):
                return True
            count["backtracks"] += 1
    go({x: list(range(n)) for x in range(n)}, {})
    return count
def min_conflicts(n, seed=42, max_steps=5000):
    rng = random.Random(seed)
    q = [rng.randrange(n) for _ in range(n)]       # random full assignment
    hits = lambda r, c: sum(1 for r2 in range(n) if r2 != r and not safe(r, c, r2, q[r2]))
    for step in range(max_steps):
        bad = [r for r in range(n) if hits(r, q[r])]
        if not bad: return step
        r = rng.choice(bad)                        # a conflicted queen
        costs = [hits(r, c) for c in range(n)]
        q[r] = rng.choice([c for c in range(n) if costs[c] == min(costs)])
    return None
for n in (8, 12, 20):
    print(n, "plain:", backtrack(n, False) if n <= 12 else "skip", "| MRV+FC:", backtrack(n, True))
for n in (8, 20, 50):
    print(f"min-conflicts n={n}: solved after {min_conflicts(n)} steps")

আসল আউটপুট:

8 plain: {'placed': 113, 'backtracks': 105} | MRV+FC: {'placed': 75, 'backtracks': 67}
12 plain: {'placed': 261, 'backtracks': 249} | MRV+FC: {'placed': 153, 'backtracks': 141}
20 plain: skip | MRV+FC: {'placed': 145, 'backtracks': 125}
min-conflicts n=8: solved after 111 steps
min-conflicts n=20: solved after 33 steps
min-conflicts n=50: solved after 74 steps

8- আর 20-queens-এর সংখ্যা ওপরের সারণির সঙ্গে হুবহু মিলেছে (113/105, 75/67, 145/125), যদিও কোডটি আলাদা, ছোট করে লেখা। 20-queens-এর সাধারণ backtracking এখানে চালানো হয়নি (skip), কারণ ১,৯৯,৬৩৫টি মান বসাতে ব্রাউজারে কয়েক সেকেন্ড লেগে যেত; সংখ্যাটি সারণিতে আছে। Min-conflicts এখানে seed 42-এ 8-queens-এ ১১১টি মেরামত ধাপ নিয়েছে, অথচ 50-queens-এ মাত্র ৭৪টি: বড় বোর্ডে সমাধান ঘন, তাই মেরামত সহজ। ছোট বোর্ডে এটি কখনো আটকে যায়, আর seed বদলালে ধাপের সংখ্যা অনেক বদলায়। এই সরল কোডের প্রতিটি ধাপ O(n2), তাই বড় n ডেস্কটপে।

ডেস্কটপে কলাম আর দুই কর্ণের রানির সংখ্যা আলাদা তালিকায় রেখে (তাতে একটি ঘরের বিরোধ O(1)-এ জানা যায়) একই কৌশল। সত্যিকারের run (Python 3.14.4, seed 42, একটি run):

~/bazarghar-csp$ python minconf.py
Python 3.14.4
n=     8  start conflicts=    10  steps=    15  time=0.00s
n=    20  start conflicts=    20  steps=    24  time=0.00s
n=   100  start conflicts=   112  steps=   161  time=0.00s
n=  1000  start conflicts=  1149  steps=   744  time=0.08s
n= 10000  start conflicts= 11630  steps=  6236  time=6.08s

দশ হাজার রানি, শুরুতে ১১,৬৩০ জোড়া বিরোধ, ৬,২৩৬টি মেরামতে সমাধান। ধাপের সংখ্যা মোটামুটি n-এর সমানুপাতে বাড়ছে, যা আগের গবেষণার পর্যবেক্ষণের সঙ্গে মেলে; সময় বেশি বাড়ছে কারণ প্রতিটি ধাপে n-টি কলাম দেখা হয়। একই শর্তে 20-queens-এ সাধারণ backtracking-এর লেগেছিল প্রায় দুই লাখ backtrack. তবে min-conflicts-এর completeness নেই (incomplete): সমাধান না থাকলে সে চিরকাল ঘুরবে, “সমাধান নেই” প্রমাণ করতে পারে না; backtracking পারে (অংশ ৪-এর দুই রঙের উদাহরণ)।

৯ · বাস্তবের constraint solver — বাজারঘরের রুটিন CP-SAT দিয়ে

বাস্তবের রুটিন, সময়সূচি বা উৎপাদন-পরিকল্পনায় সাধারণত কেউ নিজের backtracking লেখেন না। সমস্যাটি একটি solver-এর ভাষায় লেখা হয় (modelling), আর solver নিজের ভেতরে propagation, search, ব্যর্থতা থেকে শেখা আর অনেক কৌশল চালায়। একটি পরিচিত বিনামূল্যের, open source বিকল্প Google-এর OR-Tools প্যাকেজের CP-SAT solver, যা constraint programming আর SAT solver-এর কৌশল মেলায়। লেখার সময়ে PyPI-তে সর্বশেষ সংস্করণ ortools 9.15.6755.

বাজারঘরের গুদামের সাপ্তাহিক রুটিন (সব নাম আর নিয়ম বানানো): পাঁচজন কর্মী, Anika, Babul, Chumki, Dipu আর Eti; শনি থেকে শুক্র সাত দিন, প্রতিদিন সকাল (M) আর সন্ধ্যা (E) শিফট। কঠিন নিয়মগুলো অংশ ২-এর সারণিতে। নরম পছন্দ: Chumki শুক্রবার ছুটি চান (ভাঙলে জরিমানা ৩), Dipu সকালের শিফট পছন্দ করেন (প্রতি সন্ধ্যায় ২), Eti শনিবার ছুটি চান (৩); আর সবার শিফট-সংখ্যা যেন কাছাকাছি থাকে (সর্বোচ্চ আর সর্বনিম্নের পার্থক্যে ৪ গুণ জরিমানা)। solver মোট জরিমানা সবচেয়ে কম করবে।

from ortools.sat.python import cp_model
import ortools

staff = ["Anika", "Babul", "Chumki", "Dipu", "Eti"]
senior = {"Anika", "Babul", "Chumki"}
days = ["Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri"]
shifts = ["M", "E"]                        # morning, evening
need = {(d, s): 2 for d in days for s in shifts}
need[("Fri", "M")] = 1                     # Friday morning is quiet

m = cp_model.CpModel()
x = {(p, d, s): m.new_bool_var(f"{p}_{d}_{s}")
     for p in staff for d in days for s in shifts}

# hard constraints
for d in days:
    for s in shifts:
        m.add(sum(x[p, d, s] for p in staff) == need[d, s])
        m.add(sum(x[p, d, s] for p in senior) >= 1)     # a senior on every shift
    for p in staff:
        m.add(x[p, d, "M"] + x[p, d, "E"] <= 1)          # one shift a day
for p in staff:
    m.add(sum(x[p, d, s] for d in days for s in shifts) <= 6)
    for i in range(len(days) - 1):                       # no evening -> next morning
        m.add(x[p, days[i], "E"] + x[p, days[i + 1], "M"] <= 1)

# soft preferences (penalty weights)
pen = []
pen += [3 * x["Chumki", "Fri", s] for s in shifts]       # Chumki: Friday off
pen += [2 * x["Dipu", d, "E"] for d in days]             # Dipu: prefers mornings
pen += [3 * x["Eti", "Sat", s] for s in shifts]          # Eti: Saturday off
load = {p: sum(x[p, d, s] for d in days for s in shifts) for p in staff}
hi, lo = m.new_int_var(0, 14, "hi"), m.new_int_var(0, 14, "lo")
m.add_max_equality(hi, list(load.values()))
m.add_min_equality(lo, list(load.values()))
m.minimize(sum(pen) + 4 * (hi - lo))                     # fairness

solver = cp_model.CpSolver()
solver.parameters.max_time_in_seconds = 10
solver.parameters.num_workers = 1
solver.parameters.random_seed = 42
st = solver.solve(m)
print("ortools", ortools.__version__, "| status:", solver.status_name(st))
print("objective:", solver.objective_value, "| bound:", solver.best_objective_bound)
print("       " + "  ".join(f"{d:>5}" for d in days) + "  total")
for p in staff:
    row = []
    for d in days:
        row.append("".join(s for s in shifts if solver.value(x[p, d, s])) or "-")
    print(f"{p:<7}" + "  ".join(f"{c:>5}" for c in row) + f"  {solver.value(load[p]):>5}")
print("Dipu evenings:", sum(solver.value(x["Dipu", d, "E"]) for d in days),
      "| Chumki Fri:", sum(solver.value(x["Chumki", "Fri", s]) for s in shifts),
      "| Eti Sat:", sum(solver.value(x["Eti", "Sat", s]) for s in shifts))
print(f"wall time: {solver.wall_time:.3f}s, branches: {solver.num_branches}, conflicts: {solver.num_conflicts}")

প্রথম চেষ্টায় সিনিয়র ছিলেন শুধু Anika আর Babul. সত্যিকারের run-এর প্রথম লাইন:

~/bazarghar-csp$ python rota_v1.py
ortools 9.15.6755 | status: INFEASIBLE
…

Solver বলছে কোনো সমাধান নেই, আর হাতে হিসাব করলে কারণটি সরল: ১৪টি শিফটের প্রতিটিতে অন্তত একজন সিনিয়র চাই, কিন্তু দুজন সিনিয়র সপ্তাহে সর্বোচ্চ ৬টি করে, মোট ১২টি শিফট করতে পারেন। ১৪ > ১২. (INFEASIBLE অবস্থায় variable-এর মান অর্থহীন; সেই run-এর বাকি আউটপুট ছিল আবর্জনা সংখ্যা, তাই আগে status.) Chumki-কে সিনিয়র ধরা হলো (উপরের কোড)। সত্যিকারের run (Python 3.14.4, ortools 9.15.6755, এক worker, seed 42):

~/bazarghar-csp$ python rota_cpsat.py
ortools 9.15.6755 | status: OPTIMAL
objective: 4.0 | bound: 4.0
         Sat    Sun    Mon    Tue    Wed    Thu    Fri  total
Anika      E      E      -      E      E      -      M      5
Babul      M      M      M      M      -      M      E      6
Chumki     E      E      E      -      M      E      -      5
Dipu       M      -      M      M      M      M      -      5
Eti        -      M      E      E      E      E      E      6
Dipu evenings: 0 | Chumki Fri: 0 | Eti Sat: 0
wall time: 0.027s, branches: 791, conflicts: 4

OPTIMAL আর objective = bound = 4.0 মানে solver শুধু একটি রুটিন পায়নি, প্রমাণও করেছে যে এর চেয়ে কম জরিমানা সম্ভব নয়। তিনটি ব্যক্তিগত পছন্দই রক্ষা পেয়েছে (Dipu-র সন্ধ্যা ০টি, Chumki-র শুক্রবার আর Eti-র শনিবার ফাঁকা); বাকি 4 জরিমানা এসেছে সমতা থেকে: ২৭টি শিফট পাঁচজনে সমান ভাগ হয় না, তাই কেউ ৬, কেউ ৫, পার্থক্য ১ × ৪. যাচাই করুন: শুক্রবার সকালে শুধু Anika (১ জন), প্রতিটি শিফটে অন্তত একজন সিনিয়র, কেউ সন্ধ্যার পরদিন সকালে নেই (Babul বৃহস্পতিবার সকাল, শুক্রবার সন্ধ্যা: চলে)। ৭০টি হ্যাঁ/না variable-এর এই সমস্যায় solver লেগেছে কয়েক শতাংশ সেকেন্ড; অংশ ৩-এর হিসাবে অন্ধ গণনায় লাগত হাজার হাজার বছর।

ছোট সমস্যার জন্য আরও হালকা একটি Python library আছে: python-constraint, যার রক্ষণাবেক্ষণ-করা সংস্করণ লেখার সময়ে PyPI-তে python-constraint2 2.9.0 নামে (import করার নাম constraint). বিভাগের মানচিত্রে এটি দিয়ে সব সমাধান গোনা হলো:

from importlib.metadata import version
from constraint import Problem, AllDifferentConstraint

edges = [("Rangpur", "Rajshahi"), ("Rangpur", "Mymensingh"), ("Rajshahi", "Mymensingh"),
         ("Rajshahi", "Dhaka"), ("Rajshahi", "Khulna"), ("Khulna", "Dhaka"),
         ("Khulna", "Barishal"), ("Barishal", "Dhaka"), ("Barishal", "Chattogram"),
         ("Dhaka", "Chattogram"), ("Dhaka", "Sylhet"), ("Dhaka", "Mymensingh"),
         ("Mymensingh", "Sylhet"), ("Sylhet", "Chattogram")]
divisions = sorted({v for e in edges for v in e})

for k in (2, 3, 4):
    p = Problem()
    p.addVariables(divisions, list(range(k)))
    for a, b in edges:
        p.addConstraint(AllDifferentConstraint(), [a, b])
    sols = p.getSolutions()
    print(f"{k} colours: {len(sols)} solutions")
print("python-constraint2", version("python-constraint2"))
p = Problem()
p.addVariables(divisions, ["red", "green", "blue"])
for a, b in edges:
    p.addConstraint(lambda x, y: x != y, [a, b])
p.addConstraint(lambda d: d == "red", ["Dhaka"])          # unary constraint
print(p.getSolution())

সত্যিকারের run:

~/bazarghar-csp$ python map_pc.py
2 colours: 0 solutions
3 colours: 6 solutions
4 colours: 528 solutions
python-constraint2 2.9.0
{'Dhaka': 'red', 'Mymensingh': 'blue', 'Rajshahi': 'green', 'Barishal': 'green', 'Chattogram': 'blue', 'Khulna': 'blue', 'Sylhet': 'green', 'Rangpur': 'red'}

দুই রঙে ০টি সমাধান (অংশ ৪-এর প্রমাণের সঙ্গে মেলে), তিন রঙে ঠিক ৬টি। ৬ = 3!, মানে তিনটি রঙের নাম অদলবদলের বাইরে আসলে একটিই রং-বিন্যাস: ঢাকা আর রংপুর এক রঙে, চক্রের ছয়টি বিভাগ পালা করে বাকি দুই রঙে। চার রঙে ৫২৮টি। শেষ লাইনে ঢাকা = red একটি unary constraint হিসেবে যোগ করা হয়েছে, আর solver একটি সমাধান দিয়েছে।

বাস্তবে কী চলে, সতর্কভাবে। সময়সূচি, রুটিন আর রসদ-পরিকল্পনায় constraint programming, SAT-ভিত্তিক solver আর mixed-integer programming (MIP) ব্যবহারের কথা ব্যাপকভাবে প্রকাশিত; OR-Tools ছাড়াও বাণিজ্যিক ও open source অনেক solver আছে, আর MiniZinc ভাষা দিয়ে অনেকগুলো একসঙ্গে পরখ করা যায়। কোন প্রতিষ্ঠান কোনটি ব্যবহার করে, তা সাধারণত প্রকাশ্য নয়, তাই কোনো একটিকে “industry standard” বলা কঠিন। CP-SAT কয়েক বছর ধরে MiniZinc Challenge-এর মতো প্রতিযোগিতায় ভালো ফল করেছে বলে প্রকাশিত ফলাফলে দেখা যায়, তবে নিজের সমস্যায় পরখ করাই নিরাপদ।

১০ · AI-এর যুগে — AI সহকারী দিয়ে রুটিনের model লেখা

“আমার দোকানের শিফট রুটিন বানাও” বললে AI সহকারী দ্রুত একটি OR-Tools বা python-constraint কোড দেবে। কিন্তু constraint-এর কোডে ভুল প্রায়ই নীরব: solver যা বলা হয়েছে তা-ই নিখুঁতভাবে সমাধান করে, অথচ যা বলা হয়েছে তা-ই ভুল। আর ভাষার মডেল সরাসরি “মাথায়” এমন ধাঁধা সমাধান করতে গেলে নিয়ম-ভাঙা উত্তরও আত্মবিশ্বাসের সঙ্গে দিতে পারে। ভালো ভাগাভাগি: AI সহকারী model লিখতে সাহায্য করুক, solver সমাধান করুক, আর আপনার যাচাই-কোড প্রতিটি নিয়ম আলাদা পরীক্ষা করুক। AI সহকারীরা এ ধরনের কাজে প্রায়ই যা করে:

  • একটি নিয়ম বাদ দেওয়া বা উল্টে লেখা: “সন্ধ্যার পরদিন সকালে নয়” বাদ, বা <=-এর জায়গায় >=; solver তখন খুশিমনে নিয়ম-ভাঙা রুটিন “OPTIMAL” বলবে।
  • পুরোনো বা বানানো API: OR-Tools-এর পুরোনো CamelCase নাম (NewBoolVar, Solve) আর নতুন snake_case নাম (new_bool_var, solve) মিশিয়ে ফেলা, বা অস্তিত্বহীন method বানানো; python-constraint আর python-constraint2 প্যাকেজ গুলিয়ে ফেলা।
  • status না দেখে ফল পড়া: INFEASIBLE বা সময় ফুরিয়ে যাওয়া অবস্থায় variable-এর মান ছাপানো, যেমন অংশ ৯-এর প্রথম run-এর আবর্জনা সংখ্যা।
  • FEASIBLE-কে OPTIMAL বলা: সময়-সীমায় থামা solver-এর ফলকে objective আর bound-এর ফাঁক না দেখে “সেরা রুটিন” বলা।
  • জরিমানার ওজন চুপচাপ বসানো: কার পছন্দ কত দামি, তা মূল্যবোধের সিদ্ধান্ত, প্রশ্ন না করে মেনে নেওয়ার নয়।

প্রম্পটে version, প্রতিটি নিয়ম সংখ্যায়, আর যাচাইয়ের দাবি নিজেই লিখে দিন। নিচের প্রম্পটে আসল কর্মীর নাম বা ব্যক্তিগত তথ্য নেই।

আমি Python 3.14 আর ortools 9.15 ব্যবহার করছি। একটি দোকানের সাপ্তাহিক রুটিন CP-SAT দিয়ে model করো। বানানো ডেটা: ৫ জন কর্মী (A, B, C, D, E), ৭ দিন, দিনে ২টি শিফট (সকাল, সন্ধ্যা)। কঠিন নিয়ম: প্রতি শিফটে ঠিক ২ জন; A, B, C সিনিয়র, প্রতি শিফটে অন্তত ১ জন সিনিয়র; দিনে একজনের সর্বোচ্চ ১টি শিফট; সপ্তাহে সর্বোচ্চ ৬টি; সন্ধ্যার পরদিন সকালে নয়। নরম পছন্দ: D সন্ধ্যা চান না (প্রতিটি ২ জরিমানা)। ১. প্রতিটি নিয়মের পাশে একটি comment দাও, যাতে নিয়ম আর কোডের লাইন মেলানো যায়। ২. solve-এর পর status দেখো; OPTIMAL বা FEASIBLE না হলে কোনো মান ছাপাবে না। ৩. solver ছাড়া আলাদা একটি সাধারণ Python function লেখো, যা ফলের রুটিনে প্রতিটি কঠিন নিয়ম একে একে যাচাই করে, ভাঙলে কোন নিয়ম কোথায় তা ছাপায়। ৪. শুধু আমার version-এর অফিসিয়াল OR-Tools docs-এ আছে এমন method ব্যবহার করো; কোনো method নিয়ে নিশ্চিত না হলে বানিয়ে না লিখে সরাসরি বলো। ৫. সমস্যাটি অসম্ভব হলে কোন নিয়মগুলো একসঙ্গে বিরোধ করছে, তা হাতে হিসাব করে বোঝাও।

AI-এর লেখা constraint model হাতে পেলে — যাচাইয়ের চেকলিস্ট

পরীক্ষা ও ইন্টারভিউতে: বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এ ধরনের প্রশ্ন প্রায়ই আসে।

  • CSP-র তিনটি অংশ কী? Variable, প্রতিটির domain, আর অনুমোদিত মিশ্রণের নিয়ম (constraint); সমাধান = complete আর consistent assignment.
  • MRV আর LCV-এর পার্থক্য? MRV ঠিক করে কোন variable আগে (সবচেয়ে কম মান যার, fail-first); LCV ঠিক করে সেই variable-এর কোন মান আগে (প্রতিবেশীদের সবচেয়ে কম মান মোছে যেটি)।
  • Forward checking কী ধরে না, যা AC-3 ধরে? দুটি খালি variable-এর মধ্যের বিরোধ, যেমন পাশাপাশি দুই বিভাগের domain দুটিই {B}.
  • AC-3-এর খারাপতম সময়? O(e·d3): e-টি constraint, d domain-এর আকার; প্রতিটি arc বড়জোর d বার ফেরে, প্রতি revise-এ d2 জোড়া।
  • Min-conflicts কখন? বড়, সমাধান-ঘন সমস্যায় দ্রুত; কিন্তু অসম্পূর্ণ, “সমাধান নেই” প্রমাণ করতে পারে না।

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

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১একটি Sudoku-তে উত্তর একটিমাত্র রাখতে কমপক্ষে কয়টি clue লাগে, আর এই প্রশ্নের উত্তর কীভাবে প্রমাণ হলো?

অংশ ৮-এর কঠিন ধাঁধায় ২৫টি clue ছিল, আর তার কোনো একটি মুছলে উত্তর একাধিক হয়ে যায়। কিন্তু এটি শুধু এই ধাঁধার সীমা। বহু বছর ধরে উৎসাহীরা ১৭টি clue-র হাজার হাজার বৈধ ধাঁধা খুঁজে পেয়েছিলেন, কিন্তু ১৬টির একটিও নয়। প্রশ্নটি ছিল: ১৬টির ধাঁধা কি আদৌ নেই, নাকি এখনো খুঁজে পাওয়া যায়নি?

২০১২ সালে Gary McGuire, Bastian Tugemann আর Gilles Civario একটি প্রবন্ধে কম্পিউটার-নির্ভর একটি প্রমাণ প্রকাশ করেন যে ১৬ clue-র কোনো বৈধ Sudoku নেই, তাই সর্বনিম্ন সংখ্যা ১৭. তাঁদের পদ্ধতির মূল ভাবনা ছিল উল্টো দিক থেকে খোঁজা। প্রতিটি সম্ভাব্য পূর্ণ গ্রিড (প্রতিসাম্য বাদ দিলে প্রায় ৫৫০ কোটি আলাদা গ্রিড) নিয়ে প্রশ্ন: এমন ১৬টি ঘর কি বাছা যায়, যা অন্য সব বিকল্প গ্রিডকে বাদ দেয়? প্রতিটি গ্রিডে “unavoidable set” নামের কিছু ঘরের দল থাকে, যেগুলোর অন্তত একটিতে clue না থাকলে উত্তর দুটি হয়ে যায়। তাই প্রশ্নটি হয়ে দাঁড়ায় একটি hitting set সমস্যা: ১৬টি ঘর দিয়ে সব unavoidable set-কে ছোঁয়া যায় কি না। প্রকাশিত বিবরণ অনুযায়ী হিসাবটি চালাতে একটি supercomputer-এ কয়েক মিলিয়ন CPU-ঘণ্টা লেগেছিল।

এখানে CSP-র কয়েকটি শিক্ষা আছে। এক, সমস্যাকে কীভাবে লেখা হয়, তাতে খরচ বিশাল বদলায়: সব ১৬-ঘরের ধাঁধা গোনা অসম্ভব ছিল, কিন্তু “গ্রিড ধরে hitting set” লেখায় তা নাগালে আসে। দুই, প্রতিসাম্য (সারি-কলাম অদলবদল, অঙ্কের নাম বদল) বাদ দেওয়া বড় সমস্যার একটি মৌলিক কৌশল, যাকে symmetry breaking বলে; অংশ ৯-এর মানচিত্রের ৬টি সমাধানও আসলে একটিই, শুধু রঙের নাম বদল। তিন, কম্পিউটার-নির্ভর প্রমাণে কোডের ভুলের ঝুঁকি থাকে, তাই লেখকেরা আলাদা পরীক্ষা দিয়ে নিজেদের কোড যাচাই করেছিলেন।

মূল উপলব্ধি: সঠিক formulation আর প্রতিসাম্য বাদ দেওয়া এমন একটি search-কে সম্ভব করেছে, যা অন্ধ গণনায় কখনো শেষ হতো না; সঙ্গে দেখিয়েছে কম্পিউটার-নির্ভর প্রমাণের নিজস্ব যাচাইয়ের দায়।

প্র ০২যেকোনো মানচিত্র চার রঙে রং করা যায় — এই উপপাদ্যটি কম্পিউটার দিয়ে প্রমাণ হয়েছিল। তাহলে অংশ ৪-এ তিন রঙের প্রশ্নটি কেন আলাদা আর কঠিন?

Four colour theorem বলে, সমতলে আঁকা যেকোনো মানচিত্রে (যেখানে প্রতিটি অঞ্চল একটানা আর শুধু বিন্দুতে ছোঁয়াকে “পাশাপাশি” ধরা হয় না) চারটি রং যথেষ্ট। প্রশ্নটি উনিশ শতকের মাঝামাঝি Francis Guthrie তোলেন বলে প্রচলিত। প্রায় সোয়াশো বছর পর ১৯৭৬ সালে Kenneth Appel আর Wolfgang Haken কম্পিউটারের সাহায্যে প্রমাণ দেন: তাঁরা দেখান যে প্রতিটি মানচিত্রে প্রায় দুই হাজার “অনিবার্য বিন্যাসের” একটি থাকে, আর প্রতিটি বিন্যাস ছোট করা যায়; সেই হাজারো ক্ষেত্র কম্পিউটারে যাচাই করা হয়। এটিই সম্ভবত প্রথম বড় উপপাদ্য, যার প্রমাণ মানুষ হাতে পুরোটা পরীক্ষা করতে পারেননি, আর গণিতবিদদের মধ্যে “এটি কি সত্যিকারের প্রমাণ” নিয়ে বিতর্ক হয়েছিল। পরে Robertson, Sanders, Seymour আর Thomas সরল একটি প্রমাণ দেন, আর ২০০৫ সালের দিকে Georges Gonthier পুরো প্রমাণটি Coq নামের proof assistant-এ যন্ত্র-যাচাই করেন।

কিন্তু উপপাদ্যটি বলে চারটি রং সবসময় যথেষ্ট; একটি নির্দিষ্ট মানচিত্র তিন রঙে হয় কি না, সে বিষয়ে কিছু বলে না। আর এই দ্বিতীয় প্রশ্নটি, planar graph-এর জন্যও, NP-complete বলে প্রমাণিত। মানে উপপাদ্য জানা থাকলেও “তিনে হয় কি না” প্রশ্নের জন্য সব ক্ষেত্রে দ্রুত পদ্ধতি জানা নেই। ছোট মানচিত্রে এটি দ্রুত (অংশ ৯), কিন্তু বড় কঠিন বিন্যাসে search বিস্ফোরিত হতে পারে।

আরও একটি সূক্ষ্মতা: চার রঙের উপপাদ্য অস্তিত্বের প্রমাণ দেয়, আর তার থেকে quadratic সময়ের একটি অ্যালগরিদমও আসে, কিন্তু বাস্তবে অনেকে DSatur-এর মতো সরল heuristic (Brélaz, ১৯৭৯; মূল ভাবনা অংশ ৬-এর MRV আর degree-র কাছাকাছি) বা constraint solver ব্যবহার করেন, যা সব সময় সর্বনিম্ন রং দেয় না কিন্তু দ্রুত।

মূল উপলব্ধি: “সব মানচিত্রে চার রং যথেষ্ট” প্রমাণ করা আর “এই মানচিত্রে তিন রং যথেষ্ট কি না” বের করা দুটি আলাদা প্রশ্ন; প্রথমটির উত্তর জানা, দ্বিতীয়টি সাধারণভাবে কঠিন, আর CSP-র কৌশল সেখানেই কাজে লাগে।

প্র ০৩Min-conflicts বড় n-queens-এ এত দ্রুত কেন, অথচ ছোট বোর্ডে আটকে যায়? এ থেকে local search সম্পর্কে কী শেখা যায়?

১৯৯০ সালের দিকে Steven Minton, Mark Johnston, Andrew Philips আর Philip Laird একটি গবেষণায় দেখান যে min-conflicts heuristic দিয়ে দশ লাখ রানির বোর্ডও অল্প সময়ে সমাধান করা যায়, যা সে সময়ের backtracking-ভিত্তিক পদ্ধতির নাগালের বাইরে ছিল। তাঁদের অনুপ্রেরণা ছিল একটি বাস্তব সমস্যা: Hubble Space Telescope-এর পর্যবেক্ষণের সূচি বানানোর একটি পদ্ধতিতে (Johnston ও সহকর্মীদের কাজ) একটি neural network-ভিত্তিক মেরামত কৌশল ভালো ফল দিচ্ছিল, আর তাঁরা দেখান যে এর মূল কার্যকারিতা আসলে এই সরল “সবচেয়ে কম বিরোধের মান বেছে নেওয়া” নিয়ম থেকেই আসে।

দ্রুত কেন? বড় n-queens-এ সমাধান অসংখ্য আর বোর্ড জুড়ে ছড়ানো। একটি এলোমেলো পূর্ণ বিন্যাস থেকে শুরু করলে বেশিরভাগ রানি আগেই ঠিক জায়গায় বা তার কাছাকাছি থাকে; কয়েকটি বিরোধ মেরামত করলেই হয়। অংশ ৮-এর ডেস্কটপ run-এ দশ হাজার রানিতে ধাপের সংখ্যা ছিল মোটামুটি n-এর ক্রমে (৬,২৩৬)। Backtracking-এর সমস্যা উল্টো: সে প্রথম সারি থেকে ক্রম ধরে সিদ্ধান্ত নেয়, আর শুরুর দিকের একটি খারাপ সিদ্ধান্ত বহু পরে ধরা পড়ে, তখন বিশাল একটি গাছ ঘুরে আসতে হয়।

আটকে যায় কেন? Local search শুধু “আশেপাশের” বিন্যাস দেখে। কখনো এমন অবস্থা আসে যেখানে কোনো একটি রানি সরালে বিরোধ কমে না; এটি একটি local minimum বা সমতল মালভূমি। ছোট বোর্ডে সমাধান কম, এমন ফাঁদও তুলনায় বেশি। প্রতিকার হিসেবে random restart, সমান মানের মধ্যে এলোমেলো বাছাই (কোডে rng.choice), বা সাম্প্রতিক নড়াচড়া নিষিদ্ধ রাখা (tabu search) ব্যবহার হয়; এসব পর্ব ৪৪-এর বিষয়।

বাস্তবের শিক্ষা: একটি সূচি আগে থেকেই আছে আর ছোট পরিবর্তন এসেছে (একজন কর্মী অসুস্থ), সেখানে বর্তমান সূচি “মেরামত” করা প্রায়ই ভালো, কারণ মানুষ কম পরিবর্তন চায়।

মূল উপলব্ধি: সমাধান ঘন আর ছড়ানো হলে পূর্ণ বিন্যাস থেকে মেরামত অবিশ্বাস্য দ্রুত, কিন্তু সম্পূর্ণতা আর প্রমাণের ক্ষমতা হারাতে হয়; কোন সমস্যায় কোন পথ, তা এই দামের হিসাব।

প্র ০৪CSP, SAT আর integer programming — তিনটি আলাদা জগৎ, নাকি একই সমস্যার তিন ভাষা? CP-SAT নামটি কী বোঝায়?

তিনটিই “নিয়ম মেনে মান খোঁজা”, কিন্তু ভাষা আর কৌশল ভিন্ন। SAT (Boolean satisfiability) প্রশ্নে সব variable সত্য/মিথ্যা, আর নিয়ম হলো “অথবা” দিয়ে জোড়া clause. ১৯৭১ সালে Stephen Cook দেখান যে SAT NP-complete, আর তখন থেকে এটি জটিলতা-তত্ত্বের কেন্দ্রে। অনেকে ভাবতেন বাস্তবে SAT হতাশাজনক হবে, কিন্তু ১৯৯০-এর দশকের শেষ থেকে conflict-driven clause learning (CDCL) পদ্ধতি (Marques-Silva ও Sakallah-এর GRASP, পরে Chaff নামের solver) বিপ্লব আনে: প্রতিটি ব্যর্থতা থেকে একটি নতুন clause “শেখা” হয়, যাতে একই ভুল আর না হয়। তখন থেকে লক্ষ লক্ষ variable-এর শিল্প-সমস্যা, যেমন চিপের নকশা যাচাই, প্রায়ই সমাধান করা যায়।

Integer programming (MIP) সমস্যাকে লেখে রৈখিক অসমতা আর একটি লক্ষ্য-রাশি দিয়ে, আর linear programming-এর শিথিল রূপ থেকে সীমা (bound) পেয়ে branch-and-bound চালায় (DSA-র branch and bound পর্ব)।

Constraint programming (CP) সবচেয়ে কাছাকাছি এই পর্বের ভাষার: বড় domain-এর integer variable, all-different-এর মতো global constraint আর তাদের বিশেষ propagator, আর search-এর heuristic. CP-SAT নামটিই বলে দেয় এটি মিশ্রণ: model লেখা হয় CP-র ভাষায় (integer variable, all-different, interval), কিন্তু ভেতরে SAT-এর clause learning চলে, যাকে lazy clause generation বলা হয়, সঙ্গে linear relaxation আর একাধিক কৌশল সমান্তরালে। অংশ ৯-এর রুটিনে solver objective-এর সঙ্গে একটি bound-ও ছাপিয়েছে; ওই bound প্রমাণ করে এর চেয়ে ভালো সমাধান নেই।

তাই উত্তর হলো: গাণিতিকভাবে একটি প্রায়ই অন্যটিতে রূপান্তর করা যায় (Sudoku-কে SAT-এ লেখা একটি জনপ্রিয় অনুশীলন), কিন্তু কোন ভাষায় লিখলে solver সমস্যার গঠন বেশি “দেখতে” পায়, তার ওপর গতি বিশালভাবে নির্ভর করে। কোন সমস্যায় কোন পথ সেরা, তা নিয়ে বিশেষজ্ঞদেরও মতভেদ আছে।

মূল উপলব্ধি: CSP, SAT আর MIP একই পরিবারের তিন ভাষা; আধুনিক solver-এর শক্তি এসেছে তাদের কৌশল মেলানো থেকে, আর ভালো modelling-এর দক্ষতা solver বাছাইয়ের মতোই গুরুত্বপূর্ণ।

প্র ০৫Large language model কি Sudoku বা রুটিনের মতো constraint সমস্যায় solver-কে প্রতিস্থাপন করবে? Symbolic আর neural পথের এই বিতর্কে CSP কোথায় দাঁড়ায়?

CSP হলো symbolic AI-এর একটি পরিষ্কার উদাহরণ (পর্ব ৩-এর নিয়মভিত্তিক পথ): নিয়ম স্পষ্ট লেখা, আর অ্যালগরিদম নিশ্চয়তা দেয় যে উত্তর সব নিয়ম মানে, অথবা প্রমাণ করে যে উত্তর নেই। Large language model (পর্ব ৭০-এ বিস্তারিত) কাজ করে সম্পূর্ণ ভিন্নভাবে: বিপুল লেখা থেকে শেখা পরিসংখ্যানের ভিত্তিতে পরের token-এর অনুমান। এ ধরনের মডেল নিয়মের বর্ণনা বুঝে কোড লিখতে প্রায়ই ভালো, কিন্তু সরাসরি একটি কঠিন Sudoku “মাথায়” সমাধান করতে গেলে নিয়ম-ভাঙা উত্তর দিতে পারে, আর তা আত্মবিশ্বাসের সঙ্গে। বিভিন্ন গবেষণা ও পরীক্ষায় এমন দুর্বলতা বারবার রিপোর্ট হয়েছে, যদিও নতুন মডেল আর “ভেবে উত্তর দেওয়ার” কৌশলে ফল দ্রুত বদলাচ্ছে, তাই কোনো নির্দিষ্ট সংখ্যা এখানে দেওয়া নিরাপদ নয়।

মূল পার্থক্য নিশ্চয়তায়। Solver-এর OPTIMAL বা INFEASIBLE একটি প্রমাণসহ দাবি (solver-এর কোড ঠিক থাকলে); ভাষার মডেলের উত্তর একটি সম্ভাব্য অনুমান। কর্মীর রুটিন, হাসপাতালের শিফট বা পরীক্ষার সময়সূচিতে একটি নিয়ম ভাঙা মানে বাস্তব ক্ষতি। আবার উল্টো দিকও সত্য: solver নিজে জানে না নিয়মগুলো কী হওয়া উচিত; “Chumki শুক্রবার ছুটি চান” কথাটি কাগজ থেকে model-এ আনা, অস্পষ্ট নিয়ম পরিষ্কার করা, ব্যবহারকারীর সঙ্গে কথা বলা, এসব জায়গায় ভাষার মডেল সত্যিই সাহায্য করতে পারে।

তাই অনেক গবেষক আর প্রকৌশলী একটি মিশ্র পথ দেখেন, যাকে neuro-symbolic বলা হয়: ভাষার মডেল প্রাকৃতিক ভাষা থেকে model বা কোড লেখে, solver সমাধান করে, আর একটি স্বাধীন যাচাই-কোড ফল পরীক্ষা করে; ভুল হলে মডেলকে error দেখিয়ে আবার চেষ্টা। কোনটি শেষ পর্যন্ত প্রাধান্য পাবে, সে বিষয়ে সম্প্রদায়ে মতভেদ আছে; এ নিয়ে নিশ্চিত ভবিষ্যদ্বাণী করা যায় না।

মূল উপলব্ধি: নিয়ম লেখায় আর মানুষের সঙ্গে কথায় ভাষার মডেল, নিশ্চয়তাসহ সমাধানে solver, আর দুটির মাঝে স্বাধীন যাচাই; CSP-র স্পষ্ট নিয়ম আর প্রমাণের ক্ষমতা AI-এর যুগেও তাই গুরুত্ব হারায়নি।

অনুশীলন

প্রথমটি হাতে trace-এর, দ্বিতীয়টি আউটপুট অনুমানের, তৃতীয়টি নকশা ও সমালোচনার, শেষটি কোড লিখে সমাধান গোনার।

  1. হাতে হিসাব: তিনটি variable X, Y, Z, প্রতিটির domain {1, 2, 3}; নিয়ম X < Y আর Y < Z. (ক) Generate-and-test-এ কয়টি complete assignment পরখ করতে হবে? (খ) AC-3 চালান (queue-র শুরু: X→Y, Y→X, Y→Z, Z→Y). প্রতিটি revise-এ কী মুছল, আর শেষ domain কী? (গ) এরপর search লাগবে কি?

    (ক) 33 = ২৭টি।

    (খ) X→Y: X = 3-এর কোনো বড় Y নেই, X = {1, 2}. Y→X: Y = 1-এর কোনো ছোট X নেই, Y = {2, 3}; Y বদলাল, তাই Z→Y যোগ হওয়ার কথা, কিন্তু সেটি আগেই queue-তে। Y→Z: Y = 3-এর কোনো বড় Z নেই, Y = {2}; X→Y আবার queue-তে। Z→Y: Z-এর 1 আর 2 Y = 2-এর চেয়ে বড় নয়, Z = {3}. X→Y: X = 2-এর জন্য Y-তে বড় কিছু নেই (Y শুধু 2), X = {1}. শেষ: X = {1}, Y = {2}, Z = {3}, মোট ৫টি revise.

    (গ) না; প্রতিটি domain-এ একটি মান, আর সেগুলো সব নিয়ম মানে, তাই এটিই একমাত্র সমাধান।

  2. আউটপুট অনুমান করুন: তিনটি variable, সবাই পরস্পরের প্রতিবেশী (একটি ত্রিভুজ, নিয়ম ≠)। চালানোর আগে দুই লাইনের আউটপুট লিখুন, তারপর যেকোনো ব্লকে বসিয়ে মিলিয়ে দেখুন।
    dom = {"A": {1, 2, 3}, "B": {1, 2, 3}, "C": {1, 2, 3}}
    nb = {"A": ["B", "C"], "B": ["A", "C"], "C": ["A", "B"]}
    asg = {"A": 2}
    for n in nb["A"]:
        dom[n].discard(asg["A"])
    print(dom["B"], dom["C"])
    asg["B"] = min(dom["B"])
    dom["C"].discard(asg["B"])
    print(asg, dom["C"], len(dom["C"]) == 0)
    {1, 3} {1, 3}
    {'A': 2, 'B': 1} {3} False

    A = 2 বসানোর পর forward checking দুই প্রতিবেশী থেকে 2 মোছে। তারপর B-কে domain-এর সবচেয়ে ছোট মান 1 দেওয়া হলো, আর C থেকে 1 মুছল; C-তে 3 বাকি, তাই domain খালি নয় (False). পরের ধাপে C = 3 বসলেই সমাধান: A = 2, B = 1, C = 3 (লেখকের যন্ত্রে Python 3.14 দিয়ে মেলানো)।

  3. নকশা ও সমালোচনা: একটি AI সহকারী পাঁচটি পরীক্ষার সময়সূচির জন্য নিচের model লিখেছে, আর বলেছে “সব নিয়ম মানা হয়েছে, এটি সেরা সূচি”। নিয়ম ছিল: যে দুই পরীক্ষায় একই ছাত্রছাত্রী, তারা একই দিনে নয়; Math অবশ্যই AI-এর পরে; দিন ১ থেকে ৪. অন্তত চারটি সমস্যা চিহ্নিত করুন আর ভালো নকশা বলুন।
    m = cp_model.CpModel()
    day = {e: m.NewIntVar(1, 5, e) for e in exams}
    for a, b in clashes:
        m.Add(day[a] != day[b])
    m.Add(day["AI"] >= day["MATH"])
    solver = cp_model.CpSolver()
    solver.parameters.max_time_in_seconds = 1
    solver.Solve(m)
    print({e: solver.Value(day[e]) for e in exams})   # "best timetable"
    • Domain ভুল: দিন ১ থেকে ৪, অথচ NewIntVar(1, 5, …); পঞ্চম দিনে পরীক্ষা বসে যেতে পারে।
    • নিয়ম উল্টো আর দুর্বল: “Math, AI-এর পরে” মানে day["AI"] < day["MATH"]; লেখা হয়েছে >=, যা উল্টো, আর সমান দিনও মেনে নেয়।
    • Status দেখা হয়নি: Solve-এর ফল ফেলে দিয়ে সরাসরি মান ছাপানো; INFEASIBLE বা সময় ফুরালে সংখ্যাগুলো অর্থহীন।
    • “সেরা” দাবির ভিত্তি নেই: কোনো objective নেই, তাই solver যেকোনো একটি সমাধান দেয়; সেরার মাপকাঠিই (যেমন একই ছাত্রের পরপর দুই দিনে পরীক্ষা কম) বলা হয়নি।
    • যাচাই নেই আর API মিশ্র: ফল আলাদা কোডে পরীক্ষা করা হয়নি; পুরোনো CamelCase নাম চলে, কিন্তু নিজের version-এর docs দেখে এক ধরনের নাম রাখা ভালো।

    ভালো নকশা: new_int_var(1, 4, e); add(day["AI"] < day["MATH"]); প্রয়োজনে all-different-এর বদলে শুধু clash-জোড়ায় ≠; একটি স্পষ্ট objective (যেমন একই ছাত্রের পরপর দিনে পরীক্ষার সংখ্যা কম); status = solver.solve(m) দেখে OPTIMAL বা FEASIBLE হলে তবেই ফল; আর একটি সরল Python function, যা প্রতিটি clash আর ক্রমের নিয়ম ফলের সূচিতে আলাদা করে পরীক্ষা করে।

  4. কোড লিখে সমাধান গোনা: n-queens-এর সব সমাধান গোনার একটি backtracking function লিখুন (প্রতি সারিতে একটি রানি), আর n = 4, 6, 8-এর জন্য সমাধানের সংখ্যা আর মোট বসানো রানির সংখ্যা ছাপান। MRV কি এখানে সাহায্য করবে?
    def count_solutions(n):
        placed = 0
        def go(row, cols):
            nonlocal placed
            if row == n:
                return 1
            total = 0
            for c in range(n):
                if all(c != c2 and abs(c - c2) != row - r2 for r2, c2 in enumerate(cols)):
                    placed += 1
                    total += go(row + 1, cols + [c])
            return total
        sols = go(0, [])
        return sols, placed
    
    for n in (4, 6, 8):
        print(n, count_solutions(n))

    লেখকের যন্ত্রে সত্যিকারের run (Python 3.14.4): 4 (2, 16), 6 (4, 152), 8 (92, 2056). মানে 8-queens-এর ৯২টি সমাধান, যা সুপরিচিত সংখ্যা (প্রতিসাম্য বাদ দিলে ১২টি মৌলিক)। সব সমাধান গুনতে হলে প্রতিটি শাখা দেখতেই হয়, তাই LCV-এর মতো মানের ক্রম কোনো লাভ দেয় না; তবে MRV আর forward checking মৃত শাখা আগে কাটে বলে বড় n-এ বসানোর সংখ্যা কমাতে পারে।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি CSP সমাধানের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 📝 Variable, domain আর constraint লেখা
  2. ✂️ Unary constraint দিয়ে domain ছাঁটা
  3. 🌊 Search-এর আগে AC-3 দিয়ে arc consistency
  4. 🎯 MRV (সমান হলে degree) দিয়ে পরের variable বাছা
  5. 🔢 LCV অনুযায়ী মান সাজিয়ে একটি বসানো, forward checking
  6. ↩️ কোনো domain খালি হলে মান তুলে নিয়ে পরেরটি, নইলে পরের variable

৮টি অঞ্চলের একটি মানচিত্র ৩টি রঙে রং করতে হবে। Generate-and-test পদ্ধতিতে খারাপতম ক্ষেত্রে কয়টি complete assignment পরখ করতে হতে পারে?

  • 24
  • 6,561
  • 512
  • 40,320

Forward checking চলছে। তিনটি খালি variable-এর বর্তমান domain: A = {1, 2, 3}, B = {2, 3}, C = {3}. MRV heuristic পরের কোন variable বাছবে?

  • A
  • B
  • C
  • degree না জেনে বলা যায় না

দুটি variable X আর Y, দুজনেরই domain {1, 2, 3}, নিয়ম X < Y. AC-3 চালানোর পর domain কী হবে?

  • X = {1, 2}, Y = {2, 3}
  • X = {1}, Y = {2}
  • কিছুই বদলায় না
  • X = {1, 2}, Y = {1, 2, 3}

একটি মানচিত্রে পাশাপাশি দুটি খালি অঞ্চল P আর Q, দুজনেরই domain এখন {নীল}. কোন পদ্ধতি এই মুহূর্তে, নতুন কোনো মান না বসিয়েই, বুঝবে যে এই শাখায় সমাধান নেই?

  • সাধারণ backtracking
  • Forward checking
  • MRV heuristic
  • Arc consistency (AC-3)

Min-conflicts local search সম্পর্কে কোন বাক্যটি সঠিক?

  • এটি complete, তাই সমাধান না থাকলে তা প্রমাণ করে
  • বড় n-queens-এ খুব দ্রুত, কিন্তু অসম্পূর্ণ; local minimum-এ আটকে যেতে পারে
  • এটি ফাঁকা বোর্ড থেকে একটি একটি রানি বসায়
  • এটি সবসময় সর্বোত্তম সমাধানের নিশ্চয়তা দেয়

৯ × ৯ Sudoku-র প্রতিটি ঘরের সারি, কলাম আর ৩ × ৩ বাক্স মিলিয়ে ২০টি peer. সব all-different নিয়ম জোড়া-জোড়া ≠ হিসেবে লিখলে মোট কয়টি আলাদা binary constraint হবে?

  • 27
  • 1,620
  • 810
  • 972

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. CSP = variable + domain + constraint; সমাধান একটি complete ও consistent assignment. Constraint unary, binary বা global (all-different); binary নিয়মগুলো constraint graph-এর রেখা।
  2. Generate-and-test dn-এ বিস্ফোরিত হয় (৪৯ ঘরের Sudoku-তে 949); backtracking আংশিক assignment-এই নিয়ম দেখে পুরো শাখা কাটে, আর সমাধান না থাকলে তা প্রমাণও করে।
  3. MRV আর degree ঠিক করে কোন variable আগে, LCV ঠিক করে কোন মান আগে: কঠিন variable আগে, সহজ মান আগে।
  4. Forward checking প্রতিবেশী ছাঁটে, AC-3 পুরো network-এ ঢেউ ছড়ায় (Mackworth ১৯৭৭, O(e·d3)); MAC প্রতিটি ধাপে AC-3 চালায়: কম backtrack, বেশি check.
  5. বড় সমস্যায় min-conflicts দ্রুত কিন্তু অসম্পূর্ণ; বাস্তবে CP-SAT-এর মতো solver, যার status দেখে আর স্বাধীন যাচাই-কোড দিয়ে ফল মিলিয়ে তবেই বিশ্বাস।

পরের পর্বে: Local search-এর পুরো পরিবার: hill climbing আর তার ফাঁদ, random restart, simulated annealing, local beam search আর genetic algorithm; যেখানে পথ নয়, শুধু ভালো একটি অবস্থা খোঁজা হয়।

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

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান