-
-
Notifications
You must be signed in to change notification settings - Fork 438
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 absolute length fix #37001
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. I have two nitpicks (which you can chose to ignore) and one potential optimization if you want to see if it will work. Essentially by using the strong exchange condition, we can build all potential reflections (from the left):
w = self.reduced_word()
if len(w) <= 2: # trivial cases
return ZZ(len(w))
P = self.parent()
one = P.one()
reflections = []
for i in range(len(w)):
refl = P.from_reduced_word(w[:i+1] + list(reversed(w[:i])))
reflections.append(refl)
for ell in range(len(w)):
for chain in itertools.combinations(reflections, ell):
if prod(chain) == w:
return ell
I suspect this will be faster for things of longer absolute length because it should do fewer multiplications. Anyways, it's up to you if you want to pursue any optimizations.
Co-authored-by: Travis Scrimshaw <[email protected]>
Co-authored-by: Travis Scrimshaw <[email protected]>
Wouldn't this take roughly the same amount of time? Mainly because, in both cases, the thing that's going to take the longest amount of time is the |
Yes, although I realized we can generate the possible reflections linearly in s = P.simple_reflections()
rev = P.one()
cur = P.one()
reflections = []
for val in w:
cur = cur * s[val]
reflections.append(cur * rev)
rev = s[val] * rev I suspect this will be faster when the absolute length is longer. It will almost certainly be slower when the absolute length is short. Well, either way it might be good to implement this as In a related note, we should also fix the infinite loop on I know it is PR creep, but it would be good to fix all of the issues with absolute order for infinite Coxeter groups all at once. |
But the for loop afterwards is still basically the same time length as the one currently implemented. You still need two for loops and one of them uses combinations. It seems like this should roughly be the same amount of time. (Or maybe I'm not understanding?) r.e |
Looks like other tests in the combinat directory are failing. I'll run a more thorough test and see what else needs fixing. Hopefully it's not to much or else I might just revert it and say we should seperate these into two tickets to not have PR creep. |
Should be all good now. I ran all the tests of sage, and it looks like it's ok. An alternative approach would be maybe to return a list if it's finite and a generator otherwise? Or would this be to confusing because the return type changes depending on if the group is finite/infinite. (But then it would be backwards compatible, so there is that?) |
You're right that the two main loops are the same. However, the key point is the number of multiplications in the Coxeter group. The current way it's implemented requires
Great! Thank you.
I think we should just absorb any pain with the backwards incompatible change. It's somewhat difficult to deprecate and at some point, people will just have to deal with the switch (and it often happens that people have sufficiently large version jumps that render such things moot). On the other hand, I think it wouldn't be so confusing for infinite groups to be an iterator and finite to return a list since the user should expect it to be infinite in general. Although IIRC Python gets feisty when you mix non-trivial returns with iterator functions. |
Ok sounds good. I'll go ahead and make the change to the secondary version. That's what I had been asking earlier, if the product computation was "big enough" that it would cause performance differences and I think your answer above is mentioning yes.
An alternative approach that I thought of yesterday night: We can create a second function |
I think it just needs to be tested against each other to see which (generally) has the better performance. If you prefer, I can do the comparison testing.
I thought about that as well, but it the "list" version is an infinite loop for infinite types (which is a bug IMO). Plus, as you said, it adds some bloat and the current implementation more naturally says it should be a list. I think following the Python3 model -- just telling people it is now an iterator and to put |
(I deleted my previous comment as I had made a mistake in my explanation. This one should be correct. Sorry.) I just tested the code on the following word:
The reflections we get back are:
Even if we don't need Because of this, I'd say let's stick with Dyer's method as (although it's not optimised) it is currently the fastest approach we know based on research.
Sounds good. Then we can just leave it as it currently is as that's how I implemented it? Made it into an iter and updated everything to use list() around the iter for the finite cases. Sounds like this should be done? |
You can edit your posts. :) I don't think we need to change the order of the reflections, nor do we need to consider "repetitions." I put repetitions in quotes because with how the reflections are construction, we might get the same reflection appearing more than once in the list. My idea is translating removing an element from a reduced word into multiplying by a reflection. I thought this is what the strong exchange condition gives us. Now I finally have some time to write and test the code instead of just posting thoughts here. I see a mistake in the implementation of my idea: We want to multiply things in the reverse order as I was thinking of doing That being said, I ran some timings and Dyer's method is clearly faster for most examples:
Hence, I propose adding the code I have as: def absolute_chain(self):
r"""
Return a (saturated) chain in absolute order from ``self``
to ``1``.
"""
if self.is_one():
return [self]
w = self.reduced_word()
if len(w) == 1: # trivial case
return [P.one(), self]
if len(w) == 2: # trivial case
return [P.one(), P.simple_reflection(w[0]), self]
s = P.simple_reflections()
import itertools
from sage.misc.misc_c import prod
rev = P.one()
cur = P.one()
reflections = []
for val in w:
cur = cur * s[val]
reflections.append(cur * rev)
rev = s[val] * rev
for ell in range(len(w)):
for chain in itertools.combinations(reflections, ell):
if prod(reversed(chain)) == w:
return (P.one(),) + chain What do you think? I also found the same bug in not handling 1 properly in your implementation. Please add a doctest for it and add the short special case check: if len(w) <= 2: # trivial cases
from sage.rings.integer import Integer
return Integer(len(w)) |
(Yes, I know you can edit, but I didn't know how long it would take to fix the wrong information I had put, so I decided better to just delete and readd once I actually have more detailed/correct information) Oooo I think I finally see what you're trying to do. Sorry I'm a little slow with things currently due to life circumstances. Yeah, strong exchange should work. So it sounds like |
It didn't help that I had bugs in my code. No problem on slow responses. Yes, that is what it should do. I was surprised at how significantly slower it is, but I didn't try to profile it. Although it does give some more information in a clean way; plus having it would give access for the user in case it was faster. |
I don't know how you got yours so slow? (or the proposed method so fast?) because for me yours is clearly running faster. (abs_len_two is my test code for your proposal.)
So what I ended up doing is doing some more tests where I looked at elements of a certain length and how long it takes (because as discussed, the longer the word, the longer the "original" solution should take and yours should be much faster) I found out that even when the length is Note that I did change some of your code. In particular:
I added a few more examples (maybe too much?) but anyway, it's now running relatively smoothly on my end. |
Thank you. Maybe I had some stupid mistake with the implementation. Perhaps related with (5) in your changes or accidentally swapping what the extra (temporary) input value meant. Well, anyways, I will run tests on my side again tomorrow to reconfirm. One thing I got wrong with the def absolute_chain(self):
P = self.parent()
return [P.prod(elts[:i] for i in range(len(elts))] and we can then drop adding [P.one()] to the "chain" and add |
Ok, I'll need a little bit of time to work on this to make sure it all handles nicely. I think that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know you're still working on adding the additional method and fixing my mistake with the chains, but I wanted to pass along a few other minor comments that will be needed for a positive review. Basically they are just minor nitpicks.
I am wondering if the finite type implementation is a lower bound on the absolute length. Although even if we knew that information, I am not sure it would help in the general case....much less if it is mathematically interesting... |
Co-authored-by: Travis Scrimshaw <[email protected]>
I just added your changes. All that was left was a double check that everything looked good, so we should be ok. In affine case, it's true, but I don't think it will decrease performance that much unless our groups are "large" and our word is "long". I don't know if there's such a thing in other (infinite) cases. It would be useful/nice to look at, but might be a project for another day. |
Thank you. Almost there. I feel like chains should start from the smallest element and go up. This would match what the posets do:
Also, I just realized that we could get rid of the |
Sounds good. But I think for
|
:meth:`absolute_length`, | ||
:meth:`absolute_chain` | ||
|
||
EXAMPLES:: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also have examples where the absolute length is > 2 and equal to the usual length of the element (which should also be length 3 or more) to cover all of the code paths..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my forthcoming commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. It would also be good to have one test in absolute_length()
with a length > 2 (perhaps a different one with a length > 5?).
Yes, I agree that they should be consistent in the sense of should be the labeled path in the poset (with the odd positions being the edge label and even being the vertices) with
Yes, as you surmised, you just need to implement an |
Ok, these changes are done. Note that the reflections chain gives left multiplication instead of right. So I went ahead and gave an example of this to be extra clear how the multiplication is working. |
Thank you. Looks good. Just a few more small details.
Apologies for the much longer review for a fairly simple thing to fix, but I appreciate how much of an improvement this change is contributing! |
(Edit) I just noticed the changes. These should be fixed now as well.
Oops, it's been fixed.
And done =)
No! Don't apologize. This is much nicer now =D |
Co-authored-by: Travis Scrimshaw <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. LGTM.
Documentation preview for this PR (built with commit 231c475; changes) is ready! 🎉 |
Added a fix for absolute length which was previously giving wrong information. We used Theorem 1.1 from Dyer's paper "On Minimal Lengths of Expressions of Coxeter Group Elements as Products of Reflections" as suggested by Travis Scrimshaw.
Fixes #36174
📝 Checklist
⌛ Dependencies
#36174