-
-
Notifications
You must be signed in to change notification settings - Fork 482
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
improved integer vectors efficiency -Enhancement #36830
improved integer vectors efficiency -Enhancement #36830
Conversation
…d bug of InfiniteEnumeratedSets and +infinity of IntegerVectors_n(0) and IntegerVectors_k(0)
src/sage/combinat/integer_vector.py
Outdated
@@ -839,7 +838,10 @@ def __init__(self, n): | |||
sage: TestSuite(IV).run() | |||
""" | |||
self.n = n | |||
IntegerVectors.__init__(self, category=InfiniteEnumeratedSets()) | |||
if self.n==0: |
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.
There need to be spaces around ==
. Alternatively (and likely more according to the unwritten sage conventions, but I don't care):
if self.n:
IntegerVectors.__init__(self, category=InfiniteEnumeratedSets())
else:
IntegerVectors.__init__(self, category=EnumeratedSets())
This comment also applies below.
src/sage/combinat/integer_vector.py
Outdated
|
||
TESTS:: | ||
|
||
sage: IntegerVectors(200,5).cardinality() |
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.
This is not really a test, but an example (demonstrating that cardinality works for large numbers). It would also be good if there is an example that can be checked by hand, or even better, in your head.
A test would be len(list(IntegerVectors(10, 5))) == IntegerVectors(10, 5).cardinality()
.
Warning: len
automatically calls cardinality
, if available.
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.
For an example&test that is checkable by hand (for us mortals) or in your head (for Gauss), you could have
sage: IntegerVectors(99, 3).cardinality()
5050
The "check in your head" would be that if the sum of the last two elements is
You can then make a bigger demonstration with
sage: IntegerVectors(10^9 - 1, 3).cardinality()
500000000500000000
src/sage/combinat/integer_vector.py
Outdated
@@ -1315,7 +1361,7 @@ def __init__(self, n=None, k=None, **constraints): | |||
del constraints['inner'] | |||
self.constraints = constraints | |||
|
|||
if n is not None: | |||
if n is not None : |
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.
No space before :
.
src/sage/combinat/integer_vector.py
Outdated
@@ -1438,8 +1484,7 @@ def cardinality(self): | |||
if self.k is None: | |||
if self.n is None: | |||
return PlusInfinity() | |||
if ('max_length' not in self.constraints | |||
and self.constraints.get('min_part', 0) <= 0): | |||
elif 'max_length' not in self.constraints and self.constraints.get('min_part', 0) <= 0: |
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.
The original version is much more readable, please revert.
…rVectors_n and IntegerVectors_k optimised rank and unrank function
src/sage/combinat/integer_vector.py
Outdated
|
||
n, k, r = self.n, len(x), 0 | ||
for i in range(k): | ||
r += binomial(i+n-1, n) |
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.
r = sum(binomial(n+i-1, i-1) for i in range(1, k))
should be easier to understand.
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.
While we are on the topic of efficiency, it can be useful to notice that
sum(binomial(m, k) for m in range(n)) == binomial(n, k+1)
see Knuth The art of computer programming §1.2.6E or Wikipedia.
The right hand side can be slightly faster (try
k -= 1 | ||
n -= x[i] | ||
r += binomial(k + n - 1, k) | ||
|
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
src/sage/combinat/integer_vector.py
Outdated
if sum(x) != self.n: | ||
raise ValueError("argument is not a member of IntegerVectors({},{})".format(self.n, None)) | ||
|
||
n, k, r = self.n, len(x), 0 |
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.
The assignment to r
is now unnecessary.
src/sage/combinat/integer_vector.py
Outdated
|
||
n, k, r = self.n, len(x), 0 | ||
|
||
r = sum(binomial(n + i - 1, i - 1) for i in range(1, k)) |
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.
Sorry for not realizing earlier, but this can be simplified further to r = binomial(k+n-1, n+1)
, if I'm not mistaken.
src/sage/combinat/integer_vector.py
Outdated
@@ -1438,7 +1643,7 @@ def cardinality(self): | |||
if self.k is None: | |||
if self.n is None: | |||
return PlusInfinity() | |||
if ('max_length' not in self.constraints | |||
elif ('max_length' not in self.constraints |
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.
no elif
needed here, because there is a return
just before.
Is there any way to check code style on my local machine? |
|
Thanks, and sorry for not knowing that, I was not sure whether to change these, as they were present from the start, so I left them as they were. |
src/sage/combinat/integer_vector.py
Outdated
rtn.append(0) | ||
rtn.pop() | ||
|
||
while True: |
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.
maybe this while loop can be factored out into a helper function in IntegerVectors
?
In any case, we should store current_rank = self.rank(rtn)
, so we don't compute it twice.
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.
should i do the same for rank()
method?
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 think no. If I see it correctly, it would only be 3 lines there.
Excellent, thank you! There are now only a few comments left, I'll do the review once they are done. |
src/sage/combinat/integer_vector.py
Outdated
sage: IntegerVectors(2,3).unrank(5) | ||
[0, 0, 2] | ||
""" | ||
if x >= len(self): |
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.
Using len(self)
seems problematic. For me
sage: V=IntegerVectors(100000,6); c=V.cardinality(); c; V[c-1]
produces
83345834041685416895001
...
OverflowError: cannot fit 'int' into an index-sized integer
How about using cardinality
instead?
This is already a great improvement over the previous (default) cardinality and unrank methods, and worth including in the next version. If the vector sum is large, I believe further improvements in unranking are possible. In the version currently pushed,
However, I reiterate that the current version is already a big improvement. |
@jukkakohonen Thanks a lot, I'll keep working on finding a new and improved algorithm. |
src/sage/combinat/integer_vector.py
Outdated
@@ -780,6 +780,20 @@ def __contains__(self, x): | |||
return False | |||
return True | |||
|
|||
def unrank(self, x, rtn): |
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.
This method needs a docstring and an example. Also, currently it is not an unrank method, because it takes a second argument, so it should be renamed. I didn't think about it, but could it be made into a proper unrank method by providing a default argument for rtn
?
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.
The value of rtn
depends on n
and k
, and there are various ways the rtn
list is created based on any one of the values of n
and k
being None. I believe providing a default argument is not possible.
What I can do is add an if-else block inside the unrank function that checks whether n or k is None and constructs the rtn based on that.
Alternatively, I can rename the function to something like construct_element_from_rtn
and I can write a general doctest for the unrank
method.
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'm afraid I misled you, I am sorry. Possibly this shouldn't be a method of IntegerVectors
at all, but rather an auxiliary function of the module. It is not completely clear to me what it does - do I understand correctly that it first finds a vector rtn
that has rank at least equal to x
by moving the content of rtn[0]
to larger indices, and then iterating one by one through the vectors having smaller rank, until we find the vector with the correct rank?
If so (or something similar), this won't work, for example, for IntegerVectorsConstraints
, because rtn
is not guaranteed to satisfy the given conditions, right?
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.
You, got the algorithm right and the IntegerVectorsConstraints won't work as intended.
changing the name of the function will work or should i revert the changes back when there was no helper function?
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 d make it a normal Funktion and call it ·_unrank_helper·, take self as an extra argument.
src/sage/combinat/integer_vector.py
Outdated
""" | ||
if self.k == 0 and x != 0: | ||
raise IndexError(f"Index {x} is out of range for the IntegerVector.") | ||
else: |
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.
else:
is unnecessary here, because if
can only raise an error. Similarly below.
src/sage/combinat/integer_vector.py
Outdated
@@ -855,8 +855,7 @@ def __init__(self, n): | |||
self.n = n | |||
if self.n == 0: | |||
IntegerVectors.__init__(self, category=EnumeratedSets()) | |||
else: |
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.
This else:
is necessary, there is no return
or raise
before.
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.
These things are bound to happen if one goes else-hunting indiscriminately. It would be good to remember the saying "don't fix it if it isn't broken".
I have heard that some misguided coding standards try to fix "no else after return", indiscriminately, but I have not heard SageMath would be among those.
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 don't care much about any particular rule, however, I favour uniform coding style. I don't really see why always having the else:
would be any better or any worse, both have their pro and cons. In any case, at least at some point, the linter checked for else
after return
, so a fair bit of the codebase adheres to this rule.
I remember that, when I was using Turbo Pascal in the 90s, I spent a night over a forgotten semicolon (or was it a comma?). Who cares?
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 have given my advice, based on years of coding, and will leave it at that. Word to the wise.
src/sage/combinat/integer_vector.py
Outdated
@@ -998,8 +996,7 @@ def __init__(self, k): | |||
self.k = k | |||
if self.k == 0: | |||
IntegerVectors.__init__(self, category=EnumeratedSets()) | |||
else: |
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.
This else:
is necessary!
One way this can be done (but I'm not sure that all sagemath developers agree with me) is to have a doctest which takes a tolerable amount of time (e.g., half a second on a standard computer) with the good version, but will take very long if the performance degrades. |
It would indeed perform the test, but since it has the possibility of making a "short" test take a long time, it might raise some eyebrows. I would ask for opinions on Zulip before incorporating such speed tests. But I also believe in preliminary and offline tests, performed by the developer before pushing, that are more extensive than the "TESTS" cases. |
@@ -780,6 +780,20 @@ def __contains__(self, x): | |||
return False | |||
return True | |||
|
|||
def _unrank_helper(self, x, rtn): |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
while self.rank(rtn) <= x: | ||
n += 1 | ||
rtn[0] = n | ||
rtn[0] -= 1 |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
src/sage/combinat/integer_vector.py
Outdated
@@ -780,6 +780,37 @@ def __contains__(self, x): | |||
return False | |||
return True | |||
|
|||
def _unrank_helper(self, x, rtn): | |||
""" | |||
return an element at rank ``x``. |
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.
Return the
instead of return an
.
I also forgot:
INPUT:
- ``x`` - a nonnegative integer
- ``rtn`` - a list of nonnegative integers
Return an element of rank ``x`` by iterating through all integer vectors beginning with ``rtn``.
src/sage/combinat/integer_vector.py
Outdated
|
||
EXAMPLES:: | ||
|
||
sage: i=IntegerVectors(k=5) |
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.
After ::
you have to indent by 4 spaces. Also, we try to follow the same coding recommendations in the doctests, that is, sage: i = IntegerVectors(k=5)
would be better and sage: IV = IntegerVectors(k=5)
would be even better.
@adrinospy, please excuse me being so fussy! I very much like your contribution! There are only 3 comments left for positive review! |
src/sage/combinat/integer_vector.py
Outdated
- ``x`` - a nonnegative integer | ||
- ``rtn`` - a list of nonnegative integers | ||
|
||
Return the element at rank ``x`` by iterating through all integer vectors beginning with ``rtn``. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
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.
@mantepse Thank you for your kind words! These are my mistakes, and I'll try to minimize them next time.
So, last comment :-) I think I like this version
best, it is the easiest to translate into something you would do by hand. If you agree, please replace the snippet in the two |
Documentation preview for this PR (built with commit dedee8d; changes) is ready! 🎉 |
Perfekt, thank you! |
Fixes #36816
cardinality function
using stars and bars to theIntegerVector_nk
classrank
andunrank
methods inIntegerVectors_n
andIntegerVectors_k
IntegerVectors_n(0)
andIntegerVectors_k(0)
should now show cardinality as 1