পাঠ ১৩ · ৫৭-এর মধ্যে · মডিউল ৩
Home / Courses / Digital Signal Processing / স্ট্যাবিলিটি

কজালিটি, স্ট্যাবিলিটি ও BIBO স্ট্যাবিলিটি

Causality, stability & BIBO stability
১০ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • কজালিটির সংজ্ঞা এবং কোড দিয়ে একটি সিস্টেম কজাল কিনা তা যাচাই করা
  • BIBO স্ট্যাবিলিটির সংজ্ঞা এবং বাউন্ডেড ইনপুট দিয়ে আউটপুট বাউন্ডেড থাকে কিনা তা পরীক্ষা করা
  • একটি রিকার্সিভ সিস্টেমের কোয়েফিসিয়েন্ট (pole-এর মান) কীভাবে স্ট্যাবিলিটি নির্ধারণ করে তা বোঝা
  • একটি প্রকৃত অস্থির সিস্টেমের সূচকীয় বৃদ্ধি সরাসরি চোখে দেখা, নিরাপদে নিয়ন্ত্রিত একটি ডেমোতে

১ · কজালিটি — ভবিষ্যতের উপর নির্ভরতা নেই

একটি সিস্টেম কজাল (causal) হয় যদি তার আউটপুট y[n] শুধুমাত্র বর্তমান বা অতীতের ইনপুট (x[n], x[n-1], x[n-2], \ldots) ব্যবহার করে — কখনো ভবিষ্যতের ইনপুট (x[n+1], x[n+2], \ldots) ব্যবহার করে না। বাস্তব-সময়ের (real-time) যেকোনো সিস্টেমকে অবশ্যই কজাল হতে হবে, কারণ ভবিষ্যতের ডেটা এখনো আসেইনি। নিচের কোডে দুটো সিস্টেম তুলনা করা হয়েছে — একটি L12-এর পরিচিত কজাল সিস্টেম, আরেকটি ইচ্ছাকৃতভাবে অ-কজাল (non-causal)।

Python
def system_A(x, n):
    """y[n] = 0.5*x[n] + 0.5*x[n-1]  -- শুধু অতীত/বর্তমান স্যাম্পল লাগে"""
    xn = x[n]
    xn_1 = x[n - 1] if n - 1 >= 0 else 0.0
    used_indices = [n] if n - 1 < 0 else [n, n - 1]
    return 0.5 * xn + 0.5 * xn_1, used_indices

def system_B(x, n):
    """y[n] = 0.5*x[n] + 0.5*x[n+1]  -- একটি ভবিষ্যত স্যাম্পল লাগে!"""
    xn = x[n]
    xn1 = x[n + 1] if n + 1 < len(x) else 0.0
    used_indices = [n] if n + 1 >= len(x) else [n, n + 1]
    return 0.5 * xn + 0.5 * xn1, used_indices

x_test = [1, 2, 3, 4, 5, 6]

def check_causality(system, x, name):
    causal = True
    offenders = []
    for n in range(len(x)):
        _, used = system(x, n)
        for idx in used:
            if idx > n:
                causal = False
                offenders.append((n, idx))
    msg = "" if causal else f" (y[{offenders[0][0]}] এর জন্য x[{offenders[0][1]}] লাগে, একটি ভবিষ্যত স্যাম্পল)"
    print(f"{name}: causal = {causal}{msg}")
    return causal

print("System A: y[n] = 0.5x[n] + 0.5x[n-1]")
check_causality(system_A, x_test, "  ফলাফল")
print("System B: y[n] = 0.5x[n] + 0.5x[n+1]")
check_causality(system_B, x_test, "  ফলাফল")

    
কোড genuinely নিশ্চিত করে — System A: causal = True (সব ব্যবহৃত ইনডেক্স <= n)। System B: causal = False, নির্দিষ্টভাবে ধরা পড়ে y[0] গণনা করতে x[1] (একটি ভবিষ্যত স্যাম্পল) লাগে।

২ · BIBO স্ট্যাবিলিটি — বাউন্ডেড ইনপুটে বাউন্ডেড আউটপুট

একটি সিস্টেম BIBO স্ট্যাবল হয় যদি প্রতিটি বাউন্ডেড ইনপুট (অর্থাৎ |x[n]| \le M কোনো সসীম M-এর জন্য, সব n-এ) সবসময় একটি বাউন্ডেড আউটপুট দেয়। একটি সাধারণ ১ম-অর্ডার রিকার্সিভ (IIR-ধরনের) সিস্টেম বিবেচনা করা যাক:

$$y[n] = a\,y[n-1] + x[n]$$

এই a মানটিই এই সিস্টেমের poleসিস্টেমের ট্রান্সফার ফাংশনের একটি বিশেষ মান, যেখানে ফাংশনের মান অসীম হয়ে যায় — M4-এ Z-ট্রান্সফর্মে এর ফরমাল সংজ্ঞা ও বিশ্লেষণ শেখানো হবে। এই সহজ ১ম-অর্ডার সিস্টেমে pole সরাসরি কোয়েফিসিয়েন্ট a-এর সমান। — BIBO স্ট্যাবিলিটির জন্য জানা টেক্সটবুক শর্ত হলো pole-এর ম্যাগনিটিউড < 1 হতে হবে (unit circle-এর ভেতরে, M4/L18-এ পূর্ণ প্রমাণসহ)। নিচে দুটো সিস্টেম — একটি স্ট্যাবল (a=0.5) আর একটি ইচ্ছাকৃতভাবে অস্থির (a=1.5) — একই বাউন্ডেড ইনপুটে চালিয়ে সত্যিকারের ফলাফল দেখা যাক। লুপটি নিরাপদে মাত্র ১৫টি স্যাম্পলে সীমাবদ্ধ রাখা হয়েছে যাতে অস্থির কেসেও সংখ্যাগুলো নিয়ন্ত্রণের মধ্যে থাকে।

Python
def run_iir(a, x, n_samples):
    """y[n] = a*y[n-1] + x[n], জিরো ইনিশিয়াল কন্ডিশন"""
    y = []
    y_prev = 0.0
    for n in range(n_samples):
        xn = x[n] if n < len(x) else 0.0
        yn = a * y_prev + xn
        y.append(yn)
        y_prev = yn
    return y

N_SAMPLES = 15   # লুপ নিয়ন্ত্রিত -- অস্থির কেসেও সংখ্যা নিয়ন্ত্রণের মধ্যে রাখতে
bounded_input = [1.0] * N_SAMPLES   # ইউনিট স্টেপ, স্পষ্টভাবে বাউন্ডেড: |x[n]| <= 1

a_stable = 0.5
a_unstable = 1.5

y_stable = run_iir(a_stable, bounded_input, N_SAMPLES)
y_unstable = run_iir(a_unstable, bounded_input, N_SAMPLES)

print(f"System C: y[n] = {a_stable}*y[n-1] + x[n]   (pole ম্যাগনিটিউড |{a_stable}| = {abs(a_stable)} < 1)")
print(f"{'n':>3} | {'y[n]':>12}")
for n, v in enumerate(y_stable):
    print(f"{n:>3} | {v:>12.6f}")
print("সর্বোচ্চ |y[n]|:", round(max(abs(v) for v in y_stable), 6),
      "-> বাউন্ডেড থাকে (স্টেডি-স্টেট = 1/(1-a) =", round(1/(1-a_stable), 4), ")")

print()
print(f"System D: y[n] = {a_unstable}*y[n-1] + x[n]   (pole ম্যাগনিটিউড |{a_unstable}| = {abs(a_unstable)} >= 1)")
print(f"{'n':>3} | {'y[n]':>16}")
for n, v in enumerate(y_unstable):
    print(f"{n:>3} | {v:>16.4f}")
print("সর্বোচ্চ |y[n]|:", round(max(abs(v) for v in y_unstable), 4),
      "-> বাউন্ডেড ইনপুট সত্ত্বেও genuinely UNBOUNDED বৃদ্ধি")

ratio = y_unstable[-1] / y_unstable[-2]
print("y[14]/y[13] অনুপাত =", round(ratio, 4), "(n বাড়ার সাথে সাথে pole-এর মান", a_unstable, "-এর কাছাকাছি যায়)")

    
কোডের প্রকৃত আউটপুট — System C (স্ট্যাবল, a=0.5): আউটপুট মসৃণভাবে 1.0 থেকে বেড়ে 1.999939-এ পৌঁছে স্টেডি-স্টেট মান 2.0-এর কাছাকাছি স্থির হয়ে যায় — সর্বোচ্চ মান কখনোই 2.0 ছাড়ায় না, সত্যিকারভাবে বাউন্ডেড। System D (অস্থির, a=1.5): আউটপুট 1.0 থেকে শুরু করে ক্রমাগত বেড়ে মাত্র ১৫তম স্যাম্পলে (n=14) পৌঁছায় ৮৭৩.৭৮৭৮-এ — একটি সম্পূর্ণ বাউন্ডেড ইনপুট (x[n]=1 সবসময়) থেকে সম্পূর্ণ আনবাউন্ডেড আউটপুট, ঠিক pole ম্যাগনিটিউড 1.5 \ge 1 শর্তের ভবিষ্যদ্বাণী অনুযায়ী। অনুপাত y[14]/y[13] \approx 1.5017 — pole-এর মান 1.5-এর দিকে দ্রুত কনভার্জ করছে, যা নিশ্চিত করে বৃদ্ধিটি সত্যিই সূচকীয় (exponential), দৈব নয়।
মূল কথা · Key takeaway

BIBO স্ট্যাবিলিটি একটি textbook-প্রতিষ্ঠিত, কঠোর শর্ত — একটি রিকার্সিভ সিস্টেমের pole-এর ম্যাগনিটিউড অবশ্যই ইউনিট সার্কেলের কঠোরভাবে ভেতরে (< 1) থাকতে হবে, নয়তো সিস্টেমটি বাউন্ডেড ইনপুটেও আনবাউন্ডেড আউটপুট দিতে পারে — উপরের ডেমো এটি সরাসরি সংখ্যা দিয়ে দেখিয়েছে। M4-এ (বিশেষত L18) আমরা এই একই ধারণা সাধারণ ২য়-অর্ডার সিস্টেমেও প্রসারিত করব, যেখানে pole জটিল সংখ্যা হতে পারে এবং abs(pole) ব্যবহার করে ম্যাগনিটিউড গণনা করা হবে।

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

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

প্র ০১ যদি a = 1.0 ঠিক (pole ম্যাগনিটিউড ঠিক 1, না 1-এর কম না বেশি) হতো, তাহলে সিস্টেমটি কী আচরণ করবে বলে মনে হয়?

a=1 হলে y[n] = y[n-1] + x[n] — বাউন্ডেড ইনপুট x[n]=1 দিলে আউটপুট প্রতিটি স্টেপে ঠিক 1 করে বাড়বে (y[n] = n+1), অর্থাৎ রৈখিকভাবে (linearly) অসীমের দিকে যাবে — এটাও আনবাউন্ডেড, তাই এই সিস্টেমও BIBO স্ট্যাবল নয়, যদিও এটি a=1.5-এর মতো সূচকীয়ভাবে বাড়ে না। এটাই কারণ স্ট্যাবিলিটির শর্ত কঠোরভাবে |a| < 1, \le 1 নয়।

প্র ০২ System C ও System D-এর মধ্যে একমাত্র পার্থক্য কোয়েফিসিয়েন্ট a — একটি মাত্র সংখ্যার পরিবর্তন কীভাবে "সবসময় নিরাপদ" থেকে "সবসময় বিপজ্জনক" আচরণে নিয়ে যেতে পারে?

