Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix (l/r)mul! with Diagonal/Bidiagonal #55052

Merged
merged 8 commits into from
Jul 11, 2024
Merged

Fix (l/r)mul! with Diagonal/Bidiagonal #55052

merged 8 commits into from
Jul 11, 2024

Conversation

jishnub
Copy link
Contributor

@jishnub jishnub commented Jul 6, 2024

Currently, rmul!(A::AbstractMatirx, D::Diagonal) calls mul!(A, A, D), but this isn't a valid call, as mul! assumes no aliasing between the destination and the matrices to be multiplied. As a consequence,

julia> B = Bidiagonal(rand(4), rand(3), :L)
4×4 Bidiagonal{Float64, Vector{Float64}}:
 0.476892                      
 0.353756  0.139188             
          0.685839  0.309336    
                   0.369038  0.304273

julia> D = Diagonal(rand(size(B,2)));

julia> rmul!(B, D)
4×4 Bidiagonal{Float64, Vector{Float64}}:
 0.0            
 0.0  0.0        
     0.0  0.0    
         0.0  0.0

julia> B
4×4 Bidiagonal{Float64, Vector{Float64}}:
 0.0            
 0.0  0.0        
     0.0  0.0    
         0.0  0.0

This is clearly nonsense, and happens because the internal _mul! function assumes that it can safely overwrite the destination with zeros before carrying out the multiplication. This is fixed in this PR by using broadcasting instead. The current implementation is generally equally performant, albeit occasionally with a minor allocation arising from reshapeing an Array.

A similar problem also exists in l/rmul! with Bidiaognal, but that's a little harder to fix while remaining equally performant.

@jishnub jishnub added domain:linear algebra Linear algebra domain:arrays [a, r, r, a, y, s] backport 1.10 Change should be backported to the 1.10 release backport 1.11 Change should be backported to release-1.11 labels Jul 6, 2024
@dkarrasch
Copy link
Member

This is going back to where we came from, so it would be good to double check what the original concern was to make it call mul!(A, A, D). I guess this is a problem only with products of structured/banded matrices? One issue that I remember is that permuteddims allocates, and we didn't like that, so we wrote out the loops. Just to check: is there another option to fix this at the _mul! level?

@jishnub
Copy link
Contributor Author

jishnub commented Jul 6, 2024

The problem is that mul! is documented to assume no aliasing, so a call like mul!(A, A, D) will invariably lead to problems downstream. We may fix this for the methods in LinearAlgebra, but libraries that define more specific methods may still encounter errors.

One option might be to write out the loop here instead of broadcasting. My idea was that array types that specialise broadcasting would be able to access optimised methods.

@jishnub jishnub added kind:bugfix This change fixes an existing bug and removed domain:arrays [a, r, r, a, y, s] labels Jul 7, 2024
@dkarrasch
Copy link
Member

I found it: #42321.

@dkarrasch
Copy link
Member

This goes over the entire matrix and misses all the optimizations that we have in _mul! for the banded matrices. I guess that was (one of) the reason(s) to initially make [l/r]mul! run by mul!.

@jishnub
Copy link
Contributor Author

jishnub commented Jul 7, 2024

Yes, we would need to specialize lmul! and rmul! for banded matrices. The fallback would probably need to go over the entire matrix, although we could introduce a bandwidth function to limit the loop bounds. I don't think that we can reliably call mul! here, although we may reuse some of the code from the specialized methods.

@jishnub jishnub marked this pull request as draft July 7, 2024 14:36
@jishnub jishnub changed the title Fix (l/r)mul! with Diagonal Fix (l/r)mul! with Diagonal/Bidiagonal Jul 7, 2024
@jishnub jishnub marked this pull request as ready for review July 8, 2024 16:44
@jishnub jishnub merged commit 262b40a into master Jul 11, 2024
7 checks passed
@jishnub jishnub deleted the jishnub/diag_lrmul branch July 11, 2024 06:03
@KristofferC KristofferC mentioned this pull request Jul 12, 2024
42 tasks
KristofferC pushed a commit that referenced this pull request Jul 23, 2024
Currently, `rmul!(A::AbstractMatirx, D::Diagonal)` calls `mul!(A, A,
D)`, but this isn't a valid call, as `mul!` assumes no aliasing between
the destination and the matrices to be multiplied. As a consequence,
```julia
julia> B = Bidiagonal(rand(4), rand(3), :L)
4×4 Bidiagonal{Float64, Vector{Float64}}:
 0.476892   ⋅         ⋅         ⋅
 0.353756  0.139188   ⋅         ⋅
  ⋅        0.685839  0.309336   ⋅
  ⋅         ⋅        0.369038  0.304273

julia> D = Diagonal(rand(size(B,2)));

julia> rmul!(B, D)
4×4 Bidiagonal{Float64, Vector{Float64}}:
 0.0   ⋅    ⋅    ⋅
 0.0  0.0   ⋅    ⋅
  ⋅   0.0  0.0   ⋅
  ⋅    ⋅   0.0  0.0

julia> B
4×4 Bidiagonal{Float64, Vector{Float64}}:
 0.0   ⋅    ⋅    ⋅
 0.0  0.0   ⋅    ⋅
  ⋅   0.0  0.0   ⋅
  ⋅    ⋅   0.0  0.0
```
This is clearly nonsense, and happens because the internal `_mul!`
function assumes that it can safely overwrite the destination with zeros
before carrying out the multiplication. This is fixed in this PR by
using broadcasting instead. The current implementation is generally
equally performant, albeit occasionally with a minor allocation arising
from `reshape`ing an `Array`.

A similar problem also exists in `l/rmul!` with `Bidiaognal`, but that's
a little harder to fix while remaining equally performant.

(cherry picked from commit 262b40a)
@KristofferC
Copy link
Sponsor Member

This seems to fail on CI on 1.11:

Error During Test at /cache/build/tester-amdci5-15/julialang/julia-release-1-dot-11/julia-f4b43765c5/share/julia/stdlib/v1.11/LinearAlgebra/test/tridiag.jl:846
--
  | Test threw exception
  | Expression: rmul!(copy(T), D) ≈ T * D
  | MethodError: no method matching zero(::Type{Main.OffsetArrays.OffsetMatrix{Float64, Matrix{Float64}}})
  | The function `zero` exists, but no method is defined for this combination of argument types.
  |  
  | Closest candidates are:
  | zero(!Matched::Type{Union{}}, Any...)
  | @ Base number.jl:310
  | zero(!Matched::Type{Missing})
  | @ Base missing.jl:104
  | zero(!Matched::Type{Main.Test26Main_LinearAlgebra_structuredbroadcast.TestStructuredBroadcast.Zero36193})
  | @ Main.Test26Main_LinearAlgebra_structuredbroadcast /cache/build/tester-amdci5-15/julialang/julia-release-1-dot-11/julia-f4b43765c5/share/julia/stdlib/v1.11/LinearAlgebra/test/structuredbroadcast.jl:255
  | ...
  |  
  | Stacktrace:
  | [1] getindex
  | @ /cache/build/tester-amdci5-15/julialang/julia-release-1-dot-11/julia-f4b43765c5/share/julia/stdlib/v1.11/LinearAlgebra/src/tridiag.jl:689 [inlined]
  | [2] _getindex
  | @ ./abstractarray.jl:1361 [inlined]
  | [3] getindex
  | @ ./abstractarray.jl:1315 [inlined]
  | [4] iterate
  | @ ./abstractarray.jl:1212 [inlined]
  | [5] _foldl_impl(op::Base.MappingRF{typeof(LinearAlgebra.norm), Base.BottomRF{typeof(max)}}, init::Base._InitialValue, itr::LinearAlgebra.Tridiagonal{Main.OffsetArrays.OffsetMatrix{Float64, Matrix{Float64}}, Vector{Main.OffsetArrays.OffsetMatrix{Float64, Matrix{Float64}}}})

