sketch_freq_directions.nx
buildroot/runtime/sketch_freq_directions.nx
about
sketch_freq_directions.nx -- Liberty 2013 Frequent Directions sketch.
Streaming low-rank matrix approximation. Given a stream of input
rows in R^d, maintain a sketch matrix B in R^(l x d) (l << n) such
that B^T B approximates A^T A to within an additive error bound
based on the tail singular values:
|| A^T A - B^T B ||_F <= || A - A_k ||_F^2 / (l - k)
where A_k is the best rank-k approximation of A. Direct
matrix-valued analog of Misra-Gries frequency counting (Liberty's
own framing in the 2013 STOC paper).
ALGORITHM:
For each input row x in R^d:
1. If any row of B is "empty" (all zeros) -> copy x into it.
2. Else -> shrink:
a. Compute SVD: B = U Σ V^T
b. delta := σ_l^2 (smallest singular value squared)
c. Shrunk singular values: σ_i'^2 := max(0, σ_i^2 - delta)
d. B := diag(sqrt(σ_i'^2)) V^T
-- by construction the last row has σ_l' = 0 (empty)
e. Copy x into the now-empty row.
SVD WITHOUT f64 (the technical chokepoint -- see queued roadmap
2026-05-10):
This reference impl uses POWER ITERATION with DEFLATION in Q14
fixed-point. Bounded to l <= 4, d <= 8 for this version --
convergence guarantee at small sizes is reliable; for larger
matrices the accumulated round-off in Q14 degrades the
shrink quality. v2 will swap in fixed-point Jacobi or
bidiagonal + QR for arbitrary size.
MATURITY = REFERENCE_IMPL with EXPLICIT size limits. Substrate
callers requesting larger l or d get NX_FD_ERR_TOO_BIG at alloc.
COMPOSITION:
- Complements sketch_kmeans1d (clustering family) for streaming
dimensionality reduction before clustering.
- Complements sketch_correlation for streaming low-rank
dependencies 2 imports · 3 importers
imports: syscalls.nxsketch_types.nx
imported by: nx_lowrank_kv_gate.nxsketch_freq_directions_test.nxsketch_freq_directions_vs_topnorm_bench.nx
structs
| 58 | struct FreqDir |
consts
| 48 | const NX_FD_Q14: i64 = 16384 |
| 49 | const NX_FD_L_MAX: i64 = 4 |
| 50 | const NX_FD_D_MAX: i64 = 8 |
| 51 | const NX_FD_POWER_ITERS: i64 = 32 |
| 52 | const NX_FD_POWER_TOL_Q14: i64 = 1 // 1/16384 ~ 6e-5 convergence |
| 54 | const NX_FD_OK: i64 = 0 |
| 55 | const NX_FD_ERR_TOO_BIG: i64 = -1 |
| 56 | const NX_FD_ERR_DIM_MISMATCH: i64 = -2 |
functions
| 72 | func nx_fd_alloc(l: i64, d: i64) -> *FreqDir |
| 104 | func nx_fd_isqrt(x: i64) -> i64 |
| 121 | func nx_fd_abs(x: i64) -> i64 called by 1: nx_fd_power_one |
| 128 | func nx_fd_b_get(fd: *FreqDir, r: i64, c: i64) -> i64 |
| 132 | func nx_fd_b_set(fd: *FreqDir, r: i64, c: i64, v: i64) -> i64 |
| 137 | func nx_fd_row_norm_sq(fd: *FreqDir, r: i64) -> i64 |
| 148 | func nx_fd_empty_row_index(fd: *FreqDir) -> i64 |
| 163 | func nx_fd_dot(a: *i64, b: *i64, n: i64) -> i64 called by 1: nx_fd_vnorm_sq |
| 173 | func nx_fd_vnorm_sq(v: *i64, n: i64) -> i64 |
| 177 | func nx_fd_vscale(v: *i64, n: i64, factor_q14: i64) -> i64 called by 1: nx_fd_vnormalize |
| 186 | func nx_fd_vsub_scaled(target: *i64, source: *i64, n: i64, factor_q14: i64) -> i64 |
| 195 | func nx_fd_vnormalize(v: *i64, n: i64) -> i64 |
| 212 | func nx_fd_matvec_Bv(fd: *FreqDir, v: *i64, out: *i64) -> i64 |
| 227 | func nx_fd_matvec_Btw(fd: *FreqDir, w: *i64, out: *i64) -> i64 |
| 248 | func nx_fd_basis_get(fd: *FreqDir, idx: i64, c: i64) -> i64 |
| 252 | func nx_fd_basis_set(fd: *FreqDir, idx: i64, c: i64, v: i64) -> i64 called by 1: nx_fd_power_one |
| 258 | func nx_fd_deflate(fd: *FreqDir, v: *i64, found: i64) -> i64 |
| 282 | func nx_fd_power_one(fd: *FreqDir, found_idx: i64, init_idx: i64) -> i64 called by 1: nx_fd_shrink calls 7: nx_fd_deflatenx_fd_vnormalizenx_fd_matvec_Bvnx_fd_matvec_Btwnx_fd_vnorm_sqnx_fd_abs+1 |
| 355 | func nx_fd_shrink(fd: *FreqDir) -> i64 |
| 392 | func nx_fd_add_row(fd: *FreqDir, row: *i64, n: i64) -> i64 called by 3: kv_run_casemainmain calls 4: nx_fd_empty_row_indexnx_fd_shrinknx_fd_row_norm_sqnx_fd_b_set |
| 435 | func nx_fd_top_sigma_sq(fd: *FreqDir) -> i64 |
| 449 | func nx_fd_total_rows(fd: *FreqDir) -> i64 called by 1: main |
| 453 | func nx_fd_memory_bytes(fd: *FreqDir) -> i64 |
| 464 | func nx_fd_rel_err_ppb(l: i64) -> i64 called by 1: nx_fd_query_top |
| 470 | func nx_fd_query_top(fd: *FreqDir) -> *ApproxI64 |