Streaming block processing equivalence¶
Tutorial goal
Show that stateful block processing matches one-shot processing.
Note
New to the terminology? See the lattice DSP concept map and the causality/data-use guide for how online, offline, block, and MIMO examples should be read.
Context¶
Real-time DSP usually processes blocks rather than entire arrays. This tutorial checks that block boundaries do not change the result when filter state is carried correctly.
Key idea and equations¶
For a stateful recursion, the output over concatenated blocks should match the output over the full signal when final state from one block is used as initial state for the next.
How to read the result¶
The reported maximum difference should be close to roundoff.
Run command¶
python examples/streaming_block_processing.py
Source code¶
1"""Stateful streaming/block processing helpers."""
2
3from __future__ import annotations
4
5import numpy as np
6
7from lattice_dsp import AdaptiveBlockProcessor, BlockProcessor, LatticeIIR
8
9
10def main() -> None:
11 rng = np.random.default_rng(7)
12 x = rng.normal(size=4096)
13 reflection = [0.45, -0.2]
14 taps = [0.3, 0.0, 0.55]
15
16 one_shot = np.asarray(LatticeIIR(reflection, taps).process(x), dtype=float)
17 stream = BlockProcessor(reflection, taps)
18 pieces = [stream.process(x[i : i + 256]) for i in range(0, len(x), 256)]
19 blockwise = np.concatenate(pieces)
20 print("streaming matches one-shot:", np.allclose(one_shot, blockwise))
21
22 desired = one_shot
23 adaptive = AdaptiveBlockProcessor(reflection, [0.0, 0.0, 0.0], kind="rls")
24 errors = []
25 for i in range(0, len(x), 256):
26 result = adaptive.process_adapt(x[i : i + 256], desired[i : i + 256])
27 errors.append(result.error)
28 err = np.concatenate(errors)
29 print("RLS streaming tail MSE:", float(np.mean(err[-512:] ** 2)))
30 print("RLS learned taps:", np.round(adaptive.adaptive.taps, 4))
31
32
33if __name__ == "__main__":
34 main()