free_groups.py 39 KB

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