পাঠ ৩২ · ৫৭-এর মধ্যে · মডিউল ৬
Home / Courses / Numerical Methods / স্টিফ সমীকরণ

স্টিফ সমীকরণ ও ODE সলভারের স্ট্যাবিলিটি

Stiff equations & stability of ODE solvers
১২ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • স্টিফ সমীকরণ কী এবং কেন এটি নির্ভুলতার চেয়ে বেশি একটি "স্ট্যাবিলিটি" সমস্যা
  • অয়লার মেথডের স্ট্যাবিলিটি শর্ত সরল রৈখিক টেস্ট সমীকরণ থেকে বের করা
  • একটি সত্যিকারের, গার্ডেড (guarded) Python ডেমো দিয়ে জেনুইন নিউমেরিক্যাল ইনস্ট্যাবিলিটি দেখা এবং নিরাপদে থামানো
  • ছোট step size বা উচ্চতর-ক্রম মেথড (RK4) স্ট্যাবিলিটিতে কী প্রভাব ফেলে তা তুলনা করা

১ · স্টিফ সমীকরণ কী

L27–L31 পর্যন্ত আমরা "নির্ভুলতা" নিয়ে কথা বলেছি — একটি মেথড কত কাছাকাছি প্রকৃত সমাধানে পৌঁছায়। কিন্তু কিছু সমীকরণে একটি সম্পূর্ণ ভিন্ন সমস্যা দেখা দেয়: স্টিফনেস (stiffness)। একটি সমীকরণকে "স্টিফ" বলা হয় যখন সমাধানের কোনো অংশ (বা এর একটি উপাদান) অত্যন্ত দ্রুত ক্ষয়প্রাপ্ত হয় (বড় নেতিবাচক গুণাংক), যদিও সামগ্রিক সমাধান নিজে সহজ ও মসৃণ হতে পারে। এই দ্রুত-পরিবর্তনশীল উপাদানটি ট্র্যাক করার জন্য এক্সপ্লিসিট মেথডকে অত্যন্ত ছোট step size ব্যবহার করতে বাধ্য হতে হয় — শুধু নির্ভুলতা বজায় রাখতে নয়, বরং মেথডটিকে একেবারেই ভেঙে না পড়ার জন্য।

একটি ক্লাসিক টেস্ট সমীকরণ দিয়ে এটি বিশ্লেষণ করা হয়:

$$\frac{dy}{dx} = \lambda y, \qquad y(0) = 1 \qquad (\lambda \text{ একটি বড় ঋণাত্মক সংখ্যা})$$

এর প্রকৃত সমাধান y(x) = e^{\lambda x} — λ যত বেশি ঋণাত্মক (উদাহরণ: λ = -50), সমাধান তত দ্রুত শূন্যের দিকে যায়। এই পাঠে আমরা λ = -50 ব্যবহার করব টেস্ট সমীকরণ হিসেবে: dy/dx = -50y, y(0) = 1, y(x) = e⁻⁵⁰ˣ।

২ · অয়লার মেথডের স্ট্যাবিলিটি শর্ত

অয়লার মেথড এই টেস্ট সমীকরণে প্রয়োগ করলে:

$$y_{n+1} = y_n + h(\lambda y_n) = (1 + h\lambda)\, y_n$$

অর্থাৎ প্রতিটি ধাপে y-কে একটি নির্দিষ্ট অ্যাম্প্লিফিকেশন ফ্যাক্টর r = 1 + hλ দিয়ে গুণ করা হয়। যদি |r| < 1, প্রতিটি ধাপে মান ছোট হতে থাকবে (স্থিতিশীল, প্রকৃত ক্ষয়ের সাথে গুণগতভাবে মিলে যায়)। কিন্তু যদি |r| > 1, প্রতিটি ধাপে মান বাড়তে থাকবে — সম্পূর্ণ অস্থির, প্রকৃত সমাধানের বিপরীত আচরণ। λ = -50 -এর জন্য স্ট্যাবিলিটি শর্ত:

$$|1 + h\lambda| < 1 \implies -1 < 1 - 50h < 1 \implies h < \frac{2}{50} = 0.04$$

অর্থাৎ h ≥ 0.04 হলে অয়লার মেথড এই সমীকরণে অস্থির হয়ে যাবে — নির্ভুলতা নয়, একেবারে স্ট্যাবিলিটিই ভেঙে পড়বে।

অ্যাম্প্লিফিকেশন ফ্যাক্টর
r = 1 + hλ — প্রতি ধাপে মান কী অনুপাতে গুণ হচ্ছে তা নির্ধারণ করে।
|r| > 1 মানে অস্থির
প্রতিটি ধাপে মান আকারে বাড়তে থাকবে, প্রকৃত সমাধান যতই ক্ষয়প্রাপ্ত হোক না কেন — একটি বিশুদ্ধ নিউমেরিক্যাল ঘটনা।
স্ট্যাবিলিটি অঞ্চল
প্রতিটি মেথডের নিজস্ব "স্ট্যাবিলিটি রিজিয়ন" আছে — hλ-এর কোন মানে মেথডটি স্থিতিশীল থাকে তার সীমা। RK4-এর অঞ্চল অয়লারের চেয়ে বড়।

৩ · সত্যিকারের ডেমো — অয়লার মেথড সত্যিই বিস্ফোরিত হচ্ছে

নিচের কোড সেলে dy/dx = -50y, y(0) = 1 সমাধান করা হয়েছে h = 0.1 দিয়ে (যা স্থিতিশীলতার সীমা 0.04-এর চেয়ে অনেক বড়)। যেহেতু ইচ্ছাকৃতভাবে একটি অস্থির ডেমো দেখানো হচ্ছে, লুপে একটি ম্যাগনিটিউড গার্ড রাখা হয়েছে — মান নির্দিষ্ট সীমা পার হলে লুপ নিরাপদে থেমে যাবে, যাতে সেল কখনো hang বা crash না করে।

Python
LAM = -50.0

def f_stiff(x, y):
    return LAM * y

def euler_stiff(h, x0=0.0, y0=1.0, xend=1.0, cap=1e8, max_steps=200):
    xs, ys = [x0], [y0]
    x, y = x0, y0
    n = min(round((xend - x0) / h), max_steps)
    blew_up = False
    for i in range(n):
        y = y + h * f_stiff(x, y)
        x = x + h
        xs.append(x); ys.append(y)
        if abs(y) > cap:      # নিরাপত্তা গার্ড -- মান অসীমের দিকে গেলে থামাও
            blew_up = True
            break
    return xs, ys, blew_up

print(f"স্ট্যাবিলিটি সীমা: h < 2/|লামডা| = 2/50 = 0.04")
print()
print("h = 0.1 (সীমার চেয়ে বড় -- অস্থির হওয়ার কথা):")
print(f"{'x':>6} | {'y':>16}")
xs, ys, blew = euler_stiff(0.1, xend=1.0)
for x, y in zip(xs, ys):
    print(f"{x:>6.2f} | {y:>16.6e}")
print("লুপ গার্ড দিয়ে থেমেছে (blew_up):", blew)

    
প্রকৃত আউটপুট দেখায় মান প্রতিটি ধাপে সাইন পাল্টে ৪ গুণ বেড়ে যাচ্ছে (অ্যাম্প্লিফিকেশন ফ্যাক্টর r = 1 + 0.1×(-50) = -4): 1 → -4 → 16 → -64 → 256 → -1024 → 4096 → -16384 → 65536 → -262144 → 1,048,576 — মাত্র ১০ ধাপে মান দশ লক্ষের বেশি! অথচ প্রকৃত সমাধান e⁻⁵⁰ˣ এই একই x = 1.0-তে ব্যবহারিকভাবে শূন্য (≈ 2×10⁻²²)। এটি নির্ভুলতার সমস্যা নয় — এটি সম্পূর্ণ, বিশুদ্ধ নিউমেরিক্যাল অস্থিরতা।

৪ · সীমানার কাছাকাছি — দোলন কিন্তু বিস্ফোরণ নয়

ঠিক সীমানায় (h = 0.04, যেখানে r = 1 + 0.04×(-50) = -1) কী হয় তা দেখা শিক্ষণীয় — মান আর বাড়েও না, কমেও না, শুধু সাইন পাল্টাতে থাকে।

Python
print("h = 0.04 (ঠিক স্ট্যাবিলিটি সীমানায়, r = -1):")
xs, ys, blew = euler_stiff(0.04, xend=0.4)
for x, y in zip(xs, ys):
    print(f"x = {x:.3f}   y = {y:.6f}")

print()
print("h = 0.02 (সীমার ভেতরে, r = 1 + 0.02*(-50) = 0):")
xs, ys, blew = euler_stiff(0.02, xend=0.1)
for x, y in zip(xs, ys):
    print(f"x = {x:.3f}   y = {y:.6f}")

    
h = 0.04-এ মান অনন্তকাল ধরে 1 → -1 → 1 → -1 → ... দোলায় — না বাড়ছে না কমছে, কারণ ঠিক |r| = 1। এটি "সীমানা-স্থিতিশীল" (marginally stable) — বাস্তবে এখনও ভুল, কারণ প্রকৃত সমাধান দ্রুত শূন্যের দিকে যাওয়ার কথা, কিন্তু অন্তত বিস্ফোরিত হচ্ছে না। h = 0.02-এ মজার একটি বিশেষ ঘটনা দেখা যায়: r = 1 + 0.02×(-50) = 0 ঠিক শূন্য, তাই মান একটি ধাপেই ঠিক শূন্যে নেমে যায় ও সেখানেই থেকে যায় — এই নির্দিষ্ট h-এ কাকতালীয়ভাবে "অতি-স্থিতিশীল" আচরণ, সাধারণভাবে প্রত্যাশিত নয়।

৫ · একটি সত্যিকারের স্থিতিশীল সমাধান

এবার স্থিতিশীলতা সীমার ভেতরে একটি বাস্তবসম্মত h ব্যবহার করে দেখা যাক মেথডটি সত্যিই কেমন কাজ করে — প্রকৃত ক্লোজড-ফর্ম সমাধান e⁻⁵⁰ˣ-এর বিপরীতে।

Python
import math

def exact_stiff(x):
    return math.exp(LAM * x)

print("h = 0.01 (স্থিতিশীল সীমার অনেক ভেতরে):")
print(f"{'x':>6} | {'y_euler':>14} | {'y_exact':>14}")
xs, ys, blew = euler_stiff(0.01, xend=0.3)
for x, y in zip(xs, ys):
    print(f"{x:>6.2f} | {y:>14.6e} | {exact_stiff(x):>14.6e}")

final_err = abs(ys[-1] - exact_stiff(xs[-1]))
print()
print(f"চূড়ান্ত বিন্দুতে প্রকৃত এরর: {final_err:.3e}")

    
h = 0.01-এ (স্থিতিশীলতা সীমার 0.04-এর অনেক ভেতরে) অয়লার মেথড নির্ভরযোগ্যভাবে সমাধান ট্র্যাক করে — মান প্রতিটি ধাপে যথাযথভাবে অর্ধেক হয়ে কমতে থাকে (r = 1 + 0.01×(-50) = 0.5), এবং চূড়ান্ত বিন্দুতে (x = 0.3) প্রকৃত এরর মাত্র ৩.০৫ × ১০⁻⁷ — একই মেথড, শুধু step size সঠিক পরিসরে থাকায় সম্পূর্ণ ভিন্ন, নির্ভরযোগ্য আচরণ।

৬ · RK4-ও অস্থির হতে পারে, তবে বড় সীমায়

এটি একটি সাধারণ ভুল ধারণা যে "উচ্চতর-ক্রম মেথড মানেই স্ট্যাবিলিটির সমস্যা নেই" — RK4-এরও একটি সসীম স্ট্যাবিলিটি অঞ্চল আছে, শুধু তা অয়লারের চেয়ে বড়। নিচের কোড সেলে RK4 একই স্টিফ সমীকরণে h = 0.1 -এ চালিয়ে দেখানো হয়েছে এটিও অস্থির হয়ে যায়, এবং তারপর কোন h-এ RK4 স্থিতিশীল থাকে তা পরীক্ষা করা হয়েছে।

