IIR ফিল্টার ইমপ্লিমেন্টেশন — ডাইরেক্ট ফর্ম I ও II
এই পাঠে যা শিখবেন
- একটি ২য়-অর্ডার IIR ফিল্টারের সাধারণ ডিফারেন্স ইকুয়েশন এবং এর দুটি বাস্তবায়ন-স্ট্রাকচার
- Direct Form I ও Direct Form II উভয়ই স্যাম্পল-বাই-স্যাম্পল লুপ হিসেবে হাতে-কলমে বাস্তবায়ন করা
- দুটি স্বতন্ত্র বাস্তবায়ন সত্যিই অভিন্ন আউটপুট দেয় কিনা তা genuine সংখ্যাগত ক্রস-চেক দিয়ে যাচাই করা
- কেন Direct Form II কম মেমোরি ব্যবহার করে এবং বাস্তব (বিশেষত এমবেডেড) সিস্টেমে এটি কেন গুরুত্বপূর্ণ
১ · একই ফিল্টার, দুই স্ট্রাকচার
একটি সাধারণ ২য়-অর্ডার IIR ফিল্টারের ডিফারেন্স ইকুয়েশন:
$$y[n] = b_0 x[n] + b_1 x[n-1] + b_2 x[n-2] - a_1 y[n-1] - a_2 y[n-2]$$
এই একই সমীকরণ গণনা করার একাধিক উপায় আছে — কোয়েফিশিয়েন্ট বা ফলাফল বদলায় না, শুধু কীভাবে মধ্যবর্তী মানগুলো সংরক্ষণ করা হয় তা বদলায়। এই বিভিন্ন বিন্যাসকে বলা হয় স্ট্রাকচারStructure (realization)একই ট্রান্সফার ফাংশনের একাধিক সমতুল্য বাস্তবায়ন-বিন্যাস, যেগুলোর গাণিতিক আউটপুট অভিন্ন কিন্তু মেমোরি/কম্পিউটেশন ব্যবহারের ধরন ভিন্ন। বা realization। M4/L18-এর ট্রান্সফার ফাংশন একই থাকলেও, বাস্তব হার্ডওয়্যার বা কোডে এটি বাস্তবায়নের ধরন কর্মক্ষমতা (মেমোরি, নির্ভুলতা) প্রভাবিত করতে পারে।
ইনপুট ও আউটপুট ইতিহাসের জন্য আলাদা ডিলে-লাইন — সরলতম, সবচেয়ে সরাসরি ম্যাপিং সমীকরণ থেকে কোডে।
একটি একক শেয়ারড ডিলে-লাইন — একই অর্ডারের জন্য অর্ধেক মেমোরি রেজিস্টার।
২ · Direct Form I
Direct Form I সরাসরি উপরের সমীকরণ অনুসরণ করে — ইনপুট ইতিহাস ($x[n-1], x[n-2]$) এবং আউটপুট ইতিহাস ($y[n-1], y[n-2]$) সম্পূর্ণ আলাদাভাবে সংরক্ষণ করা হয়। মোট $4$টি মেমোরি রেজিস্টার লাগে (একটি ২য়-অর্ডার ফিল্টারে)।
৩ · Direct Form II
Direct Form II একটি একক মধ্যবর্তী রাশি $w[n]$ ব্যবহার করে — প্রথমে ফিডব্যাক অংশ প্রয়োগ করে $w[n]$ পাওয়া যায়, তারপর ফিডফরওয়ার্ড অংশ প্রয়োগ করে $y[n]$:
$$w[n] = x[n] - a_1 w[n-1] - a_2 w[n-2]$$ $$y[n] = b_0 w[n] + b_1 w[n-1] + b_2 w[n-2]$$
এখানে শুধু $w[n-1], w[n-2]$ — মোট $2$টি মেমোরি রেজিস্টার — সংরক্ষণ করলেই চলে, কারণ $x$ এবং $y$-এর জন্য আলাদা ডিলে-লাইনের দরকার নেই; দুটোই একই $w$-ডিলে-লাইন থেকে গণনা হয়। একটি $N$-অর্ডার ফিল্টারের জন্য সাধারণ নিয়ম: Direct Form I-এ $2N$টি রেজিস্টার লাগে, Direct Form II-এ মাত্র $N$টি — ঠিক অর্ধেক।
নিচের কোড সেলে একটি স্থিতিশীল ২য়-অর্ডার IIR ফিল্টার (কোয়েফিশিয়েন্ট $b_0=0.2, b_1=0.3, b_2=0.1$, $a_1=-0.6, a_2=0.25$) দুটি স্ট্রাকচারেই সম্পূর্ণ স্বতন্ত্রভাবে বাস্তবায়ন করে একই টেস্ট সিগন্যালে প্রয়োগ করা হয়েছে। প্রথমে পোল কম্পিউট করে স্থিতিশীলতা যাচাই করা হয়েছে (L13, L18-এর পদ্ধতি অনুসরণ করে):
import math, cmath
b0, b1, b2 = 0.2, 0.3, 0.1
a1, a2 = -0.6, 0.25
# পোল যাচাই: z^2 + a1*z + a2 = 0
disc = a1 ** 2 - 4 * a2
if disc >= 0:
r1 = (-a1 + math.sqrt(disc)) / 2
r2 = (-a1 - math.sqrt(disc)) / 2
else:
r1 = (-a1 + cmath.sqrt(disc)) / 2
r2 = (-a1 - cmath.sqrt(disc)) / 2
print(f"পোল: {r1}, {r2} |r1|={abs(r1):.4f} |r2|={abs(r2):.4f}")
N = 30
x = [math.sin(2 * math.pi * 5 * n / 50) + 0.3 * math.sin(2 * math.pi * 15 * n / 50)
for n in range(N)]
# --- Direct Form I: আলাদা x ও y ডিলে-লাইন ---
y1 = [0.0] * N
for n in range(N):
xn1 = x[n - 1] if n - 1 >= 0 else 0.0
xn2 = x[n - 2] if n - 2 >= 0 else 0.0
yn1 = y1[n - 1] if n - 1 >= 0 else 0.0
yn2 = y1[n - 2] if n - 2 >= 0 else 0.0
y1[n] = b0 * x[n] + b1 * xn1 + b2 * xn2 - a1 * yn1 - a2 * yn2
# --- Direct Form II: একক শেয়ারড ডিলে-লাইন w ---
y2 = [0.0] * N
w_hist = [0.0, 0.0] # w[n-1], w[n-2]
for n in range(N):
wn = x[n] - a1 * w_hist[0] - a2 * w_hist[1]
y2[n] = b0 * wn + b1 * w_hist[0] + b2 * w_hist[1]
w_hist[1] = w_hist[0]
w_hist[0] = wn
print()
print(f"{'n':>3} | {'DF-I y[n]':>10} | {'DF-II y[n]':>10} | {'diff':>10}")
max_diff = 0.0
for n in range(N):
diff = abs(y1[n] - y2[n])
max_diff = max(max_diff, diff)
print(f"{n:>3} | {y1[n]:>10.6f} | {y2[n]:>10.6f} | {diff:>10.2e}")
print()
print("DF-I ও DF-II আউটপুটের মধ্যে সর্বোচ্চ পার্থক্য:", max_diff)
Direct Form I-এ ইনপুট ইতিহাস ও আউটপুট ইতিহাস স্বতন্ত্র সিকোয়েন্স হিসেবে সংরক্ষিত হয় — তাই একটি $N$-অর্ডার ফিল্টারে $N$টি ইনপুট-ডিলে + $N$টি আউটপুট-ডিলে মিলিয়ে $2N$টি রেজিস্টার লাগে। Direct Form II-এর অন্তর্দৃষ্টি হলো — যেহেতু ফিডব্যাক অংশ (ডিনোমিনেটর) এবং ফিডফরওয়ার্ড অংশ (নিউমারেটর) একই ট্রান্সফার ফাংশনের অংশ, তাদের মধ্যবর্তী রাশি $w[n]$ ভাগ করে নেওয়া সম্ভব — ফলে মাত্র $N$টি রেজিস্টারেই কাজ চলে। এই অর্ধেক মেমোরি সাশ্রয় ছোট এমবেডেড DSP চিপে (যেখানে মেমোরি সীমিত ও ব্যয়বহুল) বিশেষভাবে গুরুত্বপূর্ণ — যদিও ব্যবহারিক ফিক্সড-পয়েন্ট বাস্তবায়নে DF-II-এর ভিন্ন সংখ্যাগত (নির্ভুলতা/ওভারফ্লো) আচরণও থাকতে পারে, যা এই পাঠের পরিসরের বাইরে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ DF-I আর DF-II-এর আউটপুট ঠিক শূন্য পার্থক্যে না মিলে $\approx 2.22\times10^{-16}$ পার্থক্যে মিলল কেন?
কারণ দুটি স্ট্রাকচার গাণিতিকভাবে সমতুল্য হলেও, গণনার ক্রম ভিন্ন — DF-I আগে $b_0 x[n]+b_1x[n-1]+b_2x[n-2]$ যোগ করে তারপর ফিডব্যাক বিয়োগ করে, DF-II আগে $w[n]$ (ফিডব্যাক) গণনা করে তারপর সেটি থেকে $y[n]$ (ফিডফরওয়ার্ড) গণনা করে। ফ্লোটিং-পয়েন্ট গাণিতিক অপারেশনের ক্রম বদলালে (যদিও গাণিতিকভাবে ফলাফল একই হওয়ার কথা) রাউন্ডিং এরর সামান্য ভিন্নভাবে জমা হয় — এটি এই কোর্সের CLAUDE.md-এ উল্লেখিত সাধারণ ফ্লোটিং-পয়েন্ট নির্ভুলতার সীমা, কোনো বাগ নয়।
প্র ০২ যদি এই ফিল্টারের অর্ডার ২ থেকে বাড়িয়ে ৫ করা হতো, DF-I আর DF-II-তে মোট মেমোরি রেজিস্টারের সংখ্যা কী হতো?
সাধারণ নিয়ম অনুযায়ী — DF-I-এ $2N$টি রেজিস্টার লাগে, তাই $N=5$-এ $10$টি (৫টি ইনপুট-ডিলে + ৫টি আউটপুট-ডিলে)। DF-II-এ মাত্র $N$টি রেজিস্টার লাগে, তাই $N=5$-এ মাত্র $5$টি — অর্ডার যত বাড়ে, মেমোরি সাশ্রয়ের পরম পার্থক্যও তত বাড়ে (DF-I বনাম DF-II-এর ব্যবধান সবসময় ঠিক $N$টি রেজিস্টার)।
প্র ০৩
উপরের কোড সেলে disc >= 0 চেক করে বাস্তব বনাম কমপ্লেক্স পোল আলাদাভাবে হ্যান্ডল করা
হয়েছে কেন?
২য়-অর্ডার ক্যারেক্টারিস্টিক সমীকরণ $z^2+a_1z+a_2=0$-এর সমাধান কোয়াড্রেটিক ফর্মুলা দিয়ে পাওয়া যায়,
যেখানে ডিসক্রিমিন্যান্ট ($a_1^2-4a_2$) ঋণাত্মক হলে বর্গমূল একটি কমপ্লেক্স সংখ্যা হয়ে যায় —
math.sqrt() এতে ValueError দেয় (এটি শুধু বাস্তব সংখ্যা নেয়), কিন্তু
cmath.sqrt() কমপ্লেক্স ইনপুট/আউটপুট সঠিকভাবে হ্যান্ডল করে। এই পাঠের ফিল্টারেই ডিসক্রিমিন্যান্ট
ঋণাত্মক (কমপ্লেক্স কনজুগেট পোল জোড়া) — তাই cmath শাখাটি এখানে সক্রিয় হয়।
অনুশীলন
-
চিন্তা করুন: যদি
a1, a2-কে এমনভাবে বদলানো হয় যাতে $|r_1|$ বা $|r_2|$ $1$-এর বেশি হয়ে যায়, DF-I আর DF-II কি তখনও একে অপরের সাথে মিলবে বলে আপনার ধারণা?হ্যাঁ, তারা তখনও একে অপরের সাথে মিলবে — কারণ দুটি স্ট্রাকচারের গাণিতিক সমতা পোলের অবস্থানের উপর নির্ভর করে না, শুধু কোয়েফিশিয়েন্ট একই থাকলেই হয়। তবে উভয়ের আউটপুটই তখন BIBO অস্থিতিশীল হয়ে দ্রুত বৃদ্ধি পেতে থাকবে (L13-এর মতোই) — অর্থাৎ দুটোই একে অপরের সাথে মিলবে, কিন্তু উভয়েই "ভুল" (অস্থিতিশীল) আউটপুট দেবে।
-
পরীক্ষা করুন: কোড সেলে
b0, b1, b2 = 0.2, 0.3, 0.1-কেb0, b1, b2 = 0.5, 0.0, -0.5-এ বদলে Run চেপে দেখুন সর্বোচ্চ পার্থক্য এখনও ফ্লোটিং-পয়েন্ট সীমার মধ্যেই থাকে কিনা।রান করলে দেখা যাবে সর্বোচ্চ পার্থক্য এখনও অত্যন্ত ছোট (একই মাত্রার, $10^{-16}$ থেকে $10^{-15}$-এর কাছাকাছি) থাকে — কারণ $b$ কোয়েফিশিয়েন্ট বদলালেও দুটি স্ট্রাকচারের গাণিতিক সমতা অক্ষুণ্ণ থাকে, শুধু ফিডফরওয়ার্ড অংশের ওজন বদলায়, ফিডব্যাক/ডিলে-লাইনের গঠন নয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পরবর্তী পাঠ — ডাউনস্যাম্পলিং (ডেসিমেশন) L40 · মডিউল ৯ M8-এর FIR/IIR ফিল্টার ডিজাইন শেষে এবার মাল্টিরেট সিগন্যাল প্রসেসিং — স্যাম্পল রেট কমানো ও বাড়ানোর কৌশল।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ স্যাম্পলিং, কনভোলিউশন, Z-ট্রান্সফর্ম, DTFT/DFT, FFT, FIR/IIR ফিল্টার ডিজাইন, মাল্টিরেট প্রসেসিং, স্পেকট্রাল এস্টিমেশন, র্যান্ডম সিগন্যাল প্রসেসিং, বাস্তব প্রয়োগ ও ক্যাপস্টোন।
- Numerical Methods কোর্স সহোদর কোর্স সাধারণ সংখ্যাগত অ্যালগরিদম, রুট-ফাইন্ডিং, ইন্টিগ্রেশন ও ODE সলভিং — এই কোর্সের "হাতে-লেখা" দর্শনের একই ভিত্তি।