পাঠ ৩৩ · ৫৭-এর মধ্যে · মডিউল ৭
Home / Courses / Digital Signal Processing / FIR ফিল্টার ডিজাইন

FIR ফিল্টার ইমপ্লিমেন্টেশন — ডাইরেক্ট ফর্ম

FIR filter implementation — direct form
৯ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • ডাইরেক্ট-ফর্ম FIR ইমপ্লিমেন্টেশনের কাঠামো — একটি স্যাম্পল-বাই-স্যাম্পল স্লাইডিং-উইন্ডো যোগফল
  • কেন এটি কনভোলিউশন-ফাংশন পদ্ধতির সাথে গাণিতিকভাবে সমতুল্য
  • একই ফিল্টার ও সিগন্যালে দুটো পদ্ধতি চালিয়ে ফলাফল সংখ্যাগতভাবে ক্রস-চেক করা
  • বাস্তব-সময়ের (streaming/real-time) প্রসেসিং-এ কেন ডাইরেক্ট ফর্ম ব্যবহারিকভাবে বেশি প্রাসঙ্গিক

১ · ডাইরেক্ট ফর্ম — সরাসরি ডিফারেন্স ইকুয়েশন থেকে কোড

L30-এ FIR ফিল্টারের ডিফারেন্স ইকুয়েশন দেখা হয়েছিল:

$$y[n] = \sum_{k=0}^{M-1} h[k]\,x[n-k]$$

ডাইরেক্ট ফর্ম ইমপ্লিমেন্টেশন মানে ঠিক এই সূত্রটিই — প্রতিটি আউটপুট নমুনা n-এর জন্য, ফিল্টারের M-টি ট্যাপকে ইনপুটের সংশ্লিষ্ট M-টি নমুনার (বর্তমান x[n] থেকে অতীতের দিকে x[n-M+1] পর্যন্ত) সাথে গুণ করে যোগফল বের করা — একটি "স্লাইডিং উইন্ডো" যা প্রতি ধাপে এক নমুনা করে সরে যায়। এটি M3/L09-এ শেখা কনভোলিউশনের "মুভিং ওয়েটেড সমষ্টি" রূপকটির সবচেয়ে সরাসরি কোড-রূপ।

২ · কনভোলিউশন ফাংশনের সাথে পার্থক্য — শুধু কাঠামো, গণিত নয়

L31-এর convolve(x, h) ফাংশন সম্পূর্ণ আউটপুট সিকোয়েন্স (দৈর্ঘ্য len(x)+len(h)-1) একবারে তৈরি করে, বাইরের লুপ আউটপুট ইনডেক্স n-এর উপর। ডাইরেক্ট-ফর্ম কোড একই কাজ করে, কিন্তু সাধারণত শুধুমাত্র n = 0 থেকে len(x)-1 পর্যন্ত (ইনপুটের সমান দৈর্ঘ্যের, causal আউটপুট) গণনা করে — বাস্তব সিস্টেমে আমরা সাধারণত ইনপুটের প্রতিটি নমুনার জন্য একটি আউটপুট নমুনা চাই, তার বেশি নয়। ভেতরের গণিত হুবহু একই — শুধু লুপ কীভাবে সাজানো হয়েছে তার পার্থক্য।

সহোদর ধারণার সাথে সম্পর্ক

M3/L10-এ দেখা হয়েছিল কনভোলিউশনকে নেস্টেড লুপ দিয়ে হাতে ইমপ্লিমেন্ট করলে ফলাফল হাতে-গণনার সাথে মিলে যায়। এখানে একই ধরনের ক্রস-চেক করা হচ্ছে, কিন্তু দুটো ভিন্ন কোড ইমপ্লিমেন্টেশনের মধ্যে — এটি দেখায় যে "একই গণিত, ভিন্ন কোড" নীতিটি নির্ভরযোগ্যভাবে যাচাইযোগ্য।

৩ · কোড দিয়ে ক্রস-চেক

নিচে L31-এর ঠিক একই ফিল্টার (fs=200 Hz, fc=30 Hz, M=31 ট্যাপ, হ্যামিং-উইন্ডোড সিংক) ও একই টেস্ট সিগন্যাল (১০ Hz + ৬০ Hz) পুনরায় তৈরি করে দুইভাবে প্রয়োগ করা হয়েছে — একবার কনভোলিউশন ফাংশন দিয়ে, একবার ডাইরেক্ট-ফর্ম স্লাইডিং-উইন্ডো লুপ দিয়ে।

Python
import math

fs = 200.0
f_lo = 10.0
f_hi = 60.0
N = 60
x = [math.sin(2*math.pi*f_lo*n/fs) + math.sin(2*math.pi*f_hi*n/fs) for n in range(N)]

fc = 30.0
fc_norm = fc / fs
M = 31
center = (M - 1)//2

def sinc_lp(d, fc_norm):
    if d == 0:
        return 2*fc_norm
    return math.sin(2*math.pi*fc_norm*d)/(math.pi*d)

h_ideal = [sinc_lp(n-center, fc_norm) for n in range(M)]
w = [0.54 - 0.46*math.cos(2*math.pi*n/(M-1)) for n in range(M)]
h = [h_ideal[n]*w[n] for n in range(M)]

# ---- পদ্ধতি ১: কনভোলিউশন ফাংশন (L31-এর প্যাটার্ন) ----
def convolve(a, b):
    ny = len(a)+len(b)-1
    y = [0.0]*ny
    for n in range(ny):
        s = 0.0
        for k in range(len(b)):
            if 0 <= n-k < len(a):
                s += b[k]*a[n-k]
        y[n] = s
    return y

y_conv_full = convolve(x, h)
y_conv = y_conv_full[:len(x)]   # প্রথম len(x) নমুনা = causal আউটপুট, n=0..N-1

# ---- পদ্ধতি ২: ডাইরেক্ট ফর্ম -- স্যাম্পল-বাই-স্যাম্পল স্লাইডিং-উইন্ডো সমষ্টি ----
def fir_direct_form(x, h):
    y = []
    for n in range(len(x)):
        s = 0.0
        for k in range(len(h)):
            if n - k >= 0:
                s += h[k]*x[n-k]
        y.append(s)
    return y

y_direct = fir_direct_form(x, h)

max_diff = max(abs(y_conv[n]-y_direct[n]) for n in range(len(x)))
print(f"len(y_conv) = {len(y_conv)}   len(y_direct) = {len(y_direct)}")
print(f"সর্বোচ্চ পার্থক্য (দুই পদ্ধতির মধ্যে): {max_diff}")
print()
print(f"{'n':>3} | {'y_conv[n]':>12} | {'y_direct[n]':>12} | {'diff':>10}")
for n in [0, 1, 5, 14, 15, 16, 30, 45, 59]:
    diff = abs(y_conv[n]-y_direct[n])
    print(f"{n:>3} | {y_conv[n]:>12.8f} | {y_direct[n]:>12.8f} | {diff:>10.2e}")

    
প্রকৃত আউটপুট নিশ্চিত করে — len(y_conv) = len(y_direct) = 60, এবং সর্বোচ্চ পার্থক্য ঠিক ০.০ — সব ৬০টি নমুনায়। টেবিলেও প্রতিটি সারিতে y_conv[n] ও y_direct[n] কলাম বিট-ফর-বিট সমান (যেমন n=16-এ দুটোই 0.40333240, n=30-এ দুটোই -0.99990092)। এটি একটি সরাসরি প্রমাণ যে কনভোলিউশন-ফাংশন পদ্ধতি (L31) আর ডাইরেক্ট-ফর্ম স্লাইডিং-উইন্ডো পদ্ধতি (এই পাঠ) — দুটোই ঠিক একই ডিফারেন্স ইকুয়েশন গণনা করছে, শুধু কোডের গঠন ভিন্ন।
মূল কথা · Key takeaway

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

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

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

প্র ০১ fir_direct_form ফাংশনে if n - k >= 0 শর্তটি কেন দরকার?

n - k ঋণাত্মক হলে সেটি একটি নমুনার সূচক যা এখনো "ঘটেনি" (ভবিষ্যতের ইনপুট বা সিগন্যালের শুরুর আগের নমুনা, যা সংজ্ঞায়িত নয়)। শর্তটি নিশ্চিত করে ফিল্টার শুধু বর্তমান ও অতীত ইনপুট নমুনাই ব্যবহার করছে — এটাই কজালিটির (causality, M3/L13-এ সংজ্ঞায়িত) শর্ত। এই শর্ত ছাড়া কোডটি x[n-k]-এ ঋণাত্মক ইনডেক্সে চলে যেত, যা Python-এ ভুলভাবে তালিকার শেষ থেকে মান নিয়ে আসত (wrap-around) — একটি সূক্ষ্ম কিন্তু গুরুতর বাগ।

প্র ০২ যদি y_conv আর y_direct-এর মধ্যে সর্বোচ্চ পার্থক্য ঠিক 0.0 না হয়ে 1e-14-এর মতো ছোট একটি সংখ্যা হতো, তাহলে কি সেটা একটি বাগের লক্ষণ হতো?

না, প্রয়োজনীয়ভাবে না। M6/L27-28-এ দেখা হয়েছিল ভিন্ন গণনার ক্রম (যেমন FFT বনাম ডাইরেক্ট DFT) থেকে 10⁻¹⁰ থেকে 10⁻¹⁴ মাত্রার ফ্লোটিং-পয়েন্ট পার্থক্য আসা স্বাভাবিক ও প্রত্যাশিত। এই পাঠে ঠিক 0.0 পাওয়া গেছে কারণ দুই পদ্ধতিতে গুণ-যোগের ক্রম প্রায় অভিন্ন — কিন্তু 10⁻¹⁴ মাত্রার পার্থক্যও "মিলে গেছে" বলেই গণ্য হতো, কারণ এটি ফ্লোটিং-পয়েন্ট নির্ভুলতার স্বাভাবিক সীমার মধ্যে। সমস্যা তখনই হতো যদি পার্থক্য 0.01-এর মতো তুলনামূলক বড় হতো।

প্র ০৩ ডাইরেক্ট-ফর্ম পদ্ধতিতে প্রতিটি আউটপুট নমুনার জন্য প্রায় কতগুলো গুণ (multiply) করতে হয় এই M=31-ট্যাপ ফিল্টারে, এবং সম্পূর্ণ ৬০-নমুনার সিগন্যালের জন্য মোট কত গুণ লাগে (আনুমানিক)?

প্রতিটি আউটপুট নমুনার জন্য সর্বোচ্চ M=31টি গুণ (প্রতিটি ট্যাপের জন্য একটি, যদিও শুরুর কয়েকটি নমুনায় n-k >= 0 শর্তের কারণে কম গুণ হয়)। মোটামুটি হিসেবে, ৬০টি আউটপুট নমুনার জন্য প্রায় 60 × 31 ≈ 1860টি গুণ লাগে — এটাই একটি M-ট্যাপ FIR ফিল্টারের সাধারণ কম্পিউটেশন কস্ট, প্রতি নমুনায় O(M), সম্পূর্ণ সিগন্যালে O(N×M)। L34-এ দেখা যাবে ট্যাপ সংখ্যা বাড়ালে এই কস্টও সরাসরি অনুপাতে বাড়ে।

অনুশীলন

  1. চিন্তা করুন: উপরের কোড সেলে N = 60-কে N = 30-এ পরিবর্তন করলে (টেস্ট সিগন্যাল ছোট করলে), y_conv আর y_direct-এর মধ্যে সর্বোচ্চ পার্থক্য কি বদলাবে বলে আপনার ধারণা?

    না, সর্বোচ্চ পার্থক্য এখনও ঠিক 0.0 থাকবে — কারণ দুই পদ্ধতির গাণিতিক সমতা সিগন্যালের দৈর্ঘ্যের উপর নির্ভর করে না, এটি প্রতিটি n-এ পৃথকভাবে সত্য। শুধু টেবিলে দেখানো নমুনার সংখ্যা কম হবে (যেহেতু N=30)।

  2. পরীক্ষা করুন: কোড সেলে N = 60-কে N = 30-এ পরিবর্তন করে Run চেপে আপনার হিসেব যাচাই করুন — লক্ষ্য করুন [0, 1, 5, 14, 15, 16, 30, 45, 59] তালিকার শেষ কয়েকটি ইনডেক্স (৩০-এর বেশি) এখন IndexError দেবে, কারণ সিগন্যাল ছোট হয়ে গেছে — সেই তালিকাটিও ছোট সংখ্যায় বদলে নিন এবং আবার চালান।

    তালিকা [0, 1, 5, 14, 15, 16, 29]-এ বদলে চালালে সব সারিতে diff = 0.00e+00 দেখাবে — নিশ্চিত করে যে দুই পদ্ধতির মধ্যে সংখ্যাগত সমতা সিগন্যালের দৈর্ঘ্য নির্বিশেষে বজায় থাকে।

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

আগের পাঠ
FIR ফিল্টার ডিজাইন — ফ্রিকোয়েন্সি স্যাম্পলিং মেথড