FA-91666 / Digital signal filters / Open access
Forward-backward filter runs the second pass forward · case 01
The result has twice the group delay instead of zero phase.
ROOT CAUSE
The second pass filters y without reversing it first.
VERIFIED REPAIR
Reverse, filter and reverse back.
Unsuccessful approach: The attempted repair reverses before the second pass but not after it, returning a time-reversed signal.
Case contract
Input [taps, samples] (integers). Zero-phase filtering: pad = min(3 (len(taps) - 1), len - 1); extend with odd reflection about each end (2 x[0] - x[i] for i = pad..1 before, 2 x[-1] - x[n-1-i] for i = 1..pad after); filter forward with zero initial state, reverse, filter again, reverse, and return the middle len samples.
Why this case matters
Forward-backward filtering is used for offline zero-phase smoothing; padding and reversal slips create edge transients and phase shift.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
b, xs = x
n = len(xs)
if n == 0:
return []
pad = min(3 * (len(b) - 1), n - 1)
front = [2 * xs[0] - xs[i] for i in range(pad, 0, -1)]
back = [2 * xs[-1] - xs[n - 1 - i] for i in range(1, pad + 1)]
ext = front + xs + back
def fir(sig):
return [sum(b[k] * sig[i - k] for k in range(len(b)) if i - k >= 0) for i in range(len(sig))]
y = fir(ext)
y = fir(y)
return y[pad:pad + n]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: ramp with two-tap average', [[1, 1], [1, 2, 3, 4, 5]], [4, 8, 12, 16, 20]], ['regression: step with three taps', [[1, 2, 1], [0, 0, 4, 4, 4, 4]], [0, 20, 44, 60, 64, 64]], ['regression: short signal', [[1, 1, 1], [2, 5]], [22, 21]], ['control: single sample', [[1, 2], [3]], [3]], ['control: empty', [[1, 1], []], []], ['control: random filtfilt 4', [[-1], [8, 1, 1, 8]], [8, 1, 1, 8]], ['control: random filtfilt 5', [[2, 1, -1, 3], [0]], [0]]], [['regression: random filtfilt 0', [[-1, -1], [-5, 3]], [-20, 12]], ['regression: random filtfilt 1', [[2, -1], [-1, 3, 2, 2, 8, 5, 5, 0, -3]], [-1, 13, 0, -10, 26, -1, 15, -4, -3]], ['regression: short signal', [[1, 1, 1], [2, 5]], [22, 21]], ['control: random filtfilt 12', [[0], [-2, 4, -5, -1, 8, 4, 3, 0, 1]], [0, 0, 0, 0, 0, 0, 0, 0, 0]], ['control: random filtfilt 16', [[-1, -1, 0], [8]], [8]], ['control: random filtfilt 17', [[-1, -2, -1], [-1]], [-1]], ['control: random filtfilt 27', [[0], [0, -5, 8, -2, 9, 3]], [0, 0, 0, 0, 0, 0]]], [['regression: random filtfilt 6', [[1, 3, -2, -1], [-3, -1, -4]], [-2, 59, -4]], ['regression: random filtfilt 8', [[-1, -2], [5, 5, 7]], [45, 49, 63]], ['regression: random filtfilt 1', [[2, -1], [-1, 3, 2, 2, 8, 5, 5, 0, -3]], [-1, 13, 0, -10, 26, -1, 15, -4, -3]], ['control: random filtfilt 39', [[3, 2, 3], [6]], [54]], ['control: random filtfilt 46', [[-1, 1, 2, 2], [5]], [5]], ['control: single sample', [[1, 2], [3]], [3]], ['control: empty', [[1, 1], []], []]], [['regression: random filtfilt 10', [[2, 0, -2], [-3, 9, 2, 7]], [0, 104, -20, 0]], ['regression: random filtfilt 11', [[0, 3, 3, 3], [2, 0, 6]], [162, 288, 234]], ['repair check: random filtfilt 3', [[-1], [-4, 3, -2, 7, 9, 5, 3, -2, 1]], [-4, 3, -2, 7, 9, 5, 3, -2, 1]], ['control: random filtfilt 4', [[-1], [8, 1, 1, 8]], [8, 1, 1, 8]], ['control: random filtfilt 5', [[2, 1, -1, 3], [0]], [0]], ['control: random filtfilt 12', [[0], [-2, 4, -5, -1, 8, 4, 3, 0, 1]], [0, 0, 0, 0, 0, 0, 0, 0, 0]], ['control: random filtfilt 16', [[-1, -1, 0], [8]], [8]]], [['regression: random filtfilt 14', [[0, -1, -2, -1], [-3, 7]], [-53, -12]], ['regression: random filtfilt 18', [[2, -2], [2, -3, 5, -1, 3, -2, 3]], [0, -52, 56, -40, 36, -40, 0]], ['repair check: random filtfilt 7', [[-2], [8, -5, -4, -4, -2, 9, 6, 3]], [32, -20, -16, -16, -8, 36, 24, 12]], ['control: random filtfilt 17', [[-1, -2, -1], [-1]], [-1]], ['control: random filtfilt 27', [[0], [0, -5, 8, -2, 9, 3]], [0, 0, 0, 0, 0, 0]], ['control: random filtfilt 39', [[3, 2, 3], [6]], [54]], ['control: random filtfilt 46', [[-1, 1, 2, 2], [5]], [5]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: ramp with two-tap average | [0, 4, 8, 12, 16] | [4, 8, 12, 16, 20] | Failed |
| regression: step with three taps | [-44, -20, 0, 20, 44, 60] | [0, 20, 44, 60, 64, 64] | Failed |
| regression: short signal | [0, 6] | [22, 21] | Failed |
| control: single sample | [3] | [3] | Passed |
| control: empty | [] | [] | Passed |
| control: random filtfilt 4 | [8, 1, 1, 8] | [8, 1, 1, 8] | Passed |
| control: random filtfilt 5 | [0] | [0] | Passed |
SHA-256 / 1a1f5f41fb3b5c16e306987ebc18abbce6202a17705e8c8a0a08ba6655c3e56e
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
b, xs = x
n = len(xs)
if n == 0:
return []
pad = min(3 * (len(b) - 1), n - 1)
front = [2 * xs[0] - xs[i] for i in range(pad, 0, -1)]
back = [2 * xs[-1] - xs[n - 1 - i] for i in range(1, pad + 1)]
ext = front + xs + back
def fir(sig):
return [sum(b[k] * sig[i - k] for k in range(len(b)) if i - k >= 0) for i in range(len(sig))]
y = fir(ext)
y = fir(y[::-1])
return y[pad:pad + n]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: ramp with two-tap average', [[1, 1], [1, 2, 3, 4, 5]], [4, 8, 12, 16, 20]], ['regression: step with three taps', [[1, 2, 1], [0, 0, 4, 4, 4, 4]], [0, 20, 44, 60, 64, 64]], ['regression: short signal', [[1, 1, 1], [2, 5]], [22, 21]], ['control: single sample', [[1, 2], [3]], [3]], ['control: empty', [[1, 1], []], []], ['control: random filtfilt 4', [[-1], [8, 1, 1, 8]], [8, 1, 1, 8]], ['control: random filtfilt 5', [[2, 1, -1, 3], [0]], [0]]], [['regression: random filtfilt 0', [[-1, -1], [-5, 3]], [-20, 12]], ['regression: random filtfilt 1', [[2, -1], [-1, 3, 2, 2, 8, 5, 5, 0, -3]], [-1, 13, 0, -10, 26, -1, 15, -4, -3]], ['regression: short signal', [[1, 1, 1], [2, 5]], [22, 21]], ['control: random filtfilt 12', [[0], [-2, 4, -5, -1, 8, 4, 3, 0, 1]], [0, 0, 0, 0, 0, 0, 0, 0, 0]], ['control: random filtfilt 16', [[-1, -1, 0], [8]], [8]], ['control: random filtfilt 17', [[-1, -2, -1], [-1]], [-1]], ['control: random filtfilt 27', [[0], [0, -5, 8, -2, 9, 3]], [0, 0, 0, 0, 0, 0]]], [['regression: random filtfilt 6', [[1, 3, -2, -1], [-3, -1, -4]], [-2, 59, -4]], ['regression: random filtfilt 8', [[-1, -2], [5, 5, 7]], [45, 49, 63]], ['regression: random filtfilt 1', [[2, -1], [-1, 3, 2, 2, 8, 5, 5, 0, -3]], [-1, 13, 0, -10, 26, -1, 15, -4, -3]], ['control: random filtfilt 39', [[3, 2, 3], [6]], [54]], ['control: random filtfilt 46', [[-1, 1, 2, 2], [5]], [5]], ['control: single sample', [[1, 2], [3]], [3]], ['control: empty', [[1, 1], []], []]], [['regression: random filtfilt 10', [[2, 0, -2], [-3, 9, 2, 7]], [0, 104, -20, 0]], ['regression: random filtfilt 11', [[0, 3, 3, 3], [2, 0, 6]], [162, 288, 234]], ['repair check: random filtfilt 3', [[-1], [-4, 3, -2, 7, 9, 5, 3, -2, 1]], [-4, 3, -2, 7, 9, 5, 3, -2, 1]], ['control: random filtfilt 4', [[-1], [8, 1, 1, 8]], [8, 1, 1, 8]], ['control: random filtfilt 5', [[2, 1, -1, 3], [0]], [0]], ['control: random filtfilt 12', [[0], [-2, 4, -5, -1, 8, 4, 3, 0, 1]], [0, 0, 0, 0, 0, 0, 0, 0, 0]], ['control: random filtfilt 16', [[-1, -1, 0], [8]], [8]]], [['regression: random filtfilt 14', [[0, -1, -2, -1], [-3, 7]], [-53, -12]], ['regression: random filtfilt 18', [[2, -2], [2, -3, 5, -1, 3, -2, 3]], [0, -52, 56, -40, 36, -40, 0]], ['repair check: random filtfilt 7', [[-2], [8, -5, -4, -4, -2, 9, 6, 3]], [32, -20, -16, -16, -8, 36, 24, 12]], ['control: random filtfilt 17', [[-1, -2, -1], [-1]], [-1]], ['control: random filtfilt 27', [[0], [0, -5, 8, -2, 9, 3]], [0, 0, 0, 0, 0, 0]], ['control: random filtfilt 39', [[3, 2, 3], [6]], [54]], ['control: random filtfilt 46', [[-1, 1, 2, 2], [5]], [5]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: ramp with two-tap average | [20, 16, 12, 8, 4] | [4, 8, 12, 16, 20] | Failed |
| regression: step with three taps | [64, 64, 60, 44, 20, 0] | [0, 20, 44, 60, 64, 64] | Failed |
| regression: short signal | [21, 22] | [22, 21] | Failed |
| control: single sample | [3] | [3] | Passed |
| control: empty | [] | [] | Passed |
| control: random filtfilt 4 | [8, 1, 1, 8] | [8, 1, 1, 8] | Passed |
| control: random filtfilt 5 | [0] | [0] | Passed |
SHA-256 / 1a950d97e8d4b08ea710ede08231dfaf2aaa538860292df690148f45975ccea7
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
b, xs = x
n = len(xs)
if n == 0:
return []
pad = min(3 * (len(b) - 1), n - 1)
front = [2 * xs[0] - xs[i] for i in range(pad, 0, -1)]
back = [2 * xs[-1] - xs[n - 1 - i] for i in range(1, pad + 1)]
ext = front + xs + back
def fir(sig):
return [sum(b[k] * sig[i - k] for k in range(len(b)) if i - k >= 0) for i in range(len(sig))]
y = fir(ext)
y = fir(y[::-1])[::-1]
return y[pad:pad + n]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: ramp with two-tap average', [[1, 1], [1, 2, 3, 4, 5]], [4, 8, 12, 16, 20]], ['regression: step with three taps', [[1, 2, 1], [0, 0, 4, 4, 4, 4]], [0, 20, 44, 60, 64, 64]], ['regression: short signal', [[1, 1, 1], [2, 5]], [22, 21]], ['control: single sample', [[1, 2], [3]], [3]], ['control: empty', [[1, 1], []], []], ['control: random filtfilt 4', [[-1], [8, 1, 1, 8]], [8, 1, 1, 8]], ['control: random filtfilt 5', [[2, 1, -1, 3], [0]], [0]]], [['regression: random filtfilt 0', [[-1, -1], [-5, 3]], [-20, 12]], ['regression: random filtfilt 1', [[2, -1], [-1, 3, 2, 2, 8, 5, 5, 0, -3]], [-1, 13, 0, -10, 26, -1, 15, -4, -3]], ['regression: short signal', [[1, 1, 1], [2, 5]], [22, 21]], ['control: random filtfilt 12', [[0], [-2, 4, -5, -1, 8, 4, 3, 0, 1]], [0, 0, 0, 0, 0, 0, 0, 0, 0]], ['control: random filtfilt 16', [[-1, -1, 0], [8]], [8]], ['control: random filtfilt 17', [[-1, -2, -1], [-1]], [-1]], ['control: random filtfilt 27', [[0], [0, -5, 8, -2, 9, 3]], [0, 0, 0, 0, 0, 0]]], [['regression: random filtfilt 6', [[1, 3, -2, -1], [-3, -1, -4]], [-2, 59, -4]], ['regression: random filtfilt 8', [[-1, -2], [5, 5, 7]], [45, 49, 63]], ['regression: random filtfilt 1', [[2, -1], [-1, 3, 2, 2, 8, 5, 5, 0, -3]], [-1, 13, 0, -10, 26, -1, 15, -4, -3]], ['control: random filtfilt 39', [[3, 2, 3], [6]], [54]], ['control: random filtfilt 46', [[-1, 1, 2, 2], [5]], [5]], ['control: single sample', [[1, 2], [3]], [3]], ['control: empty', [[1, 1], []], []]], [['regression: random filtfilt 10', [[2, 0, -2], [-3, 9, 2, 7]], [0, 104, -20, 0]], ['regression: random filtfilt 11', [[0, 3, 3, 3], [2, 0, 6]], [162, 288, 234]], ['repair check: random filtfilt 3', [[-1], [-4, 3, -2, 7, 9, 5, 3, -2, 1]], [-4, 3, -2, 7, 9, 5, 3, -2, 1]], ['control: random filtfilt 4', [[-1], [8, 1, 1, 8]], [8, 1, 1, 8]], ['control: random filtfilt 5', [[2, 1, -1, 3], [0]], [0]], ['control: random filtfilt 12', [[0], [-2, 4, -5, -1, 8, 4, 3, 0, 1]], [0, 0, 0, 0, 0, 0, 0, 0, 0]], ['control: random filtfilt 16', [[-1, -1, 0], [8]], [8]]], [['regression: random filtfilt 14', [[0, -1, -2, -1], [-3, 7]], [-53, -12]], ['regression: random filtfilt 18', [[2, -2], [2, -3, 5, -1, 3, -2, 3]], [0, -52, 56, -40, 36, -40, 0]], ['repair check: random filtfilt 7', [[-2], [8, -5, -4, -4, -2, 9, 6, 3]], [32, -20, -16, -16, -8, 36, 24, 12]], ['control: random filtfilt 17', [[-1, -2, -1], [-1]], [-1]], ['control: random filtfilt 27', [[0], [0, -5, 8, -2, 9, 3]], [0, 0, 0, 0, 0, 0]], ['control: random filtfilt 39', [[3, 2, 3], [6]], [54]], ['control: random filtfilt 46', [[-1, 1, 2, 2], [5]], [5]]]]
for label, args, expected in fixtures[N-1]:
check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: ramp with two-tap average | [4, 8, 12, 16, 20] | [4, 8, 12, 16, 20] | Passed |
| regression: step with three taps | [0, 20, 44, 60, 64, 64] | [0, 20, 44, 60, 64, 64] | Passed |
| regression: short signal | [22, 21] | [22, 21] | Passed |
| control: single sample | [3] | [3] | Passed |
| control: empty | [] | [] | Passed |
| control: random filtfilt 4 | [8, 1, 1, 8] | [8, 1, 1, 8] | Passed |
| control: random filtfilt 5 | [0] | [0] | Passed |
SHA-256 / b43d25acbcdb90519d18da9417760bfaf0bb760cef72e0f1bd749f05422ef514
Verification & scope
A deterministic bounded teaching model with a stipulated toy contract; exact rational arithmetic or fixed-decimal rounding keeps outputs strict JSON. It is not a production DSP library and claims no standards conformance. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:51:38.185200+00:00.
Case digest / 01be6b40a7bcb94160b878850ba9b73d107e5bbf125c8e6ce2149bb0976e5d41