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 tril and triu for special matrices #12574

Merged
merged 5 commits into from
Aug 12, 2015
Merged

Conversation

kshyatt
Copy link
Contributor

@kshyatt kshyatt commented Aug 11, 2015

As discussed in JuliaLang/LinearAlgebra.jl#161, Diagonal, Bidiagonal, Tridiagonal, SymTridiagonal, UpperTriangular, LowerTriangular, UnitUpperTriangular, UnitLowerTriangular, Symmetric, and Hermitian types didn't have tril or triu defined. Diagonal had tril! and triu! but the ! was incorrect since the underlying matrix wasn't modified.

I also added tests.

@kshyatt kshyatt added test This change adds or pertains to unit tests linear algebra Linear algebra labels Aug 11, 2015
@jakebolewski
Copy link
Member

👍 My only complaint would be that it might be too readable.

@andreasnoack
Copy link
Member

👍

@StefanKarpinski
Copy link
Member

My only complaint would be that it might be too readable.

heh

n = length(M.dv)
if abs(k) > n
throw(ArgumentError("requested diagonal, $k, out of bounds in matrix of size ($n,$n)"))
elseif M.isupper && 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.

Maybe this could be:

    elseif k > 0
          return M
    elseif M.isupper
          if k < 0
               return Bidiagonal(zeros(M.dv),zeros(M.ev),true)
          else
               return Bidiagonal(M.dv,zeros(M.ev),true)
          end
    elseif k < -1
       return Bidiagonal(zeros(M.dv),zeros(M.ev),false)
    elseif k == -1
       return Bidiagonal(zeros(M.dv),M.ev,false)
    else
       return M
    end

This avoids the rechecking of M.isupper and passing it to Bidiagonal.

@tkelman
Copy link
Contributor

tkelman commented Aug 12, 2015

Should these generally be returning copies of or references to the input data?

StefanKarpinski added a commit that referenced this pull request Aug 12, 2015
Add tril and triu for special matrices
@StefanKarpinski StefanKarpinski merged commit db787d8 into master Aug 12, 2015
@StefanKarpinski StefanKarpinski deleted the ksh/specialtrilu branch August 12, 2015 02:50
@tkelman
Copy link
Contributor

tkelman commented Aug 12, 2015

Answering my own question, judging by

triu(M::AbstractMatrix) = triu!(copy(M))
tril(M::AbstractMatrix) = tril!(copy(M))
triu!(M::AbstractMatrix) = triu!(M,0)
tril!(M::AbstractMatrix) = tril!(M,0)
I think this really needs to be consistent about copying rather than returning a reference. Sometimes-copy, sometimes-reference is not good.

edit: Or these can consistently implement tril!(A, k) for various subtypes of AbstractMatrix and let the general version take care of the copying for tril.

@kshyatt
Copy link
Contributor Author

kshyatt commented Aug 12, 2015

@tkelman so I should have all of these return copy(M) instead of M? I'm happy to submit a new PR to do that.

@tkelman
Copy link
Contributor

tkelman commented Aug 12, 2015

I think the right solution here is to define tril!(A, k) for the various specialized subtypes, which is free to do in-place modification, return references, etc. The general-purpose fallback definitions for tril!(A::AbstractMatrix) and tril(A::AbstractMatrix, k) tril(A::AbstractMatrix) should handle the copying for all of the subtypes.

edit: apparently there isn't a fallback for tril(A::AbstractMatrix, k) right now, but there probably could be tril(A::AbstractMatrix, k) = tril!(copy(A), k) ?

@kshyatt
Copy link
Contributor Author

kshyatt commented Aug 12, 2015

I guess the reason I kept many of these as tril and not tril! is for cases like:

elseif !M.isupper && k == 0
        return M
elseif M.isupper && k == 0
        return Bidiagonal(M.dv,zeros(M.ev),M.isupper)

where we either return M or a new matrix. It seems like it might make sense to just copy M in the return? Or should we just do M = Bidiagonal(M.dv,zeros(M.ev),M.isupper); return M in the lower one and put the ! in?

@tkelman
Copy link
Contributor

tkelman commented Aug 12, 2015

I think if one of these methods ever returns the original matrix itself or a reference to some part of it in the output, then that method should be named tril! (resp. triu!) to denote the resulting potential for aliasing. If it only ever returns copies then it can be a method of tril (resp. triu).

There are cases where tril or triu could get away without copying the entire input matrix, and only copy some parts of it like in the case you've shown. Do those deserve extra versions of the same basic method implementations? Maybe it could be simple and short with macro loops where tril! is defined in terms of running identity and tril is defined in terms of running copy on any piece that references the original matrix.

@StefanKarpinski
Copy link
Member

It looks like mutating versions of all of these could modify the argument matrix in-place and always return the argument matrix. In which case, the non-mutating version could always just be written as calling the mutating version on a copy of the input matrix. Might be slightly slower since the input has to be copied and you can't just allocate vectors of zeros, but I'm not sure that it would be a major difference.

@tkelman
Copy link
Contributor

tkelman commented Aug 12, 2015

I think I like that option. Instead of using zeros, the in-place versions could do fill!(0,.

The only tricky case is the non-type-stable branches for SymTridiagonal.

@StefanKarpinski
Copy link
Member

Perhaps tril and triu for SymTridiagonal should always just return Tridiagonal (which implies copying). Making it type-stable seems likely to be better for performance than the more precise type.

@andreasnoack
Copy link
Member

I thank I didn't read carefully enough yesterday. There are some problems here, e.g.

julia> A = LinAlg.UnitLowerTriangular(randn(4,4))
4x4 Base.LinAlg.UnitLowerTriangular{Float64,Array{Float64,2}}:
 1.0        0.0        0.0       0.0
 1.1291     1.0        0.0       0.0
 0.195707  -1.72338    1.0       0.0
 3.31616    0.252167  -0.598446  1.0

julia> triu(A, 1)
4x4 Base.LinAlg.UnitLowerTriangular{Float64,Array{Float64,2}}:
 1.0  0.0  0.0  0.0
 0.0  1.0  0.0  0.0
 0.0  0.0  1.0  0.0
 0.0  0.0  0.0  1.0

julia> triu(full(A), 1)
4x4 Array{Float64,2}:
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0

and I'm in doubt if we can, in general, support the trix(AbstractMatrix, Integer) methods for the special matrix types. A solution could be to split up the methods such that triu(A,0) returns a Matrix and triu(A)) potentially returns a special matrix type. That should be type safe.

@tkelman I think you might be extending the meaning of !. Usually the meaning is that the input is modified, not that a potential aliasing is happening. I can try to find some earlier discussions of this, but I think the view is that your solution would cause too many copies and that you should explicitly copy when necessary.

@tkelman
Copy link
Contributor

tkelman commented Aug 12, 2015

That should be type safe.

I think you mean type stable.

I think you might be extending the meaning of !. Usually the meaning is that the input is modified, not that a potential aliasing is happening.

True. So in cases where in-place modification is possible for these methods then tril! should do so, but I think tril should always copy. We really shouldn't be leaving aliasing up to the user to figure out and doing a sometimes-copy, sometimes-reference. Shouldn't have to read the source to determine whether or not modifying the output of tril is going to change the input.

@kshyatt
Copy link
Contributor Author

kshyatt commented Aug 12, 2015

I'm working on a PR to address your feedback. For the Unit{Lower/Upper}Triangular types we also cannot maintain type stability - should I have trix! return a {Upper/Lower}Triangular as appropriate?

@tkelman
Copy link
Contributor

tkelman commented Aug 13, 2015

I think so, that sounds appropriate.

@kshyatt
Copy link
Contributor Author

kshyatt commented Aug 13, 2015

Ok, almost done!

@@ -56,6 +56,12 @@ end
ctranspose(A::Hermitian) = A
trace(A::Hermitian) = real(trace(A.data))

#tril/triu
tril(A::Hermitian,k::Integer=0) = 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.

I don't think these are right. Aren't half the elements in A.data potentially garbage?

julia> A = rand(4,4)
4x4 Array{Float64,2}:
 0.992492  0.88037   0.40931   0.311544
 0.623913  0.93559   0.310044  0.598238
 0.393736  0.051534  0.103072  0.74386
 0.747731  0.684462  0.813315  0.0329609

julia> Symmetric(A)
tril(4x4 Base.LinAlg.Symmetric{Float64,Array{Float64,2}}:
 0.992492  0.88037   0.40931   0.311544
 0.88037   0.93559   0.310044  0.598238
 0.40931   0.310044  0.103072  0.74386
 0.311544  0.598238  0.74386   0.0329609

julia> tril(Symmetric(A))
4x4 Array{Float64,2}:
 0.992492  0.0       0.0       0.0
 0.623913  0.93559   0.0       0.0
 0.393736  0.051534  0.103072  0.0
 0.747731  0.684462  0.813315  0.0329609

julia> full(Symmetric(A))
4x4 Array{Float64,2}:
 0.992492  0.88037   0.40931   0.311544
 0.88037   0.93559   0.310044  0.598238
 0.40931   0.310044  0.103072  0.74386
 0.311544  0.598238  0.74386   0.0329609

julia> tril(full(Symmetric(A)))
4x4 Array{Float64,2}:
 0.992492  0.0       0.0       0.0
 0.88037   0.93559   0.0       0.0
 0.40931   0.310044  0.103072  0.0
 0.311544  0.598238  0.74386   0.0329609

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wow. K, on it.

The lower triangle is garbage.

kshyatt added a commit that referenced this pull request Aug 14, 2015
Address type stability issues in #12574 and fix a bug or two
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
linear algebra Linear algebra test This change adds or pertains to unit tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants