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 LU factorization in-place operations #22774

Merged
merged 4 commits into from
Aug 24, 2017

Conversation

haampie
Copy link
Contributor

@haampie haampie commented Jul 12, 2017

Fixes #22683.

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@Sacha0
Copy link
Member

Sacha0 commented Jul 14, 2017

Regarding three-argument [i]permute! methods, having run into the same lack last week I opened an issue: #22815. Best! (Wonderful meeting you last month Harmen! :) )


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))), :]
Copy link
Member

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

@@ -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
Copy link
Member

@Sacha0 Sacha0 Jul 15, 2017

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

Copy link
Contributor Author

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
Copy link
Member

@Sacha0 Sacha0 Jul 15, 2017

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

Copy link
Contributor Author

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}}

Copy link
Member

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

Copy link
Contributor Author

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?

Copy link
Member

@fredrikekre fredrikekre Aug 23, 2017

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.

Copy link
Sponsor Member

@StefanKarpinski StefanKarpinski Aug 23, 2017

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.

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
Copy link
Member

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

@haampie
Copy link
Contributor Author

haampie commented Jul 23, 2017

Thanks @Sacha0! Nice meeting you as well. I'll condense it a bit :)

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)
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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

@haampie
Copy link
Contributor Author

haampie commented Aug 22, 2017

OK... We should have used the ipiv array (I didn't even consider it), since that provides a way to sequentially apply the permutation in-place:

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

… for complex numbers, and might be skipped for real numbers; not the other way around. For clarity just test both unconditionally.
@andreasnoack andreasnoack merged commit 98c7a06 into JuliaLang:master Aug 24, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants