পাঠ ৪৬ · ৫৭-এর মধ্যে · মডিউল ৯
Home / Courses / Numerical Methods / অপ্টিমাইজেশন

লিনিয়ার প্রোগ্রামিং — সিমপ্লেক্স মেথড

Linear programming — the simplex method
১০ মিনিট পড়া মধ্যম-কঠিন · Intermediate-Advanced Python কোডসহ সম্পূর্ণ বাংলায়

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

  • লিনিয়ার প্রোগ্রামিং সমস্যা কী, এবং কেন এর সমাধান সবসময় একটি "কর্নার পয়েন্টে" পাওয়া যায়
  • সিমপ্লেক্স ট্যাবলো কীভাবে সেট আপ হয়, এবং প্রতি ধাপে কী আপডেট হয়
  • একটি সত্যিকারের, চলমান ডেমো — একটি ছোট LP সমস্যা ধাপে ধাপে সিমপ্লেক্স দিয়ে সমাধান করা
  • কেন সিমপ্লেক্স মেথড এই কোর্সে শুধু পরিচিতিমূলক স্তরে দেখানো হচ্ছে, এবং বাস্তব LP সফটওয়্যার কী করে

১ · লিনিয়ার প্রোগ্রামিং সমস্যা

লিনিয়ার প্রোগ্রামিংLinear Programming (LP)একটি লিনিয়ার লক্ষ্য-ফাংশন সর্বোচ্চ বা সর্বনিম্ন করার সমস্যা, যেখানে চলকগুলোর উপর একাধিক লিনিয়ার অসমতা/সমতা কনস্ট্রেইন্ট থাকে। সমস্যায় আমরা একটি লিনিয়ার লক্ষ্য-ফাংশন (যেমন Z = 3x + 5y) সর্বোচ্চ বা সর্বনিম্ন করতে চাই, যেখানে x, y-এর উপর একাধিক লিনিয়ার অসমতা কনস্ট্রেইন্ট আছে (যেমন 3x + 2y ≤ 18)। L45-এর লাগ্রাঞ্জ মাল্টিপ্লায়ার মূলত সমতা কনস্ট্রেইন্টের জন্য ডিজাইন করা — LP-তে কনস্ট্রেইন্ট অসমতা, যার মানে সম্ভাব্য সমাধানের সেট (feasible region) একটি বহুভুজাকার (polygon/polytope) অঞ্চল, একটি একক রেখা নয়।

ফিজিবল রিজিয়ন
সব কনস্ট্রেইন্ট একসাথে সন্তুষ্টকারী বিন্দুর সেট একটি বহুভুজাকার অঞ্চল তৈরি করে — এই অঞ্চলের প্রান্তবিন্দু (কর্নার/vertex) গুলোই সম্ভাব্য অপ্টিমাল সমাধান।
কর্নার-পয়েন্ট থিওরেম
একটি বদ্ধ, বাউন্ডেড ফিজিবল রিজিয়নে, লিনিয়ার লক্ষ্য-ফাংশনের অপ্টিমাম সবসময় একটি কর্নার পয়েন্টে (বা একাধিক কর্নার পয়েন্টে) পাওয়া যায় — কখনো অঞ্চলের "ভেতরে" নয়।
সিমপ্লেক্স ট্যাবলো
সিমপ্লেক্স মেথড একটি কর্নার পয়েন্ট থেকে শুরু করে, প্রতি ধাপে একটি প্রতিবেশী কর্নার পয়েন্টে সরে যায় যা লক্ষ্য-ফাংশনকে আরও ভালো করে — যতক্ষণ না আর কোনো উন্নতি সম্ভব না হয়।

২ · ডেমো — একটি ছোট LP সমস্যা হাতে-লেখা সিমপ্লেক্স দিয়ে

