feat: add transposeMultiply (thisᵀ · other) without materializing the transpose#211
Merged
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #211 +/- ##
==========================================
- Coverage 69.15% 68.58% -0.58%
==========================================
Files 48 49 +1
Lines 5761 5847 +86
Branches 1031 1042 +11
==========================================
+ Hits 3984 4010 +26
- Misses 1765 1824 +59
- Partials 12 13 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… transpose The `this.transpose().mmul(other)` idiom allocates the full transpose and then walks it. `transposeMultiply` computes `thisᵀ · other` directly by streaming each shared row once and applying a rank-1 update, so both operands are read row-major (contiguously) and zero entries of `this` are skipped — the two-matrix sibling of `gram()`, following the same transposed-storage/cache-sequential reasoning as the SVD/EVD work in #167. Result is identical to `this.transpose().mmul(other)`. Measured `transpose().mmul()` -> `transposeMultiply()`: ~1.16x on dense 256/512, ~1.1x on a 252x210 block, and ~46x on a sparse matrix (4 non-zeros per row). Wire the two internal callers (correlation, covariance) over to it, add unit tests (dense and sparse equivalence, row-mismatch throw), a benchmark, and the type declaration. Assisted-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8e93200 to
6336f23
Compare
Build a genuinely sparse operand with ml-sparse-matrix (added as a devDependency), densify it — the path callers such as the NMR spin simulation take — and check `sparseᵀ · b` matches `transpose().mmul()`. Also switch the benchmark's sparse case to build its input with a real SparseMatrix. Measured on that sparse-matrix-built input, transpose().mmul() -> transposeMultiply(): 12.8x at 256 (8 nnz/row), 30.8x at 512 (9 nnz/row), 47.6x at 1024 (10 nnz/row). Assisted-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
targos
approved these changes
Jul 9, 2026
targos
left a comment
Member
There was a problem hiding this comment.
As usual the comments are too verbose or use weird vocabulary.
Write rank-1 updates directly into the result matrix's rows instead of into a separate Float64Array that is then copied out. Removes an n×p allocation and a full copy-out pass, ~1.3-1.4x faster. Assisted-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds transposeMultiply(other), which computes thisᵀ · other (this matrix transposed, times another). It's the two-matrix companion to the existing gram().
The usual way to do this — this.transpose().mmul(other) — first builds a whole transposed copy of the matrix, then multiplies. transposeMultiply skips that: it reads each matrix a single time, row by row, and never builds the transpose.
Benchmark
benchmark/transposeMultiply.js,transpose().mmul()→transposeMultiply():On a genuinely sparse operand built with
ml-sparse-matrixand densified (the path callers such as the NMR spin simulation take):