Calibrating matrix-lattice static gain diagnostics¶
Tutorial goal
Use a known matrix-lattice all-pass response to validate static gain compensation diagnostics.
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¶
This tutorial is a calibration case for the experimental realization scaffold. It starts from a known matrix-lattice all-pass response, wraps it in static nonunitary gains, and then fits static left/right gains back around the lattice response. The compensated error should collapse when the only mismatch is static gain.
Key idea and equations¶
Given a lattice all-pass response G(e^{j\omega}) and a target
the diagnostic solves a least-squares problem for static matrices L and R. This
separates static nonunitary gain mismatch from dynamic lattice/all-pass mismatch.
How to read the result¶
Look for near-zero all-pass calibration error and a large improvement after fitting static gains around the gain-wrapped lattice response.
Run command¶
python examples/experimental_mimo_matrix_lattice_calibration.py
Run status¶
Return code: 0
Captured stdout¶
known lattice dimension: 3
known lattice order: 4
known lattice unitarity error: 1.341e-14
max reflection singular value: 0.7432
all-pass calibration raw error: 0.000e+00
all-pass calibration compensated error: 0.000e+00
gain-wrapped raw error: 1.148e+00
gain-wrapped compensated error: 1.142e-06
static-gain improvement: 1.01e+06x
left/right fitted gain condition: 5.143 1.000
Figures¶
experimental_mimo_matrix_lattice_calibration_error.png¶
experimental_mimo_matrix_lattice_calibration_gain.png¶
Generated data files¶
Source code¶
1"""Tutorial: calibrating matrix-lattice static gain diagnostics.
2
3This example uses a known matrix-lattice all-pass response, then wraps it in
4static nonunitary gains. The diagnostic fit should recover a near-perfect
5compensated response. This is a calibration case for the experimental
6MIMO state-space to matrix-lattice realization scaffold: it separates true
7all-pass/lattice mismatch from static gain mismatch.
8"""
9
10from __future__ import annotations
11
12import csv
13import os
14from pathlib import Path
15
16import numpy as np
17
18import lattice_dsp as ld
19
20
21def artifact_dir() -> Path:
22 path = Path(os.environ.get("LATTICE_DSP_ARTIFACT_DIR", "reports/example-artifacts"))
23 path.mkdir(parents=True, exist_ok=True)
24 return path
25
26
27def known_lattice(dimension: int = 3, order: int = 4) -> ld.MatrixLatticeAllPass:
28 rng = np.random.default_rng(421)
29 reflections = []
30 for stage in range(order):
31 raw = rng.normal(size=(dimension, dimension)) + 1j * rng.normal(size=(dimension, dimension))
32 reflections.append(ld.contractive_matrix_from_raw(0.30 * raw / (1.0 + stage)))
33 residue_raw = rng.normal(size=(dimension, dimension)) + 1j * rng.normal(
34 size=(dimension, dimension)
35 )
36 residue = ld.unitary_polar_factor(residue_raw)
37 return ld.MatrixLatticeAllPass(reflections, residue=residue)
38
39
40def main() -> None:
41 out_dir = artifact_dir()
42 dimension = 3
43 order = 4
44 n_freq = 384
45 omega = np.linspace(0.0, np.pi, n_freq)
46
47 lattice = known_lattice(dimension=dimension, order=order)
48 response = lattice.frequency_response(omega, n_threads=1)
49
50 rng = np.random.default_rng(422)
51 left_true = np.diag([1.8, 0.75, 0.35]).astype(np.complex128)
52 right_true = ld.unitary_polar_factor(
53 rng.normal(size=(dimension, dimension)) + 1j * rng.normal(size=(dimension, dimension))
54 )
55 target = np.asarray([left_true @ h @ right_true for h in response], dtype=np.complex128)
56
57 allpass_fit = ld.fit_static_matrix_gains(response, response, mode="both", n_iter=6)
58 gain_fit = ld.fit_static_matrix_gains(target, response, mode="both", n_iter=40)
59
60 summary = {
61 "dimension": dimension,
62 "order": order,
63 "known_lattice_unitarity_error": lattice.unitarity_error(omega),
64 "allpass_raw_error": allpass_fit["raw_relative_error"],
65 "allpass_compensated_error": allpass_fit["compensated_relative_error"],
66 "gain_wrapped_raw_error": gain_fit["raw_relative_error"],
67 "gain_wrapped_compensated_error": gain_fit["compensated_relative_error"],
68 "gain_improvement": float(gain_fit["raw_relative_error"])
69 / max(float(gain_fit["compensated_relative_error"]), 1e-30),
70 "left_gain_condition": gain_fit["left_gain_condition"],
71 "right_gain_condition": gain_fit["right_gain_condition"],
72 "max_reflection_singular_value": lattice.max_reflection_singular_value(),
73 }
74
75 csv_path = out_dir / "experimental_mimo_matrix_lattice_calibration_summary.csv"
76 with csv_path.open("w", newline="", encoding="utf-8") as f:
77 writer = csv.DictWriter(f, fieldnames=list(summary))
78 writer.writeheader()
79 writer.writerow(summary)
80
81 print("known lattice dimension:", dimension)
82 print("known lattice order:", order)
83 print("known lattice unitarity error:", f"{summary['known_lattice_unitarity_error']:.3e}")
84 print("max reflection singular value:", f"{summary['max_reflection_singular_value']:.4f}")
85 print("all-pass calibration raw error:", f"{summary['allpass_raw_error']:.3e}")
86 print("all-pass calibration compensated error:", f"{summary['allpass_compensated_error']:.3e}")
87 print("gain-wrapped raw error:", f"{summary['gain_wrapped_raw_error']:.3e}")
88 print("gain-wrapped compensated error:", f"{summary['gain_wrapped_compensated_error']:.3e}")
89 print("static-gain improvement:", f"{summary['gain_improvement']:.2e}x")
90 print(
91 "left/right fitted gain condition:",
92 f"{summary['left_gain_condition']:.3f}",
93 f"{summary['right_gain_condition']:.3f}",
94 )
95 print(f"wrote {csv_path}")
96
97 try:
98 import matplotlib.pyplot as plt
99 except Exception:
100 print("matplotlib is not installed; skipped figures")
101 return
102
103 compensated = np.asarray(gain_fit["compensated_response"], dtype=np.complex128)
104 raw_per_freq = np.linalg.norm(target - response, axis=(1, 2)) / np.maximum(
105 np.linalg.norm(target, axis=(1, 2)), 1e-30
106 )
107 compensated_per_freq = np.linalg.norm(target - compensated, axis=(1, 2)) / np.maximum(
108 np.linalg.norm(target, axis=(1, 2)), 1e-30
109 )
110
111 fig, ax = plt.subplots(figsize=(8.0, 4.5))
112 ax.semilogy(omega, raw_per_freq, label="raw lattice vs gain-wrapped target")
113 ax.semilogy(omega, compensated_per_freq, label="after fitted static gains")
114 ax.set_title("Static gain calibration for matrix-lattice response")
115 ax.set_xlabel("radian frequency")
116 ax.set_ylabel("relative Frobenius error")
117 ax.grid(True, alpha=0.3)
118 ax.legend()
119 fig.tight_layout()
120 fig_path = out_dir / "experimental_mimo_matrix_lattice_calibration_error.png"
121 fig.savefig(fig_path, dpi=160)
122 print(f"wrote {fig_path}")
123
124 sv = np.linalg.svd(np.asarray(gain_fit["left_gain"]), compute_uv=False)
125 fig2, ax2 = plt.subplots(figsize=(7.0, 4.0))
126 ax2.plot(np.arange(1, sv.size + 1), sv, marker="o")
127 ax2.set_title("Fitted left static-gain singular values")
128 ax2.set_xlabel("singular-value index")
129 ax2.set_ylabel("singular value")
130 ax2.grid(True, alpha=0.3)
131 fig2.tight_layout()
132 fig2_path = out_dir / "experimental_mimo_matrix_lattice_calibration_gain.png"
133 fig2.savefig(fig2_path, dpi=160)
134 print(f"wrote {fig2_path}")
135
136
137if __name__ == "__main__":
138 main()