Skip to content

feat: add transposeMultiply (thisᵀ · other) without materializing the transpose#211

Merged
lpatiny merged 3 commits into
mainfrom
feat/transpose-multiply
Jul 13, 2026
Merged

feat: add transposeMultiply (thisᵀ · other) without materializing the transpose#211
lpatiny merged 3 commits into
mainfrom
feat/transpose-multiply

Conversation

@lpatiny

@lpatiny lpatiny commented Jul 9, 2026

Copy link
Copy Markdown
Member

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():

shape speedup
dense 256² 1.16×
dense 512² 1.16×
252×210 block 1.11×

On a genuinely sparse operand built with ml-sparse-matrix and densified (the path callers such as the NMR spin simulation take):

size nnz/row speedup
256 8 12.8×
512 9 30.8×
1024 10 47.6×

@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 31.81818% with 60 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.58%. Comparing base (071d688) to head (1a7068d).

Files with missing lines Patch % Lines
benchmark/transposeMultiply.js 0.00% 59 Missing and 1 partial ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

… 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>
@lpatiny lpatiny force-pushed the feat/transpose-multiply branch from 8e93200 to 6336f23 Compare July 9, 2026 12:58
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>
@lpatiny lpatiny requested a review from targos July 9, 2026 13:15

@targos targos left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As usual the comments are too verbose or use weird vocabulary.

@lpatiny lpatiny marked this pull request as draft July 10, 2026 08:26
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>
@lpatiny lpatiny marked this pull request as ready for review July 13, 2026 12:28
@lpatiny lpatiny merged commit f0f21a7 into main Jul 13, 2026
8 of 10 checks passed
@lpatiny lpatiny deleted the feat/transpose-multiply branch July 13, 2026 12:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants