-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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 LU factorization in-place operations #22774
Conversation
9826a84
to
efb8db9
Compare
base/linalg/lu.jl
Outdated
function At_ldiv_B!(A::LU{<:Any,<:StridedMatrix}, b::StridedVector) | ||
At_ldiv_B!(UnitLowerTriangular(A.factors), At_ldiv_B!(UpperTriangular(A.factors), b)) | ||
b_permuted = b[invperm(ipiv2perm(A.ipiv, length(b)))] | ||
copy!(b, b_permuted) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
couldn't this permute in place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking the same, but permute!
copies the permutation array, so a new allocation of roughly the same size is done anyway. Also the docs suggest using b[perm]
. But if the array or number type is special, then permute!
might be better.
Regarding three-argument |
base/linalg/lu.jl
Outdated
|
||
function At_ldiv_B!(A::LU{<:Any,<:StridedMatrix}, B::StridedMatrix) | ||
At_ldiv_B!(UnitLowerTriangular(A.factors), At_ldiv_B!(UpperTriangular(A.factors), B)) | ||
B_permuted = B[invperm(ipiv2perm(A.ipiv, size(B,1))), :] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps space-separate the arguments to size
as in the similar method above? :)
test/linalg/lu.jl
Outdated
@@ -83,10 +83,29 @@ debug && println("(Automatic) Square LU decomposition. eltya: $eltya, eltyb: $el | |||
@test norm(a*(lua\c) - c, 1) < ε*κ*n # c is a vector | |||
@test norm(a'*(lua'\c) - c, 1) < ε*κ*n # c is a vector | |||
@test AbstractArray(lua) ≈ a | |||
if eltya <: Real && eltyb <: Real |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Were these eltype restrictions unnecessary? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'
and .'
are equivalent for real matrices but not for complex ones, and I think the idea has been to restrict the .'
test to complex matrices. But then it should have read <: Complex
. When fixing that I thought it wasn't really necessary to do that test conditionally; makes it easier to read what's happening.
Ac_ldiv_B!(b_dest, lua, b) | ||
Ac_ldiv_B!(c_dest, lua, c) | ||
@test norm(b_dest - lua' \ b, 1) < ε*κ*2n | ||
@test norm(c_dest - lua' \ c, 1) < ε*κ*n |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps DRY out these tests? For example, something along the lines of
for (Aq_ldiv_B!, q) in (
(A_ldiv_B!, identity),
(At_ldiv_B!, transpose),
(Ac_ldiv_B!, ctranspose) )
Aq_ldiv_B!(b_dest, lua, b)
Aq_ldiv_B!(c_dest, lua, c)
@test norm(b_dest - q(lua) \ b, 1) < ε*κ*2n
@test norm(c_dest - q(lua) \ c, 1) < ε*κ*n
end
might serve? (Edit: Missed the secondary op the first time around! :) )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried it, but it doesn't seem to work :(
> transpose(lufact(rand(3, 3))) \ rand(3)
ERROR: transpose not implemented for Base.LinAlg.LU{Float64,Array{Float64,2}}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is because A.' \ B
becomes At_ldiv_B(A, B)
(not transpose(A) \ B
). Perhaps this will do?
for (Aq_ldiv_B!, f) in (
(A_ldiv_B!, \),
(At_ldiv_B!, At_ldiv_B),
(Ac_ldiv_B!, Ac_ldiv_B) )
Aq_ldiv_B!(b_dest, lua, b)
Aq_ldiv_B!(c_dest, lua, c)
@test norm(b_dest - f(lua, b), 1) < ε*κ*2n
@test norm(c_dest - f(lua, c), 1) < ε*κ*n
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thx, so is .' \
parsed as a single operator or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes,
julia> expand(Main, :(A.'\B))
:(At_ldiv_B(A, B))
and similarly for *
as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely a hack and we want to get rid of it, but we're not quite there yet.
base/linalg/lu.jl
Outdated
B_permuted = B[ipiv2perm(A.ipiv, size(B, 1)), :] | ||
A_ldiv_B!(UpperTriangular(A.factors), A_ldiv_B!(UnitLowerTriangular(A.factors), B_permuted)) | ||
copy!(B, B_permuted) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessary in this pull request, but DRYing out these and the similar method definitions below might be nice. For example, as a first step perhaps something along the lines of
function A_ldiv_B!(A::LU{<:Any,<:StridedMatrix}, B::StridedVecOrMat)
B_permuted = _ipivpermute(B, A.ipiv)
A_ldiv_B!(UpperTriangular(A.factors), A_ldiv_B!(UnitLowerTriangular(A.factors), B_permuted))
copy!(B, B_permuted)
end
_ipivpermute(b::StridedVector, ipiv) = b[ipiv2perm(ipiv, length(b))]
_ipivpermute(B::StridedMatrix, ipiv) = B[ipiv2perm(ipiv, size(B, 1)), :]
would do the trick? :)
Thanks @Sacha0! Nice meeting you as well. I'll condense it a bit :) |
base/linalg/lu.jl
Outdated
A_ldiv_B!(UpperTriangular(A.factors), | ||
A_ldiv_B!(UnitLowerTriangular(A.factors), B[ipiv2perm(A.ipiv, size(B, 1)),:])) | ||
|
||
function A_ldiv_B!(A::LU{<:Any,<:StridedMatrix}, b::StridedVector) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is sufficient to do A_ldiv_B!(UpperTriangular(A[:U]), A_ldiv_B!(LinAlg.UnitLowerTriangular(A[:L]), view(b, A[:p])))
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have tried exactly this, but the problem is that the user works with b
itself after calling A_ldiv_B!(LU, b)
, and b
has to be permuted. So whether you do the permutation during the backward & forward substitutions implicitly with a view doesn't really matter, the permutation has to be applied in the end anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know about A[:p]
btw, thanks
OK... We should have used the function my_A_ldiv_B!(A::LU, b::StridedVector)
for i = eachindex(A.ipiv)
b[i], b[A.ipiv[i]] = b[A.ipiv[i]], b[i]
end
A_ldiv_B!(UpperTriangular(A.factors), A_ldiv_B!(UnitLowerTriangular(A.factors), b))
end |
efb8db9
to
8c1baec
Compare
… for complex numbers, and might be skipped for real numbers; not the other way around. For clarity just test both unconditionally.
2b6c676
to
ce1a9db
Compare
ce1a9db
to
944f02a
Compare
Fixes JuliaLang/LinearAlgebra.jl#445.