Sage: Ticket #11358: matrix multiplication over ZZ sometimes gives incorrect results
https://trac.sagemath.org/ticket/11358
<p>
Something is wrong with the multi-modular matrix multiplication code for matrices over ZZ. At random, and infrequently, it gives incorrect results. For example, the following code chooses random 3x2 and 2x10 integer matrices and multiplies them together using the multi-modular algorithm. It then does the same multiplication 100 more times, checks that the answer is always the same, and if not raises an exception:
</p>
<pre class="wiki">sage: for n in range(2000):
....: A = MatrixSpace(ZZ,3,2).random_element()
....: B = MatrixSpace(ZZ,2,10).random_element()
....: try_once = A._multiply_multi_modular(B)
....: for k in range(100):
....: try_again = A._multiply_multi_modular(B)
....: if try_once != try_again:
....: print "="*60
....: print "n = %s, k = %s"%(n,k)
....: print "A = "
....: print A
....: print "B ="
....: print B
....: print "first attempt = "
....: print try_once
....: print "k-th retry = "
....: print try_again
....: raise RuntimeError
....:
</pre><p>
This fails with very high probability (on Sage 4.6.2 under OS X, built from source) with output such as:
</p>
<pre class="wiki">============================================================
n = 27, k = 43
A =
[-1 0]
[ 1 0]
[ 0 1]
B =
[ -2 1 -1 0 0 -1 -1 3 -1 -1]
[ 1 1 -116 3 0 -1 -1 0 -1 0]
first attempt =
[ 2 -1 1 0 0 1 1 -3 1 1]
[ -2 1 -1 0 0 -1 -1 3 -1 -1]
[ 1 1 -116 3 0 -1 -1 0 -1 0]
k-th retry =
[ 2 1102 1 0 0 1 1 1100 1 1]
[1101 1 1102 0 0 1102 1102 3 1102 1102]
[ 1 1 987 3 0 1102 1102 0 1102 0]
---------------------------------------------------------------------------
RuntimeError [...]
</pre><p>
Note that the two candidates for the matrix product here are congruent modulo 1103, which is prime. If you rerun the code with verbose logging, using set_verbose(2), then every time it fails the two candidates for the matrix product are congruent modulo the prime being used in the multi-modular algorithm. Thus I suspect that the Chinese Remainder Theorem code in sage/ext/multi_modular.pyx is not handling a corner case properly.
</p>
<p>
SEE ALSO: <a class="closed ticket" href="https://trac.sagemath.org/ticket/10281" title="defect: Multimodular echelon form over cyclotomic fields fails (closed: fixed)">#10281</a>, which touches the sage/ext/multi_modular.pyx code in nontrivial ways.
</p>
en-usSagehttps://trac.sagemath.org/chrome/site/logo_sagemath_trac.png
https://trac.sagemath.org/ticket/11358
Trac 1.1.6tomcFri, 20 May 2011 13:57:41 GMT
https://trac.sagemath.org/ticket/11358#comment:1
https://trac.sagemath.org/ticket/11358#comment:1
<p>
This also fails on Sage 4.6.2 under Red Hat Enterprise Linux (binary install, x86_64 GNU/Linux)
</p>
TickettomcFri, 20 May 2011 15:27:29 GMT
https://trac.sagemath.org/ticket/11358#comment:2
https://trac.sagemath.org/ticket/11358#comment:2
<p>
Looking at the function mpz_crt_vec_tail() in sage/ext/multi_modular.pyx, it appears that three of the last five lines:
</p>
<pre class="wiki">cdef Integer zz
zz = PY_NEW(Integer)
mpz_set(zz.value, self.half_product)
</pre><p>
are redundant, because the variable zz is never used. (NB I am unfamiliar with both Cython and GMP, so I may be wrong here.) But why is this code there? Is it supposed to be doing something else?
</p>
TicketrbeezerFri, 20 May 2011 17:17:23 GMT
https://trac.sagemath.org/ticket/11358#comment:3
https://trac.sagemath.org/ticket/11358#comment:3
<p>
From #sagemath on IRC:
</p>
<pre class="wiki">[07:11] <sagebot> New news from tractimeline: Ticket #11358 (matrix multiplication over ZZ sometimes gives incorrect results) created
[07:32] <logix> hm, i just tried to reproduce that bug (which i can), and it failed both times when B had a full zero column
[07:32] <logix> (which is the case in the bug report too)
[07:33] <logix> perhaps that's the condition triggering the bug?
[07:34] <logix> i mean one of the conditions triggering the bug (there must be something else, as the inner loop runs more than once and it fails only sometimes
</pre>
TickettomcFri, 20 May 2011 20:17:13 GMT
https://trac.sagemath.org/ticket/11358#comment:4
https://trac.sagemath.org/ticket/11358#comment:4
<p>
Replying to <a class="ticket" href="https://trac.sagemath.org/ticket/11358#comment:3" title="Comment 3">rbeezer</a>:
</p>
<p>
I don't think that this is correct. By running the code repeatedly, I found examples without zero columns.
</p>
TicketrbeezerFri, 20 May 2011 20:26:26 GMT
https://trac.sagemath.org/ticket/11358#comment:5
https://trac.sagemath.org/ticket/11358#comment:5
<p>
Replying to <a class="ticket" href="https://trac.sagemath.org/ticket/11358#comment:4" title="Comment 4">tomc</a>:
</p>
<blockquote class="citation">
<p>
I don't think that this is correct. By running the code repeatedly, I found examples without zero columns.
</p>
</blockquote>
<p>
OK, just saw that in IRC and thought I'd capture it.
</p>
<p>
I'm curious to see what the root cause is here!
</p>
<p>
Rob
</p>
TicketdsmSat, 21 May 2011 06:28:51 GMT
https://trac.sagemath.org/ticket/11358#comment:6
https://trac.sagemath.org/ticket/11358#comment:6
<p>
I think I've got it.
</p>
<p>
This seems to happen iff a random prime happens to be chosen twice. For example, if you modify _extend_moduli_to_height_c to always reuse a prime if more than one is needed, this always happens. The same bug seems to exist in _extend_moduli_to_count as well.
</p>
<p>
The problem seems to be that in _new_random_prime, there's a test which doesn't do what it's trying to do:
</p>
<pre class="wiki"> cdef mod_int _new_random_prime(self):
# choose a new random prime
cdef Py_ssize_t i
cdef mod_int p
while True:
p = random_prime(self._u_bound, lbound =self._l_bound)
for i in range(self.n):
if p == self.moduli[i]:
break
else:
return p
</pre><p>
IIUC, the "for i in range(self.n)" loop is attempting to avoid the problem of repeated primes, but this only works if p is added to self.moduli immediately after it's returned. In the case of _extend_moduli_to_height_c, this isn't true; the addition is delayed until a later extend_with_primes call, so the above check decides that the prime isn't reused, we get repeated moduli, and the multiplication breaks.
</p>
<p>
Or even more obviously:
</p>
<pre class="wiki">sage: from sage.ext.multi_modular import MultiModularBasis_base
sage: mm = MultiModularBasis_base(1)
sage: mm._extend_moduli_to_count(1000)
1000
sage: len(mm), len(set(mm))
(1000, 843)
</pre><p>
ISTM either we can remove the check-for-duplicate code from _new_random_prime and do it externally, or we can add an argument and pass it the "accumulated" primes as we build them elsewhere so it can avoid duplicates there too. I prefer the first. Should I work up a patch?
</p>
TicketburcinSat, 21 May 2011 09:37:45 GMTcc set
https://trac.sagemath.org/ticket/11358#comment:7
https://trac.sagemath.org/ticket/11358#comment:7
<ul>
<li><strong>cc</strong>
<em>burcin</em> added
</li>
</ul>
TickettomcSat, 21 May 2011 13:20:43 GMT
https://trac.sagemath.org/ticket/11358#comment:8
https://trac.sagemath.org/ticket/11358#comment:8
<p>
Replying to <a class="ticket" href="https://trac.sagemath.org/ticket/11358#comment:6" title="Comment 6">dsm</a>:
</p>
<p>
Good catch! I agree.
</p>
<blockquote class="citation">
<p>
ISTM either we can remove the check-for-duplicate code from _new_random_prime and do it externally, or we can add an argument and pass it the "accumulated" primes as we build them elsewhere so it can avoid duplicates there too. I prefer the first. Should I work up a patch?
</p>
</blockquote>
<p>
I prefer the first too. Please write a patch and then I will review it.
</p>
TicketdsmSat, 21 May 2011 17:13:34 GMTstatus changed
https://trac.sagemath.org/ticket/11358#comment:9
https://trac.sagemath.org/ticket/11358#comment:9
<ul>
<li><strong>status</strong>
changed from <em>new</em> to <em>needs_review</em>
</li>
</ul>
<p>
Okay, here's my first attempt. Because another function calls _new_random_prime assuming that it correctly avoids duplication I had to change my intended approach a bit, but it should work.
</p>
<p>
Two other minor changes:
</p>
<p>
(1) as mentioned above, mpz_crt_vec_tail() has what looks to me like vestigial code, and I've removed it.
</p>
<p>
(2) the documentation for replace_prime_c claimed that it would replace a prime with a greater prime, but made no efforts to ensure the "greater than" part. I've changed the doc accordingly.
</p>
TickettomcSat, 21 May 2011 17:49:08 GMT
https://trac.sagemath.org/ticket/11358#comment:10
https://trac.sagemath.org/ticket/11358#comment:10
<p>
Thanks. I'll review this.
</p>
TickettomcTue, 24 May 2011 08:26:09 GMTstatus changed
https://trac.sagemath.org/ticket/11358#comment:11
https://trac.sagemath.org/ticket/11358#comment:11
<ul>
<li><strong>status</strong>
changed from <em>needs_review</em> to <em>needs_work</em>
</li>
</ul>
<p>
I can't get the patch to apply. When I apply the patch and then do
</p>
<pre class="wiki">sage -b
</pre><p>
I get
</p>
<pre class="wiki">Error converting Pyrex file to C:
------------------------------------------------------------
...
"""
cdef Py_ssize_t i
cdef mod_int p
while True:
p = random_prime(self._u_bound, lbound =self._l_bound)
if p not in self.moduli[:self.n]: return p
^
------------------------------------------------------------
/Users/tomc/sage-4.6.2/devel/sage-test-crt-fix/sage/ext/multi_modular.pyx:191:35: Cannot convert 'mod_int *' to Python object
</pre><p>
Also, in the new implementation of _new_random_prime() there is the possibility of an infinite loop if self.moduli already contains all the primes between self._lbound and self._ubound. I suggest modifying _new_random_prime() so that it:
</p>
<ul><li>makes some number of tries (10? 100?) to find a new random prime p in the range self._lbound < p < self._ubound; and
</li></ul><ul><li>if this fails then it increases self_ubound and tries again.
</li></ul>
TicketburcinTue, 24 May 2011 08:43:53 GMTowner deleted
https://trac.sagemath.org/ticket/11358#comment:12
https://trac.sagemath.org/ticket/11358#comment:12
<ul>
<li><strong>owner</strong>
changed from <em>jason, was</em> to <em>(none)</em>
</li>
</ul>
<p>
Replying to <a class="ticket" href="https://trac.sagemath.org/ticket/11358#comment:11" title="Comment 11">tomc</a>:
</p>
<blockquote class="citation">
<p>
Also, in the new implementation of _new_random_prime() there is the possibility of an infinite loop if self.moduli already contains all the primes between self._lbound and self._ubound. I suggest modifying _new_random_prime() so that it:
</p>
</blockquote>
<ul><li>makes some number of tries (10? 100?) to find a new random prime p in the range self._lbound < p < self._ubound; and
</li></ul><blockquote class="citation">
</blockquote>
<ul><li>if this fails then it increases self_ubound and tries again.
</li></ul><p>
I suggest raising an error instead of increasing <code>_ubound</code>. The upper bound is usually set so that the primes fit in a single machine word. A lot of things would break if this changed without notice.
</p>
TickettomcTue, 24 May 2011 09:33:37 GMT
https://trac.sagemath.org/ticket/11358#comment:13
https://trac.sagemath.org/ticket/11358#comment:13
<p>
Replying to <a class="ticket" href="https://trac.sagemath.org/ticket/11358#comment:12" title="Comment 12">burcin</a>:
</p>
<blockquote class="citation">
<p>
I suggest raising an error instead of increasing <code>_ubound</code>. The upper bound is usually set so that the primes fit in a single machine word. A lot of things would break if this changed without notice.
</p>
</blockquote>
<p>
If we do raise an error then it should be caught before it propagates up to the user. For instance, it should not be that if A and B are integer matrices then evaluating A*B can sometimes cause an exception because the multi-modular matrix multiplication algorithm ran out of primes: in this case we should catch the exception and evaluate A*B using a different algorithm.
</p>
<p>
Is it really the case that raising self._ubound would cause things to break? I thought from looking at the multi-modular code that it all used mpz_t types from GMP, and that these would automatically increase their allocated memory as necessary.
</p>
<p>
The multi-modular code is used in five places: dense matrix multiplication over ZZ; dense matrix multiplication over cyclotomics; dense matrix multiplication over QQ (although only in the rational reconstruction routines _lift_crt_rr() and _lift_crt_rr_with_lcm(), which I think are dead code); echelon form calculations over cyclotomics; and characteristic polynomial calculations over cyclotomics. If we decide to raise an error if the multi-modular code runs out of primes then we will need to catch this error and handle it appropriately in all five places.
</p>
TicketburcinTue, 24 May 2011 09:57:22 GMT
https://trac.sagemath.org/ticket/11358#comment:14
https://trac.sagemath.org/ticket/11358#comment:14
<p>
Replying to <a class="ticket" href="https://trac.sagemath.org/ticket/11358#comment:13" title="Comment 13">tomc</a>:
</p>
<blockquote class="citation">
<p>
Replying to <a class="ticket" href="https://trac.sagemath.org/ticket/11358#comment:12" title="Comment 12">burcin</a>:
</p>
<blockquote class="citation">
<p>
I suggest raising an error instead of increasing <code>_ubound</code>. The upper bound is usually set so that the primes fit in a single machine word. A lot of things would break if this changed without notice.
</p>
</blockquote>
<p>
If we do raise an error then it should be caught before it propagates up to the user. For instance, it should not be that if A and B are integer matrices then evaluating A*B can sometimes cause an exception because the multi-modular matrix multiplication algorithm ran out of primes: in this case we should catch the exception and evaluate A*B using a different algorithm.
</p>
</blockquote>
<p>
This is better than trying to handle it magically in the <code>MultiModularBasis</code> class.
</p>
<blockquote class="citation">
<p>
Is it really the case that raising self._ubound would cause things to break? I thought from looking at the multi-modular code that it all used mpz_t types from GMP, and that these would automatically increase their allocated memory as necessary.
</p>
</blockquote>
<p>
Multi precision integers are needed for the lifting step. One of the reasons to use a multi-modular algorithm is to take advantage of fast arithmetic implemented in hardware. This requires that the modulus is small enough to fit in a word size. For linear algebra, the dimensions of the matrix also come into play. Ideally you should reduce only once for each row*column multiplication.
</p>
<blockquote class="citation">
<p>
The multi-modular code is used in five places: dense matrix multiplication over ZZ; dense matrix multiplication over cyclotomics; dense matrix multiplication over QQ (although only in the rational reconstruction routines _lift_crt_rr() and _lift_crt_rr_with_lcm(), which I think are dead code); echelon form calculations over cyclotomics; and characteristic polynomial calculations over cyclotomics. If we decide to raise an error if the multi-modular code runs out of primes then we will need to catch this error and handle it appropriately in all five places.
</p>
</blockquote>
<p>
You cannot assume that the only users of the code are in the Sage library. This class is designed to be an abstraction that makes it easy to implement multi modular algorithms.
</p>
<p>
Perhaps we can verify that this error will never occur in these places through some theoretical argument. It is highly unlikely that we will always keep choosing the same random prime.
</p>
TickettomcTue, 24 May 2011 10:19:05 GMT
https://trac.sagemath.org/ticket/11358#comment:15
https://trac.sagemath.org/ticket/11358#comment:15
<p>
Replying to <a class="ticket" href="https://trac.sagemath.org/ticket/11358#comment:14" title="Comment 14">burcin</a>:
</p>
<blockquote class="citation">
<p>
Multi precision integers are needed for the lifting step. One of the reasons to use a multi-modular algorithm is to take advantage of fast arithmetic implemented in hardware. This requires that the modulus is small enough to fit in a word size.
</p>
</blockquote>
<p>
I agree that things will slow down if we ever raise self._ubound to be larger than a machine word: my question was whether anything would actually break. But in any case, since the <a class="missing wiki">MultiModularBasis?</a> class is explicitly documented as:
</p>
<pre class="wiki">"""
This class stores a list of machine-sized prime numbers...
"""
</pre><p>
we should not allow the code to raise self._ubound. Thus we should raise an error if we repeatedly fail to choose a new random prime. As discussed, we will need to handle this error in several other places in Sage.
</p>
<p>
Note that the situation we are discussing is very unlikely to ever occur in practice, as the default values of _lbound and _ubound are repectively 2<strong>10 and 2</strong>15 and there are many primes in between 2<strong>10 and 2</strong>15. But in theory someone could do:
</p>
<pre class="wiki">sage: mm = MultiModularBasis(lbound=4,ubound=6,...)
</pre><p>
or something equally silly, and then the multi-modular code would quickly run out of primes.
</p>
TicketdsmTue, 24 May 2011 16:26:20 GMT
https://trac.sagemath.org/ticket/11358#comment:16
https://trac.sagemath.org/ticket/11358#comment:16
<p>
Replying to <a class="ticket" href="https://trac.sagemath.org/ticket/11358#comment:11" title="Comment 11">tomc</a>:
</p>
<blockquote class="citation">
<p>
I can't get the patch to apply.
</p>
</blockquote>
<p>
Try it against one of the 4.7 release candidates. I should've mentioned what I was basing it on.
</p>
<p>
As for the case of running out of primes -- which I considered the caller's fault, and so I simply warned about it in the docstring rather than handle it -- I'm fine with whatever. (Once cython 0.15 gets integrated and we have cythonic generators it may be worth collecting random prime functions into a fast module of their own.)
</p>
<p>
Given that the moduli have to be below MAX_MODULUS, it wouldn't cost much to check whether there really are any primes left and raise an exception only if we really are done and not merely unlucky. If we're doing that, though, then we should probably raise the same exception if you ask for more primes than there are available in extend_moduli_to_count as well.
</p>
TicketwasFri, 16 Mar 2012 01:51:31 GMT
https://trac.sagemath.org/ticket/11358#comment:17
https://trac.sagemath.org/ticket/11358#comment:17
<p>
*ping* why is this ticket dead? It seems very, very important, but everybody forgot about it a year ago?!
</p>
<p>
Replying to <a class="ticket" href="https://trac.sagemath.org/ticket/11358#comment:15" title="Comment 15">tomc</a>:
</p>
<blockquote class="citation">
<p>
Note that the situation we are discussing is very unlikely to ever occur in practice, as the default
values of _lbound and _ubound are repectively 2<strong>10 and 2</strong>15 and
there are many primes in between 2<strong>10 and 2</strong>15.
</p>
</blockquote>
<p>
This is completely false. There are hardly any primes in that range:
</p>
<pre class="wiki">sage: len(prime_range(2^10,2^15))
3340
</pre><p>
The linear algebra in Sage is currently very broken in practice, perhaps due to various optimizations and re-implementations that have fundamentally broken things. For example, I'm constantly hitting a problem of "not enough primes" (see <a class="closed ticket" href="https://trac.sagemath.org/ticket/10281" title="defect: Multimodular echelon form over cyclotomic fields fails (closed: fixed)">#10281</a>), even for matrices over QQ.
</p>
TicketwasSun, 18 Mar 2012 21:18:41 GMTdescription changed
https://trac.sagemath.org/ticket/11358#comment:18
https://trac.sagemath.org/ticket/11358#comment:18
<ul>
<li><strong>description</strong>
modified (<a href="/ticket/11358?action=diff&version=18">diff</a>)
</li>
</ul>
TicketjenTue, 20 Mar 2012 18:05:08 GMTstopgaps set
https://trac.sagemath.org/ticket/11358#comment:19
https://trac.sagemath.org/ticket/11358#comment:19
<ul>
<li><strong>stopgaps</strong>
set to <em>#12710</em>
</li>
</ul>
TicketwasWed, 21 Mar 2012 18:19:08 GMTattachment set
https://trac.sagemath.org/ticket/11358
https://trac.sagemath.org/ticket/11358
<ul>
<li><strong>attachment</strong>
set to <em>11358.sage</em>
</li>
</ul>
<p>
this is a usable version of the test program in the ticket description
</p>
TicketwasWed, 21 Mar 2012 18:21:52 GMTstatus changed
https://trac.sagemath.org/ticket/11358#comment:20
https://trac.sagemath.org/ticket/11358#comment:20
<ul>
<li><strong>status</strong>
changed from <em>needs_work</em> to <em>needs_review</em>
</li>
</ul>
<p>
I've posted a patch trac_11358-wstein.patch that fixes the bug. The solution is definitely much better than what was proposed 10 years ago. You can run the attached 11358.sage to check that the exact bug cited here doesn't happen. But better is to see the new doctests which get to the heart of the matter.
</p>
TicketdsmWed, 21 Mar 2012 19:19:24 GMTstatus changed
https://trac.sagemath.org/ticket/11358#comment:21
https://trac.sagemath.org/ticket/11358#comment:21
<ul>
<li><strong>status</strong>
changed from <em>needs_review</em> to <em>needs_work</em>
</li>
</ul>
<p>
The patch does seem to eliminate the original bug for me, but there's still some strangeness for different _extend methods:
</p>
<pre class="wiki">
sage: set_random_seed(0)
sage: from sage.ext.multi_modular import MultiModularBasis_base
sage: m = MultiModularBasis_base(0);
sage: m._extend_moduli(100)
sage: len(m), len(set(m))
(101, 99)
sage: [k for k in m if list(m).count(k) > 1]
[30859, 3217, 30859, 3217]
</pre><pre class="wiki">
sage: set_random_seed(1)
sage: from sage.ext.multi_modular import MultiModularBasis_base
sage: m = MultiModularBasis_base(0);
sage: m._extend_moduli_to_count(100)
100
sage: len(m), len(set(m))
(100, 97)
sage: [k for k in m if list(m).count(k) > 1]
[4001, 5309, 23293, 4001, 23293, 5309]
</pre><p>
So I think that two of the three _extend methods still don't give results that I'd expect.
</p>
TicketwasWed, 21 Mar 2012 19:30:14 GMTstatus changed
https://trac.sagemath.org/ticket/11358#comment:22
https://trac.sagemath.org/ticket/11358#comment:22
<ul>
<li><strong>status</strong>
changed from <em>needs_work</em> to <em>needs_review</em>
</li>
</ul>
<blockquote class="citation">
<p>
So I think that two of the three _extend methods still don't give results that I'd expect.
</p>
</blockquote>
<p>
I have fixed the patch. There was one case where I didn't update use of _new_random_prime correctly.
</p>
TicketdsmWed, 21 Mar 2012 21:11:14 GMTstatus changed
https://trac.sagemath.org/ticket/11358#comment:23
https://trac.sagemath.org/ticket/11358#comment:23
<ul>
<li><strong>status</strong>
changed from <em>needs_review</em> to <em>positive_review</em>
</li>
</ul>
<p>
The new patch:
</p>
<p>
(1) Applies against 5.0.beta8 with minor fuzz.
(2) Seems to eliminate the original bug.
(3) Matches the original diagnosis about where the problem was (use of repeated primes in a multimodular basis), so I get why it *should* fix the bug.
(4) Has doctests which verify that .._height now behaves.
(5) The new version passes my separate tests confirming that the other _extend methods behave too, while the first version didn't.
</p>
<p>
Positive review.
</p>
TicketdavidloefflerMon, 26 Mar 2012 12:34:43 GMTreviewer, author set
https://trac.sagemath.org/ticket/11358#comment:24
https://trac.sagemath.org/ticket/11358#comment:24
<ul>
<li><strong>reviewer</strong>
set to <em>Douglas McNeil</em>
</li>
<li><strong>author</strong>
set to <em>William Stein</em>
</li>
</ul>
TicketjdemeyerMon, 02 Apr 2012 15:24:13 GMTstatus changed; resolution, merged set
https://trac.sagemath.org/ticket/11358#comment:25
https://trac.sagemath.org/ticket/11358#comment:25
<ul>
<li><strong>status</strong>
changed from <em>positive_review</em> to <em>closed</em>
</li>
<li><strong>resolution</strong>
set to <em>fixed</em>
</li>
<li><strong>merged</strong>
set to <em>sage-5.0.beta12</em>
</li>
</ul>
TicketjdemeyerMon, 02 Apr 2012 15:30:29 GMTattachment set
https://trac.sagemath.org/ticket/11358
https://trac.sagemath.org/ticket/11358
<ul>
<li><strong>attachment</strong>
set to <em>trac_11358-wstein.patch</em>
</li>
</ul>
<p>
apply ONLY this.
</p>
TicketjdemeyerMon, 02 Apr 2012 15:30:54 GMT
https://trac.sagemath.org/ticket/11358#comment:26
https://trac.sagemath.org/ticket/11358#comment:26
<p>
Rebased patch by removing a hunk which just added a newline.
</p>
Ticket