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

Implement sorting for Eigen #24536

Closed
wants to merge 8 commits into from
Closed

Conversation

dalum
Copy link
Contributor

@dalum dalum commented Nov 8, 2017

The helper functions were taken verbatim from @stevengj's PR: #21598 (with a single minor deprecation fix). This PR implements the (probably) least radical solution to sorting eigenfactorisations proposed in #21598, with sorting of eigenvalues being opt-in rather than opt-out, and not enforcing any particular canonical sorting.

```
"""
function sort!(F::Eigen; kw...)
perm = sortperm(F[:values]; kw...)
Copy link
Member

@stevengj stevengj Nov 8, 2017

Choose a reason for hiding this comment

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

Seems like we need a default by=eigsortby, if by wasn't supplied in kw, using e.g. the eigsortby function from #21598.

Otherwise it will fail with no method matching isless for complex eigenvalues.

Copy link
Contributor Author

@dalum dalum Nov 8, 2017

Choose a reason for hiding this comment

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

This was actually intentional, but maybe the error message is confusing? I wanted to avoid choosing any particular sorting order for complex eigenvalues, since I generally need by=angle and others might want real, imag or eigsortby.

Copy link
Contributor Author

@dalum dalum Nov 8, 2017

Choose a reason for hiding this comment

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

I can also see a good argument for having some default, and eigsortby is a pretty good candidate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This could also be done by dispatching on the eltype of the eigenvalues, defaulting to reim, without the need to define eigsortby.

Copy link
Member

Choose a reason for hiding this comment

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

Definitely the caller should be able to supply their own by, but I think there should be a default, and reim seems like a good a choice for complex values (since it will match the sorting of real values in the case of complex numbers that are purely real).

permute!(F[:values], perm)
Base.permutecols!!(F[:vectors], perm)
function sort!(F::Eigen; by=eigsortby, kw...)
if !issorted(F[:values], by=by)
Copy link
Member

Choose a reason for hiding this comment

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

need to pass kw... to issorted too.

@@ -105,6 +105,13 @@ end
end
end

let a = rand(10, 10)
f = eigfact(a)
sort!(f)
Copy link
Member

Choose a reason for hiding this comment

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

probably f = sort(eigfact(a)) instead to make sure that both sort and sort! are tested.

@kshyatt kshyatt added the domain:linear algebra Linear algebra label Nov 8, 2017
return F
end

sort(F::Eigen; kw...) = sort!(deepcopy(F), kw...)
Copy link
Member

Choose a reason for hiding this comment

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

Could be worthwhile to define a copy method for Eigen. Such methods exists for e.g. Cholesky and LU factorizations. Something like:

Base.copy(F::Eigen) = Eigen(copy(F.values), copy(F.vectors))

Sort the eigenvectors and eigenvalues of an eigenfactorization `F` in place using the eigenvalues
for comparison. See also `sort!(::AbstractVector)`.

# Examples
Copy link
Member

Choose a reason for hiding this comment

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

Should the example perhaps be attached to the sort method instead? It is a fairly common pattern throughout the documentation to have a more extensive docstring for the non-inplace method, and just reference that there is an inplace equivalent.

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 think that makes sense. But for sort(::Vector), it actually references sort! as the extensive docstring. Maybe this should be changed?

Copy link
Member

Choose a reason for hiding this comment

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

Hmm okay, I guess we don't have an official policy, in any case it is unnecessary to repeat information, so one or the other should have a more extensive docstring. Perhaps you can simply add a short docstring for the non-inplace version instead then, and reference this. I guess most of the time you want to use sort! anyway.

Also, why the reference to sort!(::AbstractVector)? For docs about the keywords?

@@ -105,6 +105,12 @@ end
end
end

let a = rand(10, 10)
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps add a b = a'a and test the case with real eigenvalues too?

let a = rand(10, 10)
f = sort(eigfact(a))
@test a ≈ f[:vectors] * Diagonal(f[:values]) / f[:vectors]
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 the purpose is to sort, should we test that too? E.g. @test issorted(...)

@fredrikekre
Copy link
Member

Should probably also add sort[!](::Eigen) to appropriate manual section, otherwise it will show up at some random place with the rest of the sort[!] methods.

sort!(F::Base.LinAlg.Eigen; kw...)

Sort the eigenvectors and eigenvalues of an eigenfactorization `F` in place using the eigenvalues
for comparison. See also `sort!(::AbstractVector)`.
Copy link
Member

Choose a reason for hiding this comment

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

Why point to sort!(::AbstractVector)? Other docstrings for sort! will be listed in the same place 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.

Unless you do something like ?> sort!(F) to filter out all the noise. 🙂 Or in the manual.

Copy link
Member

Choose a reason for hiding this comment

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

Even then, it's not clear what this reference means: does this function support all keyword arguments accepted by sort!(::AbstractVector)? Is it just that they are similar? Also, the generic docstring is defined for any v, not just ::AbstractVector.

Copy link
Contributor Author

@dalum dalum Nov 9, 2017

Choose a reason for hiding this comment

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

You are right. It is because it accepts the same keyword arguments. I will change the wording to make it more clear. Also, AFAIU, the "generic" docstring is a fallback to all docstrings, but the one currently being shown is only for AbstractVector, despite the missing ::AbstractVector in the signature of the docstring.

Copy link
Sponsor Member

@StefanKarpinski StefanKarpinski left a comment

Choose a reason for hiding this comment

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

Overall, this is great to have a nice interface for, but I'm not convinced that adding this as a method to sort and sort! for the Eigen type is the right way to go. Those functions are for sorting collections of values, which I'm not sure we can legitimately consider factorization objects to be. However, permuting the rows/columns of factorizations according to some sort order seems to be a more general thing, so perhaps it would make sense to have sortfact and sortfact! functions?

cols = indices(a,2)
(i in cols && j in cols) || throw(BoundsError())
for k in indices(a,1)
@inbounds a[k,i],a[k,j] = a[k,j],a[k,i]
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

would be clearer with spaces after the outer commas

end
end
# like permute!! applied to each row of a, in-place in a (overwriting p).
function permutecols!!(a::AbstractMatrix, p::AbstractVector{<:Integer})
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

might be generally useful to have and export, along with corresponding permutecols!, permutecols, permuterows!!, permuterows! and permuterows (yikes, that's a lot variations).

@nalimilan
Copy link
Member

How about adding a sort::Bool keyword argument to the corresponding factorization functions? That sounds simpler than adding a new function which will be harder to discover.

@StefanKarpinski
Copy link
Sponsor Member

Or sort::Union{Bool,Function}=false where if you ask for true you get a "standard" sort order, false is unsorted and if you pass a function, that's the function whose value is used to sort the eigenvalues (in this case). Is it always that eigenvalues that are used for sorting?

@stevengj
Copy link
Member

stevengj commented Nov 9, 2017

See @andreasnoack's comment about a sort keyword for eig. But yes, I think is is always eigenvalues that are used for sorting. Any more complicated filtering based on the eigevectors is something you'd want to implement on your own as a postprocessing step.

@dalum dalum closed this Feb 25, 2019
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

6 participants