graph LR SPEC["Filter spec\nN, wpass, wstop"] OPT["Ellipsoid optimizer\ncutting-plane"] SF["Spectral factor\nR(w) โ h"] CSD["CSD quantize\nh โ ยฑ2^k"] VER["Verilog\nshift + add"] SPEC --> OPT OPT -->|"feasible r"| SF SF --> CSD CSD -->|"residual cut"| OPT CSD --> VER style SPEC fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style OPT fill:#fff9c4,stroke:#f57f17,stroke-width:3px style SF fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style CSD fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style VER fill:#d1c4e9,stroke:#6a1b9a,stroke-width:3px
Where 3.66 s of main() went (cProfile) _scan_constraints 3.04 s ยท 83% spectral_fact_fft 0.22 s ยท 6% csd_quantize 0.15 s ยท 4% ellipsoid update 0.01 s 553 912 tiny per-row ndarray.dot calls โ ~373 per iteration
graph TD subgraph BEFORE["Before ยท per-row"] B1["row k"] --> B2["dot(row, x)"] --> B3["check"] --> B4["row k+1"] end subgraph AFTER["After ยท vectorized"] A1["A @ x\none BLAS call"] --> A2["boolean masks\n+ argmax"] end style B1 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style B2 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style B3 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style B4 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style A1 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style A2 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
Scan primitive on the real (480 ร 32) spectrum per-row dots 799.76 ยตs S @ x matvec 8.51 ยตs โ 94ร faster on the primitive
graph LR PEEK["_peek(rr)\nread cursor\n(no advance)"] MASK["mask.any()\nfirst True\ncyclic from start"] SET["rr._cur = k\nland on violation"] NONE["no violation\ncursor unchanged"] PEEK --> MASK MASK -->|"found"| SET MASK -->|"none"| NONE style PEEK fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style SET fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style NONE fill:#fff9c4,stroke:#f57f17,stroke-width:3px
Before (red) vs After (green) assess_feas 3.05 s 0.082 s ยท 37ร main() 3.66 s 0.49 s ยท 7.5ร end-to-end wall 3.01 s 1.87 s ยท 1.6ร After the scan fix the run is import-bound (~1.4 s of numpy/ellalgo import).
graph LR SCAN["constraint scan\n0.082 s โ "] FFT["spectral_fact\n0.15 s โญ๏ธ next"] CSD["csd_quantize\n0.14 s โญ๏ธ next"] IMP["imports\n1.4 s ๐"] IMP --> SCAN SCAN --> FFT FFT --> CSD style SCAN fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style FFT fill:#fff9c4,stroke:#f57f17,stroke-width:3px style CSD fill:#fff9c4,stroke:#f57f17,stroke-width:3px style IMP fill:#f8bbd0,stroke:#c2185b,stroke-width:3px
graph LR PY["multiplierless.py\noracle + FFT + CSD"] CPP["multiplierless-cpp\nArr ยท RoundRobin ยท FFTW"] PY -.->|"same algorithm"| CPP CM["CMake + CPM\npinned deps"] XM["xmake\nlocal siblings"] CM --> CPP XM --> CPP style PY fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style CPP fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style CM fill:#d1c4e9,stroke:#6a1b9a,stroke-width:3px style XM fill:#ffe0b2,stroke:#e65100,stroke-width:3px
C++ cutting_plane_optim โ 21 546 iterations original per-row scan 0.170 s full-matvec port 0.327 s โ 1.9ร slower โ a regression, not an optimization
5-run median ยท bitwise-identical summation order original 0.172 s raw pointers 0.160 s ยท ~7%
FirDesign on sample_filter.json Python ๐ 1.87 s C++ โ๏ธ 0.197 s ยท ~9ร faster The C++ sibling was already fast โ so the Python optimization had little to transfer.
graph LR PY["multiplierless.py\noracle + FFT + CSD"] CPP["multiplierless-cpp\nArr ยท RoundRobin ยท FFTW"] RS["multiplierless-rs\nArr ยท RoundRobin ยท rustfft"] PY -.->|"same algorithm"| CPP PY -.->|"same algorithm"| RS style PY fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style CPP fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style RS fill:#ffe0b2,stroke:#e65100,stroke-width:3px
graph LR subgraph ONE["1 accumulator ยท 32 deep"] I1["x0"] --> I2["x1"] --> I3["x2"] --> I4["... x31"] end subgraph FOUR["4 accumulators ยท 8 deep"] F0["a0: x0, x4, ..."] F1["a1: x1, x5, ..."] F2["a2: x2, x6, ..."] F3["a3: x3, x7, ..."] end style I1 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style I2 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style I3 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style I4 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style F0 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style F1 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style F2 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style F3 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
Rust ยท release, N = 32 raw dot / row 23.3 ns 10.1 ns ยท 2.3ร assess_optim (full scan) 13.9 ยตs 10.7 ยตs ยท 1.3ร end-to-end run 0.083 s 0.082 s ยท flat
graph LR Q["Where is the time?"] Q -->|"interpreter overhead"| PY["Python\nhuge win โ "] Q -->|"compiled, early exit"| CC["C++ / Rust\nscan not the bottleneck"] CC --> K["kernel still faster\n(oracle API) โ "] CC --> E["end-to-end flat โ ๏ธ"] style Q fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style PY fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style CC fill:#fff9c4,stroke:#f57f17,stroke-width:3px style K fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style E fill:#f8bbd0,stroke:#c2185b,stroke-width:3px