টেস্ট সমস্যা: ম্যাক্সিমাইজ Z = 3x + 5y, শর্তসমূহ x ≤ 4, 2y ≤ 12, 3x + 2y ≤ 18, এবং x, y ≥ 0। প্রতিটি অসমতাকে একটি "স্ল্যাক ভেরিয়েবল" (s1, s2, s3) যোগ করে সমতায় রূপান্তর করা হয় (যেমন x + s1 = 4), যা টেবিল-ভিত্তিক সিমপ্লেক্স মেথডের জন্য প্রয়োজনীয় প্রমিত রূপ।

Python
# maximize Z = 3x + 5y
# s.t.  x        <= 4
#           2y   <= 12
#       3x + 2y  <= 18
#       x, y >= 0

# tableau columns: [x, y, s1, s2, s3, RHS]
tableau = [
    [1, 0, 1, 0, 0, 4],
    [0, 2, 0, 1, 0, 12],
    [3, 2, 0, 0, 1, 18],
]
obj = [-3, -5, 0, 0, 0, 0]
basis = ['s1', 's2', 's3']
var_names = ['x', 'y', 's1', 's2', 's3']

def print_tableau(tableau, obj, basis, iteration):
    print(f"-- ইটারেশন {iteration} --")
    header = "      " + "".join(f"{v:>8}" for v in var_names) + f"{'RHS':>8}"
    print(header)
    for bi, row in zip(basis, tableau):
        print(f"{bi:>5} " + "".join(f"{c:8.3f}" for c in row))
    print(f"{'Z':>5} " + "".join(f"{c:8.3f}" for c in obj))
    print()

iteration = 0
print_tableau(tableau, obj, basis, iteration)

while min(obj[:-1]) < -1e-9:
    pivot_col = obj.index(min(obj[:-1]))          # এন্টারিং ভেরিয়েবল
    ratios = []
    for i, row in enumerate(tableau):
        if row[pivot_col] > 1e-9:
            ratios.append((row[-1] / row[pivot_col], i))
    ratios.sort()
    pivot_row = ratios[0][1]                        # রেশিও টেস্ট (লিভিং ভেরিয়েবল)

    pivot_val = tableau[pivot_row][pivot_col]
    tableau[pivot_row] = [v / pivot_val for v in tableau[pivot_row]]
    for i, row in enumerate(tableau):
        if i != pivot_row:
            factor = row[pivot_col]
            tableau[i] = [row[j] - factor * tableau[pivot_row][j] for j in range(len(row))]
    factor = obj[pivot_col]
    obj = [obj[j] - factor * tableau[pivot_row][j] for j in range(len(obj))]

    basis[pivot_row] = var_names[pivot_col]
    iteration += 1
    print_tableau(tableau, obj, basis, iteration)

solution = {v: 0.0 for v in var_names}
for bi, row in zip(basis, tableau):
    solution[bi] = row[-1]

print("=== অপ্টিমাল সমাধান ===")
print(f"x = {solution['x']:.4f}, y = {solution['y']:.4f}")
print(f"Z (সর্বোচ্চ মান) = {obj[-1]:.4f}")

x, y = solution['x'], solution['y']
print(f"চেক: x = {x:.4f} <= 4 ? {x <= 4 + 1e-9}")
print(f"চেক: 2y = {2*y:.4f} <= 12 ? {2*y <= 12 + 1e-9}")
print(f"চেক: 3x+2y = {3*x + 2*y:.4f} <= 18 ? {3*x + 2*y <= 18 + 1e-9}")

    
মাত্র ২টি ট্যাবলো-আপডেট (ইটারেশন ১ ও ২) পরে সিমপ্লেক্স মেথড অপ্টিমাল সমাধান x = 2.0000, y = 6.0000 খুঁজে বের করে, যেখানে Z = 36.0000। তিনটি কনস্ট্রেইন্ট চেক করলে দেখা যায় x = 2 ≤ 4 ✓, 2y = 12 ≤ 12 ✓ (নিখুঁতভাবে সীমায়, "টাইট" কনস্ট্রেইন্ট), এবং 3x+2y = 18 ≤ 18 ✓ (এটিও নিখুঁতভাবে সীমায়) — এটি নিশ্চিত করে অপ্টিমাল সমাধানটি ফিজিবল রিজিয়নের একটি কর্নার পয়েন্টে পাওয়া গেছে, যেখানে দুটি কনস্ট্রেইন্ট একসাথে সীমায় (active) আছে — ঠিক যেমন কর্নার-পয়েন্ট থিওরেম ভবিষ্যদ্বাণী করে।
এই পাঠের সুযোগ ও সীমাবদ্ধতা

এই সিমপ্লেক্স ইমপ্লিমেন্টেশনটি একটি পরিচিতিমূলক, সরলীকৃত সংস্করণ — এটি শুধু "≤" কনস্ট্রেইন্ট এবং একটি প্রাথমিকভাবে ফিজিবল (feasible) স্টার্টিং পয়েন্ট ধরে নেয় (স্ল্যাক ভেরিয়েবলগুলো দিয়ে সরাসরি শুরু করা যায়)। বাস্তব LP সমস্যায় প্রায়ই "≥" বা "=" কনস্ট্রেইন্ট, অসীমসংখ্যক চলক, এবং হাজার হাজার কনস্ট্রেইন্ট থাকে — সেসব ক্ষেত্রে প্রোডাকশন-গ্রেড সফটওয়্যার (যেমন সিমপ্লেক্সের উন্নত সংস্করণ বা ইন্টেরিয়র-পয়েন্ট মেথড ব্যবহারকারী CPLEX, Gurobi, বা ওপেন-সোর্স scipy.optimize.linprog) ব্যবহৃত হয় — এই কোর্সের সুযোগের বাইরে, কিন্তু এখানকার মূল ধারণাগুলো (ফিজিবল রিজিয়ন, কর্নার পয়েন্ট, ট্যাবলো-ভিত্তিক আপডেট) সেই সব সফটওয়্যারের ভিত্তি।

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

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

প্র ০১ উপরের সমাধানে 2y = 12 এবং 3x+2y = 18 — দুটোই নিখুঁতভাবে তাদের সীমায় পৌঁছেছে, কিন্তু x = 2 ≤ 4 সীমা থেকে অনেক দূরে। এই পার্থক্যের অর্থ কী?

যে কনস্ট্রেইন্ট নিখুঁতভাবে সীমায় পৌঁছায় তাকে "টাইট" বা "সক্রিয় (active)" কনস্ট্রেইন্ট বলা হয় — এগুলোই মূলত অপ্টিমাল সমাধানকে "আটকে" রাখে। x ≤ 4 এখানে সক্রিয় নয় (slack আছে, কারণ x=2, সীমা থেকে ২ দূরে) — অর্থাৎ এই নির্দিষ্ট কনস্ট্রেইন্টটি আসলে চূড়ান্ত সমাধানে কোনো প্রভাব ফেলছে না; এটি সরিয়ে দিলেও একই অপ্টিমাল সমাধান পাওয়া যেত।

প্র ০২ সিমপ্লেক্স মেথড প্রতি ধাপে একটি কর্নার পয়েন্ট থেকে আরেকটি কর্নার পয়েন্টে যায়, ফিজিবল রিজিয়নের "ভেতরের" বিন্দুগুলো কখনো পরীক্ষা করে না। এটি কি একটি দুর্বলতা, নাকি সুবিধা বলে আপনার মনে হয়?

এটি একটি বড় সুবিধা — কর্নার-পয়েন্ট থিওরেম অনুযায়ী অপ্টিমাল সমাধান সবসময় একটি কর্নার পয়েন্টে থাকে, তাই ভেতরের অসীমসংখ্যক বিন্দু পরীক্ষা করার কোনো প্রয়োজন নেই — শুধু সসীমসংখ্যক কর্নার পয়েন্ট (এবং বাস্তবে তার একটি ছোট অংশ) পরীক্ষা করলেই যথেষ্ট, যা সিমপ্লেক্স মেথডকে বাস্তবে দ্রুত করে তোলে।

প্র ০৩ এই পাঠে আমরা মাত্র ২টি চলক ও ৩টি কনস্ট্রেইন্টের একটি ছোট সমস্যা সমাধান করেছি, মাত্র ২টি ইটারেশনে। বাস্তব সাপ্লাই-চেইন বা প্রোডাকশন-প্ল্যানিং সমস্যায় হাজার হাজার চলক ও কনস্ট্রেইন্ট থাকতে পারে — এই স্কেলে সিমপ্লেক্স মেথডের ট্যাবলো-ভিত্তিক পদ্ধতি নিয়ে কী চ্যালেঞ্জ হতে পারে বলে আপনার মনে হয়?

একটি সম্পূর্ণ ট্যাবলো (প্রতিটি চলক ও স্ল্যাক ভেরিয়েবলের জন্য একটি কলাম) স্মৃতিতে রাখা এবং প্রতি ধাপে পুরো ট্যাবলো আপডেট করা হাজার হাজার চলকের ক্ষেত্রে গণনা ও মেমরির দিক থেকে ব্যয়বহুল হয়ে যায় — এই কারণেই বাস্তব বড়-স্কেল LP সফটওয়্যার স্পার্স-ম্যাট্রিক্স টেকনিক (M10/L48-এ বিস্তারিত) এবং রিভাইজড সিমপ্লেক্স বা ইন্টেরিয়র-পয়েন্ট পদ্ধতি ব্যবহার করে, যা সম্পূর্ণ ট্যাবলো একসাথে না রেখে অনেক বেশি দক্ষতার সাথে কাজ করে।

অনুশীলন

  1. চিন্তা করুন: উপরের কোড সেলে লক্ষ্য-ফাংশন obj = [-3, -5, 0, 0, 0, 0] (অর্থাৎ Z = 3x + 5y)-এর বদলে obj = [-5, -3, 0, 0, 0, 0] (Z = 5x + 3y) ব্যবহার করলে অপ্টিমাল সমাধান বিন্দু একই থাকবে, নাকি বদলে যাবে বলে আপনার ধারণা?

    ফিজিবল রিজিয়ন (কনস্ট্রেইন্ট) অপরিবর্তিত থাকায় কর্নার পয়েন্টগুলো একই থাকবে, কিন্তু লক্ষ্য-ফাংশনের গুণাঙ্ক পাল্টানোয় "কোন কর্নার পয়েন্ট সবচেয়ে ভালো" তা বদলে যেতে পারে — যেহেতু এখন x-এর গুণাঙ্ক (৫) y-এর গুণাঙ্কের (৩) চেয়ে বড়, অপ্টিমাল সমাধান সম্ভবত এমন কর্নারে সরে যাবে যেখানে x বড়।

  2. পরীক্ষা করুন: উপরের কোড সেলে obj = [-3, -5, 0, 0, 0, 0]-কে obj = [-5, -3, 0, 0, 0, 0]-এ পরিবর্তন করে Run চেপে আপনার অনুমান যাচাই করুন।

    নতুন অপ্টিমাল সমাধান হয় x = 4.0000, y = 3.0000 (আগে ছিল x=2, y=6), এবং Z = 29.0000 — নিশ্চিত করে অনুমান সঠিক ছিল: x-এর গুণাঙ্ক বাড়ানোর ফলে অপ্টিমাল কর্নার পয়েন্ট এমন একটিতে সরে গেছে যেখানে x = 4 (তার সীমায়) এবং y ছোট।

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

আগের পাঠ
কনস্ট্রেইনড অপ্টিমাইজেশন — লাগ্রাঞ্জ মাল্টিপ্লায়ার