@KristofferC KristofferC mentioned this pull request Jul 24, 2024
46 tasks
KristofferC added a commit that referenced this pull request Jul 26, 2024
Backported PRs:
- [x] #54201 <!-- Fix generic triangular solves with empty matrices -->
- [x] #54358 <!-- Create `jl_clear_coverage_data` to dynamically reset
coverage -->
- [x] #54908 <!-- LazyString in interpolated error messages in
threadingconstructs -->
- [x] #54952 <!-- LAPACK: Avoid repr call in `chkvalidparam` -->
- [x] #54898 <!-- fix concurrent module loading return value -->
- [x] #55082 <!-- Add fast method for copyto!(::Memory, ::Memory) -->
- [x] #55084 <!-- Use triple quotes in TOML.print when string contains
newline -->
- [x] #55141 <!-- Update the aarch64 devdocs to reflect the current
state of its support -->
- [x] #54955 <!-- don't throw EOFError from sleep -->
- [x] #54871 <!-- Make warn missed transformations pass optional -->
- [x] #55178 <!-- Compat for `Base.@nospecializeinfer` -->
- [x] #55197 <!-- compat notice for a[begin] indexing -->
- [x] #54917 <!-- Fix potential underrun with annotation merging -->
- [x] #55209 <!-- correction to compat notice for a[begin] -->
- [x] #55203 <!-- document mutable struct const fields -->
- [x] #54791 <!-- Bump libblastrampoline to v5.10.1 -->
- [x] #54950 <!-- SuiteSparse: Bump version -->
- [x] #54956 <!-- Fix accidental early evaluation of imported `using`
binding -->
- [x] #54996 <!-- inference: add missing `MustAlias` widening in
`_getfield_tfunc` -->
- [x] #55070 <!-- LinearAlgebra: LazyString in error messages for
Diagonal/Bidiagonal -->
- [x] #54574 <!-- Make ScopedValues public -->
- [x] #54739 <!-- finish implementation of upgradable stdlibs -->
- [x] #54965 <!-- RFC: Make `include_dependency(path;
track_content=true)` the default -->
- [x] #53286 <!-- Raise an error when using `include_dependency` with
non-existent file or directory -->
- [x] #55066 <!-- fix loading of repeated/concurrent modules -->
- [x] #52694 <!-- Reinstate similar for AbstractQ for backward
compatibility -->
- [x] #55218 <!-- Artifacts: use a different way of getting the UUID of
a module -->
- [x] #54891 <!-- #54739-related fixes for loading stdlibs -->
- [x] #55072 <!-- trace-compile: don't generate `precompile` statements
for OpaqueClosure methods -->
- [x] #55188 <!-- Make Core.TypeofUnion use the type method table -->

Need manual backport:
- [ ] #55052 <!-- Fix `(l/r)mul!` with `Diagonal`/`Bidiagonal` -->


Contains multiple commits, manual intervention needed:

Non-merged PRs with backport label:
- [ ] #55169 <!-- `propertynames` for SVD respects private argument -->
- [ ] #55148 <!-- Random: Mark unexported public symbols as public -->
- [ ] #55017 <!-- TOML: Make `Dates` a type parameter -->
- [ ] #55013 <!-- [docs] change docstring to match code -->
- [ ] #54919 <!-- Fix annotated join with non-concrete eltype iters -->
- [ ] #54457 <!-- Make `String(::Memory)` copy -->
- [ ] #53957 <!-- tweak how filtering is done for what packages should
be precompiled -->
- [ ] #51479 <!-- prevent code loading from lookin in the versioned
environment when building Julia -->
- [ ] #50813 <!-- More doctests for Sockets and capitalization fix -->
- [ ] #50157 <!-- improve docs for `@inbounds` and
`Base.@propagate_inbounds` -->
- [ ] #41244 <!-- Fix shell `cd` error when working dir has been deleted
-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport 1.10 Change should be backported to the 1.10 release backport 1.11 Change should be backported to release-1.11 domain:linear algebra Linear algebra kind:bugfix This change fixes an existing bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants