Skip to content

Commit

Permalink
Trac #27914: py3: hash collisions of Laurent polynomials
Browse files Browse the repository at this point in the history
This ticket adjusts the hash of multivariate Laurent polynomials, so
that it agrees with the hash of univariate Laurent polynomials.

This solves the following problem: Using Python 3, this doctest in
`laurent_polynomial.pyx` fails about 1 out of 4 times.
{{{
            sage: L.<w,z> = LaurentPolynomialRing(QQ)
            sage: len({hash(w^i*z^j) for i in [-2..2] for j in [-2..2]})
            25
}}}
Due to hash collisions, the result can be smaller than 25 (such as 23 or
21). This gets even worse when using a larger range of monomials.

Regardless of that, it is desirable that univariate and multivariate
Laurent polynomials have the same hash anyway. The univariate hash
implementation does not seem to have these collisions, so adopting that
implementation solves this problem.

For reference, the univariate and multivariate hashes were implemented
in #21272 and #23864.

URL: https://trac.sagemath.org/27914
Reported by: gh-mwageringel
Ticket author(s): Markus Wageringel
Reviewer(s): Frédéric Chapoton
  • Loading branch information
Release Manager committed Jun 4, 2019
2 parents b4a1854 + 26eb5fd commit 41e4710
Showing 1 changed file with 21 additions and 9 deletions.
30 changes: 21 additions & 9 deletions src/sage/rings/polynomial/laurent_polynomial.pyx
Expand Up @@ -560,9 +560,6 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial):
sage: hash(R.zero()) == hash(t - t)
True
"""
if self.__n == 0:
return hash(self.__u)

# we reimplement below the hash of polynomials to handle negative
# degrees
cdef long result = 0
Expand All @@ -573,10 +570,11 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial):
result_mon = hash(self.__u[i])
if result_mon:
j = i + self.__n
result_mon = (1000003 * result_mon) ^ var_hash_name
if j > 0:
result_mon = (1000003 * result_mon) ^ var_hash_name
result_mon = (1000003 * result_mon) ^ j
elif j < 0:
result_mon = (1000003 * result_mon) ^ var_hash_name
result_mon = (700005 * result_mon) ^ j
result += result_mon
return result
Expand Down Expand Up @@ -1905,7 +1903,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial):
r"""
TESTS:
Test that the hash is non-constant::
Test that the hash is non-constant (see also :trac:`27914`)::
sage: L.<w,z> = LaurentPolynomialRing(QQ)
sage: len({hash(w^i*z^j) for i in [-2..2] for j in [-2..2]})
Expand Down Expand Up @@ -1941,6 +1939,18 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial):
True
sage: hash(1 - 7*x0 + x1*x2) == hash(L(1 - 7*x0 + x1*x2))
True
Check that :trac:`27914` is fixed::
sage: L.<w,z> = LaurentPolynomialRing(QQ)
sage: Lw = LaurentPolynomialRing(QQ, 'w')
sage: Lz = LaurentPolynomialRing(QQ, 'z')
sage: all(hash(w^k) == hash(Lw(w^k))
....: and hash(z^k) == hash(Lz(z^k)) for k in (-5..5))
True
sage: p = w^-1 + 2 + w
sage: hash(p) == hash(Lw(p))
True
"""
# we reimplement the hash from multipolynomial to handle negative exponents
# (see multi_polynomial.pyx)
Expand All @@ -1955,10 +1965,12 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial):
if c_hash != 0:
for p in range(n):
exponent = m[p] + self._mon[p]
if not exponent:
continue
c_hash = (1000003 * c_hash) ^ var_name_hash[p]
c_hash = (1000003 * c_hash) ^ exponent
if exponent > 0:
c_hash = (1000003 * c_hash) ^ var_name_hash[p]
c_hash = (1000003 * c_hash) ^ exponent
elif exponent < 0:
c_hash = (1000003 * c_hash) ^ var_name_hash[p]
c_hash = (700005 * c_hash) ^ exponent
result += c_hash

return result
Expand Down

0 comments on commit 41e4710

Please sign in to comment.