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

Run status

Return code: 0

Captured stdout

streaming matches one-shot: True
RLS streaming tail MSE: 2.269267880262604e-21
RLS learned taps: [0.3  0.   0.55]

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()