Qu

filter_ba

Builtin index · Signal processing

result = filter_ba(b, a, x)

Input: b, a, x · Output: result

What it is

b, a: vectors (numerator/denominator coefficients); x: a length-N vector or Signal.

Overloads

One call shape — this function is not overloaded.

CodeInputOutputDescription
result = filter_ba(b, a, x)
b vectorb, a: vectors (numerator/denominator coefficients).
a vector or Signalx: a length-N vector or Signal.
x vector or Signalx: a length-N vector or Signal.
result vector or Signal
Returns a same-length vector or Signal. Apply a transfer function given as raw numerator and denominator coefficients, direct form II transposed. For a function you derived yourself, or a script ported from MATLAB (filter(b, a, x)) or scipy (lfilter) — filter itself is the higher-order predicate filter here, so the name is taken. Warns above order 8, where a direct form loses accuracy and second-order sections do not.

Examples

From the documentation, and run by the build -- the output and the figure below are what the engine actually produced. Simplest first.

fs = 1000
ep = ellip(4, 1, 40, "low", 100, fs)
c1 = cheby1(4, 1, "low", 100, fs)
c2 = cheby2(4, 40, "low", 100, fs)
print("ellip stable: {is_stable(ep)}, cheby1 stable: {is_stable(c1)}, cheby2 stable: {is_stable(c2)}")

fir = fir1(6, 100, kind="low", window="hamming", fs=fs)
b = fir.b
a = [1]
n = 0:1:255
t = n / fs
x = sin(2*pi*40*t) + sin(2*pi*300*t)
y  = filter_ba(b, a, x)
y2 = lfilter(b, a, x)               # lfilter is the same builtin under another name

print("filter_ba/lfilter agree: {max(abs(y - y2)) < 1e-12}")

lsq = firls(20, [0, 0.2, 0.3, 1.0], [1, 1, 0, 0])   # low-pass band edges/response

print("firls taps: {length(lsq.b)}")

gd = group_delay(fir, 256)
print("FIR group delay (should be ~order/2=3): {gd[10]}")
print("ellip filter has {length(poles(ep))} poles")

ep.fvtool()   # 2x2 magnitude/phase/group-delay/impulse-response figure

pzplot(ep)    # pole-zero plot with the unit circle and stability verdict

output
ellip stable: true, cheby1 stable: true, cheby2 stable: true
filter_ba/lfilter agree: true
firls taps: 21
FIR group delay (should be ~order/2=3): 3
ellip filter has 4 poles
-80 -60 -40 -20 0 0 150 300 450 Magnitude Response Frequency (Hz) Magnitude (dB) -150 -100 -50 0 50 100 150 0 150 300 450 Phase Response Frequency (Hz) Phase (deg) -250 -200 -150 -100 -50 0 0 150 300 450 Group Delay Frequency (Hz) Delay (samples) -20 -10 0 10 20 0 20 40 60 x=-0.135, y=0.991 x=-0.135, y=-0.991 x=0.57, y=0.821 x=0.57, y=-0.821 Pole-Zero Plot (stable) Real part Imaginary part

from signal-processing

How it works

sosfilt(filt, x) — causal (one-pass) filtering; has the usual IIR phase lag. See filtfilt for zero-phase. Branches on the filter's representation: sos (IIR) uses the biquad cascade; b (FIR) is exactly conv(x, b, "full") truncated to len(x) (see numeric::filter::fir_filt's doc comment). Filtering never changes sample count or sample rate, so a Signal input keeps its Fs on the way out — the same "still the same time domain" convention diff/cumsum already follow (§41.2); a plain Vec input still returns a plain Vec, unchanged. filter_ba(b, a, x) — apply a transfer function given as raw numerator and denominator coefficients. Qu's filters are normally OBJECTS from butter/cheby1/ fir1, applied with sosfilt, and that is the better API: second-order sections are numerically well behaved where a high-order direct form is not. But it left no way to apply a transfer function you derived yourself, or ported from MATLAB or scipy, where filter(b, a, x) and lfilter(b, a, x) are the first thing anyone writes. lfilter is accepted as a second name for exactly that reason; filter itself is taken by the higher-order predicate filter. Direct form II transposed, the same recurrence scipy uses: y[n] = b0*x[n] + z0[n-1] z0[n] = b1*x[n] + z1[n-1] - a1*y[n] z1[n] = b2*x[n] - a2*y[n] It needs one state per coefficient rather than two histories, and it is the form that stays accurate when the coefficients span a wide dynamic range. A warning, not a guard. A direct-form filter above about order 8 loses accuracy fast: the polynomial coefficients of a high-order design differ by many orders of magnitude and the recurrence cannot represent the poles precisely. That is why butter returns sections. This says so rather than refusing, because a ported script is usually right about what it wants.

From the note above this builtin in the interpreter, where the reasoning was written down as it was built.

See also

lfilter — documented in the same entry.