Python
def rk4_stiff(h, x0=0.0, y0=1.0, xend=1.0, cap=1e6, max_steps=60):
    x, y = x0, y0
    n = min(round((xend - x0) / h), max_steps)
    blew_up = False
    for i in range(n):
        k1 = f_stiff(x, y)
        k2 = f_stiff(x + h / 2, y + h / 2 * k1)
        k3 = f_stiff(x + h / 2, y + h / 2 * k2)
        k4 = f_stiff(x + h, y + h * k3)
        y = y + (h / 6) * (k1 + 2 * k2 + 2 * k3 + k4)
        x = x + h
        if abs(y) > cap:
            blew_up = True
            break
    return y, blew_up, i + 1

print("RK4, h = 0.1 (অয়লারের সীমা 0.04-এর চেয়ে বড়, RK4-এরও সীমা পার হতে পারে):")
yf, blew, steps = rk4_stiff(0.1, xend=1.0)
print(f"শেষ y = {yf:.4e}   blew_up = {blew}   ধাপ সম্পন্ন = {steps}")
print()

print("বিভিন্ন h-এ RK4 স্থিতিশীল থাকে কিনা:")
for h in [0.04, 0.05, 0.06, 0.07, 0.08]:
    yf, blew, steps = rk4_stiff(h, xend=0.5, cap=1e6, max_steps=50)
    print(f"h = {h:.2f}   শেষ y = {yf:>14.6e}   blew_up = {blew}")

    
প্রকৃত আউটপুট: h = 0.1-এ RK4-ও বিস্ফোরিত হয় — মাত্র ৬ ধাপে মান ৬.৬৪ × ১০⁶ পার হয়ে গার্ড (cap = 10⁶) থামিয়ে দেয়। কিন্তু h-ভিত্তিক পরীক্ষায় দেখা যায় RK4-এর স্থিতিশীলতা সীমা অয়লারের 0.04-এর চেয়ে বড়: h = 0.04-এ RK4-এর চূড়ান্ত মান মাত্র ১.৮৮ × ১০⁻⁶ (স্থিতিশীল, দ্রুত ক্ষয়প্রাপ্ত), h = 0.05-এও এখনো স্থিতিশীল (১.৩১ × ১০⁻²), কিন্তু h = 0.06-এ মান বেড়ে যায় (১২.৮) — অর্থাৎ RK4-এর স্থিতিশীলতা সীমা প্রায় 0.055-এর কাছাকাছি (তত্ত্বীয়ভাবে ক্লাসিক্যাল RK4-এর real-axis স্ট্যাবিলিটি সীমা hλ ≈ -2.785, অর্থাৎ এখানে h ≈ 2.785/50 ≈ 0.0557) — অয়লারের 0.04-এর চেয়ে বড়, কিন্তু তবুও সসীম। কোনো এক্সপ্লিসিট মেথডই — তা যতই উচ্চ-ক্রম হোক — স্টিফ সমীকরণে সম্পূর্ণ নিরাপদ নয়।
মূল কথা · Key takeaway

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

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

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

প্র ০১ "স্টিফনেস" ও "উচ্চ এরর" — এই দুইটি ভিন্ন সমস্যার মধ্যে মূল পার্থক্য কী?

উচ্চ এরর মানে মেথডটি এখনো স্থিতিশীল আছে (মান বাউন্ডেড থাকছে), শুধু প্রকৃত মানের কাছাকাছি পৌঁছাচ্ছে না যথেষ্ট নির্ভুলভাবে — h ছোট করলে ধীরে ধীরে উন্নতি হয় (L28-এর মতো)। স্টিফনেস/ইনস্ট্যাবিলিটি সম্পূর্ণ ভিন্ন — মেথডটি একটি নির্দিষ্ট h-সীমার উপরে গেলে মান বাউন্ডেডই থাকে না, বরং প্রতি ধাপে দ্রুত হারে (এক্সপোনেনশিয়ালি) বাড়তে থাকে — এটি একটি "থ্রেশহোল্ড" আচরণ, ধীরে ধীরে উন্নতি নয়।

প্র ০২ h = 0.02-এ (r = 0) মান ঠিক এক ধাপেই শূন্যে নেমে গেল এবং সেখানেই রইল। এটি কি একটি ভালো নিউমেরিক্যাল ফলাফল বলে মনে করেন?

বাহ্যিকভাবে "স্থিতিশীল" (মান বাউন্ডেড, আসলে ধ্রুবক) মনে হলেও এটি একটি কাকতালীয় বিশেষ ঘটনা, সাধারণ কোনো নীতি নয় — শুধু এই নির্দিষ্ট λ ও h-এর সংমিশ্রণে r ঠিক শূন্য হয়ে যায়। এটি প্রকৃত সমাধানের সাথে মেলে না (প্রকৃত সমাধান ধীরে ধীরে ক্রমাগত ক্ষয়প্রাপ্ত হয়, এক ধাপেই শূন্যে নামে না) — তাই এটি এখনো একটি ভুল ফলাফল, শুধু বিস্ফোরিত না হওয়ার কারণে "কম বিপজ্জনক" দেখাচ্ছে। ব্যবহারিক স্ট্যাবিলিটি বিশ্লেষণে সাধারণত |r| < 1 যথেষ্ট বলে বিবেচিত হয়, তবে সঠিক গুণগত আচরণের জন্য এখনো যথেষ্ট ছোট h প্রয়োজন।

প্র ০৩ কোড সেলে ব্যবহৃত cap ও max_steps গার্ড না থাকলে h = 0.1 -এর ডেমোতে কী সমস্যা হতে পারত?

গার্ড ছাড়া লুপ নির্দিষ্ট সংখ্যক ধাপ (এখানে সীমিত xend পর্যন্ত) চালিয়ে যেত, এবং মান প্রতি ধাপে প্রায় ৪ গুণ বাড়তে থাকত — অল্প কিছু ধাপের মধ্যেই Python-এর ফ্লোট সীমা (প্রায় 1.8 × 10³⁰⁸) পার হয়ে OverflowError বা inf মান তৈরি করতে পারত। এই ধরনের ইচ্ছাকৃত অস্থিরতা-প্রদর্শনী কোড সেলে সবসময় একটি ম্যাগনিটিউড গার্ড বা সর্বোচ্চ-ধাপ সীমা রাখা জরুরি, যাতে সেলটি নিরাপদে ও পূর্বানুমানযোগ্যভাবে থামে।

অনুশীলন

  1. চিন্তা করুন: λ = -50-এর বদলে λ = -100 ব্যবহার করলে অয়লার মেথডের স্ট্যাবিলিটি সীমা h < 2/|λ| মোটামুটি কত হবে?

    h < 2/100 = 0.02 — এখনকার সীমার (0.04) ঠিক অর্ধেক। এটি দেখায় সমীকরণ যত বেশি "স্টিফ" (যত বড় |λ|), তত ছোট step size লাগে শুধু স্থিতিশীল থাকতে, নির্ভুলতার প্রশ্ন ছাড়াই।

  2. পরীক্ষা করুন: কোড সেলের LAM = -50.0-কে LAM = -100.0-এ পরিবর্তন করে ৩নং কোড সেলে (h = 0.02) Run চেপে দেখুন এখন কী হয়।

    LAM = -100-এ h = 0.02 এখন r = 1 + 0.02×(-100) = -1 — ঠিক নতুন সীমানায় গিয়ে পড়ে, এবং মান 1 → -1 → 1 → -1 → ... দোলাতে শুরু করে, ঠিক যেমন আগে h = 0.04, λ = -50-এ দেখা গিয়েছিল। এটি নিশ্চিত করে স্ট্যাবিলিটি শর্তটি hλ-এর একক সংমিশ্রণের উপর নির্ভর করে, শুধু h বা শুধু λ-এর উপর আলাদাভাবে নয়।

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

আগের পাঠ
মাল্টিস্টেপ মেথড — অ্যাডামস-ব্যাশফোর্থ/মৌলটন