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

Address type stability issues in #12574 and fix a bug or two #12594

Merged
merged 6 commits into from
Aug 14, 2015

Conversation

kshyatt
Copy link
Contributor

@kshyatt kshyatt commented Aug 13, 2015

The trix methods should have been trix! since they return references. The methods for Unit{Upper/Lower]Triangular matrices were wrong (now fixed) and I also caught a bug in istriu/istril for Tridiagonals.

cc: @andreasnoack and @tkelman

@kshyatt kshyatt added the linear algebra Linear algebra label Aug 13, 2015
@@ -108,7 +108,7 @@ ctranspose(M::Bidiagonal) = Bidiagonal(conj(M.dv), conj(M.ev), !M.isupper)
istriu(M::Bidiagonal) = M.isupper || all(M.ev .== 0)
istril(M::Bidiagonal) = !M.isupper || all(M.ev .== 0)

function tril(M::Bidiagonal, k::Integer=0)
function tril!(M::Bidiagonal, k::Integer=0)
Copy link
Contributor

Choose a reason for hiding this comment

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

this could use fill! instead of allocating new zeros arrays, right?

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 tested this locally and it works. Will wait for CI to finish then update.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this applies throughout for (most of?) the rest of the types too. Anywhere it's possible to make the ! versions allocation-free and in-place, then I think that would be a good goal. And aim for type-stability but with the smallest amount of type widening that makes sense. The one-arg case could possibly be made to return a more specialized type than the two-arg case for some types since it's more specific in its behavior, but that would require extra methods rather than using default argument values and could be left as a future enhancement.

@kshyatt
Copy link
Contributor Author

kshyatt commented Aug 13, 2015

Updated! Should be far fewer allocs now.

elseif k == 0
return D
else
return zeros(D)
Copy link
Contributor

Choose a reason for hiding this comment

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

fill!(D.diag, 0) 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.

Noooo ok and I need a whitespace fix. This is not my night.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not a big deal, stop me if I'm getting too nitpicky.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No it's good. I want this to work and work well.

@tkelman
Copy link
Contributor

tkelman commented Aug 13, 2015

Not this PR's fault, but are the docs for tril(M, k) wrong? It should be k-th superdiagonal, same as triu(M, k), not k-th subdiagonal like the docs currently say, right?

@kshyatt
Copy link
Contributor Author

kshyatt commented Aug 13, 2015

@jiahao wanted that phrasing, ask him :)

elseif k == 0
return UnitUpperTriangular(eye(A))
else
return UnitUpperTriangular(triu(tril(A.data,k)))
return UnitUpperTriangular(triu!(tril!(A.data,k)))
Copy link
Contributor

Choose a reason for hiding this comment

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

this one hurts my head a little - maybe the easiest way to get this right, and get the rest of tril!(A::UnitUpperTriangular, k) for almost free would be first set the diagonals of A.data to one, then return tril!(UpperTriangular(A.data), k) ?

@tkelman
Copy link
Contributor

tkelman commented Aug 13, 2015

But that would be the opposite meaning. What the docs describe is not the implemented behavior:

julia> A = rand(3,3)
3x3 Array{Float64,2}:
 0.560414  0.632475  0.901667
 0.342709  0.46708   0.133265
 0.604388  0.354852  0.706773

julia> tril(A, 1)
3x3 Array{Float64,2}:
 0.560414  0.632475  0.0
 0.342709  0.46708   0.133265
 0.604388  0.354852  0.706773

julia> triu(A, 1)
3x3 Array{Float64,2}:
 0.0  0.632475  0.901667
 0.0  0.0       0.133265
 0.0  0.0       0.0

tril(A::UnitLowerTriangular,k::Integer=0) = UnitLowerTriangular(tril(tril(A.data),k))
tril!(A::UnitLowerTriangular,k::Integer=0) = UnitLowerTriangular(tril!(tril!(A.data),k))

tril(A::UpperTriangular,k::Integer=0) = tril!(copy(A),k)
Copy link
Contributor

Choose a reason for hiding this comment

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

should the generic fallbacks here

triu(M::AbstractMatrix) = triu!(copy(M))
tril(M::AbstractMatrix) = tril!(copy(M))
triu!(M::AbstractMatrix) = triu!(M,0)
tril!(M::AbstractMatrix) = tril!(M,0)

