Skip to content

Propagate NaN and Inf to the unstored entries in sparse rmul!, lmul! and mul! - #1101

Merged
ViralBShah merged 2 commits into
mainfrom
vs/scale-nonfinite
Oct 16, 2026
Merged

ViralBShah merged 2 commits into
mainfrom
vs/scale-nonfinite

Conversation

@ViralBShah

@ViralBShah ViralBShah commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Scaling a sparse matrix or vector in place by a number scales only the stored values, so an entry that is not stored stays zero when the number is NaN or infinite, although 0 * NaN and 0 * Inf are NaN. The dense array, the out-of-place matrix B * NaN and the rmul! docstring all give NaN. The out-of-place vector x * NaN has the same bug.

Before

using SparseArrays, LinearAlgebra
B = sparse([1], [1], [2.0], 2, 2); x = sparsevec([1], [2.0], 2)
Matrix(B * NaN)                   # [NaN NaN; NaN NaN]
Matrix(rmul!(copy(B), NaN))       # [NaN 0.0; 0.0 0.0]
Matrix(lmul!(Inf, copy(B)))       # [Inf 0.0; 0.0 0.0]
Matrix(mul!(similar(B), B, NaN))  # [NaN 0.0; 0.0 0.0]
Vector(rmul!(copy(x), NaN))       # [NaN, 0.0]
Vector(x * NaN)                   # [NaN, 0.0]

After

Matrix(B * NaN)                   # [NaN NaN; NaN NaN]
Matrix(rmul!(copy(B), NaN))       # [NaN NaN; NaN NaN]
Matrix(lmul!(Inf, copy(B)))       # [Inf NaN; NaN NaN]
Matrix(mul!(similar(B), B, NaN))  # [NaN NaN; NaN NaN]
Vector(rmul!(copy(x), NaN))       # [NaN, NaN]
Vector(x * NaN)                   # [NaN, NaN]

The fix mirrors rdiv! and ldiv! by a number, which already handle 0 / 0. rmul! and lmul! on a sparse matrix, a sparse vector and a column view, mul!(C, A, b) and mul!(C, b, A), and the vector x * a and a * x keep scaling the stored values when the implicit zero times the number is zero (or every entry is stored), and otherwise go through broadcast! or broadcast, which store the new entries. A fixed-pattern destination therefore throws ArgumentError with nothing written, as rdiv! does. The implicit zero is taken from _implicit_zero, so an eltype without a zero (Any, a matrix type) keeps scaling its stored values instead of throwing. The finite case is unchanged (minimum of @benchmark, Apple silicon, nightly):

main branch
rmul!(A, 2.0), 10⁴×10⁴, nnz 1.0e5 13.2 µs, 0 allocs 13.3 µs, 0 allocs
mul!(C, A, 2.0), same 24.4 µs, 0 allocs 23.5 µs, 0 allocs
rmul!(A, 2.0), 100×100, nnz 490 46.8 ns, 0 allocs 46.7 ns, 0 allocs
x * 2.0, nnz 93 57.8 ns, 4 allocs 57.2 ns, 4 allocs
rmul!(view(A, :, 1), 2.0) 4.9 ns, 0 allocs 8.4 ns, 0 allocs

Left out: products with a Diagonal (or a dense vector) that holds NaN or Inf still skip the entries that are not stored, as all sparse products do; that is a separate question from scaling by a number. rdiv! and ldiv! on an eltype without a zero still throw, as on main.

Authored with Claude Code.

🤖 Generated with Claude Code

…l!` and `mul!`

`0 * NaN` and `0 * Inf` are NaN, so scaling only the stored values gives a
different result from the dense array and from the out-of-place `A * b`.
As `rdiv!` and `ldiv!` already do, fall back to `broadcast!` when the
implicit zero would not stay zero.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.82%. Comparing base (de9a824) to head (37f372f).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1101      +/-   ##
==========================================
- Coverage   94.82%   94.82%   -0.01%     
==========================================
  Files          16       16              
  Lines       10236    10234       -2     
==========================================
- Hits         9706     9704       -2     
  Misses        530      530              

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ViralBShah
ViralBShah merged commit 11b874d into main Oct 16, 2026
12 checks passed
@ViralBShah
ViralBShah deleted the vs/scale-nonfinite branch October 16, 2026 03:23
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.

1 participant