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

Handling of non-square AbstractQ #1172

Open
araujoms opened this issue Jan 16, 2025 · 7 comments
Open

Handling of non-square AbstractQ #1172

araujoms opened this issue Jan 16, 2025 · 7 comments

Comments

@araujoms
Copy link
Contributor

I'm not sure whether this is a bug or intentional, but the handling of non-square AbstractQ is rather inconsistent.

import LinearAlgebra.QRPackedQ
q = QRPackedQ(randn(4,3),randn(3))
size(q) != size(Matrix(q))
size(Matrix(q')) != size(Matrix(q)')
size(Matrix(q')*Matrix(q)) != size(q'*q)
size(Matrix(q)'*Matrix(q)) != size(q'*q)
size(Matrix(q')*Matrix(q)) != size(Matrix(q)'*Matrix(q))
s = QRPackedQ(randn(5,4),randn(4))
size(Matrix(s)*Matrix(q)) != size(s*q)
@dkarrasch
Copy link
Member

Without going through the list in detail, I suspect that most of it is intentional, though the intention may be misguided. In that case a concrete argument/use case would be helpful for reconsideration. Matrix(q) may return a non-square representation (as the docstring says), whereas in some of those operations (like q'q), where you need to make one of the factors a matrix to apply the other factor, we use its size, which is square IIRC, for the matrixification.

@araujoms
Copy link
Contributor Author

araujoms commented Jan 16, 2025

My intention was to use QRPackedQ to represent an isometry, so I need the non-square representation. Doesn't look like it's possible without reimplementing everything, though.

@dkarrasch
Copy link
Member

But aren't both representations/shapes isometries? The current behaviour is very old Julian behaviour. Matrix(q) may yield non-square, collect(q) always returns the square version of it. For many operations, you need make a choice about what should be the standard size, and that choice has always (AFAIR) been the square version.

@araujoms
Copy link
Contributor Author

An isometry is a d x k matrix V such that V'*V = I (and k <= d). A unitary is a particular case of an isometry where d == k. If you take the QR decomposition of a d x k matrix, the result is a d x k isometry and a k x k upper triangular. A d x k isometry can be represented by a sequence of k Householder reflections, so it fits perfectly as a QRPackedQ with a d x k factor and a k-element vector τ. I assume this is why Matrix(q) displays the d x k isometry.

If this is old behaviour I assume it's unlikely to change, but I don't see where it would be useful to regard it as a d x d matrix instead. Also, as my examples show, this is done very inconsistently. I don't see why Matrix(q) should be d x k but suddenly Matrix(q') should be d x d instead of k x d. My last example is specially strange: when computing s*q only the second matrix is converted to a square, the first stays 5 x 4. It gets even weirder if you do q'*s'. Now both matrices are converted to squares, and we get an error that the dimensions are incompatible.

@dkarrasch
Copy link
Member

Sure, but Householder reflections are naturally square, so you get the square matrix representation out of the very same data.

As for the Matrix(q') case, that is debatable. Traditionally, we build the matrix representation from left multiplication. It is for this order that QRPackedQ can be both d x k and d x d, but there is no such "ambiguity" for its adjoint (for multiplication from the left). Instead, the adjoint has the corresponding size ambiguity for multiplication from the right.

And the case q'*s' is again difficult: if both factors have size ambiguity, which one do you pick, and why?

At least, and I'm somewhat happy about that because I did a major AbstractQ overhaul in v1.10, your tests in the OP are consistent across the versions.

@dkarrasch
Copy link
Member

Would be interesting to know what other languages do about it (Python, Matlab)...

@araujoms
Copy link
Contributor Author

Sure, the same Householder reflections also define a square matrix, what I'm asking is whether is it useful for anything. I don't think so. I just looked it up, and Python (numpy) by default returns a non-square matrix, whereas MATLAB by default returns a square matrix. Both have can return the other one as an option. And neither can handle the Householder reflections efficiently, so kudos to you there.

I'm aware of your AbstractQ overhaul, it was pretty nice. It's what made me decide to subtype AbstractQ to produce random unitaries efficiently in my package. Now I was trying to extend it to isometries.

I don't think defaulting to left multiplication is a good reason to choose anything. Computational complexity might be a good reason, but it's the same for both cases. I think consistency with Matrix(q) is a much better reason.

As for q'*s', I don't think the question is difficult. One option produces an error, the other a useful result. Moreover, the latter is consistent with Matrix(s*q).

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

No branches or pull requests

2 participants