1355 lines
39 KiB
Python
1355 lines
39 KiB
Python
|
from __future__ import annotations
|
||
|
|
||
|
from sympy.core import S
|
||
|
from sympy.core.expr import Expr
|
||
|
from sympy.core.symbol import Symbol, symbols as _symbols
|
||
|
from sympy.core.sympify import CantSympify
|
||
|
from sympy.printing.defaults import DefaultPrinting
|
||
|
from sympy.utilities import public
|
||
|
from sympy.utilities.iterables import flatten, is_sequence
|
||
|
from sympy.utilities.magic import pollute
|
||
|
from sympy.utilities.misc import as_int
|
||
|
|
||
|
|
||
|
@public
|
||
|
def free_group(symbols):
|
||
|
"""Construct a free group returning ``(FreeGroup, (f_0, f_1, ..., f_(n-1))``.
|
||
|
|
||
|
Parameters
|
||
|
==========
|
||
|
|
||
|
symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y, z = free_group("x, y, z")
|
||
|
>>> F
|
||
|
<free group on the generators (x, y, z)>
|
||
|
>>> x**2*y**-1
|
||
|
x**2*y**-1
|
||
|
>>> type(_)
|
||
|
<class 'sympy.combinatorics.free_groups.FreeGroupElement'>
|
||
|
|
||
|
"""
|
||
|
_free_group = FreeGroup(symbols)
|
||
|
return (_free_group,) + tuple(_free_group.generators)
|
||
|
|
||
|
@public
|
||
|
def xfree_group(symbols):
|
||
|
"""Construct a free group returning ``(FreeGroup, (f_0, f_1, ..., f_(n-1)))``.
|
||
|
|
||
|
Parameters
|
||
|
==========
|
||
|
|
||
|
symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics.free_groups import xfree_group
|
||
|
>>> F, (x, y, z) = xfree_group("x, y, z")
|
||
|
>>> F
|
||
|
<free group on the generators (x, y, z)>
|
||
|
>>> y**2*x**-2*z**-1
|
||
|
y**2*x**-2*z**-1
|
||
|
>>> type(_)
|
||
|
<class 'sympy.combinatorics.free_groups.FreeGroupElement'>
|
||
|
|
||
|
"""
|
||
|
_free_group = FreeGroup(symbols)
|
||
|
return (_free_group, _free_group.generators)
|
||
|
|
||
|
@public
|
||
|
def vfree_group(symbols):
|
||
|
"""Construct a free group and inject ``f_0, f_1, ..., f_(n-1)`` as symbols
|
||
|
into the global namespace.
|
||
|
|
||
|
Parameters
|
||
|
==========
|
||
|
|
||
|
symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics.free_groups import vfree_group
|
||
|
>>> vfree_group("x, y, z")
|
||
|
<free group on the generators (x, y, z)>
|
||
|
>>> x**2*y**-2*z # noqa: F821
|
||
|
x**2*y**-2*z
|
||
|
>>> type(_)
|
||
|
<class 'sympy.combinatorics.free_groups.FreeGroupElement'>
|
||
|
|
||
|
"""
|
||
|
_free_group = FreeGroup(symbols)
|
||
|
pollute([sym.name for sym in _free_group.symbols], _free_group.generators)
|
||
|
return _free_group
|
||
|
|
||
|
|
||
|
def _parse_symbols(symbols):
|
||
|
if not symbols:
|
||
|
return ()
|
||
|
if isinstance(symbols, str):
|
||
|
return _symbols(symbols, seq=True)
|
||
|
elif isinstance(symbols, (Expr, FreeGroupElement)):
|
||
|
return (symbols,)
|
||
|
elif is_sequence(symbols):
|
||
|
if all(isinstance(s, str) for s in symbols):
|
||
|
return _symbols(symbols)
|
||
|
elif all(isinstance(s, Expr) for s in symbols):
|
||
|
return symbols
|
||
|
raise ValueError("The type of `symbols` must be one of the following: "
|
||
|
"a str, Symbol/Expr or a sequence of "
|
||
|
"one of these types")
|
||
|
|
||
|
|
||
|
##############################################################################
|
||
|
# FREE GROUP #
|
||
|
##############################################################################
|
||
|
|
||
|
_free_group_cache: dict[int, FreeGroup] = {}
|
||
|
|
||
|
class FreeGroup(DefaultPrinting):
|
||
|
"""
|
||
|
Free group with finite or infinite number of generators. Its input API
|
||
|
is that of a str, Symbol/Expr or a sequence of one of
|
||
|
these types (which may be empty)
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
sympy.polys.rings.PolyRing
|
||
|
|
||
|
References
|
||
|
==========
|
||
|
|
||
|
.. [1] https://www.gap-system.org/Manuals/doc/ref/chap37.html
|
||
|
|
||
|
.. [2] https://en.wikipedia.org/wiki/Free_group
|
||
|
|
||
|
"""
|
||
|
is_associative = True
|
||
|
is_group = True
|
||
|
is_FreeGroup = True
|
||
|
is_PermutationGroup = False
|
||
|
relators: list[Expr] = []
|
||
|
|
||
|
def __new__(cls, symbols):
|
||
|
symbols = tuple(_parse_symbols(symbols))
|
||
|
rank = len(symbols)
|
||
|
_hash = hash((cls.__name__, symbols, rank))
|
||
|
obj = _free_group_cache.get(_hash)
|
||
|
|
||
|
if obj is None:
|
||
|
obj = object.__new__(cls)
|
||
|
obj._hash = _hash
|
||
|
obj._rank = rank
|
||
|
# dtype method is used to create new instances of FreeGroupElement
|
||
|
obj.dtype = type("FreeGroupElement", (FreeGroupElement,), {"group": obj})
|
||
|
obj.symbols = symbols
|
||
|
obj.generators = obj._generators()
|
||
|
obj._gens_set = set(obj.generators)
|
||
|
for symbol, generator in zip(obj.symbols, obj.generators):
|
||
|
if isinstance(symbol, Symbol):
|
||
|
name = symbol.name
|
||
|
if hasattr(obj, name):
|
||
|
setattr(obj, name, generator)
|
||
|
|
||
|
_free_group_cache[_hash] = obj
|
||
|
|
||
|
return obj
|
||
|
|
||
|
def _generators(group):
|
||
|
"""Returns the generators of the FreeGroup.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y, z = free_group("x, y, z")
|
||
|
>>> F.generators
|
||
|
(x, y, z)
|
||
|
|
||
|
"""
|
||
|
gens = []
|
||
|
for sym in group.symbols:
|
||
|
elm = ((sym, 1),)
|
||
|
gens.append(group.dtype(elm))
|
||
|
return tuple(gens)
|
||
|
|
||
|
def clone(self, symbols=None):
|
||
|
return self.__class__(symbols or self.symbols)
|
||
|
|
||
|
def __contains__(self, i):
|
||
|
"""Return True if ``i`` is contained in FreeGroup."""
|
||
|
if not isinstance(i, FreeGroupElement):
|
||
|
return False
|
||
|
group = i.group
|
||
|
return self == group
|
||
|
|
||
|
def __hash__(self):
|
||
|
return self._hash
|
||
|
|
||
|
def __len__(self):
|
||
|
return self.rank
|
||
|
|
||
|
def __str__(self):
|
||
|
if self.rank > 30:
|
||
|
str_form = "<free group with %s generators>" % self.rank
|
||
|
else:
|
||
|
str_form = "<free group on the generators "
|
||
|
gens = self.generators
|
||
|
str_form += str(gens) + ">"
|
||
|
return str_form
|
||
|
|
||
|
__repr__ = __str__
|
||
|
|
||
|
def __getitem__(self, index):
|
||
|
symbols = self.symbols[index]
|
||
|
return self.clone(symbols=symbols)
|
||
|
|
||
|
def __eq__(self, other):
|
||
|
"""No ``FreeGroup`` is equal to any "other" ``FreeGroup``.
|
||
|
"""
|
||
|
return self is other
|
||
|
|
||
|
def index(self, gen):
|
||
|
"""Return the index of the generator `gen` from ``(f_0, ..., f_(n-1))``.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y = free_group("x, y")
|
||
|
>>> F.index(y)
|
||
|
1
|
||
|
>>> F.index(x)
|
||
|
0
|
||
|
|
||
|
"""
|
||
|
if isinstance(gen, self.dtype):
|
||
|
return self.generators.index(gen)
|
||
|
else:
|
||
|
raise ValueError("expected a generator of Free Group %s, got %s" % (self, gen))
|
||
|
|
||
|
def order(self):
|
||
|
"""Return the order of the free group.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y = free_group("x, y")
|
||
|
>>> F.order()
|
||
|
oo
|
||
|
|
||
|
>>> free_group("")[0].order()
|
||
|
1
|
||
|
|
||
|
"""
|
||
|
if self.rank == 0:
|
||
|
return S.One
|
||
|
else:
|
||
|
return S.Infinity
|
||
|
|
||
|
@property
|
||
|
def elements(self):
|
||
|
"""
|
||
|
Return the elements of the free group.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> (z,) = free_group("")
|
||
|
>>> z.elements
|
||
|
{<identity>}
|
||
|
|
||
|
"""
|
||
|
if self.rank == 0:
|
||
|
# A set containing Identity element of `FreeGroup` self is returned
|
||
|
return {self.identity}
|
||
|
else:
|
||
|
raise ValueError("Group contains infinitely many elements"
|
||
|
", hence cannot be represented")
|
||
|
|
||
|
@property
|
||
|
def rank(self):
|
||
|
r"""
|
||
|
In group theory, the `rank` of a group `G`, denoted `G.rank`,
|
||
|
can refer to the smallest cardinality of a generating set
|
||
|
for G, that is
|
||
|
|
||
|
\operatorname{rank}(G)=\min\{ |X|: X\subseteq G, \left\langle X\right\rangle =G\}.
|
||
|
|
||
|
"""
|
||
|
return self._rank
|
||
|
|
||
|
@property
|
||
|
def is_abelian(self):
|
||
|
"""Returns if the group is Abelian.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, x, y, z = free_group("x y z")
|
||
|
>>> f.is_abelian
|
||
|
False
|
||
|
|
||
|
"""
|
||
|
return self.rank in (0, 1)
|
||
|
|
||
|
@property
|
||
|
def identity(self):
|
||
|
"""Returns the identity element of free group."""
|
||
|
return self.dtype()
|
||
|
|
||
|
def contains(self, g):
|
||
|
"""Tests if Free Group element ``g`` belong to self, ``G``.
|
||
|
|
||
|
In mathematical terms any linear combination of generators
|
||
|
of a Free Group is contained in it.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, x, y, z = free_group("x y z")
|
||
|
>>> f.contains(x**3*y**2)
|
||
|
True
|
||
|
|
||
|
"""
|
||
|
if not isinstance(g, FreeGroupElement):
|
||
|
return False
|
||
|
elif self != g.group:
|
||
|
return False
|
||
|
else:
|
||
|
return True
|
||
|
|
||
|
def center(self):
|
||
|
"""Returns the center of the free group `self`."""
|
||
|
return {self.identity}
|
||
|
|
||
|
|
||
|
############################################################################
|
||
|
# FreeGroupElement #
|
||
|
############################################################################
|
||
|
|
||
|
|
||
|
class FreeGroupElement(CantSympify, DefaultPrinting, tuple):
|
||
|
"""Used to create elements of FreeGroup. It cannot be used directly to
|
||
|
create a free group element. It is called by the `dtype` method of the
|
||
|
`FreeGroup` class.
|
||
|
|
||
|
"""
|
||
|
is_assoc_word = True
|
||
|
|
||
|
def new(self, init):
|
||
|
return self.__class__(init)
|
||
|
|
||
|
_hash = None
|
||
|
|
||
|
def __hash__(self):
|
||
|
_hash = self._hash
|
||
|
if _hash is None:
|
||
|
self._hash = _hash = hash((self.group, frozenset(tuple(self))))
|
||
|
return _hash
|
||
|
|
||
|
def copy(self):
|
||
|
return self.new(self)
|
||
|
|
||
|
@property
|
||
|
def is_identity(self):
|
||
|
if self.array_form == ():
|
||
|
return True
|
||
|
else:
|
||
|
return False
|
||
|
|
||
|
@property
|
||
|
def array_form(self):
|
||
|
"""
|
||
|
SymPy provides two different internal kinds of representation
|
||
|
of associative words. The first one is called the `array_form`
|
||
|
which is a tuple containing `tuples` as its elements, where the
|
||
|
size of each tuple is two. At the first position the tuple
|
||
|
contains the `symbol-generator`, while at the second position
|
||
|
of tuple contains the exponent of that generator at the position.
|
||
|
Since elements (i.e. words) do not commute, the indexing of tuple
|
||
|
makes that property to stay.
|
||
|
|
||
|
The structure in ``array_form`` of ``FreeGroupElement`` is of form:
|
||
|
|
||
|
``( ( symbol_of_gen, exponent ), ( , ), ... ( , ) )``
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, x, y, z = free_group("x y z")
|
||
|
>>> (x*z).array_form
|
||
|
((x, 1), (z, 1))
|
||
|
>>> (x**2*z*y*x**2).array_form
|
||
|
((x, 2), (z, 1), (y, 1), (x, 2))
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
letter_repr
|
||
|
|
||
|
"""
|
||
|
return tuple(self)
|
||
|
|
||
|
@property
|
||
|
def letter_form(self):
|
||
|
"""
|
||
|
The letter representation of a ``FreeGroupElement`` is a tuple
|
||
|
of generator symbols, with each entry corresponding to a group
|
||
|
generator. Inverses of the generators are represented by
|
||
|
negative generator symbols.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, a, b, c, d = free_group("a b c d")
|
||
|
>>> (a**3).letter_form
|
||
|
(a, a, a)
|
||
|
>>> (a**2*d**-2*a*b**-4).letter_form
|
||
|
(a, a, -d, -d, a, -b, -b, -b, -b)
|
||
|
>>> (a**-2*b**3*d).letter_form
|
||
|
(-a, -a, b, b, b, d)
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
array_form
|
||
|
|
||
|
"""
|
||
|
return tuple(flatten([(i,)*j if j > 0 else (-i,)*(-j)
|
||
|
for i, j in self.array_form]))
|
||
|
|
||
|
def __getitem__(self, i):
|
||
|
group = self.group
|
||
|
r = self.letter_form[i]
|
||
|
if r.is_Symbol:
|
||
|
return group.dtype(((r, 1),))
|
||
|
else:
|
||
|
return group.dtype(((-r, -1),))
|
||
|
|
||
|
def index(self, gen):
|
||
|
if len(gen) != 1:
|
||
|
raise ValueError()
|
||
|
return (self.letter_form).index(gen.letter_form[0])
|
||
|
|
||
|
@property
|
||
|
def letter_form_elm(self):
|
||
|
"""
|
||
|
"""
|
||
|
group = self.group
|
||
|
r = self.letter_form
|
||
|
return [group.dtype(((elm,1),)) if elm.is_Symbol \
|
||
|
else group.dtype(((-elm,-1),)) for elm in r]
|
||
|
|
||
|
@property
|
||
|
def ext_rep(self):
|
||
|
"""This is called the External Representation of ``FreeGroupElement``
|
||
|
"""
|
||
|
return tuple(flatten(self.array_form))
|
||
|
|
||
|
def __contains__(self, gen):
|
||
|
return gen.array_form[0][0] in tuple([r[0] for r in self.array_form])
|
||
|
|
||
|
def __str__(self):
|
||
|
if self.is_identity:
|
||
|
return "<identity>"
|
||
|
|
||
|
str_form = ""
|
||
|
array_form = self.array_form
|
||
|
for i in range(len(array_form)):
|
||
|
if i == len(array_form) - 1:
|
||
|
if array_form[i][1] == 1:
|
||
|
str_form += str(array_form[i][0])
|
||
|
else:
|
||
|
str_form += str(array_form[i][0]) + \
|
||
|
"**" + str(array_form[i][1])
|
||
|
else:
|
||
|
if array_form[i][1] == 1:
|
||
|
str_form += str(array_form[i][0]) + "*"
|
||
|
else:
|
||
|
str_form += str(array_form[i][0]) + \
|
||
|
"**" + str(array_form[i][1]) + "*"
|
||
|
return str_form
|
||
|
|
||
|
__repr__ = __str__
|
||
|
|
||
|
def __pow__(self, n):
|
||
|
n = as_int(n)
|
||
|
group = self.group
|
||
|
if n == 0:
|
||
|
return group.identity
|
||
|
|
||
|
if n < 0:
|
||
|
n = -n
|
||
|
return (self.inverse())**n
|
||
|
|
||
|
result = self
|
||
|
for i in range(n - 1):
|
||
|
result = result*self
|
||
|
# this method can be improved instead of just returning the
|
||
|
# multiplication of elements
|
||
|
return result
|
||
|
|
||
|
def __mul__(self, other):
|
||
|
"""Returns the product of elements belonging to the same ``FreeGroup``.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, x, y, z = free_group("x y z")
|
||
|
>>> x*y**2*y**-4
|
||
|
x*y**-2
|
||
|
>>> z*y**-2
|
||
|
z*y**-2
|
||
|
>>> x**2*y*y**-1*x**-2
|
||
|
<identity>
|
||
|
|
||
|
"""
|
||
|
group = self.group
|
||
|
if not isinstance(other, group.dtype):
|
||
|
raise TypeError("only FreeGroup elements of same FreeGroup can "
|
||
|
"be multiplied")
|
||
|
if self.is_identity:
|
||
|
return other
|
||
|
if other.is_identity:
|
||
|
return self
|
||
|
r = list(self.array_form + other.array_form)
|
||
|
zero_mul_simp(r, len(self.array_form) - 1)
|
||
|
return group.dtype(tuple(r))
|
||
|
|
||
|
def __truediv__(self, other):
|
||
|
group = self.group
|
||
|
if not isinstance(other, group.dtype):
|
||
|
raise TypeError("only FreeGroup elements of same FreeGroup can "
|
||
|
"be multiplied")
|
||
|
return self*(other.inverse())
|
||
|
|
||
|
def __rtruediv__(self, other):
|
||
|
group = self.group
|
||
|
if not isinstance(other, group.dtype):
|
||
|
raise TypeError("only FreeGroup elements of same FreeGroup can "
|
||
|
"be multiplied")
|
||
|
return other*(self.inverse())
|
||
|
|
||
|
def __add__(self, other):
|
||
|
return NotImplemented
|
||
|
|
||
|
def inverse(self):
|
||
|
"""
|
||
|
Returns the inverse of a ``FreeGroupElement`` element
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, x, y, z = free_group("x y z")
|
||
|
>>> x.inverse()
|
||
|
x**-1
|
||
|
>>> (x*y).inverse()
|
||
|
y**-1*x**-1
|
||
|
|
||
|
"""
|
||
|
group = self.group
|
||
|
r = tuple([(i, -j) for i, j in self.array_form[::-1]])
|
||
|
return group.dtype(r)
|
||
|
|
||
|
def order(self):
|
||
|
"""Find the order of a ``FreeGroupElement``.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, x, y = free_group("x y")
|
||
|
>>> (x**2*y*y**-1*x**-2).order()
|
||
|
1
|
||
|
|
||
|
"""
|
||
|
if self.is_identity:
|
||
|
return S.One
|
||
|
else:
|
||
|
return S.Infinity
|
||
|
|
||
|
def commutator(self, other):
|
||
|
"""
|
||
|
Return the commutator of `self` and `x`: ``~x*~self*x*self``
|
||
|
|
||
|
"""
|
||
|
group = self.group
|
||
|
if not isinstance(other, group.dtype):
|
||
|
raise ValueError("commutator of only FreeGroupElement of the same "
|
||
|
"FreeGroup exists")
|
||
|
else:
|
||
|
return self.inverse()*other.inverse()*self*other
|
||
|
|
||
|
def eliminate_words(self, words, _all=False, inverse=True):
|
||
|
'''
|
||
|
Replace each subword from the dictionary `words` by words[subword].
|
||
|
If words is a list, replace the words by the identity.
|
||
|
|
||
|
'''
|
||
|
again = True
|
||
|
new = self
|
||
|
if isinstance(words, dict):
|
||
|
while again:
|
||
|
again = False
|
||
|
for sub in words:
|
||
|
prev = new
|
||
|
new = new.eliminate_word(sub, words[sub], _all=_all, inverse=inverse)
|
||
|
if new != prev:
|
||
|
again = True
|
||
|
else:
|
||
|
while again:
|
||
|
again = False
|
||
|
for sub in words:
|
||
|
prev = new
|
||
|
new = new.eliminate_word(sub, _all=_all, inverse=inverse)
|
||
|
if new != prev:
|
||
|
again = True
|
||
|
return new
|
||
|
|
||
|
def eliminate_word(self, gen, by=None, _all=False, inverse=True):
|
||
|
"""
|
||
|
For an associative word `self`, a subword `gen`, and an associative
|
||
|
word `by` (identity by default), return the associative word obtained by
|
||
|
replacing each occurrence of `gen` in `self` by `by`. If `_all = True`,
|
||
|
the occurrences of `gen` that may appear after the first substitution will
|
||
|
also be replaced and so on until no occurrences are found. This might not
|
||
|
always terminate (e.g. `(x).eliminate_word(x, x**2, _all=True)`).
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, x, y = free_group("x y")
|
||
|
>>> w = x**5*y*x**2*y**-4*x
|
||
|
>>> w.eliminate_word( x, x**2 )
|
||
|
x**10*y*x**4*y**-4*x**2
|
||
|
>>> w.eliminate_word( x, y**-1 )
|
||
|
y**-11
|
||
|
>>> w.eliminate_word(x**5)
|
||
|
y*x**2*y**-4*x
|
||
|
>>> w.eliminate_word(x*y, y)
|
||
|
x**4*y*x**2*y**-4*x
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
substituted_word
|
||
|
|
||
|
"""
|
||
|
if by is None:
|
||
|
by = self.group.identity
|
||
|
if self.is_independent(gen) or gen == by:
|
||
|
return self
|
||
|
if gen == self:
|
||
|
return by
|
||
|
if gen**-1 == by:
|
||
|
_all = False
|
||
|
word = self
|
||
|
l = len(gen)
|
||
|
|
||
|
try:
|
||
|
i = word.subword_index(gen)
|
||
|
k = 1
|
||
|
except ValueError:
|
||
|
if not inverse:
|
||
|
return word
|
||
|
try:
|
||
|
i = word.subword_index(gen**-1)
|
||
|
k = -1
|
||
|
except ValueError:
|
||
|
return word
|
||
|
|
||
|
word = word.subword(0, i)*by**k*word.subword(i+l, len(word)).eliminate_word(gen, by)
|
||
|
|
||
|
if _all:
|
||
|
return word.eliminate_word(gen, by, _all=True, inverse=inverse)
|
||
|
else:
|
||
|
return word
|
||
|
|
||
|
def __len__(self):
|
||
|
"""
|
||
|
For an associative word `self`, returns the number of letters in it.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, a, b = free_group("a b")
|
||
|
>>> w = a**5*b*a**2*b**-4*a
|
||
|
>>> len(w)
|
||
|
13
|
||
|
>>> len(a**17)
|
||
|
17
|
||
|
>>> len(w**0)
|
||
|
0
|
||
|
|
||
|
"""
|
||
|
return sum(abs(j) for (i, j) in self)
|
||
|
|
||
|
def __eq__(self, other):
|
||
|
"""
|
||
|
Two associative words are equal if they are words over the
|
||
|
same alphabet and if they are sequences of the same letters.
|
||
|
This is equivalent to saying that the external representations
|
||
|
of the words are equal.
|
||
|
There is no "universal" empty word, every alphabet has its own
|
||
|
empty word.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, swapnil0, swapnil1 = free_group("swapnil0 swapnil1")
|
||
|
>>> f
|
||
|
<free group on the generators (swapnil0, swapnil1)>
|
||
|
>>> g, swap0, swap1 = free_group("swap0 swap1")
|
||
|
>>> g
|
||
|
<free group on the generators (swap0, swap1)>
|
||
|
|
||
|
>>> swapnil0 == swapnil1
|
||
|
False
|
||
|
>>> swapnil0*swapnil1 == swapnil1/swapnil1*swapnil0*swapnil1
|
||
|
True
|
||
|
>>> swapnil0*swapnil1 == swapnil1*swapnil0
|
||
|
False
|
||
|
>>> swapnil1**0 == swap0**0
|
||
|
False
|
||
|
|
||
|
"""
|
||
|
group = self.group
|
||
|
if not isinstance(other, group.dtype):
|
||
|
return False
|
||
|
return tuple.__eq__(self, other)
|
||
|
|
||
|
def __lt__(self, other):
|
||
|
"""
|
||
|
The ordering of associative words is defined by length and
|
||
|
lexicography (this ordering is called short-lex ordering), that
|
||
|
is, shorter words are smaller than longer words, and words of the
|
||
|
same length are compared w.r.t. the lexicographical ordering induced
|
||
|
by the ordering of generators. Generators are sorted according
|
||
|
to the order in which they were created. If the generators are
|
||
|
invertible then each generator `g` is larger than its inverse `g^{-1}`,
|
||
|
and `g^{-1}` is larger than every generator that is smaller than `g`.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, a, b = free_group("a b")
|
||
|
>>> b < a
|
||
|
False
|
||
|
>>> a < a.inverse()
|
||
|
False
|
||
|
|
||
|
"""
|
||
|
group = self.group
|
||
|
if not isinstance(other, group.dtype):
|
||
|
raise TypeError("only FreeGroup elements of same FreeGroup can "
|
||
|
"be compared")
|
||
|
l = len(self)
|
||
|
m = len(other)
|
||
|
# implement lenlex order
|
||
|
if l < m:
|
||
|
return True
|
||
|
elif l > m:
|
||
|
return False
|
||
|
for i in range(l):
|
||
|
a = self[i].array_form[0]
|
||
|
b = other[i].array_form[0]
|
||
|
p = group.symbols.index(a[0])
|
||
|
q = group.symbols.index(b[0])
|
||
|
if p < q:
|
||
|
return True
|
||
|
elif p > q:
|
||
|
return False
|
||
|
elif a[1] < b[1]:
|
||
|
return True
|
||
|
elif a[1] > b[1]:
|
||
|
return False
|
||
|
return False
|
||
|
|
||
|
def __le__(self, other):
|
||
|
return (self == other or self < other)
|
||
|
|
||
|
def __gt__(self, other):
|
||
|
"""
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, x, y, z = free_group("x y z")
|
||
|
>>> y**2 > x**2
|
||
|
True
|
||
|
>>> y*z > z*y
|
||
|
False
|
||
|
>>> x > x.inverse()
|
||
|
True
|
||
|
|
||
|
"""
|
||
|
group = self.group
|
||
|
if not isinstance(other, group.dtype):
|
||
|
raise TypeError("only FreeGroup elements of same FreeGroup can "
|
||
|
"be compared")
|
||
|
return not self <= other
|
||
|
|
||
|
def __ge__(self, other):
|
||
|
return not self < other
|
||
|
|
||
|
def exponent_sum(self, gen):
|
||
|
"""
|
||
|
For an associative word `self` and a generator or inverse of generator
|
||
|
`gen`, ``exponent_sum`` returns the number of times `gen` appears in
|
||
|
`self` minus the number of times its inverse appears in `self`. If
|
||
|
neither `gen` nor its inverse occur in `self` then 0 is returned.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y = free_group("x, y")
|
||
|
>>> w = x**2*y**3
|
||
|
>>> w.exponent_sum(x)
|
||
|
2
|
||
|
>>> w.exponent_sum(x**-1)
|
||
|
-2
|
||
|
>>> w = x**2*y**4*x**-3
|
||
|
>>> w.exponent_sum(x)
|
||
|
-1
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
generator_count
|
||
|
|
||
|
"""
|
||
|
if len(gen) != 1:
|
||
|
raise ValueError("gen must be a generator or inverse of a generator")
|
||
|
s = gen.array_form[0]
|
||
|
return s[1]*sum([i[1] for i in self.array_form if i[0] == s[0]])
|
||
|
|
||
|
def generator_count(self, gen):
|
||
|
"""
|
||
|
For an associative word `self` and a generator `gen`,
|
||
|
``generator_count`` returns the multiplicity of generator
|
||
|
`gen` in `self`.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y = free_group("x, y")
|
||
|
>>> w = x**2*y**3
|
||
|
>>> w.generator_count(x)
|
||
|
2
|
||
|
>>> w = x**2*y**4*x**-3
|
||
|
>>> w.generator_count(x)
|
||
|
5
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
exponent_sum
|
||
|
|
||
|
"""
|
||
|
if len(gen) != 1 or gen.array_form[0][1] < 0:
|
||
|
raise ValueError("gen must be a generator")
|
||
|
s = gen.array_form[0]
|
||
|
return s[1]*sum([abs(i[1]) for i in self.array_form if i[0] == s[0]])
|
||
|
|
||
|
def subword(self, from_i, to_j, strict=True):
|
||
|
"""
|
||
|
For an associative word `self` and two positive integers `from_i` and
|
||
|
`to_j`, `subword` returns the subword of `self` that begins at position
|
||
|
`from_i` and ends at `to_j - 1`, indexing is done with origin 0.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, a, b = free_group("a b")
|
||
|
>>> w = a**5*b*a**2*b**-4*a
|
||
|
>>> w.subword(2, 6)
|
||
|
a**3*b
|
||
|
|
||
|
"""
|
||
|
group = self.group
|
||
|
if not strict:
|
||
|
from_i = max(from_i, 0)
|
||
|
to_j = min(len(self), to_j)
|
||
|
if from_i < 0 or to_j > len(self):
|
||
|
raise ValueError("`from_i`, `to_j` must be positive and no greater than "
|
||
|
"the length of associative word")
|
||
|
if to_j <= from_i:
|
||
|
return group.identity
|
||
|
else:
|
||
|
letter_form = self.letter_form[from_i: to_j]
|
||
|
array_form = letter_form_to_array_form(letter_form, group)
|
||
|
return group.dtype(array_form)
|
||
|
|
||
|
def subword_index(self, word, start = 0):
|
||
|
'''
|
||
|
Find the index of `word` in `self`.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, a, b = free_group("a b")
|
||
|
>>> w = a**2*b*a*b**3
|
||
|
>>> w.subword_index(a*b*a*b)
|
||
|
1
|
||
|
|
||
|
'''
|
||
|
l = len(word)
|
||
|
self_lf = self.letter_form
|
||
|
word_lf = word.letter_form
|
||
|
index = None
|
||
|
for i in range(start,len(self_lf)-l+1):
|
||
|
if self_lf[i:i+l] == word_lf:
|
||
|
index = i
|
||
|
break
|
||
|
if index is not None:
|
||
|
return index
|
||
|
else:
|
||
|
raise ValueError("The given word is not a subword of self")
|
||
|
|
||
|
def is_dependent(self, word):
|
||
|
"""
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y = free_group("x, y")
|
||
|
>>> (x**4*y**-3).is_dependent(x**4*y**-2)
|
||
|
True
|
||
|
>>> (x**2*y**-1).is_dependent(x*y)
|
||
|
False
|
||
|
>>> (x*y**2*x*y**2).is_dependent(x*y**2)
|
||
|
True
|
||
|
>>> (x**12).is_dependent(x**-4)
|
||
|
True
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
is_independent
|
||
|
|
||
|
"""
|
||
|
try:
|
||
|
return self.subword_index(word) is not None
|
||
|
except ValueError:
|
||
|
pass
|
||
|
try:
|
||
|
return self.subword_index(word**-1) is not None
|
||
|
except ValueError:
|
||
|
return False
|
||
|
|
||
|
def is_independent(self, word):
|
||
|
"""
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
is_dependent
|
||
|
|
||
|
"""
|
||
|
return not self.is_dependent(word)
|
||
|
|
||
|
def contains_generators(self):
|
||
|
"""
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y, z = free_group("x, y, z")
|
||
|
>>> (x**2*y**-1).contains_generators()
|
||
|
{x, y}
|
||
|
>>> (x**3*z).contains_generators()
|
||
|
{x, z}
|
||
|
|
||
|
"""
|
||
|
group = self.group
|
||
|
gens = set()
|
||
|
for syllable in self.array_form:
|
||
|
gens.add(group.dtype(((syllable[0], 1),)))
|
||
|
return set(gens)
|
||
|
|
||
|
def cyclic_subword(self, from_i, to_j):
|
||
|
group = self.group
|
||
|
l = len(self)
|
||
|
letter_form = self.letter_form
|
||
|
period1 = int(from_i/l)
|
||
|
if from_i >= l:
|
||
|
from_i -= l*period1
|
||
|
to_j -= l*period1
|
||
|
diff = to_j - from_i
|
||
|
word = letter_form[from_i: to_j]
|
||
|
period2 = int(to_j/l) - 1
|
||
|
word += letter_form*period2 + letter_form[:diff-l+from_i-l*period2]
|
||
|
word = letter_form_to_array_form(word, group)
|
||
|
return group.dtype(word)
|
||
|
|
||
|
def cyclic_conjugates(self):
|
||
|
"""Returns a words which are cyclic to the word `self`.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y = free_group("x, y")
|
||
|
>>> w = x*y*x*y*x
|
||
|
>>> w.cyclic_conjugates()
|
||
|
{x*y*x**2*y, x**2*y*x*y, y*x*y*x**2, y*x**2*y*x, x*y*x*y*x}
|
||
|
>>> s = x*y*x**2*y*x
|
||
|
>>> s.cyclic_conjugates()
|
||
|
{x**2*y*x**2*y, y*x**2*y*x**2, x*y*x**2*y*x}
|
||
|
|
||
|
References
|
||
|
==========
|
||
|
|
||
|
.. [1] https://planetmath.org/cyclicpermutation
|
||
|
|
||
|
"""
|
||
|
return {self.cyclic_subword(i, i+len(self)) for i in range(len(self))}
|
||
|
|
||
|
def is_cyclic_conjugate(self, w):
|
||
|
"""
|
||
|
Checks whether words ``self``, ``w`` are cyclic conjugates.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y = free_group("x, y")
|
||
|
>>> w1 = x**2*y**5
|
||
|
>>> w2 = x*y**5*x
|
||
|
>>> w1.is_cyclic_conjugate(w2)
|
||
|
True
|
||
|
>>> w3 = x**-1*y**5*x**-1
|
||
|
>>> w3.is_cyclic_conjugate(w2)
|
||
|
False
|
||
|
|
||
|
"""
|
||
|
l1 = len(self)
|
||
|
l2 = len(w)
|
||
|
if l1 != l2:
|
||
|
return False
|
||
|
w1 = self.identity_cyclic_reduction()
|
||
|
w2 = w.identity_cyclic_reduction()
|
||
|
letter1 = w1.letter_form
|
||
|
letter2 = w2.letter_form
|
||
|
str1 = ' '.join(map(str, letter1))
|
||
|
str2 = ' '.join(map(str, letter2))
|
||
|
if len(str1) != len(str2):
|
||
|
return False
|
||
|
|
||
|
return str1 in str2 + ' ' + str2
|
||
|
|
||
|
def number_syllables(self):
|
||
|
"""Returns the number of syllables of the associative word `self`.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, swapnil0, swapnil1 = free_group("swapnil0 swapnil1")
|
||
|
>>> (swapnil1**3*swapnil0*swapnil1**-1).number_syllables()
|
||
|
3
|
||
|
|
||
|
"""
|
||
|
return len(self.array_form)
|
||
|
|
||
|
def exponent_syllable(self, i):
|
||
|
"""
|
||
|
Returns the exponent of the `i`-th syllable of the associative word
|
||
|
`self`.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, a, b = free_group("a b")
|
||
|
>>> w = a**5*b*a**2*b**-4*a
|
||
|
>>> w.exponent_syllable( 2 )
|
||
|
2
|
||
|
|
||
|
"""
|
||
|
return self.array_form[i][1]
|
||
|
|
||
|
def generator_syllable(self, i):
|
||
|
"""
|
||
|
Returns the symbol of the generator that is involved in the
|
||
|
i-th syllable of the associative word `self`.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, a, b = free_group("a b")
|
||
|
>>> w = a**5*b*a**2*b**-4*a
|
||
|
>>> w.generator_syllable( 3 )
|
||
|
b
|
||
|
|
||
|
"""
|
||
|
return self.array_form[i][0]
|
||
|
|
||
|
def sub_syllables(self, from_i, to_j):
|
||
|
"""
|
||
|
`sub_syllables` returns the subword of the associative word `self` that
|
||
|
consists of syllables from positions `from_to` to `to_j`, where
|
||
|
`from_to` and `to_j` must be positive integers and indexing is done
|
||
|
with origin 0.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> f, a, b = free_group("a, b")
|
||
|
>>> w = a**5*b*a**2*b**-4*a
|
||
|
>>> w.sub_syllables(1, 2)
|
||
|
b
|
||
|
>>> w.sub_syllables(3, 3)
|
||
|
<identity>
|
||
|
|
||
|
"""
|
||
|
if not isinstance(from_i, int) or not isinstance(to_j, int):
|
||
|
raise ValueError("both arguments should be integers")
|
||
|
group = self.group
|
||
|
if to_j <= from_i:
|
||
|
return group.identity
|
||
|
else:
|
||
|
r = tuple(self.array_form[from_i: to_j])
|
||
|
return group.dtype(r)
|
||
|
|
||
|
def substituted_word(self, from_i, to_j, by):
|
||
|
"""
|
||
|
Returns the associative word obtained by replacing the subword of
|
||
|
`self` that begins at position `from_i` and ends at position `to_j - 1`
|
||
|
by the associative word `by`. `from_i` and `to_j` must be positive
|
||
|
integers, indexing is done with origin 0. In other words,
|
||
|
`w.substituted_word(w, from_i, to_j, by)` is the product of the three
|
||
|
words: `w.subword(0, from_i)`, `by`, and
|
||
|
`w.subword(to_j len(w))`.
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
eliminate_word
|
||
|
|
||
|
"""
|
||
|
lw = len(self)
|
||
|
if from_i >= to_j or from_i > lw or to_j > lw:
|
||
|
raise ValueError("values should be within bounds")
|
||
|
|
||
|
# otherwise there are four possibilities
|
||
|
|
||
|
# first if from=1 and to=lw then
|
||
|
if from_i == 0 and to_j == lw:
|
||
|
return by
|
||
|
elif from_i == 0: # second if from_i=1 (and to_j < lw) then
|
||
|
return by*self.subword(to_j, lw)
|
||
|
elif to_j == lw: # third if to_j=1 (and from_i > 1) then
|
||
|
return self.subword(0, from_i)*by
|
||
|
else: # finally
|
||
|
return self.subword(0, from_i)*by*self.subword(to_j, lw)
|
||
|
|
||
|
def is_cyclically_reduced(self):
|
||
|
r"""Returns whether the word is cyclically reduced or not.
|
||
|
A word is cyclically reduced if by forming the cycle of the
|
||
|
word, the word is not reduced, i.e a word w = `a_1 ... a_n`
|
||
|
is called cyclically reduced if `a_1 \ne a_n^{-1}`.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y = free_group("x, y")
|
||
|
>>> (x**2*y**-1*x**-1).is_cyclically_reduced()
|
||
|
False
|
||
|
>>> (y*x**2*y**2).is_cyclically_reduced()
|
||
|
True
|
||
|
|
||
|
"""
|
||
|
if not self:
|
||
|
return True
|
||
|
return self[0] != self[-1]**-1
|
||
|
|
||
|
def identity_cyclic_reduction(self):
|
||
|
"""Return a unique cyclically reduced version of the word.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y = free_group("x, y")
|
||
|
>>> (x**2*y**2*x**-1).identity_cyclic_reduction()
|
||
|
x*y**2
|
||
|
>>> (x**-3*y**-1*x**5).identity_cyclic_reduction()
|
||
|
x**2*y**-1
|
||
|
|
||
|
References
|
||
|
==========
|
||
|
|
||
|
.. [1] https://planetmath.org/cyclicallyreduced
|
||
|
|
||
|
"""
|
||
|
word = self.copy()
|
||
|
group = self.group
|
||
|
while not word.is_cyclically_reduced():
|
||
|
exp1 = word.exponent_syllable(0)
|
||
|
exp2 = word.exponent_syllable(-1)
|
||
|
r = exp1 + exp2
|
||
|
if r == 0:
|
||
|
rep = word.array_form[1: word.number_syllables() - 1]
|
||
|
else:
|
||
|
rep = ((word.generator_syllable(0), exp1 + exp2),) + \
|
||
|
word.array_form[1: word.number_syllables() - 1]
|
||
|
word = group.dtype(rep)
|
||
|
return word
|
||
|
|
||
|
def cyclic_reduction(self, removed=False):
|
||
|
"""Return a cyclically reduced version of the word. Unlike
|
||
|
`identity_cyclic_reduction`, this will not cyclically permute
|
||
|
the reduced word - just remove the "unreduced" bits on either
|
||
|
side of it. Compare the examples with those of
|
||
|
`identity_cyclic_reduction`.
|
||
|
|
||
|
When `removed` is `True`, return a tuple `(word, r)` where
|
||
|
self `r` is such that before the reduction the word was either
|
||
|
`r*word*r**-1`.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y = free_group("x, y")
|
||
|
>>> (x**2*y**2*x**-1).cyclic_reduction()
|
||
|
x*y**2
|
||
|
>>> (x**-3*y**-1*x**5).cyclic_reduction()
|
||
|
y**-1*x**2
|
||
|
>>> (x**-3*y**-1*x**5).cyclic_reduction(removed=True)
|
||
|
(y**-1*x**2, x**-3)
|
||
|
|
||
|
"""
|
||
|
word = self.copy()
|
||
|
g = self.group.identity
|
||
|
while not word.is_cyclically_reduced():
|
||
|
exp1 = abs(word.exponent_syllable(0))
|
||
|
exp2 = abs(word.exponent_syllable(-1))
|
||
|
exp = min(exp1, exp2)
|
||
|
start = word[0]**abs(exp)
|
||
|
end = word[-1]**abs(exp)
|
||
|
word = start**-1*word*end**-1
|
||
|
g = g*start
|
||
|
if removed:
|
||
|
return word, g
|
||
|
return word
|
||
|
|
||
|
def power_of(self, other):
|
||
|
'''
|
||
|
Check if `self == other**n` for some integer n.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.combinatorics import free_group
|
||
|
>>> F, x, y = free_group("x, y")
|
||
|
>>> ((x*y)**2).power_of(x*y)
|
||
|
True
|
||
|
>>> (x**-3*y**-2*x**3).power_of(x**-3*y*x**3)
|
||
|
True
|
||
|
|
||
|
'''
|
||
|
if self.is_identity:
|
||
|
return True
|
||
|
|
||
|
l = len(other)
|
||
|
if l == 1:
|
||
|
# self has to be a power of one generator
|
||
|
gens = self.contains_generators()
|
||
|
s = other in gens or other**-1 in gens
|
||
|
return len(gens) == 1 and s
|
||
|
|
||
|
# if self is not cyclically reduced and it is a power of other,
|
||
|
# other isn't cyclically reduced and the parts removed during
|
||
|
# their reduction must be equal
|
||
|
reduced, r1 = self.cyclic_reduction(removed=True)
|
||
|
if not r1.is_identity:
|
||
|
other, r2 = other.cyclic_reduction(removed=True)
|
||
|
if r1 == r2:
|
||
|
return reduced.power_of(other)
|
||
|
return False
|
||
|
|
||
|
if len(self) < l or len(self) % l:
|
||
|
return False
|
||
|
|
||
|
prefix = self.subword(0, l)
|
||
|
if prefix == other or prefix**-1 == other:
|
||
|
rest = self.subword(l, len(self))
|
||
|
return rest.power_of(other)
|
||
|
return False
|
||
|
|
||
|
|
||
|
def letter_form_to_array_form(array_form, group):
|
||
|
"""
|
||
|
This method converts a list given with possible repetitions of elements in
|
||
|
it. It returns a new list such that repetitions of consecutive elements is
|
||
|
removed and replace with a tuple element of size two such that the first
|
||
|
index contains `value` and the second index contains the number of
|
||
|
consecutive repetitions of `value`.
|
||
|
|
||
|
"""
|
||
|
a = list(array_form[:])
|
||
|
new_array = []
|
||
|
n = 1
|
||
|
symbols = group.symbols
|
||
|
for i in range(len(a)):
|
||
|
if i == len(a) - 1:
|
||
|
if a[i] == a[i - 1]:
|
||
|
if (-a[i]) in symbols:
|
||
|
new_array.append((-a[i], -n))
|
||
|
else:
|
||
|
new_array.append((a[i], n))
|
||
|
else:
|
||
|
if (-a[i]) in symbols:
|
||
|
new_array.append((-a[i], -1))
|
||
|
else:
|
||
|
new_array.append((a[i], 1))
|
||
|
return new_array
|
||
|
elif a[i] == a[i + 1]:
|
||
|
n += 1
|
||
|
else:
|
||
|
if (-a[i]) in symbols:
|
||
|
new_array.append((-a[i], -n))
|
||
|
else:
|
||
|
new_array.append((a[i], n))
|
||
|
n = 1
|
||
|
|
||
|
|
||
|
def zero_mul_simp(l, index):
|
||
|
"""Used to combine two reduced words."""
|
||
|
while index >=0 and index < len(l) - 1 and l[index][0] == l[index + 1][0]:
|
||
|
exp = l[index][1] + l[index + 1][1]
|
||
|
base = l[index][0]
|
||
|
l[index] = (base, exp)
|
||
|
del l[index + 1]
|
||
|
if l[index][1] == 0:
|
||
|
del l[index]
|
||
|
index -= 1
|