Skip to content

Commit

Permalink
Trac #26221: Enable hash for FreeMonoid_class
Browse files Browse the repository at this point in the history
As in #26162 and other tickets, removing `__eq__` does the job. Also:

- use `UniqueRepresentation` instead of `UniqueFactory` for constructing
instances of `FreeMonoid`.
- clean up caching of subclasses: no need to do this by hand, since they
will now inherit from `UniqueRepresentation`.

Without this change, there are Python 3 doctest failures in
`sage/algebras/lie_algebras`, among other places. For example,
{{{
File "src/sage/algebras/lie_algebras/lie_algebra.py", line 1402, in sage
.algebras.lie_algebras.lie_algebra.LiftMorphismToAssociative.preimage
Failed example:
    L = LieAlgebra(associative=R)
Exception raised:
    Traceback (most recent call last):
      File "sage/misc/cachefunc.pyx", line 1000, in
sage.misc.cachefunc.CachedFunction.__call__
(build/cythonized/sage/misc/cachefunc.c:6175)
        return self.cache[k]
      File "sage/misc/weak_dict.pyx", line 706, in
sage.misc.weak_dict.WeakValueDictionary.__getitem__
(build/cythonized/sage/misc/weak_dict.c:3538)
        cdef PyObject* wr = PyDict_GetItemWithError(self, k)
    TypeError: unhashable type: 'FreeMonoid_class_with_category'

...
}}}

Part of #24551.

URL: https://trac.sagemath.org/26221
Reported by: jhpalmieri
Ticket author(s): John Palmieri
Reviewer(s): Travis Scrimshaw
  • Loading branch information
Release Manager authored and vbraun committed Sep 10, 2018
2 parents 7bedb82 + 8d3b505 commit cf6f90d
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 305 deletions.
225 changes: 97 additions & 128 deletions src/sage/monoids/free_monoid.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,48 +34,40 @@

from sage.combinat.words.finite_word import FiniteWord_class

from sage.structure.factory import UniqueFactory
from sage.structure.unique_representation import UniqueRepresentation
from sage.misc.decorators import rename_keyword
from sage.rings.all import ZZ


class FreeMonoidFactory(UniqueFactory):
def is_FreeMonoid(x):
"""
Create the free monoid in `n` generators.
INPUT:
- ``n`` - integer
- ``names`` - names of generators
OUTPUT: free monoid
Return True if `x` is a free monoid.
EXAMPLES::
sage: FreeMonoid(0,'')
Free monoid on 0 generators ()
sage: F.<a,b,c,d,e> = FreeMonoid(5); F
Free monoid on 5 generators (a, b, c, d, e)
sage: F(1)
1
sage: mul([ a, b, a, c, b, d, c, d ], F(1))
a*b*a*c*b*d*c*d
sage: from sage.monoids.free_monoid import is_FreeMonoid
sage: is_FreeMonoid(5)
False
sage: is_FreeMonoid(FreeMonoid(7,'a'))
True
sage: is_FreeMonoid(FreeAbelianMonoid(7,'a'))
False
sage: is_FreeMonoid(FreeAbelianMonoid(0,''))
False
sage: is_FreeMonoid(FreeMonoid(index_set=ZZ))
True
sage: is_FreeMonoid(FreeAbelianMonoid(index_set=ZZ))
False
"""
def create_key(self, n, names):
n = int(n)
names = normalize_names(n, names)
return (n, names)
def create_object(self, version, key, **kwds):
return FreeMonoid_class(*key)

FreeMonoid_factory = FreeMonoidFactory("sage.monoids.free_monoid.FreeMonoid_factory")
if isinstance(x, FreeMonoid):
return True
from sage.monoids.indexed_free_monoid import IndexedFreeMonoid
return isinstance(x, IndexedFreeMonoid)