কারণ রিকার্সিভ সিস্টেমে আউটপুট নিজের আগের মানের উপর নির্ভর করে — প্রতিটি নতুন স্যাম্পল আগের মানকে a দিয়ে গুণ করে। |a| < 1 হলে বারবার গুণ করলে মান ক্রমশ ছোট হয়ে যায় (জ্যামিতিক ক্ষয়, geometric decay), কিন্তু |a| > 1 হলে বারবার গুণ করলে মান ক্রমশ বড় হতে থাকে (জ্যামিতিক বৃদ্ধি)। এই "থ্রেশহোল্ড এফেক্ট" (a=1-এর ঠিক দুই পাশে সম্পূর্ণ ভিন্ন আচরণ) রিকার্সিভ/IIR সিস্টেমের একটি মৌলিক বৈশিষ্ট্য, আর এটাই কেন IIR ফিল্টার ডিজাইনে (M8) স্ট্যাবিলিটি চেক করা অপরিহার্য একটি ধাপ।

প্র ০৩ L09-L12-এর FIR-ধরনের সিস্টেমগুলো (যেমন y[n]=0.5x[n]+0.5x[n-1]) কি কখনো BIBO অস্থির হতে পারে? কেন বা কেন নয়?

না — একটি FIR সিস্টেম (যেখানে আউটপুট শুধু ইনপুটের উপর নির্ভর করে, নিজের আগের আউটপুটের উপর নয়) সবসময় BIBO স্ট্যাবল, যতক্ষণ এর কোয়েফিসিয়েন্টগুলো (ইমপালস রেসপন্স h[n]) সসীম সংখ্যক এবং প্রতিটি সসীম মানের হয়। কারণ আউটপুট হলো ইনপুটের সসীম সংখ্যক স্যাম্পলের একটি ওয়েটেড যোগফল মাত্র — কোনো "ফিডব্যাক" নেই যা মানকে বারবার গুণ করে বাড়িয়ে দিতে পারে। এটাই FIR বনাম IIR-এর একটি বড় ব্যবহারিক পার্থক্য (M7 বনাম M8-এ বিস্তারিত)।

অনুশীলন

  1. চিন্তা করুন: System C-এর (a=0.5) স্টেডি-স্টেট মান সূত্র 1/(1-a) ব্যবহার করে হিসেব করুন যদি a = 0.8 হতো তাহলে স্টেডি-স্টেট আউটপুট কত হতো (একই x[n]=1 ইনপুটে)।

    1/(1-0.8) = 1/0.2 = 5.0 — অর্থাৎ a=0.8 হলে সিস্টেম এখনও স্ট্যাবল (|0.8| < 1), কিন্তু স্টেডি-স্টেটে পৌঁছাতে বেশি সময় লাগবে (ধীর ক্ষয়) এবং চূড়ান্ত মানও বড় (৫.০, a=0.5-এর ২.০-এর তুলনায়) — a যত 1-এর কাছাকাছি, স্টেডি-স্টেট মান তত বড় হয়।

  2. পরীক্ষা করুন: BIBO কোড সেলে a_stable = 0.5-কে a_stable = 0.8-এ পরিবর্তন করে Run চেপে চালান — শেষ কয়েকটি y[n] মান কি ৫.০-এর কাছাকাছি স্থির হচ্ছে?

    হ্যাঁ — রান করলে n=14-এ y[n] প্রায় ৪.৮২৪১ দেখাবে (১৫ স্যাম্পলে এখনও পুরোপুরি 5.0-এ না পৌঁছালেও ক্রমাগত কাছাকাছি যাচ্ছে) — আপনার হাতে-করা হিসেবের লক্ষ্য মান ৫.০-এর দিকেই স্পষ্টভাবে অগ্রসর হচ্ছে। লক্ষ্য করুন a=0.8-এ ১৫ স্যাম্পল পরও a=0.5-এর (যা 1.999939-এ প্রায় পুরোপুরি পৌঁছে গিয়েছিল) তুলনায় স্টেডি-স্টেট থেকে অনেক দূরে — a যত 1-এর কাছে যায়, স্টেডি-স্টেটে পৌঁছাতে তত বেশি স্যাম্পল লাগে — এটাই ব্যবহারিক ফিল্টার ডিজাইনে "সেটলিং টাইম" ধারণার ভিত্তি।

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

আগের পাঠ
ডিফারেন্স ইকুয়েশন ও ইমপালস রেসপন্স