be tweaked to cover all of these?

@tkelman
Copy link
Contributor

tkelman commented Aug 13, 2015

This is turning out to be a surprisingly complicated set of methods to get right. Someone who's good at diagrams could make a pretty neat picture about the algebraic closedness of the different operations here for all types and super/sub diagonal cases.

@kshyatt
Copy link
Contributor Author

kshyatt commented Aug 13, 2015

OK I think I have something semi-reasonable now. Let's give it a whirl.

n = size(D,1)
if abs(k) > n
throw(ArgumentError("requested diagonal, $k, out of bounds in matrix of size ($n,$n)"))
elseif k != 0
Copy link
Contributor

Choose a reason for hiding this comment

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

only if > 0, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can't k be negative? Oh, derp, I see.

@jiahao
Copy link
Member

jiahao commented Aug 13, 2015

Someone who's good at diagrams could make a pretty neat picture about the algebraic closedness of the different operations here for all types and super/sub diagonal cases.

See #8240 where I discuss how the algebraic structure shows up as banded matrices.

It should be k-th superdiagonal, same as triu(M, k), not k-th subdiagonal like the docs currently say, right?

I don't recall... but certainly tril(randn(4,4), -1) produces the matrix below and starting from the 1st subdiagonal,

for i in diagind(A)
A.data[i] = one(eltype(A))
end
return UpperTriangular(triu!(tril!(A.data,k)))
Copy link
Contributor

Choose a reason for hiding this comment

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

triu! still not necessary here

@tkelman
Copy link
Contributor

tkelman commented Aug 13, 2015

tril(randn(4,4), -1) produces the matrix below and starting from the 1st subdiagonal,

Right, so either the docs should say "-k th" subdiagonal, or "k th superdiagonal."

@jiahao
Copy link
Member

jiahao commented Aug 13, 2015

yes

@tkelman
Copy link
Contributor

tkelman commented Aug 13, 2015

the algebraic structure shows up as banded matrices.

Well, banded has a much nicer, easier to deal with algebraic structure than our current menagerie. The less uniform more complicated version we have now would make for a more interesting picture, but that's not necessarily an endorsement.

end

tril(A::UnitLowerTriangular,k::Integer=0) = UnitLowerTriangular(tril(tril(A.data),k))
tril(A::UpperTriangular,k::Integer=0) = tril!(copy(A),k)
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure why github collapsed #12594 (comment), but that still applies. I think the others that have been collapsed as of df4c774 have been addressed, but will review again tomorrow.

edit: links to collapsed comments apparently don't always work so well, that was

should the generic fallbacks here

triu(M::AbstractMatrix) = triu!(copy(M))
tril(M::AbstractMatrix) = tril!(copy(M))
triu!(M::AbstractMatrix) = triu!(M,0)
tril!(M::AbstractMatrix) = tril!(M,0)

be tweaked to cover all of these?

