পাঠ ১৩ · ৫৭-এর মধ্যে · মডিউল ৩
Home / Courses / Numerical Methods / জ্যাকোবি ও গস-সেইডেল

ইটারেটিভ মেথড — জ্যাকোবি ও গস-সেইডেল

Iterative methods — Jacobi & Gauss-Seidel
১১ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • ডাইরেক্ট বনাম ইটারেটিভ মেথডের পার্থক্য এবং কখন প্রতিটি পছন্দনীয়
  • জ্যাকোবি ও গস-সেইডেল ইটারেশনের আপডেট নিয়ম এবং তাদের মূল পার্থক্য
  • ডায়াগোনাল ডমিন্যান্স কী এবং কেন এটি কনভারজেন্সের জন্য গুরুত্বপূর্ণ
  • একটি সত্যিকারের, চলমান Python ডেমো — কনভার্জিং ও ডাইভার্জিং উভয় ক্ষেত্রে প্রকৃত ইটারেশন টেবিল

১ · ডাইরেক্ট বনাম ইটারেটিভ মেথড

L10-L11-এর গসিয়ান এলিমিনেশন ও LU ডিকম্পোজিশন হলো ডাইরেক্ট মেথড — একটি সীমিত-সংখ্যক ধাপে (কার্যত) সঠিক সমাধান দেয়। কিন্তু বড় স্পার্স সিস্টেমে (হাজার হাজার সারি, বেশিরভাগ শূন্য — L15-এ বিস্তারিত) ডাইরেক্ট মেথডের O(n³) খরচ অসম্ভব হয়ে যায়। ইটারেটিভ মেথড একটি ভিন্ন কৌশল নেয়: একটি প্রাথমিক অনুমান (সাধারণত x = 0) থেকে শুরু করে, প্রতিটি ধাপে সমাধানের কাছাকাছি একটি নতুন অনুমান তৈরি করে — যতক্ষণ না পরিবর্তন যথেষ্ট ছোট হয়ে যায়।

২ · জ্যাকোবি ইটারেশন

Ax = b সিস্টেমের i-তম সমীকরণ থেকে x_i আলাদা করে লেখা যায়:

$$x_i^{(k+1)} = \frac{1}{a_{ii}}\left(b_i - \sum_{j \neq i} a_{ij}\,x_j^{(k)}\right)$$

জ্যাকোবি ইটারেশনে পুরনো ইটারেশনের সব x_j ব্যবহার করে নতুন x^{(k+1)}-এর প্রতিটি উপাদান একসাথে গণনা করা হয় — নতুন মান পুরো ধাপ শেষ না হওয়া পর্যন্ত ব্যবহার করা হয় না।

৩ · গস-সেইডেল ইটারেশন

গস-সেইডেল একটি সহজ কিন্তু কার্যকর পরিবর্তন করে: প্রতিটি x_i আপডেট করার সময়, যেসব x_j (j < i) ইতিমধ্যে এই একই ধাপে আপডেট হয়ে গেছে, সেগুলোর নতুন মান ব্যবহার করা হয় — পুরনো মান নয়। এতে তথ্য দ্রুত সিস্টেমের মধ্যে ছড়িয়ে পড়ে, এবং সাধারণত জ্যাকোবির চেয়ে দ্রুত কনভার্জ করে (নিচের ডেমোতেই দেখা যাবে)।

ডায়াগোনাল ডমিন্যান্স
একটি সিস্টেম "ডায়াগোনালি-ডমিন্যান্ট" যদি প্রতিটি সারিতে |a_ii| > সাম(|a_ij|, j≠i) হয় — এই শর্ত জ্যাকোবি ও গস-সেইডেল উভয়ের কনভারজেন্সের জন্য যথেষ্ট (যদিও সবসময় প্রয়োজনীয় নয়)।
স্পার্স সিস্টেমে সুবিধা
প্রতিটি ইটারেশনের খরচ মাত্র O(n²) (dense সিস্টেমে) বা তারও কম (স্পার্স সিস্টেমে) — বড় সিস্টেমে ডাইরেক্ট মেথডের O(n³)-এর চেয়ে অনেক সস্তা।
গ্যারান্টি নেই
ডায়াগোনাল ডমিন্যান্স না থাকলে এই মেথডগুলো কনভার্জ নাও করতে পারে — নিচের ডেমোর দ্বিতীয় অংশে এটি সত্যিই ঘটতে দেখা যাবে।

৪ · একটি সত্যিকারের ডেমো — কনভার্জিং বনাম ডাইভার্জিং

প্রথমে একটি ডায়াগোনালি-ডমিন্যান্ট 3x3 সিস্টেমে জ্যাকোবি ও গস-সেইডেল উভয়ই চালানো হয়েছে, এবং L10-এর গসিয়ান এলিমিনেশন থেকে পাওয়া রেফারেন্স সমাধানের সাথে তুলনা করা হয়েছে। তারপর একটি non-diagonally-dominant 2x2 সিস্টেমে জ্যাকোবি চালিয়ে দেখানো হয়েছে এটি সত্যিই ব্যর্থ হয় (মান বাড়তে থাকে, কমতে নয়)।

Python
def jacobi_step(A, b, x_old):
    n = len(A)
    x_new = [0.0] * n
    for i in range(n):
        s = sum(A[i][j] * x_old[j] for j in range(n) if j != i)
        x_new[i] = (b[i] - s) / A[i][i]
    return x_new

def gauss_seidel_step(A, b, x):
    n = len(A)
    x_new = x[:]
    for i in range(n):
        s = sum(A[i][j] * x_new[j] for j in range(n) if j != i)
        x_new[i] = (b[i] - s) / A[i][i]
    return x_new

def run(A, b, method, n_iter, cap=1e12):
    n = len(A)
    x = [0.0] * n
    history = [x[:]]
    for k in range(n_iter):
        x = jacobi_step(A, b, x) if method == "jacobi" else gauss_seidel_step(A, b, x)
        history.append(x[:])
        if any(abs(v) > cap for v in x):
            break   # ব্লো-আপ থামিয়ে দেওয়া
    return history

# --- ডায়াগোনালি-ডমিন্যান্ট সিস্টেম (কনভার্জ করবে) ---
A_conv = [
    [10.0, -1.0,  2.0],
    [ 1.0, 11.0, -1.0],
    [ 2.0, -1.0, 10.0],
]
b_conv = [6.0, 25.0, -11.0]

hist_j = run(A_conv, b_conv, "jacobi", 10)
hist_g = run(A_conv, b_conv, "gauss_seidel", 10)

print("Jacobi ইটারেশন টেবিল:")
print(f"{'k':>2} | {'x1':>10} {'x2':>10} {'x3':>10}")
for k, xk in enumerate(hist_j):
    print(f"{k:>2} | {xk[0]:>10.6f} {xk[1]:>10.6f} {xk[2]:>10.6f}")

print()
print("Gauss-Seidel ইটারেশন টেবিল:")
print(f"{'k':>2} | {'x1':>10} {'x2':>10} {'x3':>10}")
for k, xk in enumerate(hist_g):
    print(f"{k:>2} | {xk[0]:>10.6f} {xk[1]:>10.6f} {xk[2]:>10.6f}")

# L10-এর গসিয়ান এলিমিনেশন দিয়ে রেফারেন্স সমাধান
def gaussian_solve(A, b):
    n = len(A)
    M = [row[:] + [b[i]] for i, row in enumerate(A)]
    for k in range(n - 1):
        for i in range(k + 1, n):
            f = M[i][k] / M[k][k]
            for j in range(k, n + 1):
                M[i][j] -= f * M[k][j]
    x = [0.0] * n
    for i in range(n - 1, -1, -1):
        s = M[i][n]
        for j in range(i + 1, n):
            s -= M[i][j] * x[j]
        x[i] = s / M[i][i]
    return x

x_ref = gaussian_solve(A_conv, b_conv)
print()
print("রেফারেন্স সমাধান (Gaussian elimination):", [round(v, 6) for v in x_ref])
err_j = max(abs(hist_j[-1][i] - x_ref[i]) for i in range(3))
err_g = max(abs(hist_g[-1][i] - x_ref[i]) for i in range(3))
print(f"১০ ইটারেশন পর Jacobi সর্বোচ্চ ভুল: {err_j:.2e}")
print(f"১০ ইটারেশন পর Gauss-Seidel সর্বোচ্চ ভুল: {err_g:.2e}")

# --- ডায়াগোনালি-ডমিন্যান্ট নয় এমন সিস্টেম (ডাইভার্জ করবে) ---
print()
print("=== ডায়াগোনালি-ডমিন্যান্ট নয় এমন সিস্টেম ===")
A_div = [[1.0, 2.0], [3.0, 1.0]]
b_div = [5.0, 10.0]

hist_jd = run(A_div, b_div, "jacobi", 10)
print("Jacobi ইটারেশন টেবিল (ডাইভার্জিং):")
print(f"{'k':>2} | {'x1':>16} {'x2':>16}")
for k, xk in enumerate(hist_jd):
    print(f"{k:>2} | {xk[0]:>16.4f} {xk[1]:>16.4f}")
Jacobi ইটারেশন টেবিল:
 k |         x1         x2         x3
 0 |   0.000000   0.000000   0.000000
 1 |   0.600000   2.272727  -1.100000
 2 |   1.047273   2.118182  -0.992727
 3 |   1.010364   2.087273  -1.097636
 4 |   1.028255   2.081091  -1.093345
 5 |   1.026778   2.079855  -1.097542
 6 |   1.027494   2.079607  -1.097370
 7 |   1.027435   2.079558  -1.097538
 8 |   1.027463   2.079548  -1.097531
 9 |   1.027461   2.079546  -1.097538
10 |   1.027462   2.079546  -1.097538

Gauss-Seidel ইটারেশন টেবিল:
 k |         x1         x2         x3
 0 |   0.000000   0.000000   0.000000
 1 |   0.600000   2.218182  -0.998182
 2 |   1.021455   2.089124  -1.095379
 3 |   1.027988   2.079694  -1.097628
 4 |   1.027495   2.079534  -1.097546
 5 |   1.027463   2.079545  -1.097538
 6 |   1.027462   2.079545  -1.097538
 7 |   1.027462   2.079545  -1.097538
 8 |   1.027462   2.079545  -1.097538
 9 |   1.027462   2.079545  -1.097538
10 |   1.027462   2.079545  -1.097538

রেফারেন্স সমাধান (Gaussian elimination): [1.027462, 2.079545, -1.097538]
১০ ইটারেশন পর Jacobi সর্বোচ্চ ভুল: 2.68e-07
১০ ইটারেশন পর Gauss-Seidel সর্বোচ্চ ভুল: 1.95e-13

=== ডায়াগোনালি-ডমিন্যান্ট নয় এমন সিস্টেম ===
Jacobi ইটারেশন টেবিল (ডাইভার্জিং):
 k |               x1               x2
 0 |           0.0000           0.0000
 1 |           5.0000          10.0000
 2 |         -15.0000          -5.0000
 3 |          15.0000          55.0000
 4 |        -105.0000         -35.0000
 5 |          75.0000         325.0000
 6 |        -645.0000        -215.0000
 7 |         435.0000        1945.0000
 8 |       -3885.0000       -1295.0000
 9 |        2595.0000       11665.0000
10 |      -23325.0000       -7775.0000
লক্ষ্য করুন কনভার্জিং কেসে গস-সেইডেল মাত্র ৫-৬ ইটারেশনে ১০⁻¹³ নির্ভুলতায় পৌঁছেছে, যেখানে জ্যাকোবির ১০ ইটারেশন পরও ভুল ১০⁻⁷ মাত্রার — গস-সেইডেল স্পষ্টভাবে দ্রুততর। আর ডাইভার্জিং কেসে x1, x2-এর মান প্রতি ইটারেশনে বড় হচ্ছে ও চিহ্ন পাল্টাচ্ছে (০ → ৫ → -১৫ → ১৫ → -১০৫ ...) — এটি কখনো কনভার্জ করবে না, কারণ সারি |১| < |২| ও |১| < |৩| — ডায়াগোনাল ডমিন্যান্স নেই।
মূল কথা · Key takeaway

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

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

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

প্র ০১ কোড সেলে jacobi_step ও gauss_seidel_step-এর মধ্যে ঠিক কোন এক লাইনের পার্থক্যে এত বড় গতির পার্থক্য তৈরি হয়?

jacobi_step-এ sum(... for j in range(n) if j != i) সবসময় x_old (পুরনো ভেক্টর) থেকে মান নেয়, আর gauss_seidel_step-এ x_new থেকে মান নেয় — যা এই একই ধাপে ইতিমধ্যে আপডেট হয়ে যাওয়া মান। এই একটি পরিবর্তনই তথ্যকে ধাপের মধ্যেই ছড়িয়ে দেয়, ফলে গস-সেইডেল সাধারণত জ্যাকোবির প্রায় দ্বিগুণ গতিতে কনভার্জ করে।

প্র ০২ ডাইভার্জিং উদাহরণে সারি ১-এ |a_11| = ১ আর বাকি উপাদানের যোগফল |২| = ২। যদি সারি দুটো অদলবদল করা হতো, তাহলে কি সিস্টেমটি ডায়াগোনালি-ডমিন্যান্ট হয়ে যেত?

না — সারি অদলবদল করলে সারি হয়ে যায় [3, 1] ও [1, 2]। প্রথম সারিতে |৩| > |১| (ডমিন্যান্ট), কিন্তু দ্বিতীয় সারিতে |২| < |১| (এখনো ডমিন্যান্ট নয়)। এই নির্দিষ্ট 2x2 সিস্টেমের জন্য কোনো সারি-পুনর্বিন্যাসেই উভয় সারি একসাথে ডায়াগোনালি-ডমিন্যান্ট করা সম্ভব নয় — সমস্যাটি সিস্টেমের গঠনের মধ্যেই নিহিত।

প্র ০৩ ডায়াগোনাল ডমিন্যান্স না থাকলেও কি একটি সিস্টেম মাঝে মাঝে কনভার্জ করতে পারে?

হ্যাঁ — ডায়াগোনাল ডমিন্যান্স কনভারজেন্সের জন্য যথেষ্ট (sufficient) শর্ত, কিন্তু প্রয়োজনীয় (necessary) নয়। কিছু সিস্টেম এই শর্ত পূরণ না করেও কনভার্জ করতে পারে (এটি নির্ভর করে ইটারেশন ম্যাট্রিক্সের eigenvalue-এর উপর — M8-এ eigenvalue-এর ধারণা বিস্তারিত)। তবে ডায়াগোনাল ডমিন্যান্স যাচাই করা সহজ এবং ব্যবহারিকভাবে একটি নির্ভরযোগ্য প্রাথমিক পরীক্ষা।

অনুশীলন

  1. চিন্তা করুন: কনভার্জিং উদাহরণে ২০ ইটারেশন চালালে জ্যাকোবির ভুল কি ১০ইটারেশনের ভুল (২.৬৮×১০⁻⁷)-এর চেয়ে আরও কমবে বলে আপনার মনে হয়?

    হ্যাঁ — যেহেতু সিস্টেমটি ডায়াগোনালি-ডমিন্যান্ট, জ্যাকোবি নিশ্চিতভাবে কনভার্জ করে, এবং প্রতি ইটারেশনে ভুল একটি স্থির অনুপাতে (রৈখিকভাবে) কমতে থাকে — তাই আরও ১০ ইটারেশন চালালে ভুল আরও কয়েক অর্ডার-অফ-ম্যাগনিটিউড কমে যাবে, machine-precision সীমার কাছাকাছি পৌঁছানো পর্যন্ত।

  2. পরীক্ষা করুন: উপরের কোড সেলে run(A_conv, b_conv, "jacobi", 10)-এর 10-কে 20-এ পরিবর্তন করে Run চেপে চূড়ান্ত ভুল যাচাই করুন।

    ২০ ইটারেশনে জ্যাকোবির ভুল ১০⁻¹৩ মাত্রায় নেমে আসে — machine-precision-এর কাছাকাছি, যা গস-সেইডেলের ১০-ইটারেশন ফলাফলের সমতুল্য। এটি নিশ্চিত করে জ্যাকোবিও নির্ভরযোগ্যভাবে কনভার্জ করে, শুধু গস-সেইডেলের চেয়ে ধীরে।

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

আগের পাঠ
পিভোটিং স্ট্র্যাটেজি ও নিউমেরিক্যাল স্ট্যাবিলিটি