1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354 |
- 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
|