Problem 402289 · medium · Level 04 Non-Linear Data Structures

Placing the Poles and Zeros of a Hum Filter

poles · zeros · transfer function · polynomial multiplication · complex conjugates · DC gain

A guitar amplifier picks up mains hum, and its designer wants a filter that removes exactly that frequency. The neat way to design one is to say where the transfer function should be zero and where it should blow up, and let algebra produce the coefficients.

A filter with zeros q1, q2, ... and poles p1, p2, ... (complex numbers) has

H(z) = k * (1 - q1 * z^-1) * (1 - q2 * z^-1) * ...  /  ((1 - p1 * z^-1) * (1 - p2 * z^-1) * ...)

Each factor (1 - r * z^-1) is the coefficient list [1, -r], so b and a are products of such lists (multiply them as in "Two Echo Units"). A zero on the unit circle at angle w (q = exp(1j * w)) removes the frequency f = w * fs / (2 * pi) completely; a pole just inside the circle at the same angle makes the notch narrow. Real filters have real coefficients, so every non-real zero or pole comes with its complex conjugate (r.conjugate(), the mirror image below the real axis); multiplying the pair gives a real quadratic, so after the products, keep only the real parts (.real).

Choose the factor k so that the filter passes a constant unchanged, H(1) = 1: compute b and a with k = 1, then multiply every b coefficient by A(1) / B(1) (sums of the lists, as in "Bass and Treble at a Glance"). If abs(B(1)) < 1e-9 (a zero at z = 1: the filter blocks constants by design) leave k = 1.

Write design(zeros, poles) that returns the pair (b, a) of lists of real numbers, with len(zeros) + 1 and len(poles) + 1 entries.

Examples

Input:  zeros = [1j, -1j], poles = [0.9j, -0.9j]
Output: ([0.905, 0.0, 0.905], [1.0, 0.0, 0.81])
Explanation: (1 - 1j z^-1)(1 + 1j z^-1) = 1 + z^-2 and (1 - 0.9j z^-1)(1 + 0.9j z^-1) = 1 + 0.81 z^-2.
B(1) = 2, A(1) = 1.81, so b is scaled by 0.905. This notch removes fs / 4 (the zeros at angle pi / 2).

Input:  zeros = [1], poles = [0.95]
Output: ([1.0, -1.0], [1.0, -0.95])
Explanation: a zero at z = 1 removes any constant offset (a "DC blocker"), so k stays 1.

Input:  zeros = [-1], poles = [0.5]
Output: ([0.25, 0.25], [1.0, -0.5])

Constraints

  • at most 12 zeros and 12 poles; non-real ones come in conjugate pairs; no pole at z = 1
  • answers are compared with a tolerance of 1e-6; a list may be given as a tuple

Goals

  • Build the coefficient lists of a filter from its zeros and poles
  • See that conjugate pairs of roots give real coefficients
  • Scale the numerator so the filter passes a constant unchanged
Starting Python…