triu(A::Symmetric,k::Integer=0) = triu(A.data,k)
function tril(A::Hermitian, k::Integer=0)
if A.uplo == 'U' && k <= 0
return tril(A.data',k)
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be tril!(A.data',k), since the data' already makes a copy? Similarly below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. I've updated the PR to fix this.

@StefanKarpinski
Copy link
Member

This is turning out to be a surprisingly complicated set of methods to get right.

That's an excellent sign of something worth having in a library :-)

@kshyatt
Copy link
Contributor Author

kshyatt commented Aug 13, 2015

I have updated the PR to fix @tkelman and @stevengj's catches. Does this look ok, now?

triu(A::Hermitian,k::Integer=0) = triu(A.data,k)
tril(A::Symmetric,k::Integer=0) = tril(A.data,k)
triu(A::Symmetric,k::Integer=0) = triu(A.data,k)
function tril(A::Hermitian, k::Integer=0)
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be Union{Hermitian,Symmetric} right? The implementations look like exact copies.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It passes tests locally with this change. Do we need to run CI again?

@tkelman
Copy link
Contributor

tkelman commented Aug 13, 2015

@kshyatt
Copy link
Contributor Author

kshyatt commented Aug 13, 2015

Ok, I've gone through on my end and dealt with everything but the last one. #12594 (comment). I'm not 100% how to tweak the fallbacks - advice?

@tkelman
Copy link
Contributor

tkelman commented Aug 13, 2015

The decision to make there is whether adding general fallbacks that look like

triu(M::AbstractMatrix, k::Integer) = triu!(copy(M),k)
tril(M::AbstractMatrix, k::Integer) = tril!(copy(M),k)

makes sense, now that you've implemented a whole bunch of these methods for many of the subtypes of AbstractMatrix. Would be cleaner and should subsume the dozen-ish versions you've added here.

edit: sorry, just the first 2, the 3rd and 4th lines there were meaningless

@kshyatt
Copy link
Contributor Author

kshyatt commented Aug 13, 2015

Shall I give a go and report back?

E: It's working!!!

triu(A::Hermitian,k::Integer=0) = triu(A.data,k)
tril(A::Symmetric,k::Integer=0) = tril(A.data,k)
triu(A::Symmetric,k::Integer=0) = triu(A.data,k)
function tril(A::Union{Hermitian,Symmetric}, k::Integer=0)
Copy link
Contributor

Choose a reason for hiding this comment

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

Ack, I was wrong, while the earlier implementations were identical for Hermitian vs Symmetric, they actually shouldn't be. We can have Symmetric for a complex element type in which case these should be using .' instead of '. But for Hermitian they should use '.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh nooooo!

Ok, let CI run, then update?

kshyatt added a commit that referenced this pull request Aug 14, 2015
Address type stability issues in #12574 and fix a bug or two
@kshyatt kshyatt merged commit b59b0dd into master Aug 14, 2015
@kshyatt kshyatt deleted the ksh/fixtrix branch August 14, 2015 08:12
@ScottPJones
Copy link
Contributor

I noticed when reviewing the previous change for error messages and coverage, and looking at this one, that the BunchKaufman, Cholesky, CholeskyPivoted, Symmetric, Hermitian types use uplo::Char, but the Bidiagonal type uses a isupper::Bool.
I think the code in general would be more efficient if it consistently used isupper::Bool, so I wonder if I'm missing something.

@tkelman
Copy link
Contributor

tkelman commented Aug 14, 2015

That inconsistency is a bit annoying, but they serve slightly different purposes. For the factorization and Symmetric/Hermitian objects, the matrices aren't really completely upper or lower triangular - the uplo parameter indicates which triangle the important information is stored in. Using uplo::Char also maps more directly to the BLAS and LAPACK API's. I'm not sure if we are binding to much of LAPACK for Bidiagonal, but it might make more sense to switch Bidiagonal from using isupper::Bool to uplo::Char for consistency.

@ScottPJones
Copy link
Contributor

I had looked at the bindings, but even so, I think overall it would be more efficient if all of them were changed to use isupper::Bool (I can attempt a PR later). For the bindings, it's only a single instruction (on x86 at least) to convert the bool to 'L'/'U'. i.e. leal 76(%edi,%edi,8), %eax, and it can save 3 bytes in the structures 😀, besides simplifying a lot of places in the code I looked at.

@tkelman
Copy link
Contributor

tkelman commented Aug 16, 2015

Remember these are Fortran API's, so you have to pass a reference to the char anyway. I doubt such a PR would be especially popular with the people who've been developing and maintaining the blas and lapack bindings.

@ScottPJones
Copy link
Contributor

I wasn't aware of that for the Fortran interface. Why do you believe something that could simplify the code would not be popular with them?
It would still be just an instruction to pass either a reference to a L or U.

@tkelman
Copy link
Contributor

tkelman commented Aug 16, 2015

I'm guessing, but it seems like unnecessary churn that would make our code map less directly to the standard-for-decades, widely used API's for linear algebra. You can always open a PR, but "simplify the code" is a relative term that is going to mean different things to different people.

@ScottPJones
Copy link
Contributor

There may need to be some "churn", it looks like the code as is won't even work on a big-endian machine. It's passing a pointer to a 32-bit Char value to something expecting a pointer to a 8-bit char.
It will get '\0' on big-endian systems, not 'L' or 'U'.

@stevengj
Copy link
Member

@ScottPJones, if you pass &uplo::Char in ccall for a Ptr{UInt8} argument, Julia will do the right thing: it converts the Char to UInt8 before passing the address. And this conversion is always safe because the Char is checked to have an ASCII value before the conversion.

@tkelman
Copy link
Contributor

tkelman commented Aug 17, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
linear algebra Linear algebra
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants