Quickstart

This quickstart shows the core idea of lattice-dsp: represent recursive filters with reflection/PARCOR coefficients and lattice-ladder structures rather than treating IIR denominator coefficients as unconstrained parameters.

For a full guided walkthrough, build the generated tutorial gallery:

./scripts/build_docs_with_results.sh
xdg-open docs/_build/html/examples/index.html

Reflection coefficients to a stable IIR denominator

Reflection coefficients satisfy abs(k_i) < 1. For scalar all-pole denominators, this gives a stability-aware parameterization: bounded reflection coefficients map to stable denominators.

import numpy as np
import lattice_dsp as ld

k = np.array([0.7, -0.4, 0.25])
a = ld.reflection_to_denominator(k)
restored = ld.denominator_to_reflection(a)

print("denominator", a)
print("restored", restored)

Lattice-ladder realization

A lattice-ladder filter uses reflection coefficients for the recursive part and ladder taps for the numerator/output combination. This is the scalar IIR path that most directly motivates the package.

filt = ld.LatticeLadderIIR([0.35, -0.2, 0.1], [0.1, -0.05, 0.2, 0.8])
x = np.random.default_rng(0).standard_normal(1024)
y = filt.process(x)

Adaptive recursive filtering

The adaptive examples are intended to demonstrate stable recursive identification. They are not generic black-box optimizers; the point is to update a recursive model while controlling the denominator through lattice coordinates.

model = ld.LatticeLadderRLS(order=3, forgetting_factor=0.99, delta=100.0)
x = np.random.default_rng(0).standard_normal(4096)
d = np.convolve(x, [0.25, -0.15, 0.65], mode="full")[: len(x)]
y, e = model.process(x, d)
print("tail MSE", np.mean(e[-512:] ** 2))

Matrix/MIMO lattice example

The matrix lattice utilities explore multichannel all-pass and paraunitary behavior. They are useful for experiments in compact MIMO responses, energy-preserving transforms, multichannel AR diagnostics, and ML-inspired unitary layers.

rng = np.random.default_rng(0)
K = ld.contractive_matrix_from_raw(
    0.2 * (rng.standard_normal((4, 4)) + 1j * rng.standard_normal((4, 4))),
    margin=1e-6,
)
R = ld.unitary_polar_factor(rng.standard_normal((4, 4)) + 1j * rng.standard_normal((4, 4)))
filt = ld.MatrixLatticeAllPass([K], R)
H = filt.frequency_response(np.linspace(0, np.pi, 128))
y = filt.to_online_filter().process(np.ones((64, 3)))

# H[w]^H H[w] should be close to identity for every frequency.
I = np.eye(4)
err = max(np.linalg.norm(h.conj().T @ h - I) for h in H)
print(err)

Causal MIMO lattice prediction

The multichannel AR layer can also run as a sample-by-sample lattice predictor after its matrix reflection coefficients have been fitted.

x_train = np.random.default_rng(1).standard_normal((4096, 3))
r = ld.multichannel_autocorrelation(x_train, order=3)
fit = ld.block_levinson_durbin(r, order=3)
pred = ld.MIMOLatticePredictor.from_levinson(fit)

y_hat = pred.predict()       # before observing y_t
err = pred.update(x_train[0]) # after observing y_t

Flagship robustness tutorial

After installing the docs/examples extras, build the generated tutorials and open the LMS/H∞ robustness tutorial:

./scripts/build_docs_with_results.sh
xdg-open docs/_build/html/examples/generated/hinf_lms_reproduction.html

The rendered page is also available from the docs navigation at Examples tutorials / LMS as a minimax robust filter. This tutorial explains why LMS can be interpreted as a minimax robust filter, not only as an approximate least-squares recursion.