はじめに
デジタルフィルタを設計・解析するとき、差分方程式をそのまま扱うのは煩雑です。連続時間系においてラプラス変換が微分方程式を代数方程式に変換するように、Z変換は差分方程式を代数方程式に変換し、デジタルフィルタの理論的な扱いを格段に簡単にします。
Z変換を使うと次のことが体系的に理解できます。
- デジタルフィルタの伝達関数 \(H(z)\) の導出
- 極(pole)と零点(zero)の配置によるフィルタ特性の直感的な把握
- システムの安定性条件の数学的な表現
- 単位円上でH(z)を評価することによる周波数応答の計算
本記事では、Z変換の定義と基本性質から出発し、逆Z変換、極零点プロット、デジタルフィルタの伝達関数と安定性、そして周波数応答との関係まで順を追って解説します。最後にPython(scipy)を使った実装例を示します。
バターワースフィルタの設計やFIR/IIRフィルタの比較を読んでいると、本記事の内容がより深く理解できます。
Z変換の定義と収束領域
両側Z変換
離散時間信号 \(x[n]\) (\(n \in \mathbb{Z}\) )の両側Z変換は次のように定義されます。
\[X(z) = \mathcal{Z}\{x[n]\} = \sum_{n=-\infty}^{\infty} x[n]\, z^{-n} \tag{1}\]ここで \(z\) は複素変数です。\(z = re^{j\omega}\) (\(r > 0\) )と書けば、式 \((1)\) は次のように展開できます。
\[X(re^{j\omega}) = \sum_{n=-\infty}^{\infty} x[n]\, r^{-n} e^{-j\omega n} \tag{2}\]これは信号 \(x[n] r^{-n}\) の離散時間フーリエ変換(DTFT)と同じ形をしています。したがって Z変換はDTFTを複素平面全体に一般化したものと見なすことができます。
片側Z変換
因果的なシステム(\(n < 0\) で \(x[n] = 0\) )を扱うとき、片側Z変換が使われます。
\[X(z) = \sum_{n=0}^{\infty} x[n]\, z^{-n} \tag{3}\]デジタルフィルタの設計では因果システムが標準であり、特に断りがない限り片側Z変換を前提とします。
収束領域(ROC: Region of Convergence)
無限級数である式 \((1)\) は \(z\) のすべての値で収束するとは限りません。Z変換が収束する \(z\) の集合を**収束領域(ROC)**といいます。
ROCは原点を中心とした環状領域 \(r_1 < |z| < r_2\) として表されます。重要な特性を以下に示します。
- 有限長信号: ROCは \(z = 0\) または \(z = \infty\) を除いた全複素平面(あるいは全体)
- 右側信号(\(n \geq 0\) のみ非ゼロ): ROCは \(|z| > r_1\) (ある半径より外側)
- 左側信号(\(n \leq 0\) のみ非ゼロ): ROCは \(|z| < r_2\) (ある半径より内側)
- ROCには極は含まれない
例として、\(x[n] = a^n u[n]\) (\(u[n]\) は単位ステップ関数)のZ変換を求めます。
\[X(z) = \sum_{n=0}^{\infty} a^n z^{-n} = \sum_{n=0}^{\infty} (az^{-1})^n = \frac{1}{1 - az^{-1}} = \frac{z}{z - a} \tag{4}\]この級数は \(|az^{-1}| < 1\) 、すなわち \(|z| > |a|\) のとき収束します。ROCは \(|z| > |a|\) の外側の領域です。
Z変換の重要な性質
線形性
\[\mathcal{Z}\{\alpha x_1[n] + \beta x_2[n]\} = \alpha X_1(z) + \beta X_2(z) \tag{5}\]ROCは \(\text{ROC}_1 \cap \text{ROC}_2\) の少なくとも部分集合を含みます。
時間シフト
\[\mathcal{Z}\{x[n - k]\} = z^{-k} X(z) \tag{6}\]証明: 定義式に代入し \(m = n - k\) と置換します。
\[\sum_{n=-\infty}^{\infty} x[n-k] z^{-n} = \sum_{m=-\infty}^{\infty} x[m] z^{-(m+k)} = z^{-k} \sum_{m=-\infty}^{\infty} x[m] z^{-m} = z^{-k} X(z)\]この性質は差分方程式をZ域の代数方程式に変換するために不可欠です。\(z^{-1}\) は「1サンプル遅延」を表す演算子として機能します。
畳み込み定理
\[\mathcal{Z}\{x_1[n] * x_2[n]\} = X_1(z) \cdot X_2(z) \tag{7}\]畳み込みがZ域では積に変わります。これによってフィルタ処理(入力信号とインパルス応答の畳み込み)が伝達関数の乗算として表現できます。
時間反転
\[\mathcal{Z}\{x[-n]\} = X(z^{-1}) \tag{8}\]z域微分(乗算則)
\[\mathcal{Z}\{n \cdot x[n]\} = -z \frac{d}{dz} X(z) \tag{9}\]初期値定理と最終値定理
因果信号に対して、次の定理が成り立ちます。
初期値定理:
\[x[0] = \lim_{z \to \infty} X(z) \tag{10}\]最終値定理:
\[\lim_{n \to \infty} x[n] = \lim_{z \to 1} (z-1) X(z) \tag{11}\]最終値定理は、システムの定常状態での出力を、時間域でシミュレーションすることなく、Z域の代数演算だけから求めるのに役立ちます。ただし、\((z-1)X(z)\) の極がすべて単位円の内部にある場合にのみ適用できます。
主な変換対
| \(x[n]\) | \(X(z)\) | ROC |
|---|---|---|
| \(\delta[n]\) | \(1\) | すべての \(z\) |
| \(u[n]\) | \(\dfrac{z}{z-1}\) | \(\|z\| > 1\) |
| \(a^n u[n]\) | \(\dfrac{z}{z-a}\) | \(\|z\| > \|a\|\) |
| \(n u[n]\) | \(\dfrac{z}{(z-1)^2}\) | \(\|z\| > 1\) |
| \(\cos(\omega_0 n) u[n]\) | \(\dfrac{z(z-\cos\omega_0)}{z^2 - 2z\cos\omega_0 + 1}\) | \(\|z\| > 1\) |
| \(r^n \cos(\omega_0 n) u[n]\) | \(\dfrac{z(z - r\cos\omega_0)}{z^2 - 2rz\cos\omega_0 + r^2}\) | \(\|z\| > r\) |
逆Z変換
部分分数展開法
最も広く使われる方法です。\(X(z)\) を有理関数として表し、既知の変換対に対応する形に分解します。
\(H(z)/z\) (または \(X(z)/z\) )を部分分数に展開するのが標準的な手順です。
例: 次の \(X(z)\) を逆変換します。
\[X(z) = \frac{z^2}{(z - 0.5)(z - 0.8)}, \quad |z| > 0.8 \tag{12}\]\(X(z)/z\) を部分分数展開します。
\[\frac{X(z)}{z} = \frac{z}{(z-0.5)(z-0.8)} = \frac{A}{z-0.5} + \frac{B}{z-0.8}\] \[A = \left.\frac{z}{z-0.8}\right|_{z=0.5} = \frac{0.5}{0.5-0.8} = -\frac{5}{3}\] \[B = \left.\frac{z}{z-0.5}\right|_{z=0.8} = \frac{0.8}{0.8-0.5} = \frac{8}{3}\]したがって
\[X(z) = -\frac{5}{3}\cdot\frac{z}{z-0.5} + \frac{8}{3}\cdot\frac{z}{z-0.8}\]ROCが \(|z| > 0.8\) (右側信号)より
\[x[n] = \left(-\frac{5}{3}(0.5)^n + \frac{8}{3}(0.8)^n\right)u[n] \tag{13}\]べき級数展開法(長除法)
\(X(z)\) を \(z^{-1}\) の整級数として展開する方法です。
\[X(z) = x[0] + x[1]z^{-1} + x[2]z^{-2} + \cdots \tag{14}\]分子を分母で長除法すると各係数が直接求まります。有限長フィルタ(FIR)や数値的な検証に便利です。
例: \(X(z) = \dfrac{1 + z^{-1}}{1 - 0.5z^{-1}}\) を展開します。
長除法を行うと \(x[0]=1,\; x[1]=1.5,\; x[2]=0.75,\; x[3]=0.375, \ldots\) となり、\(x[n] = (0.5)^{n-1} \cdot 1.5 \cdot u[n-1] + \delta[n]\) と求まります。
留数法(Cauchy積分公式)
\[x[n] = \frac{1}{2\pi j} \oint_C X(z) z^{n-1} dz = \sum_k \text{Res}\left[X(z)z^{n-1}, z_k\right] \tag{15}\]ここで \(C\) はROC内の反時計回りの閉曲線であり、\(z_k\) は閉曲線内の極です。理論的解析に重要ですが、実用上は部分分数展開法が使いやすいです。
極と零点
定義
有理関数 \(X(z) = \frac{N(z)}{D(z)}\) (\(N\) 、\(D\) は多項式)において、
- \(N(z_0) = 0\) となる \(z_0\) : 零点(zero)
- \(D(z_p) = 0\) となる \(z_p\) : 極(pole)
\(M\) 次分子・\(N\) 次分母の有理関数は \(M\) 個の零点と \(N\) 個の極を持ちます(無限遠の零点・極も考慮)。
極零点プロット(Pole-Zero Plot)
複素平面(z平面)上に極を ×、零点を ○ でプロットしたものを極零点図(pole-zero plot)といいます。デジタルフィルタの特性を視覚的に把握するための最も重要なツールです。
フィルタ周波数応答の大きさは、z平面上を単位円に沿って \(z = e^{j\omega}\) と動かしたときの \(H(z)\) の絶対値に対応します。
- 零点が単位円に近い: その角周波数付近でゲインが減衰する(ノッチ)
- 極が単位円に近い: その角周波数付近でゲインが増大する(ピーク)
- 極が単位円上または外部: システムが不安定
安定性条件
LTI(線形時不変)離散時間システムが BIBO 安定である条件:
\[\text{すべての極 } z_k \text{ が単位円の内部にある}: \quad |z_k| < 1 \quad \forall k \tag{16}\]これは直感的にも理解できます。極 \(z_k = r_k e^{j\theta_k}\) に対応する時間域の成分は \(r_k^n e^{j\theta_k n}\) に比例します。\(r_k < 1\) であれば \(n \to \infty\) で減衰し、\(r_k > 1\) では発散します。
伝達関数と差分方程式
伝達関数の定義
線形時不変システムの伝達関数 \(H(z)\) は、出力の Z 変換 \(Y(z)\) と入力の Z 変換 \(X(z)\) の比として定義されます。
\[H(z) = \frac{Y(z)}{X(z)} \tag{17}\]畳み込み定理より \(Y(z) = H(z) X(z)\) となるため、\(H(z)\) はインパルス応答 \(h[n]\) の Z 変換です。
差分方程式から伝達関数へ
一般的な \(N\) 次差分方程式は次のとおりです。
\[y[n] = -\sum_{k=1}^{N} a_k\, y[n-k] + \sum_{k=0}^{M} b_k\, x[n-k] \tag{18}\]両辺のZ変換を取り、時間シフト性質 \(\mathcal{Z}\{y[n-k]\} = z^{-k} Y(z)\) を使うと
\[Y(z) = -\sum_{k=1}^{N} a_k z^{-k} Y(z) + \sum_{k=0}^{M} b_k z^{-k} X(z)\]整理すると
\[H(z) = \frac{Y(z)}{X(z)} = \frac{\sum_{k=0}^{M} b_k z^{-k}}{1 + \sum_{k=1}^{N} a_k z^{-k}} \tag{19}\]これが一般的なIIRフィルタの伝達関数の形です。
FIRフィルタとIIRフィルタのZ域表現
FIRフィルタ(Finite Impulse Response)は帰還なし(すべての \(a_k = 0\) )なので
\[H_{\text{FIR}}(z) = \sum_{k=0}^{M} b_k z^{-k} = b_0 + b_1 z^{-1} + \cdots + b_M z^{-M} \tag{20}\]これは極を持たない(原点の極のみ)多項式です。零点のみで特性を設計します。
IIRフィルタ(Infinite Impulse Response)は帰還あり(\(a_k \neq 0\) )なので
\[H_{\text{IIR}}(z) = \frac{B(z)}{A(z)} = \frac{\sum_{k=0}^{M} b_k z^{-k}}{1 + \sum_{k=1}^{N} a_k z^{-k}} \tag{21}\]極と零点の両方を持ちます。少ない次数で急峻なフィルタを実現できますが、設計と安定性管理が必要です。詳細は FIRフィルタとIIRフィルタの比較 を参照してください。
極零点形式
伝達関数を極と零点の積で表すと
\[H(z) = b_0 \frac{\prod_{k=1}^{M} (1 - q_k z^{-1})}{\prod_{k=1}^{N} (1 - p_k z^{-1})} \tag{22}\]ここで \(q_k\) は零点、\(p_k\) は極です。この表現は極零点プロットと直接対応し、フィルタ特性の直感的な理解に優れています。
安定性の解析
安定・不安定・限界安定
| 極の位置 | システムの振る舞い | 判定 |
|---|---|---|
| \(\|p_k\| < 1\) (全極) | インパルス応答が減衰 | 安定 |
| \(\|p_k\| = 1\) (単極) | インパルス応答が振動持続 | 限界安定 |
| \(\|p_k\| > 1\) (一極でも) | インパルス応答が発散 | 不安定 |
2次セクションと数値安定性
高次のIIRフィルタをそのまま実装すると、有限精度演算による数値誤差で極が単位円外へ移動する可能性があります。これを防ぐために、IIRフィルタは**2次セクション(SOS: Second-Order Sections)**に分解して実装するのが標準的です。
\[H(z) = \prod_{k=1}^{K} H_k(z), \quad H_k(z) = \frac{b_{k,0} + b_{k,1}z^{-1} + b_{k,2}z^{-2}}{1 + a_{k,1}z^{-1} + a_{k,2}z^{-2}} \tag{23}\]scipy.signal.butter(..., output='sos') はこの形式で係数を返します。
周波数応答との関係
単位円上での評価
デジタルフィルタの周波数応答は、伝達関数 \(H(z)\) を単位円 \(z = e^{j\omega}\) (\(\omega \in [-\pi, \pi]\) )上で評価することで得られます。
\[H(e^{j\omega}) = H(z)\big|_{z=e^{j\omega}} = \sum_{n=-\infty}^{\infty} h[n]\, e^{-j\omega n} \tag{24}\]これは離散時間フーリエ変換(DTFT)と一致します。高速フーリエ変換(FFT)はこれの離散サンプルです。
極零点から周波数応答を求める幾何学的解釈
式 \((22)\) に \(z = e^{j\omega}\) を代入すると
\[H(e^{j\omega}) = b_0 \frac{\prod_{k=1}^{M} (e^{j\omega} - q_k)}{\prod_{k=1}^{N} (e^{j\omega} - p_k)} \tag{25}\]この各因子は複素平面上の「ベクトル」として解釈できます。
- \(|e^{j\omega} - q_k|\) : 単位円上の点 \(e^{j\omega}\) から零点 \(q_k\) までの距離
- \(|e^{j\omega} - p_k|\) : 単位円上の点 \(e^{j\omega}\) から極 \(p_k\) までの距離
したがって
\[|H(e^{j\omega})| = |b_0| \frac{\prod_k |e^{j\omega} - q_k|}{\prod_k |e^{j\omega} - p_k|} \tag{26}\] \[\angle H(e^{j\omega}) = \sum_k \angle(e^{j\omega} - q_k) - \sum_k \angle(e^{j\omega} - p_k) \tag{27}\]この幾何学的解釈から次のことが分かります。
- 単位円に近い極がある角周波数 → 分母が小さくなる → ゲインが増大
- 単位円に近い零点がある角周波数 → 分子が小さくなる → ゲインが減衰
- 単位円上の零点がある角周波数 → \(|H| = 0\) (完全除去)
群遅延の理論的な取り扱いについては 位相と群遅延の解析 も参照してください。
Python実装
基本ライブラリのインポート
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import butter, freqz, tf2zpk, zpk2tf, sosfilt
極零点プロット関数(zplane)
def zplane(b, a, ax=None, title="Pole-Zero Plot"):
"""
デジタルフィルタの極零点プロットを描画する。
Parameters
----------
b : array_like
分子係数(b[0] + b[1]*z^-1 + ...)
a : array_like
分母係数(a[0] + a[1]*z^-1 + ...)
ax : matplotlib Axes, optional
プロット先の Axes。None の場合は新規作成。
title : str
プロットタイトル
Returns
-------
zeros : ndarray
零点の配列
poles : ndarray
極の配列
"""
if ax is None:
fig, ax = plt.subplots(figsize=(6, 6))
# 極・零点を計算
zeros, poles, gain = tf2zpk(b, a)
# 単位円を描画
theta = np.linspace(0, 2 * np.pi, 512)
ax.plot(np.cos(theta), np.sin(theta), "k--", linewidth=0.8, alpha=0.5, label="Unit Circle")
# 実軸・虚軸
ax.axhline(0, color="k", linewidth=0.5, alpha=0.4)
ax.axvline(0, color="k", linewidth=0.5, alpha=0.4)
# 零点(○)
ax.scatter(zeros.real, zeros.imag, s=80, marker="o", facecolors="none",
edgecolors="blue", linewidths=1.5, zorder=5, label=f"Zeros ({len(zeros)})")
# 極(×)
ax.scatter(poles.real, poles.imag, s=80, marker="x",
color="red", linewidths=1.5, zorder=5, label=f"Poles ({len(poles)})")
# 軸設定
ax.set_xlabel("Real Part")
ax.set_ylabel("Imaginary Part")
ax.set_title(title)
ax.set_aspect("equal")
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
# 表示範囲を自動調整(単位円が収まるよう余白を確保)
lim = max(1.5, np.max(np.abs(poles)) * 1.2, np.max(np.abs(zeros)) * 1.2)
ax.set_xlim(-lim, lim)
ax.set_ylim(-lim, lim)
return zeros, poles
安定・不安定システムの比較
# --- 安定システム(全極が単位円内) ---
b_stable = np.array([1.0])
a_stable = np.array([1.0, -0.5, 0.3]) # 極: |p| < 1
# --- 不安定システム(極が単位円外に出る) ---
b_unstable = np.array([1.0])
a_unstable = np.array([1.0, -1.5, 0.9]) # 極: |p| > 1
fig, axes = plt.subplots(1, 2, figsize=(12, 6))
z_s, p_s = zplane(b_stable, a_stable, ax=axes[0], title="Stable System\n(All Poles Inside Unit Circle)")
z_u, p_u = zplane(b_unstable, a_unstable, ax=axes[1], title="Unstable System\n(Poles Outside Unit Circle)")
print(f"Stable system poles: {np.roots(a_stable)}")
print(f" Magnitudes: {np.abs(np.roots(a_stable))}")
print(f"Unstable system poles: {np.roots(a_unstable)}")
print(f" Magnitudes: {np.abs(np.roots(a_unstable))}")
plt.tight_layout()
plt.show()
出力例:
Stable system poles: [0.25+0.479j 0.25-0.479j]
Magnitudes: [0.541 0.541]
Unstable system poles: [0.75+0.661j 0.75-0.661j]
Magnitudes: [1.0 1.0] ← 単位円上(限界安定)
バターワースフィルタの極零点解析
# バターワースフィルタ(ローパス、カットオフ 100 Hz、サンプリング 1000 Hz)
fs = 1000 # Hz
fc = 100 # Hz
orders = [2, 4, 6]
fig, axes = plt.subplots(len(orders), 2, figsize=(14, 4 * len(orders)))
for i, N in enumerate(orders):
# SOS形式で設計
sos = butter(N, fc, fs=fs, output='sos')
# 伝達関数形式に変換
b, a = butter(N, fc, fs=fs, output='ba')
# 左列: 極零点プロット
zplane(b, a, ax=axes[i, 0], title=f"Butterworth N={N} Pole-Zero Plot")
# 右列: 周波数応答
w, h = freqz(b, a, worN=4096, fs=fs)
axes[i, 1].plot(w, 20 * np.log10(np.abs(h) + 1e-12), "b", linewidth=1.5)
axes[i, 1].axvline(fc, color="gray", linestyle="--", alpha=0.7, label=f"fc={fc}Hz")
axes[i, 1].axhline(-3, color="red", linestyle=":", alpha=0.7, label="-3 dB")
axes[i, 1].set_xlim(0, fs / 2)
axes[i, 1].set_ylim(-80, 5)
axes[i, 1].set_xlabel("Frequency [Hz]")
axes[i, 1].set_ylabel("Magnitude [dB]")
axes[i, 1].set_title(f"Butterworth N={N} Frequency Response")
axes[i, 1].legend()
axes[i, 1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
伝達関数から周波数応答(幾何学的解釈の可視化)
def freq_response_from_zpk(zeros, poles, gain, omega_range=None):
"""
極零点から周波数応答の大きさと位相を幾何学的に計算する。
Parameters
----------
zeros : array_like
零点の配列(複素数)
poles : array_like
極の配列(複素数)
gain : float
ゲイン定数
omega_range : array_like, optional
評価する角周波数の配列(rad/sample)
Returns
-------
omega : ndarray
角周波数
H_mag : ndarray
振幅応答
H_phase : ndarray
位相応答 [rad]
"""
if omega_range is None:
omega_range = np.linspace(0, np.pi, 1024)
H_mag = np.ones(len(omega_range)) * np.abs(gain)
H_phase = np.zeros(len(omega_range))
for omega in range(len(omega_range)):
z = np.exp(1j * omega_range[omega])
# 零点からの距離の積
for q in zeros:
H_mag[omega] *= np.abs(z - q)
H_phase[omega] += np.angle(z - q)
# 極からの距離の積
for p in poles:
H_mag[omega] /= np.abs(z - p)
H_phase[omega] -= np.angle(z - p)
return omega_range, H_mag, H_phase
# 例: 2次バターワースLPFの幾何学的周波数応答
b, a = butter(2, 0.2) # デジタル周波数で 0.2π (正規化)
zeros, poles, gain = tf2zpk(b, a)
omega, H_mag, H_phase = freq_response_from_zpk(zeros, poles, gain)
# scipy.signal.freqz による結果と比較
w, h = freqz(b, a, worN=1024)
fig, axes = plt.subplots(2, 1, figsize=(10, 7))
axes[0].plot(omega / np.pi, 20 * np.log10(H_mag + 1e-12), "b--",
linewidth=2, label="Geometric (from z-plane)")
axes[0].plot(w / np.pi, 20 * np.log10(np.abs(h) + 1e-12), "r",
linewidth=1, alpha=0.7, label="scipy.signal.freqz")
axes[0].set_xlabel("Normalized Frequency [× π rad/sample]")
axes[0].set_ylabel("Magnitude [dB]")
axes[0].set_title("Frequency Response: Geometric vs freqz")
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[0].set_ylim(-60, 5)
axes[1].plot(omega / np.pi, np.degrees(np.unwrap(H_phase)), "b--",
linewidth=2, label="Geometric")
axes[1].plot(w / np.pi, np.degrees(np.unwrap(np.angle(h))), "r",
linewidth=1, alpha=0.7, label="scipy.signal.freqz")
axes[1].set_xlabel("Normalized Frequency [× π rad/sample]")
axes[1].set_ylabel("Phase [degrees]")
axes[1].set_title("Phase Response")
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
部分分数展開による逆Z変換(数値確認)
from scipy.signal import residuez
# H(z) = (1 + 0.5z^-1) / (1 - 0.9z^-1 + 0.2z^-2)
b = np.array([1.0, 0.5])
a = np.array([1.0, -0.9, 0.2])
# residuez は X(z) = sum(r_k / (1 - p_k z^-1)) + k の形で返す
r, p, k = residuez(b, a)
print("部分分数展開: H(z) = sum(r_k / (1 - p_k * z^-1)) + k(z)")
for i, (ri, pi) in enumerate(zip(r, p)):
print(f" r[{i}] = {ri:.4f}, p[{i}] = {pi:.4f} (|p| = {abs(pi):.4f})")
print(f" 直達項 k = {k}")
# 逆Z変換(インパルス応答の解析計算)
N = 20
n = np.arange(N)
h_analytical = np.zeros(N, dtype=complex)
for ri, pi in zip(r, p):
h_analytical += ri * pi**n
h_analytical = h_analytical.real
# scipy.signal.lfilter による数値計算と比較
from scipy.signal import lfilter, unit_impulse
impulse = unit_impulse(N)
h_numerical = lfilter(b, a, impulse)
fig, ax = plt.subplots(figsize=(10, 4))
ax.stem(n, h_numerical, linefmt="b-", markerfmt="bo", basefmt="k-", label="lfilter (numerical)")
ax.stem(n, h_analytical, linefmt="r--", markerfmt="rx", basefmt="k-", label="Partial fraction (analytical)")
ax.set_xlabel("Sample n")
ax.set_ylabel("Amplitude")
ax.set_title("Impulse Response: Partial Fraction vs Direct Computation")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print(f"\n最大誤差: {np.max(np.abs(h_numerical - h_analytical)):.2e}")
伝達関数・SOS・ZPK形式の相互変換
from scipy.signal import tf2sos, sos2tf, zpk2sos
# バターワース4次フィルタを各形式で表現
b, a = butter(4, 0.3)
# BA → ZPK
zeros, poles, gain = tf2zpk(b, a)
# ZPK → BA(逆変換)
b_r, a_r = zpk2tf(zeros, poles, gain)
# BA → SOS
sos = tf2sos(b, a)
# SOS → BA
b_sos, a_sos = sos2tf(sos)
print("=== 4次バターワースフィルタの表現形式比較 ===")
print(f"\nBA形式 (b): {b}")
print(f"BA形式 (a): {a}")
print(f"\n極 (poles): {poles}")
print(f"零点 (zeros): {zeros}")
print(f"ゲイン: {gain:.6f}")
print(f"\nSOS形式:\n{sos}")
# 全極の絶対値(安定性確認)
print(f"\n全極の絶対値(すべて < 1 ならば安定):")
for i, p in enumerate(poles):
print(f" p[{i}] = {p:.4f}, |p| = {abs(p):.4f} {'✓' if abs(p) < 1 else '✗'}")
まとめ
本記事では Z 変換の理論と応用を体系的に解説しました。
| トピック | 要点 |
|---|---|
| Z変換の定義 | \(X(z) = \sum x[n] z^{-n}\) 。収束領域(ROC)に注意 |
| 時間シフト性質 | \(z^{-1}\) が1サンプル遅延に対応。差分方程式を代数方程式に変換 |
| 畳み込み定理 | 時間域の畳み込み ↔ Z域の乗算。フィルタリングを \(Y=HX\) で表現 |
| 逆Z変換 | 部分分数展開法が実用的。既知の変換対に分解 |
| 極・零点 | 極 \(\times\) 、零点 \(\circ\) を複素平面にプロット |
| 安定性 | 全極が単位円内部(\(\|p_k\| < 1\) ) |
| 伝達関数 | \(H(z) = B(z)/A(z)\) 。差分方程式と一対一対応 |
| 周波数応答 | \(H(e^{j\omega}) = H(z)\big\|_{z=e^{j\omega}}\) (単位円上) |
| 幾何学的解釈 | 極零点からの距離の積・商でゲインを計算 |
Z変換はデジタル信号処理の中核をなす道具です。極零点の配置を見るだけでフィルタの特性を素早く把握できるようになると、フィルタ設計の理解が格段に深まります。
関連記事
- 高速フーリエ変換(FFT)の仕組みとPython実装 — 周波数解析の基礎。Z変換を単位円上に制限するとDTFTになり、その離散サンプルがDFT/FFTです。
- バターワースフィルタの設計原理とPython実装 — IIRフィルタ設計の代表例。本記事で学んだ極零点プロットと伝達関数の知識が直接活用できます。
- FIRフィルタとIIRフィルタの比較 — Z域での表現(FIRは全零フィルタ、IIRは極あり)の違いを理解するための記事です。
- 位相と群遅延の解析 — \(H(e^{j\omega})\) の位相特性から群遅延を導出し、フィルタの時間的な歪みを解析します。
参考文献
- Oppenheim, A. V., & Schafer, R. W. (2009). Discrete-Time Signal Processing (3rd ed.). Prentice Hall.
- Proakis, J. G., & Manolakis, D. G. (2006). Digital Signal Processing: Principles, Algorithms, and Applications (4th ed.). Prentice Hall.
- Antoniou, A. (2005). Digital Signal Processing: Signals, Systems, and Filters. McGraw-Hill.
- SciPy scipy.signal documentation