@rename_keyword(deprecation=15289, n="index_set")
def FreeMonoid(index_set=None, names=None, commutative=False, **kwds):
r"""
Return a free monoid on `n` generators or with the generators indexed by
a set `I`.
class FreeMonoid(Monoid_class, UniqueRepresentation):
"""
Return a free monoid on `n` generators or with the generators
indexed by a set `I`.
We construct free monoids by specifing either:
Expand All @@ -84,20 +76,29 @@ def FreeMonoid(index_set=None, names=None, commutative=False, **kwds):
INPUT:
- ``index_set`` -- an indexing set for the generators; if an integer,
than this becomes `\{0, 1, \ldots, n-1\}`
- ``index_set`` -- an indexing set for the generators; if an
integer `n`, than this becomes `\{0, 1, \ldots, n-1\}`
- ``names`` -- names of generators
- ``commutative`` -- (default: ``False``) whether the free monoid is
commutative or not
- ``commutative`` -- (default: ``False``) whether the free
monoid is commutative or not
OUTPUT:
A free monoid.
EXAMPLES::
sage: F = FreeMonoid(3,'x'); F
Free monoid on 3 generators (x0, x1, x2)
sage: x = F.gens()
sage: x[0]*x[1]**5 * (x[0]*x[2])
x0*x1^5*x0*x2
sage: F = FreeMonoid(3, 'a')
sage: F
Free monoid on 3 generators (a0, a1, a2)
sage: F.<a,b,c,d,e> = FreeMonoid(); F
Free monoid on 5 generators (a, b, c, d, e)
sage: FreeMonoid(index_set=ZZ)
Expand All @@ -107,95 +108,86 @@ def FreeMonoid(index_set=None, names=None, commutative=False, **kwds):
Free abelian monoid on 3 generators (x, y, z)
sage: FreeMonoid(index_set=ZZ, commutative=True)
Free abelian monoid indexed by Integer Ring
TESTS::
sage: FreeMonoid(index_set=ZZ, names='x,y,z')
Free monoid indexed by Integer Ring
"""
if 'abelian' in kwds:
commutative = kwds.pop('abelian')

if commutative:
from sage.monoids.free_abelian_monoid import FreeAbelianMonoid
return FreeAbelianMonoid(index_set, names, **kwds)
@staticmethod
def __classcall_private__(cls, index_set=None, names=None,
commutative=False, **kwds):
r"""
Construct a free monoid or a free abelian monoid, depending on the
input. Also, normalize the input.
if isinstance(index_set, str): # Swap args (this works if names is None as well)
names, index_set = index_set, names
EXAMPLES::
if index_set is None and names is not None:
if isinstance(names, str):
index_set = names.count(',')
else:
index_set = len(names)
sage: F.<a,b,c,d,e> = FreeMonoid(); F
Free monoid on 5 generators (a, b, c, d, e)
sage: FreeMonoid(index_set=ZZ)
Free monoid indexed by Integer Ring
sage: F.<x,y,z> = FreeMonoid(abelian=True); F
Free abelian monoid on 3 generators (x, y, z)
sage: FreeMonoid(index_set=ZZ, commutative=True)
Free abelian monoid indexed by Integer Ring
sage: F = FreeMonoid(index_set=ZZ, names='x,y,z')
sage: G = FreeMonoid(index_set=ZZ, names=['x', 'y', 'z'])
sage: F == G
True
sage: F is G
True
sage: FreeMonoid(2, names='a,b') is FreeMonoid(names=['a','b'])
True
Fix a bug when ``index_set`` is ``None`` and ``names`` is a
string (:trac:`26221`)::
sage: FreeMonoid(2, names=['a','b']) is FreeMonoid(names='a,b')
True
"""
if 'abelian' in kwds:
commutative = kwds.pop('abelian')

if index_set not in ZZ:
if names is not None:
names = normalize_names(-1, names)
from sage.monoids.indexed_free_monoid import IndexedFreeMonoid
return IndexedFreeMonoid(index_set, names=names, **kwds)
if commutative:
from sage.monoids.free_abelian_monoid import FreeAbelianMonoid
return FreeAbelianMonoid(index_set, names, **kwds)

if names is None:
raise ValueError("names must be specified")
return FreeMonoid_factory(index_set, names)
if isinstance(index_set, str): # Swap args (this works if names is None as well)
names, index_set = index_set, names

def is_FreeMonoid(x):
"""
Return True if `x` is a free monoid.
if index_set is None and names is not None:
if isinstance(names, str):
index_set = names.count(',') + 1
else:
index_set = len(names)

EXAMPLES::
if index_set not in ZZ:
if names is not None:
names = normalize_names(-1, names)
from sage.monoids.indexed_free_monoid import IndexedFreeMonoid
return IndexedFreeMonoid(index_set, names=names, **kwds)

sage: from sage.monoids.free_monoid import is_FreeMonoid
sage: is_FreeMonoid(5)
False
sage: is_FreeMonoid(FreeMonoid(7,'a'))
True
sage: is_FreeMonoid(FreeAbelianMonoid(7,'a'))
False
sage: is_FreeMonoid(FreeAbelianMonoid(0,''))
False
sage: is_FreeMonoid(FreeMonoid(index_set=ZZ))
True
sage: is_FreeMonoid(FreeAbelianMonoid(index_set=ZZ))
False
"""
if isinstance(x, FreeMonoid_class):
return True
from sage.monoids.indexed_free_monoid import IndexedFreeMonoid
return isinstance(x, IndexedFreeMonoid)
if names is None:
raise ValueError("names must be specified")
names = normalize_names(index_set, names)
return super(FreeMonoid, cls).__classcall__(cls, index_set, names)

class FreeMonoid_class(Monoid_class):
"""
The free monoid on `n` generators.
"""
Element = FreeMonoidElement

def __init__(self, n, names=None):
"""
Create free monoid on `n` generators.
Return a free monoid on `n` generators or with the generators
indexed by a set `I`.
INPUT:
- ``n`` - integer
- ``n`` -- number of generators
- ``names`` - (optional) variable name or list of
variable names
- ``names`` -- names of generators
EXAMPLES::
sage: F = FreeMonoid(3,'x'); F
Free monoid on 3 generators (x0, x1, x2)
sage: x = F.gens()
sage: x[0]*x[1]**5 * (x[0]*x[2])
x0*x1^5*x0*x2
sage: F = FreeMonoid(3, 'a')
sage: F
Free monoid on 3 generators (a0, a1, a2)
::
TESTS::
sage: M = FreeMonoid(3, names=['a','b','c'])
sage: TestSuite(M).run()
sage: F.<x,y> = FreeMonoid()
sage: TestSuite(F).run()
"""
if not isinstance(n, integer_types + (Integer,)):
raise TypeError("n (=%s) must be an integer."%n)
Expand All @@ -205,29 +197,6 @@ def __init__(self, n, names=None):
#self._assign_names(names)
Monoid_class.__init__(self,names)

def __eq__(self, other):
"""
Test for equality.
"""
if self is other:
return True
if not isinstance(other, FreeMonoid_class):
return False
if self.__ngens != other.__ngens:
return False
try:
if self.variable_names() != other.variable_names():
return False
except ValueError:
pass
return True

def __ne__(self, other):
"""
Test for unequality.
"""
return not (self == other)

def _repr_(self):
return "Free monoid on %s generators %s"%(self.__ngens,self.gens())

Expand Down
2 changes: 1 addition & 1 deletion src/sage/monoids/free_monoid_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class FreeMonoidElement(MonoidElement):
sage: x**(-1)
Traceback (most recent call last):
...
TypeError: bad operand type for unary ~: 'FreeMonoid_class_with_category.element_class'
TypeError: bad operand type for unary ~: 'FreeMonoid_with_category.element_class'
"""
def __init__(self, F, x, check=True):
"""
Expand Down

0 comments on commit cf6f90d

Please sign in to comment.