Skip to content

feat: add Matrix.mmulByTranspose()#207

Merged
lpatiny merged 2 commits into
mainfrom
add-mmul-by-transpose
Jun 23, 2026
Merged

feat: add Matrix.mmulByTranspose()#207
lpatiny merged 2 commits into
mainfrom
add-mmul-by-transpose

Conversation

@lpatiny

@lpatiny lpatiny commented Jun 20, 2026

Copy link
Copy Markdown
Member

What

Adds Matrix.prototype.mmulByTranspose(scale?):

  • J.mmulByTranspose()J · Jᵀ (an m×m result from an m×n matrix).
  • J.mmulByTranspose(scale)J · diag(scale) · Jᵀ, the weighted Gram matrix, with scale a per-column factor.

The result is symmetric, so only the upper triangle is computed and mirrored, and the transpose is never materialized.

Why

This is the Gram-style product at the heart of the (weighted) normal equations — e.g. the Gauss-Newton Hessian J·W·Jᵀ in Levenberg–Marquardt. Computing it as J.mmul(J.transpose().scale(...)) allocates a full transpose, makes a separate scaling pass, and computes all entries; this method does none of that.

Numbers (48×2000 matrix)

new mmul(transpose()) speedup
J·Jᵀ (unweighted) 1.46 ms 3.53 ms 2.41×
J·diag(w)·Jᵀ (weighted) 1.60 ms 3.84 ms 2.41×

Unweighted result is bit-for-bit identical to mmul(transpose()); weighted matches to ~1e-13 (summation order).

Notes

  • src/matrix.js (implementation), matrix.d.ts (types), src/__tests__/matrix/utility.test.js (tests).
  • npm test passes (251 tests, eslint, prettier).
  • Profiling a Levenberg–Marquardt step (48 params × 2000 points) showed this product is ~92% of the per-iteration cost, so it's the single highest-leverage primitive there.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 65.22%. Comparing base (436258b) to head (0feb530).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #207      +/-   ##
==========================================
+ Coverage   65.02%   65.22%   +0.19%     
==========================================
  Files          47       47              
  Lines        5656     5688      +32     
  Branches      959      969      +10     
==========================================
+ Hits         3678     3710      +32     
  Misses       1967     1967              
  Partials       11       11              

☔ 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.

@lpatiny lpatiny requested a review from targos June 22, 2026 07:15
@targos

targos commented Jun 22, 2026

Copy link
Copy Markdown
Member

Can you resolve the conflicts?

lpatiny added 2 commits June 22, 2026 18:25
Adds `mmulByTranspose()`, which returns the matrix product of a matrix by
its own transpose (`this · thisᵀ`). The result is symmetric, so only the
upper triangle is computed and mirrored, and the transpose is never
materialized — about twice as fast as `this.mmul(this.transpose())`.

This is the Gram-style product at the heart of the normal equations
(e.g. the Gauss-Newton Hessian `J·Jᵀ` in Levenberg-Marquardt). Measured
~2.4x faster than `mmul(transpose())` on a 48x2000 matrix, bit-for-bit
identical.

Assisted-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`mmulByTranspose(scale)` now computes `this · diag(scale) · thisᵀ`, the
weighted Gram matrix, with `scale` a per-column factor. Without `scale`
it is unchanged (`this · thisᵀ`).

This covers weighted normal equations (`J·W·Jᵀ`) in a single symmetric,
transpose-free pass — ~2.4x faster than `mmul(transpose().scale(...))`.

Assisted-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lpatiny lpatiny force-pushed the add-mmul-by-transpose branch from f1e7b11 to 0feb530 Compare June 22, 2026 16:31
@lpatiny lpatiny merged commit 9b3835a into main Jun 23, 2026
10 checks passed
@lpatiny lpatiny deleted the add-mmul-by-transpose branch June 23, 2026 07:04
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