FIR ফিল্টার ইমপ্লিমেন্টেশন — ডাইরেক্ট ফর্ম
এই পাঠে যা শিখবেন
- ডাইরেক্ট-ফর্ম 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) পুনরায় তৈরি করে দুইভাবে প্রয়োগ করা হয়েছে — একবার কনভোলিউশন ফাংশন দিয়ে, একবার ডাইরেক্ট-ফর্ম স্লাইডিং-উইন্ডো লুপ দিয়ে।
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) আর ডাইরেক্ট-ফর্ম স্লাইডিং-উইন্ডো পদ্ধতি (এই পাঠ) — দুটোই ঠিক একই ডিফারেন্স ইকুয়েশন গণনা করছে, শুধু
কোডের গঠন ভিন্ন।
ডাইরেক্ট ফর্ম আর কনভোলিউশন-ফাংশন পদ্ধতি গাণিতিকভাবে অভিন্ন — বেছে নেওয়ার সিদ্ধান্ত নির্ভর করে ব্যবহারিক
প্রেক্ষাপটের উপর। বাস্তব-সময়ের সিস্টেমে (যেমন একটি অডিও ডিভাইস যা প্রতি মুহূর্তে একটি নতুন স্যাম্পল
পায়) ডাইরেক্ট ফর্মই স্বাভাবিক পছন্দ, কারণ এটি প্রতিটি নতুন স্যাম্পল আসার সাথে সাথে একটি আউটপুট দিতে পারে
— পুরো ভবিষ্যৎ সিগন্যাল আগে থেকে জানার দরকার নেই। 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-এ দেখা যাবে ট্যাপ সংখ্যা বাড়ালে এই কস্টও
সরাসরি অনুপাতে বাড়ে।
অনুশীলন
-
চিন্তা করুন: উপরের কোড সেলে
N = 60-কেN = 30-এ পরিবর্তন করলে (টেস্ট সিগন্যাল ছোট করলে),y_convআরy_direct-এর মধ্যে সর্বোচ্চ পার্থক্য কি বদলাবে বলে আপনার ধারণা?না, সর্বোচ্চ পার্থক্য এখনও ঠিক
0.0থাকবে — কারণ দুই পদ্ধতির গাণিতিক সমতা সিগন্যালের দৈর্ঘ্যের উপর নির্ভর করে না, এটি প্রতিটিn-এ পৃথকভাবে সত্য। শুধু টেবিলে দেখানো নমুনার সংখ্যা কম হবে (যেহেতুN=30)। -
পরীক্ষা করুন: কোড সেলে
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 ফিল্টার ডিজাইন ট্রেড-অফ — অর্ডার বনাম পারফরম্যান্স পরবর্তী পাঠ ফিল্টার ট্যাপ সংখ্যা বাড়ালে/কমালে এটেনুয়েশন শার্পনেস ও কম্পিউটেশন কস্টে কী প্রভাব পড়ে — সত্যিকারের কম্পিউটেড তুলনা।
- ডিসক্রিট কনভোলিউশন — হাতে গণনা M3 রিভিউ এই পাঠের কনভোলিউশন ফাংশনের ভিত্তি — নেস্টেড লুপ দিয়ে হাতে-গণনা যাচাই।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ স্যাম্পলিং, কনভোলিউশন, Z-ট্রান্সফর্ম, DTFT/DFT, FFT, FIR/IIR ফিল্টার ডিজাইন, মাল্টিরেট প্রসেসিং, স্পেকট্রাল এস্টিমেশন, র্যান্ডম সিগন্যাল প্রসেসিং, বাস্তব প্রয়োগ ও ক্যাপস্টোন।