পাঠ ৪২ · ৫৭-এর মধ্যে · মডিউল ৯
Home / Courses / Digital Signal Processing / পলিফেজ ফিল্টার

পলিফেজ ফিল্টার ও এফিসিয়েন্ট মাল্টিরেট স্ট্রাকচার

Polyphase filters & efficient multirate structures
১১ মিনিট পড়া মধ্যম-কঠিন · Intermediate-Advanced Python কোডসহ সম্পূর্ণ বাংলায়

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

  • কেন সরল "ফিল্টার-তারপর-ডেসিমেট" পদ্ধতি অপ্রয়োজনীয় গণনায় সময় নষ্ট করে
  • ফিল্টার ট্যাপকে লিস্ট-স্লাইসিং দিয়ে Mটি ফেজ-সাব-ফিল্টারে ভাগ করা
  • পলিফেজ কাঠামো L40-এর সরাসরি পদ্ধতির সাথে সংখ্যাগতভাবে অভিন্ন ফলাফল দেয় তা নিজে যাচাই করা
  • প্রকৃত গুণন-অপারেশন গুনে দেখা (fabricated টাইমিং নয়) যে পলিফেজ কাঠামো সত্যিই কম গণনায় একই ফলাফল দেয়

১ · সমস্যাটা কী — অপচয় হওয়া গণনা

L40-এ আমরা একটি সিগন্যালকে M ফ্যাক্টরে ডেসিমেট করার আগে একটি numtaps-ট্যাপ অ্যান্টি-অ্যালিয়াস FIR ফিল্টার প্রয়োগ করেছিলাম — M3/L10-এর নেস্টেড-লুপ কনভোলিউশন দিয়ে, যেখানে প্রতিটি ইনপুট রেটের আউটপুট স্যাম্পল গণনা করা হয়েছিল, তারপর তার মধ্যে থেকে শুধু প্রতি M-তমটি রাখা হয়েছিল। অর্থাৎ প্রতি Mটি গণনা-করা আউটপুটের M-1টিই তৎক্ষণাৎ বাতিল — কিন্তু সেগুলো গণনা করতে পূর্ণ numtaps গুণন-অপারেশন ব্যয় হয়ে গেছে।

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

$$h_p[i] = h[p + M\cdot i], \qquad p = 0, 1, \dots, M-1$$

Python-এ এটি নিছক স্লাইসিং: h_p = h[p::M]। প্রতিটি h_p মূল ফিল্টারের প্রায় numtaps/M ট্যাপ লম্বা — মূল ফিল্টারের তুলনায় অনেক ছোট।

ফেজ-স্লাইসিং
h[0::M], h[1::M], ... — প্রতিটি সাব-ফিল্টার ট্যাপ-সংখ্যায় প্রায় M ভাগের ১।
নিম্ন রেটে গণনা
প্রতিটি সাব-ফিল্টার ইনপুট সিগন্যালের একটি ডেসিমেটেড (নিম্ন-রেট) ফেজে কাজ করে — উচ্চ রেটে কখনও পূর্ণ কনভোলিউশন চালানো হয় না।
একই গাণিতিক ফলাফল
বীজগণিতীয়ভাবে এটি শুধু মূল কনভোলিউশন সামেশনকে পুনর্বিন্যাস করা — তাই আউটপুট অভিন্ন থাকা উচিত (নিচে যাচাই করা হয়েছে)।

২ · সত্যিকারের ক্রস-চেক — একই ইনপুট, একই আউটপুট, কম গুণন

L40-এর ঠিক একই টেস্ট সিগন্যাল ও ফিল্টার ব্যবহার করে (৩ Hz + ৮ Hz, fs=48, M=4, numtaps=25) দুটো পদ্ধতি পাশাপাশি চালানো যাক — L40-এর সরাসরি ফিল্টার-তারপর-ডেসিমেট, আর এখানকার পলিফেজ কাঠামো — এবং প্রতিটি পদ্ধতির প্রকৃত গুণন-অপারেশন গুনে রাখা যাক।

Python
import math

def sinc_lowpass(numtaps, fc):
    M = (numtaps - 1) / 2.0
    h = []
    for n in range(numtaps):
        d = n - M
        if abs(d) < 1e-9:
            hn = 2 * fc
        else:
            hn = math.sin(2 * math.pi * fc * d) / (math.pi * d)
        w = 0.54 - 0.46 * math.cos(2 * math.pi * n / (numtaps - 1))
        h.append(hn * w)
    return h

def convolve_counted(x, h):
    """ডিসক্রিট কনভোলিউশন (M3/L10) -- সাথে প্রকৃত গুণন-অপারেশন গুনে রাখা"""
    nx, nh = len(x), len(h)
    y = [0.0] * (nx + nh - 1)
    mults = 0
    for n in range(nx):
        for k in range(nh):
            y[n + k] += x[n] * h[k]
            mults += 1
    return y, mults

def x_get(x, j):
    """রেঞ্জের বাইরের ইনডেক্সকে শূন্য ধরা (zero-padding), L40-এর মতোই"""
    return x[j] if 0 <= j < len(x) else 0.0

# -- L40-এর হুবহু একই টেস্ট সিগন্যাল ও ফিল্টার --
fs, N = 48.0, 48
f_low, f_high = 3.0, 8.0
x = [math.sin(2*math.pi*f_low*n/fs) + 0.8*math.sin(2*math.pi*f_high*n/fs) for n in range(N)]

M = 4
numtaps = 25
fc = (fs/M/2) / fs
h = sinc_lowpass(numtaps, fc)
delay = (numtaps - 1) // 2

# ---- পদ্ধতি ১: সরাসরি (L40-এর মতো) -- পুরো কনভোলিউশন, তারপর ডেসিমেট ----
y_full, mults_direct = convolve_counted(x, h)
y_aligned = y_full[delay: delay + N]
y_direct_dec = y_aligned[::M]
n_out = len(y_direct_dec)

# ---- পদ্ধতি ২: পলিফেজ -- M-টি ফেজ-সাব-ফিল্টার, প্রতিটি নিম্ন রেটে ----
h_phases = [h[p::M] for p in range(M)]
for p in range(M):
    print(f"h_phase[{p}] দৈর্ঘ্য: {len(h_phases[p])} ট্যাপ")

y_poly = [0.0] * n_out
mults_poly = 0
for p in range(M):
    h_p = h_phases[p]
    c = delay - p
    j_min = -(len(h_p) - 1)                                  # সাব-ফিল্টারের জন্য প্রয়োজনীয় "ইতিহাস"
    e_p = [x_get(x, M*j + c) for j in range(j_min, n_out)]    # ফেজ p-এর নিম্ন-রেট ইনপুট সেগমেন্ট
    branch_out, branch_mults = convolve_counted(e_p, h_p)
    mults_poly += branch_mults
    offset = len(h_p) - 1
    for m in range(n_out):
        y_poly[m] += branch_out[m + offset]

print(f"\n{'m':>3} | {'সরাসরি (L40)':>13} | {'পলিফেজ':>13} | {'পার্থক্য':>10}")
max_diff = 0.0
for m in range(n_out):
    diff = abs(y_direct_dec[m] - y_poly[m])
    max_diff = max(max_diff, diff)
    print(f"{m:>3} | {y_direct_dec[m]:>13.6f} | {y_poly[m]:>13.6f} | {diff:>10.2e}")

print(f"\nসর্বোচ্চ পার্থক্য: {max_diff:.2e}  (ফ্লোটিং-পয়েন্ট নির্ভুলতার সীমা -- ব্যবহারিকভাবে অভিন্ন)")
print(f"\nগুণন-অপারেশন -- সরাসরি: {mults_direct},  পলিফেজ: {mults_poly},  অনুপাত: {mults_direct/mults_poly:.2f}x")

    
দুটো পদ্ধতির আউটপুট (সরাসরি আর পলিফেজ কলাম) সব m-এর জন্য একই — সর্বোচ্চ পার্থক্য মাত্র ২.২২ × ১০⁻¹⁶, যা শুধু ফ্লোটিং-পয়েন্ট রাউন্ডিং-এর কারণে, বীজগণিতীয়ভাবে সম্পূর্ণ অভিন্ন প্রমাণ করে (M6-এ FFT বনাম DFT ক্রস-চেকেও একই ধরনের ক্ষুদ্র পার্থক্য দেখা গিয়েছিল)। কিন্তু গুণন-অপারেশনে সরাসরি পদ্ধতি লাগে ১২০০টি (N × numtaps = 48 × 25), আর পলিফেজ পদ্ধতি লাগে মাত্র ৪৩২টি — প্রায় ২.৭৮ গুণ কম, একই ফলাফলের জন্য।

৩ · সাশ্রয় লম্বা সিগন্যালে তত্ত্বীয় সীমার কাছাকাছি পৌঁছায়

২.৭৮× সাশ্রয় তত্ত্বীয় সর্বোচ্চ M = 4×-এর চেয়ে কম কেন? কারণ প্রতিটি ফেজ-সাব-ফিল্টারের নিজস্ব একটি ছোট "ইতিহাস" প্রান্ত (boundary overhead) দরকার (উপরের কোডে j_min) — একটি ছোট সিগন্যালে (N=48) এই ফিক্সড ওভারহেড মোট গণনার একটি বড় অংশ। সিগন্যাল যত লম্বা হয়, এই ওভারহেড তত কম গুরুত্বপূর্ণ হয়ে পড়ে (আনুপাতিকভাবে) — নিচের কোডে বিভিন্ন N-এর জন্য একই ফিল্টার-আকৃতি ব্যবহার করে এটি সত্যিই ঘটে কি না দেখা যাক।

Python
# একই ফিল্টার-আকৃতি (h_phases-এর সাব-ফিল্টার দৈর্ঘ্য), বিভিন্ন সিগন্যাল দৈর্ঘ্য N
h_lens = [len(hp) for hp in h_phases]   # [7, 6, 6, 6], উপরের সেল থেকে

print(f"{'N':>6} | {'n_out':>6} | {'mults_direct':>13} | {'mults_poly':>11} | {'অনুপাত':>8}")
for N_test in [48, 96, 480, 4800]:
    n_out_test = N_test // M
    mults_direct_test = N_test * numtaps
    mults_poly_test = sum((n_out_test + hl - 1) * hl for hl in h_lens)
    ratio = mults_direct_test / mults_poly_test
    print(f"{N_test:>6} | {n_out_test:>6} | {mults_direct_test:>13} | {mults_poly_test:>11} | {ratio:>7.2f}x")

print(f"\nতত্ত্বীয় সর্বোচ্চ সাশ্রয় (M): {M}x -- N বাড়ার সাথে সাথে অনুপাত এই সীমার কাছাকাছি পৌঁছায়")

    
টেবিলে দেখা যায় N=৪৮-এ অনুপাত ২.৭৮×, N=৯৬-এ ৩.২৮×, N=৪৮০-এ ৩.৮৩×, আর N=৪৮০০-এ ৩.৯৮× — ধারাবাহিকভাবে তত্ত্বীয় সীমা M=4×-এর কাছাকাছি পৌঁছাচ্ছে। এটি একটি সাধারণ প্যাটার্ন — ফিক্সড বাউন্ডারি ওভারহেড দীর্ঘ সিগন্যালে আনুপাতিকভাবে নগণ্য হয়ে যায়, তাই বাস্তব অ্যাপ্লিকেশনে (যেখানে সিগন্যাল হাজার হাজার স্যাম্পল লম্বা) পলিফেজ কাঠামো প্রায় পুরো তত্ত্বীয় M-গুণ সাশ্রয় বাস্তবে পাওয়া যায়।
মূল কথা · Key takeaway

পলিফেজ ডিকম্পোজিশন কোনো নতুন গাণিতিক ফলাফল দেয় না — এটি ঠিক একই কনভোলিউশন সামেশনকে ফেজ অনুযায়ী পুনর্বিন্যাস করে যাতে গণনা সবসময় সবচেয়ে নিম্ন সম্ভাব্য রেটে ঘটে, কখনও অপ্রয়োজনীয় উচ্চ-রেট আউটপুট গণনা করা হয় না। এটাই বাস্তব DSP হার্ডওয়্যার ও লাইব্রেরিতে (অডিও রিস্যাম্পলার, রেডিও রিসিভার) ডেসিমেশন ও ইন্টারপোলেশন বাস্তবায়নের প্রমিত পদ্ধতি। L43-এ আমরা L40 (ডেসিমেশন) আর L41 (ইন্টারপোলেশন)-কে একসাথে জুড়ে একটি সম্পূর্ণ, নন-ইন্টিজার রেট-কনভার্সন পাইপলাইন তৈরি করব।

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

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

প্র ০১ পলিফেজ ও সরাসরি পদ্ধতির আউটপুট সংখ্যাগতভাবে অভিন্ন কেন হওয়া উচিত — এটি কি একটি কাকতালীয় মিল, নাকি একটি গাণিতিক নিশ্চয়তা?

গাণিতিক নিশ্চয়তা। পলিফেজ ডিকম্পোজিশন শুধু মূল কনভোলিউশন সামেশন y[n] = Σ h[k]·x[n-k]-কে k = p + M·i প্রতিস্থাপন করে দুটি সামেশনে ভেঙে দেয় (বাইরের সামেশন p-এর উপর, ভেতরেরটা i-এর উপর) — এটি একটি বিশুদ্ধ বীজগণিতীয় পুনর্বিন্যাস, কোনো আনুমানিকতা বা তথ্য-ক্ষয় নেই। তাই ফলাফল (ফ্লোটিং-পয়েন্ট নির্ভুলতা ছাড়া) হুবহু মিলবেই — উপরের কোড সেই নিশ্চয়তাকে সংখ্যাগতভাবে যাচাই করেছে মাত্র।

প্র ০২ numtaps (ফিল্টারের ট্যাপ সংখ্যা) বাড়ালে (যেমন ২৫ থেকে ১০১) পলিফেজ পদ্ধতির সাশ্রয়ের অনুপাতের উপর কী প্রভাব পড়বে বলে আপনার ধারণা?

সাশ্রয়ের অনুপাত তত্ত্বীয় সীমা M-এর আরও কাছাকাছি পৌঁছাবে, বিশেষত ছোট N-এও। কারণ প্রতিটি ফেজ-সাব-ফিল্টারের বাউন্ডারি ওভারহেড (j_min-এর প্রস্থ) সাব-ফিল্টারের দৈর্ঘ্যের সমানুপাতিক — কিন্তু মূল তুলনায় গুরুত্বপূর্ণ বিষয় হলো n_out (আউটপুট দৈর্ঘ্য)-এর তুলনায় ওভারহেডের আকার, যা মূলত N-এর উপর নির্ভর করে, numtaps-এর উপর নয় — তাই মূল প্রভাব এখনও N-নির্ভর থাকবে, তবে বড় numtaps-এ প্রতি সাব-ফিল্টারে গণনার পরিমাণ বাড়ায় ওভারহেডের আপেক্ষিক প্রভাব সামান্য কমতে পারে।

প্র ০৩ বাস্তব অডিও প্রসেসিং হার্ডওয়্যারে (যেমন একটি স্মার্টফোনের অডিও চিপ) পলিফেজ কাঠামো কেন গুরুত্বপূর্ণ হতে পারে, শুধু "কোড দ্রুত চলা" ছাড়াও?

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

অনুশীলন

  1. চিন্তা করুন: যদি M = 8 নেওয়া হতো (numtaps=25 অপরিবর্তিত), প্রতিটি h_phase[p]-এর দৈর্ঘ্য মোটামুটি কত হবে বলে আপনার ধারণা?

    মোটামুটি numtaps/M = 25/8 ≈ 3 ট্যাপ (কিছু ফেজে ৩, কিছুতে ৪ — ঠিক যেমন M=4-এ কিছু ফেজ ৭ আর কিছু ৬ ট্যাপ ছিল, নির্ভর করে numtaps ঠিক M দিয়ে বিভাজ্য কি না তার উপর)।

  2. পরীক্ষা করুন: প্রথম কোড সেলে M = 4-কে M = 8-এ পরিবর্তন করে (fc-এর সূত্র fc = (fs/M/2)/fs স্বয়ংক্রিয়ভাবে আপডেট হবে) Run চেপে দেখুন সর্বোচ্চ পার্থক্য এখনও ফ্লোটিং-পয়েন্ট সীমায় থাকে কি না, এবং নতুন গুণন-অনুপাত কত আসে।

    রান করলে দেখা যাবে সর্বোচ্চ পার্থক্য এখনও প্রায় শূন্য (ফ্লোটিং-পয়েন্ট নির্ভুলতার সীমায়) থাকবে — ক্রস-চেক M-এর মান নির্বিশেষে সবসময় সত্য থাকা উচিত, কারণ এটি একটি বীজগণিতীয় অভিন্নতা। গুণন-অনুপাত সাধারণত M=8-এ M=4-এর চেয়ে বেশি হবে (তত্ত্বীয় সর্বোচ্চ সীমা এখন ৮×), যদিও ছোট N=48-এ বাউন্ডারি ওভারহেডের কারণে পুরো ৮× পাওয়া যাবে না।

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

আগের পাঠ
আপস্যাম্পলিং (ইন্টারপোলেশন)