free_groups.py 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354
  1. from __future__ import annotations
  2. from sympy.core import S
  3. from sympy.core.expr import Expr
  4. from sympy.core.symbol import Symbol, symbols as _symbols
  5. from sympy.core.sympify import CantSympify
  6. from sympy.printing.defaults import DefaultPrinting
  7. from sympy.utilities import public
  8. from sympy.utilities.iterables import flatten, is_sequence
  9. from sympy.utilities.magic import pollute
  10. from sympy.utilities.misc import as_int
  11. @public
  12. def free_group(symbols):
  13. """Construct a free group returning ``(FreeGroup, (f_0, f_1, ..., f_(n-1))``.
  14. Parameters
  15. ==========
  16. symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)
  17. Examples
  18. ========
  19. >>> from sympy.combinatorics import free_group
  20. >>> F, x, y, z = free_group("x, y, z")
  21. >>> F
  22. <free group on the generators (x, y, z)>
  23. >>> x**2*y**-1
  24. x**2*y**-1
  25. >>> type(_)
  26. <class 'sympy.combinatorics.free_groups.FreeGroupElement'>
  27. """
  28. _free_group = FreeGroup(symbols)
  29. return (_free_group,) + tuple(_free_group.generators)
  30. @public
  31. def xfree_group(symbols):
  32. """Construct a free group returning ``(FreeGroup, (f_0, f_1, ..., f_(n-1)))``.
  33. Parameters
  34. ==========
  35. symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)
  36. Examples
  37. ========
  38. >>> from sympy.combinatorics.free_groups import xfree_group
  39. >>> F, (x, y, z) = xfree_group("x, y, z")
  40. >>> F
  41. <free group on the generators (x, y, z)>
  42. >>> y**2*x**-2*z**-1
  43. y**2*x**-2*z**-1
  44. >>> type(_)
  45. <class 'sympy.combinatorics.free_groups.FreeGroupElement'>
  46. """
  47. _free_group = FreeGroup(symbols)
  48. return (_free_group, _free_group.generators)
  49. @public
  50. def vfree_group(symbols):
  51. """Construct a free group and inject ``f_0, f_1, ..., f_(n-1)`` as symbols
  52. into the global namespace.
  53. Parameters
  54. ==========
  55. symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty)
  56. Examples
  57. ========
  58. >>> from sympy.combinatorics.free_groups import vfree_group
  59. >>> vfree_group("x, y, z")
  60. <free group on the generators (x, y, z)>
  61. >>> x**2*y**-2*z # noqa: F821
  62. x**2*y**-2*z
  63. >>> type(_)
  64. <class 'sympy.combinatorics.free_groups.FreeGroupElement'>
  65. """
  66. _free_group = FreeGroup(symbols)
  67. pollute([sym.name for sym in _free_group.symbols], _free_group.generators)
  68. return _free_group
  69. def _parse_symbols(symbols):
  70. if not symbols:
  71. return ()
  72. if isinstance(symbols, str):
  73. return _symbols(symbols, seq=True)
  74. elif isinstance(symbols, (Expr, FreeGroupElement)):
  75. return (symbols,)
  76. elif is_sequence(symbols):
  77. if all(isinstance(s, str) for s in symbols):
  78. return _symbols(symbols)
  79. elif all(isinstance(s, Expr) for s in symbols):
  80. return symbols
  81. raise ValueError("The type of `symbols` must be one of the following: "
  82. "a str, Symbol/Expr or a sequence of "
  83. "one of these types")
  84. ##############################################################################
  85. # FREE GROUP #
  86. ##############################################################################
  87. _free_group_cache: dict[int, FreeGroup] = {}
  88. class FreeGroup(DefaultPrinting):
  89. """
  90. Free group with finite or infinite number of generators. Its input API
  91. is that of a str, Symbol/Expr or a sequence of one of
  92. these types (which may be empty)
  93. See Also
  94. ========
  95. sympy.polys.rings.PolyRing
  96. References
  97. ==========
  98. .. [1] https://www.gap-system.org/Manuals/doc/ref/chap37.html
  99. .. [2] https://en.wikipedia.org/wiki/Free_group
  100. """
  101. is_associative = True
  102. is_group = True
  103. is_FreeGroup = True
  104. is_PermutationGroup = False
  105. relators: list[Expr] = []
  106. def __new__(cls, symbols):
  107. symbols = tuple(_parse_symbols(symbols))
  108. rank = len(symbols)
  109. _hash = hash((cls.__name__, symbols, rank))
  110. obj = _free_group_cache.get(_hash)
  111. if obj is None:
  112. obj = object.__new__(cls)
  113. obj._hash = _hash
  114. obj._rank = rank
  115. # dtype method is used to create new instances of FreeGroupElement
  116. obj.dtype = type("FreeGroupElement", (FreeGroupElement,), {"group": obj})
  117. obj.symbols = symbols
  118. obj.generators = obj._generators()
  119. obj._gens_set = set(obj.generators)
  120. for symbol, generator in zip(obj.symbols, obj.generators):
  121. if isinstance(symbol, Symbol):
  122. name = symbol.name
  123. if hasattr(obj, name):
  124. setattr(obj, name, generator)
  125. _free_group_cache[_hash] = obj
  126. return obj
  127. def _generators(group):
  128. """Returns the generators of the FreeGroup.
  129. Examples
  130. ========
  131. >>> from sympy.combinatorics import free_group
  132. >>> F, x, y, z = free_group("x, y, z")
  133. >>> F.generators
  134. (x, y, z)
  135. """
  136. gens = []
  137. for sym in group.symbols:
  138. elm = ((sym, 1),)
  139. gens.append(group.dtype(elm))
  140. return tuple(gens)
  141. def clone(self, symbols=None):
  142. return self.__class__(symbols or self.symbols)
  143. def __contains__(self, i):
  144. """Return True if ``i`` is contained in FreeGroup."""
  145. if not isinstance(i, FreeGroupElement):
  146. return False
  147. group = i.group
  148. return self == group
  149. def __hash__(self):
  150. return self._hash
  151. def __len__(self):
  152. return self.rank
  153. def __str__(self):
  154. if self.rank > 30:
  155. str_form = "<free group with %s generators>" % self.rank
  156. else:
  157. str_form = "<free group on the generators "
  158. gens = self.generators
  159. str_form += str(gens) + ">"
  160. return str_form
  161. __repr__ = __str__
  162. def __getitem__(self, index):
  163. symbols = self.symbols[index]
  164. return self.clone(symbols=symbols)
  165. def __eq__(self, other):
  166. """No ``FreeGroup`` is equal to any "other" ``FreeGroup``.
  167. """
  168. return self is other
  169. def index(self, gen):
  170. """Return the index of the generator `gen` from ``(f_0, ..., f_(n-1))``.
  171. Examples
  172. ========
  173. >>> from sympy.combinatorics import free_group
  174. >>> F, x, y = free_group("x, y")
  175. >>> F.index(y)
  176. 1
  177. >>> F.index(x)
  178. 0
  179. """
  180. if isinstance(gen, self.dtype):
  181. return self.generators.index(gen)
  182. else:
  183. raise ValueError("expected a generator of Free Group %s, got %s" % (self, gen))
  184. def order(self):
  185. """Return the order of the free group.
  186. Examples
  187. ========
  188. >>> from sympy.combinatorics import free_group
  189. >>> F, x, y = free_group("x, y")
  190. >>> F.order()
  191. oo
  192. >>> free_group("")[0].order()
  193. 1
  194. """
  195. if self.rank == 0:
  196. return S.One
  197. else:
  198. return S.Infinity
  199. @property
  200. def elements(self):
  201. """
  202. Return the elements of the free group.
  203. Examples
  204. ========
  205. >>> from sympy.combinatorics import free_group
  206. >>> (z,) = free_group("")
  207. >>> z.elements
  208. {<identity>}
  209. """
  210. if self.rank == 0:
  211. # A set containing Identity element of `FreeGroup` self is returned
  212. return {self.identity}
  213. else:
  214. raise ValueError("Group contains infinitely many elements"
  215. ", hence cannot be represented")
  216. @property
  217. def rank(self):
  218. r"""
  219. In group theory, the `rank` of a group `G`, denoted `G.rank`,
  220. can refer to the smallest cardinality of a generating set
  221. for G, that is
  222. \operatorname{rank}(G)=\min\{ |X|: X\subseteq G, \left\langle X\right\rangle =G\}.
  223. """
  224. return self._rank
  225. @property
  226. def is_abelian(self):
  227. """Returns if the group is Abelian.
  228. Examples
  229. ========
  230. >>> from sympy.combinatorics import free_group
  231. >>> f, x, y, z = free_group("x y z")
  232. >>> f.is_abelian
  233. False
  234. """
  235. return self.rank in (0, 1)
  236. @property
  237. def identity(self):
  238. """Returns the identity element of free group."""
  239. return self.dtype()
  240. def contains(self, g):
  241. """Tests if Free Group element ``g`` belong to self, ``G``.
  242. In mathematical terms any linear combination of generators
  243. of a Free Group is contained in it.
  244. Examples
  245. ========
  246. >>> from sympy.combinatorics import free_group
  247. >>> f, x, y, z = free_group("x y z")
  248. >>> f.contains(x**3*y**2)
  249. True
  250. """
  251. if not isinstance(g, FreeGroupElement):
  252. return False
  253. elif self != g.group:
  254. return False
  255. else:
  256. return True
  257. def center(self):
  258. """Returns the center of the free group `self`."""
  259. return {self.identity}
  260. ############################################################################
  261. # FreeGroupElement #
  262. ############################################################################
  263. class FreeGroupElement(CantSympify, DefaultPrinting, tuple):
  264. """Used to create elements of FreeGroup. It cannot be used directly to
  265. create a free group element. It is called by the `dtype` method of the
  266. `FreeGroup` class.
  267. """
  268. is_assoc_word = True
  269. def new(self, init):
  270. return self.__class__(init)
  271. _hash = None
  272. def __hash__(self):
  273. _hash = self._hash
  274. if _hash is None:
  275. self._hash = _hash = hash((self.group, frozenset(tuple(self))))
  276. return _hash
  277. def copy(self):
  278. return self.new(self)
  279. @property
  280. def is_identity(self):
  281. if self.array_form == ():
  282. return True
  283. else:
  284. return False
  285. @property
  286. def array_form(self):
  287. """
  288. SymPy provides two different internal kinds of representation
  289. of associative words. The first one is called the `array_form`
  290. which is a tuple containing `tuples` as its elements, where the
  291. size of each tuple is two. At the first position the tuple
  292. contains the `symbol-generator`, while at the second position
  293. of tuple contains the exponent of that generator at the position.
  294. Since elements (i.e. words) do not commute, the indexing of tuple
  295. makes that property to stay.
  296. The structure in ``array_form`` of ``FreeGroupElement`` is of form:
  297. ``( ( symbol_of_gen, exponent ), ( , ), ... ( , ) )``
  298. Examples
  299. ========
  300. >>> from sympy.combinatorics import free_group
  301. >>> f, x, y, z = free_group("x y z")
  302. >>> (x*z).array_form
  303. ((x, 1), (z, 1))
  304. >>> (x**2*z*y*x**2).array_form
  305. ((x, 2), (z, 1), (y, 1), (x, 2))
  306. See Also
  307. ========
  308. letter_repr
  309. """
  310. return tuple(self)
  311. @property
  312. def letter_form(self):
  313. """
  314. The letter representation of a ``FreeGroupElement`` is a tuple
  315. of generator symbols, with each entry corresponding to a group
  316. generator. Inverses of the generators are represented by
  317. negative generator symbols.
  318. Examples
  319. ========
  320. >>> from sympy.combinatorics import free_group
  321. >>> f, a, b, c, d = free_group("a b c d")
  322. >>> (a**3).letter_form
  323. (a, a, a)
  324. >>> (a**2*d**-2*a*b**-4).letter_form
  325. (a, a, -d, -d, a, -b, -b, -b, -b)
  326. >>> (a**-2*b**3*d).letter_form
  327. (-a, -a, b, b, b, d)
  328. See Also
  329. ========
  330. array_form
  331. """
  332. return tuple(flatten([(i,)*j if j > 0 else (-i,)*(-j)
  333. for i, j in self.array_form]))
  334. def __getitem__(self, i):
  335. group = self.group
  336. r = self.letter_form[i]
  337. if r.is_Symbol:
  338. return group.dtype(((r, 1),))
  339. else:
  340. return group.dtype(((-r, -1),))
  341. def index(self, gen):
  342. if len(gen) != 1:
  343. raise ValueError()
  344. return (self.letter_form).index(gen.letter_form[0])
  345. @property
  346. def letter_form_elm(self):
  347. """
  348. """
  349. group = self.group
  350. r = self.letter_form
  351. return [group.dtype(((elm,1),)) if elm.is_Symbol \
  352. else group.dtype(((-elm,-1),)) for elm in r]
  353. @property
  354. def ext_rep(self):
  355. """This is called the External Representation of ``FreeGroupElement``
  356. """
  357. return tuple(flatten(self.array_form))
  358. def __contains__(self, gen):
  359. return gen.array_form[0][0] in tuple([r[0] for r in self.array_form])
  360. def __str__(self):
  361. if self.is_identity:
  362. return "<identity>"
  363. str_form = ""
  364. array_form = self.array_form
  365. for i in range(len(array_form)):
  366. if i == len(array_form) - 1:
  367. if array_form[i][1] == 1:
  368. str_form += str(array_form[i][0])
  369. else:
  370. str_form += str(array_form[i][0]) + \
  371. "**" + str(array_form[i][1])
  372. else:
  373. if array_form[i][1] == 1:
  374. str_form += str(array_form[i][0]) + "*"
  375. else:
  376. str_form += str(array_form[i][0]) + \
  377. "**" + str(array_form[i][1]) + "*"
  378. return str_form
  379. __repr__ = __str__
  380. def __pow__(self, n):
  381. n = as_int(n)
  382. group = self.group
  383. if n == 0:
  384. return group.identity
  385. if n < 0:
  386. n = -n
  387. return (self.inverse())**n
  388. result = self
  389. for i in range(n - 1):
  390. result = result*self
  391. # this method can be improved instead of just returning the
  392. # multiplication of elements
  393. return result
  394. def __mul__(self, other):
  395. """Returns the product of elements belonging to the same ``FreeGroup``.
  396. Examples
  397. ========
  398. >>> from sympy.combinatorics import free_group
  399. >>> f, x, y, z = free_group("x y z")
  400. >>> x*y**2*y**-4
  401. x*y**-2
  402. >>> z*y**-2
  403. z*y**-2
  404. >>> x**2*y*y**-1*x**-2
  405. <identity>
  406. """
  407. group = self.group
  408. if not isinstance(other, group.dtype):
  409. raise TypeError("only FreeGroup elements of same FreeGroup can "
  410. "be multiplied")
  411. if self.is_identity:
  412. return other
  413. if other.is_identity:
  414. return self
  415. r = list(self.array_form + other.array_form)
  416. zero_mul_simp(r, len(self.array_form) - 1)
  417. return group.dtype(tuple(r))
  418. def __truediv__(self, other):
  419. group = self.group
  420. if not isinstance(other, group.dtype):
  421. raise TypeError("only FreeGroup elements of same FreeGroup can "
  422. "be multiplied")
  423. return self*(other.inverse())
  424. def __rtruediv__(self, other):
  425. group = self.group
  426. if not isinstance(other, group.dtype):
  427. raise TypeError("only FreeGroup elements of same FreeGroup can "
  428. "be multiplied")
  429. return other*(self.inverse())
  430. def __add__(self, other):
  431. return NotImplemented
  432. def inverse(self):
  433. """
  434. Returns the inverse of a ``FreeGroupElement`` element
  435. Examples
  436. ========
  437. >>> from sympy.combinatorics import free_group
  438. >>> f, x, y, z = free_group("x y z")
  439. >>> x.inverse()
  440. x**-1
  441. >>> (x*y).inverse()
  442. y**-1*x**-1
  443. """
  444. group = self.group
  445. r = tuple([(i, -j) for i, j in self.array_form[::-1]])
  446. return group.dtype(r)
  447. def order(self):
  448. """Find the order of a ``FreeGroupElement``.
  449. Examples
  450. ========
  451. >>> from sympy.combinatorics import free_group
  452. >>> f, x, y = free_group("x y")
  453. >>> (x**2*y*y**-1*x**-2).order()
  454. 1
  455. """
  456. if self.is_identity:
  457. return S.One
  458. else:
  459. return S.Infinity
  460. def commutator(self, other):
  461. """
  462. Return the commutator of `self` and `x`: ``~x*~self*x*self``
  463. """
  464. group = self.group
  465. if not isinstance(other, group.dtype):
  466. raise ValueError("commutator of only FreeGroupElement of the same "
  467. "FreeGroup exists")
  468. else:
  469. return self.inverse()*other.inverse()*self*other
  470. def eliminate_words(self, words, _all=False, inverse=True):
  471. '''
  472. Replace each subword from the dictionary `words` by words[subword].
  473. If words is a list, replace the words by the identity.
  474. '''
  475. again = True
  476. new = self
  477. if isinstance(words, dict):
  478. while again:
  479. again = False
  480. for sub in words:
  481. prev = new
  482. new = new.eliminate_word(sub, words[sub], _all=_all, inverse=inverse)
  483. if new != prev:
  484. again = True
  485. else:
  486. while again:
  487. again = False
  488. for sub in words:
  489. prev = new
  490. new = new.eliminate_word(sub, _all=_all, inverse=inverse)
  491. if new != prev:
  492. again = True
  493. return new
  494. def eliminate_word(self, gen, by=None, _all=False, inverse=True):
  495. """
  496. For an associative word `self`, a subword `gen`, and an associative
  497. word `by` (identity by default), return the associative word obtained by
  498. replacing each occurrence of `gen` in `self` by `by`. If `_all = True`,
  499. the occurrences of `gen` that may appear after the first substitution will
  500. also be replaced and so on until no occurrences are found. This might not
  501. always terminate (e.g. `(x).eliminate_word(x, x**2, _all=True)`).
  502. Examples
  503. ========
  504. >>> from sympy.combinatorics import free_group
  505. >>> f, x, y = free_group("x y")
  506. >>> w = x**5*y*x**2*y**-4*x
  507. >>> w.eliminate_word( x, x**2 )
  508. x**10*y*x**4*y**-4*x**2
  509. >>> w.eliminate_word( x, y**-1 )
  510. y**-11
  511. >>> w.eliminate_word(x**5)
  512. y*x**2*y**-4*x
  513. >>> w.eliminate_word(x*y, y)
  514. x**4*y*x**2*y**-4*x
  515. See Also
  516. ========
  517. substituted_word
  518. """
  519. if by is None:
  520. by = self.group.identity
  521. if self.is_independent(gen) or gen == by:
  522. return self
  523. if gen == self:
  524. return by
  525. if gen**-1 == by:
  526. _all = False
  527. word = self
  528. l = len(gen)
  529. try:
  530. i = word.subword_index(gen)
  531. k = 1
  532. except ValueError:
  533. if not inverse:
  534. return word
  535. try:
  536. i = word.subword_index(gen**-1)
  537. k = -1
  538. except ValueError:
  539. return word
  540. word = word.subword(0, i)*by**k*word.subword(i+l, len(word)).eliminate_word(gen, by)
  541. if _all:
  542. return word.eliminate_word(gen, by, _all=True, inverse=inverse)
  543. else:
  544. return word
  545. def __len__(self):
  546. """
  547. For an associative word `self`, returns the number of letters in it.
  548. Examples
  549. ========
  550. >>> from sympy.combinatorics import free_group
  551. >>> f, a, b = free_group("a b")
  552. >>> w = a**5*b*a**2*b**-4*a
  553. >>> len(w)
  554. 13
  555. >>> len(a**17)
  556. 17
  557. >>> len(w**0)
  558. 0
  559. """
  560. return sum(abs(j) for (i, j) in self)
  561. def __eq__(self, other):
  562. """
  563. Two associative words are equal if they are words over the
  564. same alphabet and if they are sequences of the same letters.
  565. This is equivalent to saying that the external representations
  566. of the words are equal.
  567. There is no "universal" empty word, every alphabet has its own
  568. empty word.
  569. Examples
  570. ========
  571. >>> from sympy.combinatorics import free_group
  572. >>> f, swapnil0, swapnil1 = free_group("swapnil0 swapnil1")
  573. >>> f
  574. <free group on the generators (swapnil0, swapnil1)>
  575. >>> g, swap0, swap1 = free_group("swap0 swap1")
  576. >>> g
  577. <free group on the generators (swap0, swap1)>
  578. >>> swapnil0 == swapnil1
  579. False
  580. >>> swapnil0*swapnil1 == swapnil1/swapnil1*swapnil0*swapnil1
  581. True
  582. >>> swapnil0*swapnil1 == swapnil1*swapnil0
  583. False
  584. >>> swapnil1**0 == swap0**0
  585. False
  586. """
  587. group = self.group
  588. if not isinstance(other, group.dtype):
  589. return False
  590. return tuple.__eq__(self, other)
  591. def __lt__(self, other):
  592. """
  593. The ordering of associative words is defined by length and
  594. lexicography (this ordering is called short-lex ordering), that
  595. is, shorter words are smaller than longer words, and words of the
  596. same length are compared w.r.t. the lexicographical ordering induced
  597. by the ordering of generators. Generators are sorted according
  598. to the order in which they were created. If the generators are
  599. invertible then each generator `g` is larger than its inverse `g^{-1}`,
  600. and `g^{-1}` is larger than every generator that is smaller than `g`.
  601. Examples
  602. ========
  603. >>> from sympy.combinatorics import free_group
  604. >>> f, a, b = free_group("a b")
  605. >>> b < a
  606. False
  607. >>> a < a.inverse()
  608. False
  609. """
  610. group = self.group
  611. if not isinstance(other, group.dtype):
  612. raise TypeError("only FreeGroup elements of same FreeGroup can "
  613. "be compared")
  614. l = len(self)
  615. m = len(other)
  616. # implement lenlex order
  617. if l < m:
  618. return True
  619. elif l > m:
  620. return False
  621. for i in range(l):
  622. a = self[i].array_form[0]
  623. b = other[i].array_form[0]
  624. p = group.symbols.index(a[0])
  625. q = group.symbols.index(b[0])
  626. if p < q:
  627. return True
  628. elif p > q:
  629. return False
  630. elif a[1] < b[1]:
  631. return True
  632. elif a[1] > b[1]:
  633. return False
  634. return False
  635. def __le__(self, other):
  636. return (self == other or self < other)
  637. def __gt__(self, other):
  638. """
  639. Examples
  640. ========
  641. >>> from sympy.combinatorics import free_group
  642. >>> f, x, y, z = free_group("x y z")
  643. >>> y**2 > x**2
  644. True
  645. >>> y*z > z*y
  646. False
  647. >>> x > x.inverse()
  648. True
  649. """
  650. group = self.group
  651. if not isinstance(other, group.dtype):
  652. raise TypeError("only FreeGroup elements of same FreeGroup can "
  653. "be compared")
  654. return not self <= other
  655. def __ge__(self, other):
  656. return not self < other
  657. def exponent_sum(self, gen):
  658. """
  659. For an associative word `self` and a generator or inverse of generator
  660. `gen`, ``exponent_sum`` returns the number of times `gen` appears in
  661. `self` minus the number of times its inverse appears in `self`. If
  662. neither `gen` nor its inverse occur in `self` then 0 is returned.
  663. Examples
  664. ========
  665. >>> from sympy.combinatorics import free_group
  666. >>> F, x, y = free_group("x, y")
  667. >>> w = x**2*y**3
  668. >>> w.exponent_sum(x)
  669. 2
  670. >>> w.exponent_sum(x**-1)
  671. -2
  672. >>> w = x**2*y**4*x**-3
  673. >>> w.exponent_sum(x)
  674. -1
  675. See Also
  676. ========
  677. generator_count
  678. """
  679. if len(gen) != 1:
  680. raise ValueError("gen must be a generator or inverse of a generator")
  681. s = gen.array_form[0]
  682. return s[1]*sum([i[1] for i in self.array_form if i[0] == s[0]])
  683. def generator_count(self, gen):
  684. """
  685. For an associative word `self` and a generator `gen`,
  686. ``generator_count`` returns the multiplicity of generator
  687. `gen` in `self`.
  688. Examples
  689. ========
  690. >>> from sympy.combinatorics import free_group
  691. >>> F, x, y = free_group("x, y")
  692. >>> w = x**2*y**3
  693. >>> w.generator_count(x)
  694. 2
  695. >>> w = x**2*y**4*x**-3
  696. >>> w.generator_count(x)
  697. 5
  698. See Also
  699. ========
  700. exponent_sum
  701. """
  702. if len(gen) != 1 or gen.array_form[0][1] < 0:
  703. raise ValueError("gen must be a generator")
  704. s = gen.array_form[0]
  705. return s[1]*sum([abs(i[1]) for i in self.array_form if i[0] == s[0]])
  706. def subword(self, from_i, to_j, strict=True):
  707. """
  708. For an associative word `self` and two positive integers `from_i` and
  709. `to_j`, `subword` returns the subword of `self` that begins at position
  710. `from_i` and ends at `to_j - 1`, indexing is done with origin 0.
  711. Examples
  712. ========
  713. >>> from sympy.combinatorics import free_group
  714. >>> f, a, b = free_group("a b")
  715. >>> w = a**5*b*a**2*b**-4*a
  716. >>> w.subword(2, 6)
  717. a**3*b
  718. """
  719. group = self.group
  720. if not strict:
  721. from_i = max(from_i, 0)
  722. to_j = min(len(self), to_j)
  723. if from_i < 0 or to_j > len(self):
  724. raise ValueError("`from_i`, `to_j` must be positive and no greater than "
  725. "the length of associative word")
  726. if to_j <= from_i:
  727. return group.identity
  728. else:
  729. letter_form = self.letter_form[from_i: to_j]
  730. array_form = letter_form_to_array_form(letter_form, group)
  731. return group.dtype(array_form)
  732. def subword_index(self, word, start = 0):
  733. '''
  734. Find the index of `word` in `self`.
  735. Examples
  736. ========
  737. >>> from sympy.combinatorics import free_group
  738. >>> f, a, b = free_group("a b")
  739. >>> w = a**2*b*a*b**3
  740. >>> w.subword_index(a*b*a*b)
  741. 1
  742. '''
  743. l = len(word)
  744. self_lf = self.letter_form
  745. word_lf = word.letter_form
  746. index = None
  747. for i in range(start,len(self_lf)-l+1):
  748. if self_lf[i:i+l] == word_lf:
  749. index = i
  750. break
  751. if index is not None:
  752. return index
  753. else:
  754. raise ValueError("The given word is not a subword of self")
  755. def is_dependent(self, word):
  756. """
  757. Examples
  758. ========
  759. >>> from sympy.combinatorics import free_group
  760. >>> F, x, y = free_group("x, y")
  761. >>> (x**4*y**-3).is_dependent(x**4*y**-2)
  762. True
  763. >>> (x**2*y**-1).is_dependent(x*y)
  764. False
  765. >>> (x*y**2*x*y**2).is_dependent(x*y**2)
  766. True
  767. >>> (x**12).is_dependent(x**-4)
  768. True
  769. See Also
  770. ========
  771. is_independent
  772. """
  773. try:
  774. return self.subword_index(word) is not None
  775. except ValueError:
  776. pass
  777. try:
  778. return self.subword_index(word**-1) is not None
  779. except ValueError:
  780. return False
  781. def is_independent(self, word):
  782. """
  783. See Also
  784. ========
  785. is_dependent
  786. """
  787. return not self.is_dependent(word)
  788. def contains_generators(self):
  789. """
  790. Examples
  791. ========
  792. >>> from sympy.combinatorics import free_group
  793. >>> F, x, y, z = free_group("x, y, z")
  794. >>> (x**2*y**-1).contains_generators()
  795. {x, y}
  796. >>> (x**3*z).contains_generators()
  797. {x, z}
  798. """
  799. group = self.group
  800. gens = set()
  801. for syllable in self.array_form:
  802. gens.add(group.dtype(((syllable[0], 1),)))
  803. return set(gens)
  804. def cyclic_subword(self, from_i, to_j):
  805. group = self.group
  806. l = len(self)
  807. letter_form = self.letter_form
  808. period1 = int(from_i/l)
  809. if from_i >= l:
  810. from_i -= l*period1
  811. to_j -= l*period1
  812. diff = to_j - from_i
  813. word = letter_form[from_i: to_j]
  814. period2 = int(to_j/l) - 1
  815. word += letter_form*period2 + letter_form[:diff-l+from_i-l*period2]
  816. word = letter_form_to_array_form(word, group)
  817. return group.dtype(word)
  818. def cyclic_conjugates(self):
  819. """Returns a words which are cyclic to the word `self`.
  820. Examples
  821. ========
  822. >>> from sympy.combinatorics import free_group
  823. >>> F, x, y = free_group("x, y")
  824. >>> w = x*y*x*y*x
  825. >>> w.cyclic_conjugates()
  826. {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}
  827. >>> s = x*y*x**2*y*x
  828. >>> s.cyclic_conjugates()
  829. {x**2*y*x**2*y, y*x**2*y*x**2, x*y*x**2*y*x}
  830. References
  831. ==========
  832. .. [1] https://planetmath.org/cyclicpermutation
  833. """
  834. return {self.cyclic_subword(i, i+len(self)) for i in range(len(self))}
  835. def is_cyclic_conjugate(self, w):
  836. """
  837. Checks whether words ``self``, ``w`` are cyclic conjugates.
  838. Examples
  839. ========
  840. >>> from sympy.combinatorics import free_group
  841. >>> F, x, y = free_group("x, y")
  842. >>> w1 = x**2*y**5
  843. >>> w2 = x*y**5*x
  844. >>> w1.is_cyclic_conjugate(w2)
  845. True
  846. >>> w3 = x**-1*y**5*x**-1
  847. >>> w3.is_cyclic_conjugate(w2)
  848. False
  849. """
  850. l1 = len(self)
  851. l2 = len(w)
  852. if l1 != l2:
  853. return False
  854. w1 = self.identity_cyclic_reduction()
  855. w2 = w.identity_cyclic_reduction()
  856. letter1 = w1.letter_form
  857. letter2 = w2.letter_form
  858. str1 = ' '.join(map(str, letter1))
  859. str2 = ' '.join(map(str, letter2))
  860. if len(str1) != len(str2):
  861. return False
  862. return str1 in str2 + ' ' + str2
  863. def number_syllables(self):
  864. """Returns the number of syllables of the associative word `self`.
  865. Examples
  866. ========
  867. >>> from sympy.combinatorics import free_group
  868. >>> f, swapnil0, swapnil1 = free_group("swapnil0 swapnil1")
  869. >>> (swapnil1**3*swapnil0*swapnil1**-1).number_syllables()
  870. 3
  871. """
  872. return len(self.array_form)
  873. def exponent_syllable(self, i):
  874. """
  875. Returns the exponent of the `i`-th syllable of the associative word
  876. `self`.
  877. Examples
  878. ========
  879. >>> from sympy.combinatorics import free_group
  880. >>> f, a, b = free_group("a b")
  881. >>> w = a**5*b*a**2*b**-4*a
  882. >>> w.exponent_syllable( 2 )
  883. 2
  884. """
  885. return self.array_form[i][1]
  886. def generator_syllable(self, i):
  887. """
  888. Returns the symbol of the generator that is involved in the
  889. i-th syllable of the associative word `self`.
  890. Examples
  891. ========
  892. >>> from sympy.combinatorics import free_group
  893. >>> f, a, b = free_group("a b")
  894. >>> w = a**5*b*a**2*b**-4*a
  895. >>> w.generator_syllable( 3 )
  896. b
  897. """
  898. return self.array_form[i][0]
  899. def sub_syllables(self, from_i, to_j):
  900. """
  901. `sub_syllables` returns the subword of the associative word `self` that
  902. consists of syllables from positions `from_to` to `to_j`, where
  903. `from_to` and `to_j` must be positive integers and indexing is done
  904. with origin 0.
  905. Examples
  906. ========
  907. >>> from sympy.combinatorics import free_group
  908. >>> f, a, b = free_group("a, b")
  909. >>> w = a**5*b*a**2*b**-4*a
  910. >>> w.sub_syllables(1, 2)
  911. b
  912. >>> w.sub_syllables(3, 3)
  913. <identity>
  914. """
  915. if not isinstance(from_i, int) or not isinstance(to_j, int):
  916. raise ValueError("both arguments should be integers")
  917. group = self.group
  918. if to_j <= from_i:
  919. return group.identity
  920. else:
  921. r = tuple(self.array_form[from_i: to_j])
  922. return group.dtype(r)
  923. def substituted_word(self, from_i, to_j, by):
  924. """
  925. Returns the associative word obtained by replacing the subword of
  926. `self` that begins at position `from_i` and ends at position `to_j - 1`
  927. by the associative word `by`. `from_i` and `to_j` must be positive
  928. integers, indexing is done with origin 0. In other words,
  929. `w.substituted_word(w, from_i, to_j, by)` is the product of the three
  930. words: `w.subword(0, from_i)`, `by`, and
  931. `w.subword(to_j len(w))`.
  932. See Also
  933. ========
  934. eliminate_word
  935. """
  936. lw = len(self)
  937. if from_i >= to_j or from_i > lw or to_j > lw:
  938. raise ValueError("values should be within bounds")
  939. # otherwise there are four possibilities
  940. # first if from=1 and to=lw then
  941. if from_i == 0 and to_j == lw:
  942. return by
  943. elif from_i == 0: # second if from_i=1 (and to_j < lw) then
  944. return by*self.subword(to_j, lw)
  945. elif to_j == lw: # third if to_j=1 (and from_i > 1) then
  946. return self.subword(0, from_i)*by
  947. else: # finally
  948. return self.subword(0, from_i)*by*self.subword(to_j, lw)
  949. def is_cyclically_reduced(self):
  950. r"""Returns whether the word is cyclically reduced or not.
  951. A word is cyclically reduced if by forming the cycle of the
  952. word, the word is not reduced, i.e a word w = `a_1 ... a_n`
  953. is called cyclically reduced if `a_1 \ne a_n^{-1}`.
  954. Examples
  955. ========
  956. >>> from sympy.combinatorics import free_group
  957. >>> F, x, y = free_group("x, y")
  958. >>> (x**2*y**-1*x**-1).is_cyclically_reduced()
  959. False
  960. >>> (y*x**2*y**2).is_cyclically_reduced()
  961. True
  962. """
  963. if not self:
  964. return True
  965. return self[0] != self[-1]**-1
  966. def identity_cyclic_reduction(self):
  967. """Return a unique cyclically reduced version of the word.
  968. Examples
  969. ========
  970. >>> from sympy.combinatorics import free_group
  971. >>> F, x, y = free_group("x, y")
  972. >>> (x**2*y**2*x**-1).identity_cyclic_reduction()
  973. x*y**2
  974. >>> (x**-3*y**-1*x**5).identity_cyclic_reduction()
  975. x**2*y**-1
  976. References
  977. ==========
  978. .. [1] https://planetmath.org/cyclicallyreduced
  979. """
  980. word = self.copy()
  981. group = self.group
  982. while not word.is_cyclically_reduced():
  983. exp1 = word.exponent_syllable(0)
  984. exp2 = word.exponent_syllable(-1)
  985. r = exp1 + exp2
  986. if r == 0:
  987. rep = word.array_form[1: word.number_syllables() - 1]
  988. else:
  989. rep = ((word.generator_syllable(0), exp1 + exp2),) + \
  990. word.array_form[1: word.number_syllables() - 1]
  991. word = group.dtype(rep)
  992. return word
  993. def cyclic_reduction(self, removed=False):
  994. """Return a cyclically reduced version of the word. Unlike
  995. `identity_cyclic_reduction`, this will not cyclically permute
  996. the reduced word - just remove the "unreduced" bits on either
  997. side of it. Compare the examples with those of
  998. `identity_cyclic_reduction`.
  999. When `removed` is `True`, return a tuple `(word, r)` where
  1000. self `r` is such that before the reduction the word was either
  1001. `r*word*r**-1`.
  1002. Examples
  1003. ========
  1004. >>> from sympy.combinatorics import free_group
  1005. >>> F, x, y = free_group("x, y")
  1006. >>> (x**2*y**2*x**-1).cyclic_reduction()
  1007. x*y**2
  1008. >>> (x**-3*y**-1*x**5).cyclic_reduction()
  1009. y**-1*x**2
  1010. >>> (x**-3*y**-1*x**5).cyclic_reduction(removed=True)
  1011. (y**-1*x**2, x**-3)
  1012. """
  1013. word = self.copy()
  1014. g = self.group.identity
  1015. while not word.is_cyclically_reduced():
  1016. exp1 = abs(word.exponent_syllable(0))
  1017. exp2 = abs(word.exponent_syllable(-1))
  1018. exp = min(exp1, exp2)
  1019. start = word[0]**abs(exp)
  1020. end = word[-1]**abs(exp)
  1021. word = start**-1*word*end**-1
  1022. g = g*start
  1023. if removed:
  1024. return word, g
  1025. return word
  1026. def power_of(self, other):
  1027. '''
  1028. Check if `self == other**n` for some integer n.
  1029. Examples
  1030. ========
  1031. >>> from sympy.combinatorics import free_group
  1032. >>> F, x, y = free_group("x, y")
  1033. >>> ((x*y)**2).power_of(x*y)
  1034. True
  1035. >>> (x**-3*y**-2*x**3).power_of(x**-3*y*x**3)
  1036. True
  1037. '''
  1038. if self.is_identity:
  1039. return True
  1040. l = len(other)
  1041. if l == 1:
  1042. # self has to be a power of one generator
  1043. gens = self.contains_generators()
  1044. s = other in gens or other**-1 in gens
  1045. return len(gens) == 1 and s
  1046. # if self is not cyclically reduced and it is a power of other,
  1047. # other isn't cyclically reduced and the parts removed during
  1048. # their reduction must be equal
  1049. reduced, r1 = self.cyclic_reduction(removed=True)
  1050. if not r1.is_identity:
  1051. other, r2 = other.cyclic_reduction(removed=True)
  1052. if r1 == r2:
  1053. return reduced.power_of(other)
  1054. return False
  1055. if len(self) < l or len(self) % l:
  1056. return False
  1057. prefix = self.subword(0, l)
  1058. if prefix == other or prefix**-1 == other:
  1059. rest = self.subword(l, len(self))
  1060. return rest.power_of(other)
  1061. return False
  1062. def letter_form_to_array_form(array_form, group):
  1063. """
  1064. This method converts a list given with possible repetitions of elements in
  1065. it. It returns a new list such that repetitions of consecutive elements is
  1066. removed and replace with a tuple element of size two such that the first
  1067. index contains `value` and the second index contains the number of
  1068. consecutive repetitions of `value`.
  1069. """
  1070. a = list(array_form[:])
  1071. new_array = []
  1072. n = 1
  1073. symbols = group.symbols
  1074. for i in range(len(a)):
  1075. if i == len(a) - 1:
  1076. if a[i] == a[i - 1]:
  1077. if (-a[i]) in symbols:
  1078. new_array.append((-a[i], -n))
  1079. else:
  1080. new_array.append((a[i], n))
  1081. else:
  1082. if (-a[i]) in symbols:
  1083. new_array.append((-a[i], -1))
  1084. else:
  1085. new_array.append((a[i], 1))
  1086. return new_array
  1087. elif a[i] == a[i + 1]:
  1088. n += 1
  1089. else:
  1090. if (-a[i]) in symbols:
  1091. new_array.append((-a[i], -n))
  1092. else:
  1093. new_array.append((a[i], n))
  1094. n = 1
  1095. def zero_mul_simp(l, index):
  1096. """Used to combine two reduced words."""
  1097. while index >=0 and index < len(l) - 1 and l[index][0] == l[index + 1][0]:
  1098. exp = l[index][1] + l[index + 1][1]
  1099. base = l[index][0]
  1100. l[index] = (base, exp)
  1101. del l[index + 1]
  1102. if l[index][1] == 0:
  1103. del l[index]
  1104. index -= 1