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

Add nullspace and cond functions for QRPivoted matrices #54519

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

danilonumeroso
Copy link
Contributor

This PR closes #54354 and also closes #40970.

@dkarrasch dkarrasch added the domain:linear algebra Linear algebra label May 19, 2024
stdlib/LinearAlgebra/src/qr.jl Outdated Show resolved Hide resolved
stdlib/LinearAlgebra/src/qr.jl Show resolved Hide resolved
stdlib/LinearAlgebra/test/qr.jl Outdated Show resolved Hide resolved
stdlib/LinearAlgebra/test/qr.jl Outdated Show resolved Hide resolved
Co-authored-by: Daniel Karrasch <daniel.karrasch@posteo.de>
Comment on lines +553 to +573
function cond(A::QRPivoted, p::Real=2)
m = min(size(A.R)...)
if p == 2
maxv = abs(A.R[1,1])
return iszero(maxv) ? oftype(real(maxv), Inf) : maxv / abs(A.R[m,m])
elseif p == 1 || p == Inf
checksquare(A.R)
R = UpperTriangular(A.R)
try
Rinv = inv(R)
return opnorm(R, p)*opnorm(Rinv, p)
catch e
if isa(e, LAPACKException) || isa(e, SingularException)
return convert(float(real(eltype(A))), Inf)
else
rethrow()
end
end
end
throw(ArgumentError(lazy"p-norm must be 1, 2 or Inf, got $p"))
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since both Q and the permutation don't change norms, isn't this simply

Suggested change
function cond(A::QRPivoted, p::Real=2)
m = min(size(A.R)...)
if p == 2
maxv = abs(A.R[1,1])
return iszero(maxv) ? oftype(real(maxv), Inf) : maxv / abs(A.R[m,m])
elseif p == 1 || p == Inf
checksquare(A.R)
R = UpperTriangular(A.R)
try
Rinv = inv(R)
return opnorm(R, p)*opnorm(Rinv, p)
catch e
if isa(e, LAPACKException) || isa(e, SingularException)
return convert(float(real(eltype(A))), Inf)
else
rethrow()
end
end
end
throw(ArgumentError(lazy"p-norm must be 1, 2 or Inf, got $p"))
end
cond(Q::QRPivoted, p::Real=2) = cond(Q.R, p)

? If we add this, we could also add the corresponding method for Q::QR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed: #53214 (comment).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q doesn't change 2-norms, but it changes 1 norms and infinity norms.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. So, together with your comment below, I agree that we should forward this to cond(Q.R, 2) in the p=2 case and otherwise throw and recommend using the original matrix, or, cond(Matrix(Q), p).

@dkarrasch dkarrasch requested a review from stevengj May 20, 2024 16:56
(m == 0 || n == 0) && return Matrix{eigtype(eltype(A.Q))}(I, n, n)

indstart = rank(A; atol, rtol) + 1
return A.Q[:, indstart:end]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I recall correctly, we don't currently have efficient methods for slicing Q matrices (which are internally indirectly represented by linear operators). See https://discourse.julialang.org/t/what-is-the-most-efficient-way-of-obtaining-the-orthogonal-column-space-of-a-matrix/72212/13

So you should probably need to do something like:

d = n + 1 - indstart # dimension of nullspace
I_slice = zeros(eltype(A), m, d) # construct last d columns of I
for i = 1:d; I_slice[i, indstart + d - 1] = 1; end
return A.Q * I_slice

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do:

@inline function _getindex(Q::AbstractQ, ::Colon, J::AbstractVector{<:Integer})
Y = zeros(eltype(Q), size(Q, 2), length(J))
@inbounds for (i,j) in enumerate(J)
Y[j,i] = oneunit(eltype(Q))
end
lmul!(Q, Y)
end

m = min(size(A.R)...)
if p == 2
maxv = abs(A.R[1,1])
return iszero(maxv) ? oftype(real(maxv), Inf) : maxv / abs(A.R[m,m])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return iszero(maxv) ? oftype(real(maxv), Inf) : maxv / abs(A.R[m,m])
return iszero(maxv) ? oftype(maxv, Inf) : maxv / abs(A.R[m,m])

real seems redundant here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This algorithm seems incorrect. If $A = QR$, then cond(A) == cond(R), but cond(R) is not the ratio of diagonal entries of R.

R = UpperTriangular(A.R)
try
Rinv = inv(R)
return opnorm(R, p)*opnorm(Rinv, p)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This algorithm also seems wrong?

I'm not there that there is any good way to compute the p=1 or Inf condition numbers from the QR factorization? Might better to just throw an error in this case, since the user is better off calling cond on the original matrix AFAICT.

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.

Add nullspace(::QRPivoted) and cond(::QRPivoted) methods qr factorisation method for nullspace
3 participants