fp_groups.py 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348
  1. """Finitely Presented Groups and its algorithms. """
  2. from sympy.core.singleton import S
  3. from sympy.core.symbol import symbols
  4. from sympy.combinatorics.free_groups import (FreeGroup, FreeGroupElement,
  5. free_group)
  6. from sympy.combinatorics.rewritingsystem import RewritingSystem
  7. from sympy.combinatorics.coset_table import (CosetTable,
  8. coset_enumeration_r,
  9. coset_enumeration_c)
  10. from sympy.combinatorics import PermutationGroup
  11. from sympy.matrices.normalforms import invariant_factors
  12. from sympy.matrices import Matrix
  13. from sympy.polys.polytools import gcd
  14. from sympy.printing.defaults import DefaultPrinting
  15. from sympy.utilities import public
  16. from sympy.utilities.magic import pollute
  17. from itertools import product
  18. @public
  19. def fp_group(fr_grp, relators=()):
  20. _fp_group = FpGroup(fr_grp, relators)
  21. return (_fp_group,) + tuple(_fp_group._generators)
  22. @public
  23. def xfp_group(fr_grp, relators=()):
  24. _fp_group = FpGroup(fr_grp, relators)
  25. return (_fp_group, _fp_group._generators)
  26. # Does not work. Both symbols and pollute are undefined. Never tested.
  27. @public
  28. def vfp_group(fr_grpm, relators):
  29. _fp_group = FpGroup(symbols, relators)
  30. pollute([sym.name for sym in _fp_group.symbols], _fp_group.generators)
  31. return _fp_group
  32. def _parse_relators(rels):
  33. """Parse the passed relators."""
  34. return rels
  35. ###############################################################################
  36. # FINITELY PRESENTED GROUPS #
  37. ###############################################################################
  38. class FpGroup(DefaultPrinting):
  39. """
  40. The FpGroup would take a FreeGroup and a list/tuple of relators, the
  41. relators would be specified in such a way that each of them be equal to the
  42. identity of the provided free group.
  43. """
  44. is_group = True
  45. is_FpGroup = True
  46. is_PermutationGroup = False
  47. def __init__(self, fr_grp, relators):
  48. relators = _parse_relators(relators)
  49. self.free_group = fr_grp
  50. self.relators = relators
  51. self.generators = self._generators()
  52. self.dtype = type("FpGroupElement", (FpGroupElement,), {"group": self})
  53. # CosetTable instance on identity subgroup
  54. self._coset_table = None
  55. # returns whether coset table on identity subgroup
  56. # has been standardized
  57. self._is_standardized = False
  58. self._order = None
  59. self._center = None
  60. self._rewriting_system = RewritingSystem(self)
  61. self._perm_isomorphism = None
  62. return
  63. def _generators(self):
  64. return self.free_group.generators
  65. def make_confluent(self):
  66. '''
  67. Try to make the group's rewriting system confluent
  68. '''
  69. self._rewriting_system.make_confluent()
  70. return
  71. def reduce(self, word):
  72. '''
  73. Return the reduced form of `word` in `self` according to the group's
  74. rewriting system. If it's confluent, the reduced form is the unique normal
  75. form of the word in the group.
  76. '''
  77. return self._rewriting_system.reduce(word)
  78. def equals(self, word1, word2):
  79. '''
  80. Compare `word1` and `word2` for equality in the group
  81. using the group's rewriting system. If the system is
  82. confluent, the returned answer is necessarily correct.
  83. (If it is not, `False` could be returned in some cases
  84. where in fact `word1 == word2`)
  85. '''
  86. if self.reduce(word1*word2**-1) == self.identity:
  87. return True
  88. elif self._rewriting_system.is_confluent:
  89. return False
  90. return None
  91. @property
  92. def identity(self):
  93. return self.free_group.identity
  94. def __contains__(self, g):
  95. return g in self.free_group
  96. def subgroup(self, gens, C=None, homomorphism=False):
  97. '''
  98. Return the subgroup generated by `gens` using the
  99. Reidemeister-Schreier algorithm
  100. homomorphism -- When set to True, return a dictionary containing the images
  101. of the presentation generators in the original group.
  102. Examples
  103. ========
  104. >>> from sympy.combinatorics.fp_groups import FpGroup
  105. >>> from sympy.combinatorics import free_group
  106. >>> F, x, y = free_group("x, y")
  107. >>> f = FpGroup(F, [x**3, y**5, (x*y)**2])
  108. >>> H = [x*y, x**-1*y**-1*x*y*x]
  109. >>> K, T = f.subgroup(H, homomorphism=True)
  110. >>> T(K.generators)
  111. [x*y, x**-1*y**2*x**-1]
  112. '''
  113. if not all(isinstance(g, FreeGroupElement) for g in gens):
  114. raise ValueError("Generators must be `FreeGroupElement`s")
  115. if not all(g.group == self.free_group for g in gens):
  116. raise ValueError("Given generators are not members of the group")
  117. if homomorphism:
  118. g, rels, _gens = reidemeister_presentation(self, gens, C=C, homomorphism=True)
  119. else:
  120. g, rels = reidemeister_presentation(self, gens, C=C)
  121. if g:
  122. g = FpGroup(g[0].group, rels)
  123. else:
  124. g = FpGroup(free_group('')[0], [])
  125. if homomorphism:
  126. from sympy.combinatorics.homomorphisms import homomorphism
  127. return g, homomorphism(g, self, g.generators, _gens, check=False)
  128. return g
  129. def coset_enumeration(self, H, strategy="relator_based", max_cosets=None,
  130. draft=None, incomplete=False):
  131. """
  132. Return an instance of ``coset table``, when Todd-Coxeter algorithm is
  133. run over the ``self`` with ``H`` as subgroup, using ``strategy``
  134. argument as strategy. The returned coset table is compressed but not
  135. standardized.
  136. An instance of `CosetTable` for `fp_grp` can be passed as the keyword
  137. argument `draft` in which case the coset enumeration will start with
  138. that instance and attempt to complete it.
  139. When `incomplete` is `True` and the function is unable to complete for
  140. some reason, the partially complete table will be returned.
  141. """
  142. if not max_cosets:
  143. max_cosets = CosetTable.coset_table_max_limit
  144. if strategy == 'relator_based':
  145. C = coset_enumeration_r(self, H, max_cosets=max_cosets,
  146. draft=draft, incomplete=incomplete)
  147. else:
  148. C = coset_enumeration_c(self, H, max_cosets=max_cosets,
  149. draft=draft, incomplete=incomplete)
  150. if C.is_complete():
  151. C.compress()
  152. return C
  153. def standardize_coset_table(self):
  154. """
  155. Standardized the coset table ``self`` and makes the internal variable
  156. ``_is_standardized`` equal to ``True``.
  157. """
  158. self._coset_table.standardize()
  159. self._is_standardized = True
  160. def coset_table(self, H, strategy="relator_based", max_cosets=None,
  161. draft=None, incomplete=False):
  162. """
  163. Return the mathematical coset table of ``self`` in ``H``.
  164. """
  165. if not H:
  166. if self._coset_table is not None:
  167. if not self._is_standardized:
  168. self.standardize_coset_table()
  169. else:
  170. C = self.coset_enumeration([], strategy, max_cosets=max_cosets,
  171. draft=draft, incomplete=incomplete)
  172. self._coset_table = C
  173. self.standardize_coset_table()
  174. return self._coset_table.table
  175. else:
  176. C = self.coset_enumeration(H, strategy, max_cosets=max_cosets,
  177. draft=draft, incomplete=incomplete)
  178. C.standardize()
  179. return C.table
  180. def order(self, strategy="relator_based"):
  181. """
  182. Returns the order of the finitely presented group ``self``. It uses
  183. the coset enumeration with identity group as subgroup, i.e ``H=[]``.
  184. Examples
  185. ========
  186. >>> from sympy.combinatorics import free_group
  187. >>> from sympy.combinatorics.fp_groups import FpGroup
  188. >>> F, x, y = free_group("x, y")
  189. >>> f = FpGroup(F, [x, y**2])
  190. >>> f.order(strategy="coset_table_based")
  191. 2
  192. """
  193. if self._order is not None:
  194. return self._order
  195. if self._coset_table is not None:
  196. self._order = len(self._coset_table.table)
  197. elif len(self.relators) == 0:
  198. self._order = self.free_group.order()
  199. elif len(self.generators) == 1:
  200. self._order = abs(gcd([r.array_form[0][1] for r in self.relators]))
  201. elif self._is_infinite():
  202. self._order = S.Infinity
  203. else:
  204. gens, C = self._finite_index_subgroup()
  205. if C:
  206. ind = len(C.table)
  207. self._order = ind*self.subgroup(gens, C=C).order()
  208. else:
  209. self._order = self.index([])
  210. return self._order
  211. def _is_infinite(self):
  212. '''
  213. Test if the group is infinite. Return `True` if the test succeeds
  214. and `None` otherwise
  215. '''
  216. used_gens = set()
  217. for r in self.relators:
  218. used_gens.update(r.contains_generators())
  219. if not set(self.generators) <= used_gens:
  220. return True
  221. # Abelianisation test: check is the abelianisation is infinite
  222. abelian_rels = []
  223. for rel in self.relators:
  224. abelian_rels.append([rel.exponent_sum(g) for g in self.generators])
  225. m = Matrix(Matrix(abelian_rels))
  226. if 0 in invariant_factors(m):
  227. return True
  228. else:
  229. return None
  230. def _finite_index_subgroup(self, s=None):
  231. '''
  232. Find the elements of `self` that generate a finite index subgroup
  233. and, if found, return the list of elements and the coset table of `self` by
  234. the subgroup, otherwise return `(None, None)`
  235. '''
  236. gen = self.most_frequent_generator()
  237. rels = list(self.generators)
  238. rels.extend(self.relators)
  239. if not s:
  240. if len(self.generators) == 2:
  241. s = [gen] + [g for g in self.generators if g != gen]
  242. else:
  243. rand = self.free_group.identity
  244. i = 0
  245. while ((rand in rels or rand**-1 in rels or rand.is_identity)
  246. and i<10):
  247. rand = self.random()
  248. i += 1
  249. s = [gen, rand] + [g for g in self.generators if g != gen]
  250. mid = (len(s)+1)//2
  251. half1 = s[:mid]
  252. half2 = s[mid:]
  253. draft1 = None
  254. draft2 = None
  255. m = 200
  256. C = None
  257. while not C and (m/2 < CosetTable.coset_table_max_limit):
  258. m = min(m, CosetTable.coset_table_max_limit)
  259. draft1 = self.coset_enumeration(half1, max_cosets=m,
  260. draft=draft1, incomplete=True)
  261. if draft1.is_complete():
  262. C = draft1
  263. half = half1
  264. else:
  265. draft2 = self.coset_enumeration(half2, max_cosets=m,
  266. draft=draft2, incomplete=True)
  267. if draft2.is_complete():
  268. C = draft2
  269. half = half2
  270. if not C:
  271. m *= 2
  272. if not C:
  273. return None, None
  274. C.compress()
  275. return half, C
  276. def most_frequent_generator(self):
  277. gens = self.generators
  278. rels = self.relators
  279. freqs = [sum([r.generator_count(g) for r in rels]) for g in gens]
  280. return gens[freqs.index(max(freqs))]
  281. def random(self):
  282. import random
  283. r = self.free_group.identity
  284. for i in range(random.randint(2,3)):
  285. r = r*random.choice(self.generators)**random.choice([1,-1])
  286. return r
  287. def index(self, H, strategy="relator_based"):
  288. """
  289. Return the index of subgroup ``H`` in group ``self``.
  290. Examples
  291. ========
  292. >>> from sympy.combinatorics import free_group
  293. >>> from sympy.combinatorics.fp_groups import FpGroup
  294. >>> F, x, y = free_group("x, y")
  295. >>> f = FpGroup(F, [x**5, y**4, y*x*y**3*x**3])
  296. >>> f.index([x])
  297. 4
  298. """
  299. # TODO: use |G:H| = |G|/|H| (currently H can't be made into a group)
  300. # when we know |G| and |H|
  301. if H == []:
  302. return self.order()
  303. else:
  304. C = self.coset_enumeration(H, strategy)
  305. return len(C.table)
  306. def __str__(self):
  307. if self.free_group.rank > 30:
  308. str_form = "<fp group with %s generators>" % self.free_group.rank
  309. else:
  310. str_form = "<fp group on the generators %s>" % str(self.generators)
  311. return str_form
  312. __repr__ = __str__
  313. #==============================================================================
  314. # PERMUTATION GROUP METHODS
  315. #==============================================================================
  316. def _to_perm_group(self):
  317. '''
  318. Return an isomorphic permutation group and the isomorphism.
  319. The implementation is dependent on coset enumeration so
  320. will only terminate for finite groups.
  321. '''
  322. from sympy.combinatorics import Permutation
  323. from sympy.combinatorics.homomorphisms import homomorphism
  324. if self.order() is S.Infinity:
  325. raise NotImplementedError("Permutation presentation of infinite "
  326. "groups is not implemented")
  327. if self._perm_isomorphism:
  328. T = self._perm_isomorphism
  329. P = T.image()
  330. else:
  331. C = self.coset_table([])
  332. gens = self.generators
  333. images = [[C[i][2*gens.index(g)] for i in range(len(C))] for g in gens]
  334. images = [Permutation(i) for i in images]
  335. P = PermutationGroup(images)
  336. T = homomorphism(self, P, gens, images, check=False)
  337. self._perm_isomorphism = T
  338. return P, T
  339. def _perm_group_list(self, method_name, *args):
  340. '''
  341. Given the name of a `PermutationGroup` method (returning a subgroup
  342. or a list of subgroups) and (optionally) additional arguments it takes,
  343. return a list or a list of lists containing the generators of this (or
  344. these) subgroups in terms of the generators of `self`.
  345. '''
  346. P, T = self._to_perm_group()
  347. perm_result = getattr(P, method_name)(*args)
  348. single = False
  349. if isinstance(perm_result, PermutationGroup):
  350. perm_result, single = [perm_result], True
  351. result = []
  352. for group in perm_result:
  353. gens = group.generators
  354. result.append(T.invert(gens))
  355. return result[0] if single else result
  356. def derived_series(self):
  357. '''
  358. Return the list of lists containing the generators
  359. of the subgroups in the derived series of `self`.
  360. '''
  361. return self._perm_group_list('derived_series')
  362. def lower_central_series(self):
  363. '''
  364. Return the list of lists containing the generators
  365. of the subgroups in the lower central series of `self`.
  366. '''
  367. return self._perm_group_list('lower_central_series')
  368. def center(self):
  369. '''
  370. Return the list of generators of the center of `self`.
  371. '''
  372. return self._perm_group_list('center')
  373. def derived_subgroup(self):
  374. '''
  375. Return the list of generators of the derived subgroup of `self`.
  376. '''
  377. return self._perm_group_list('derived_subgroup')
  378. def centralizer(self, other):
  379. '''
  380. Return the list of generators of the centralizer of `other`
  381. (a list of elements of `self`) in `self`.
  382. '''
  383. T = self._to_perm_group()[1]
  384. other = T(other)
  385. return self._perm_group_list('centralizer', other)
  386. def normal_closure(self, other):
  387. '''
  388. Return the list of generators of the normal closure of `other`
  389. (a list of elements of `self`) in `self`.
  390. '''
  391. T = self._to_perm_group()[1]
  392. other = T(other)
  393. return self._perm_group_list('normal_closure', other)
  394. def _perm_property(self, attr):
  395. '''
  396. Given an attribute of a `PermutationGroup`, return
  397. its value for a permutation group isomorphic to `self`.
  398. '''
  399. P = self._to_perm_group()[0]
  400. return getattr(P, attr)
  401. @property
  402. def is_abelian(self):
  403. '''
  404. Check if `self` is abelian.
  405. '''
  406. return self._perm_property("is_abelian")
  407. @property
  408. def is_nilpotent(self):
  409. '''
  410. Check if `self` is nilpotent.
  411. '''
  412. return self._perm_property("is_nilpotent")
  413. @property
  414. def is_solvable(self):
  415. '''
  416. Check if `self` is solvable.
  417. '''
  418. return self._perm_property("is_solvable")
  419. @property
  420. def elements(self):
  421. '''
  422. List the elements of `self`.
  423. '''
  424. P, T = self._to_perm_group()
  425. return T.invert(P._elements)
  426. @property
  427. def is_cyclic(self):
  428. """
  429. Return ``True`` if group is Cyclic.
  430. """
  431. if len(self.generators) <= 1:
  432. return True
  433. try:
  434. P, T = self._to_perm_group()
  435. except NotImplementedError:
  436. raise NotImplementedError("Check for infinite Cyclic group "
  437. "is not implemented")
  438. return P.is_cyclic
  439. def abelian_invariants(self):
  440. """
  441. Return Abelian Invariants of a group.
  442. """
  443. try:
  444. P, T = self._to_perm_group()
  445. except NotImplementedError:
  446. raise NotImplementedError("abelian invariants is not implemented"
  447. "for infinite group")
  448. return P.abelian_invariants()
  449. def composition_series(self):
  450. """
  451. Return subnormal series of maximum length for a group.
  452. """
  453. try:
  454. P, T = self._to_perm_group()
  455. except NotImplementedError:
  456. raise NotImplementedError("composition series is not implemented"
  457. "for infinite group")
  458. return P.composition_series()
  459. class FpSubgroup(DefaultPrinting):
  460. '''
  461. The class implementing a subgroup of an FpGroup or a FreeGroup
  462. (only finite index subgroups are supported at this point). This
  463. is to be used if one wishes to check if an element of the original
  464. group belongs to the subgroup
  465. '''
  466. def __init__(self, G, gens, normal=False):
  467. super().__init__()
  468. self.parent = G
  469. self.generators = list({g for g in gens if g != G.identity})
  470. self._min_words = None #for use in __contains__
  471. self.C = None
  472. self.normal = normal
  473. def __contains__(self, g):
  474. if isinstance(self.parent, FreeGroup):
  475. if self._min_words is None:
  476. # make _min_words - a list of subwords such that
  477. # g is in the subgroup if and only if it can be
  478. # partitioned into these subwords. Infinite families of
  479. # subwords are presented by tuples, e.g. (r, w)
  480. # stands for the family of subwords r*w**n*r**-1
  481. def _process(w):
  482. # this is to be used before adding new words
  483. # into _min_words; if the word w is not cyclically
  484. # reduced, it will generate an infinite family of
  485. # subwords so should be written as a tuple;
  486. # if it is, w**-1 should be added to the list
  487. # as well
  488. p, r = w.cyclic_reduction(removed=True)
  489. if not r.is_identity:
  490. return [(r, p)]
  491. else:
  492. return [w, w**-1]
  493. # make the initial list
  494. gens = []
  495. for w in self.generators:
  496. if self.normal:
  497. w = w.cyclic_reduction()
  498. gens.extend(_process(w))
  499. for w1 in gens:
  500. for w2 in gens:
  501. # if w1 and w2 are equal or are inverses, continue
  502. if w1 == w2 or (not isinstance(w1, tuple)
  503. and w1**-1 == w2):
  504. continue
  505. # if the start of one word is the inverse of the
  506. # end of the other, their multiple should be added
  507. # to _min_words because of cancellation
  508. if isinstance(w1, tuple):
  509. # start, end
  510. s1, s2 = w1[0][0], w1[0][0]**-1
  511. else:
  512. s1, s2 = w1[0], w1[len(w1)-1]
  513. if isinstance(w2, tuple):
  514. # start, end
  515. r1, r2 = w2[0][0], w2[0][0]**-1
  516. else:
  517. r1, r2 = w2[0], w2[len(w1)-1]
  518. # p1 and p2 are w1 and w2 or, in case when
  519. # w1 or w2 is an infinite family, a representative
  520. p1, p2 = w1, w2
  521. if isinstance(w1, tuple):
  522. p1 = w1[0]*w1[1]*w1[0]**-1
  523. if isinstance(w2, tuple):
  524. p2 = w2[0]*w2[1]*w2[0]**-1
  525. # add the product of the words to the list is necessary
  526. if r1**-1 == s2 and not (p1*p2).is_identity:
  527. new = _process(p1*p2)
  528. if new not in gens:
  529. gens.extend(new)
  530. if r2**-1 == s1 and not (p2*p1).is_identity:
  531. new = _process(p2*p1)
  532. if new not in gens:
  533. gens.extend(new)
  534. self._min_words = gens
  535. min_words = self._min_words
  536. def _is_subword(w):
  537. # check if w is a word in _min_words or one of
  538. # the infinite families in it
  539. w, r = w.cyclic_reduction(removed=True)
  540. if r.is_identity or self.normal:
  541. return w in min_words
  542. else:
  543. t = [s[1] for s in min_words if isinstance(s, tuple)
  544. and s[0] == r]
  545. return [s for s in t if w.power_of(s)] != []
  546. # store the solution of words for which the result of
  547. # _word_break (below) is known
  548. known = {}
  549. def _word_break(w):
  550. # check if w can be written as a product of words
  551. # in min_words
  552. if len(w) == 0:
  553. return True
  554. i = 0
  555. while i < len(w):
  556. i += 1
  557. prefix = w.subword(0, i)
  558. if not _is_subword(prefix):
  559. continue
  560. rest = w.subword(i, len(w))
  561. if rest not in known:
  562. known[rest] = _word_break(rest)
  563. if known[rest]:
  564. return True
  565. return False
  566. if self.normal:
  567. g = g.cyclic_reduction()
  568. return _word_break(g)
  569. else:
  570. if self.C is None:
  571. C = self.parent.coset_enumeration(self.generators)
  572. self.C = C
  573. i = 0
  574. C = self.C
  575. for j in range(len(g)):
  576. i = C.table[i][C.A_dict[g[j]]]
  577. return i == 0
  578. def order(self):
  579. if not self.generators:
  580. return S.One
  581. if isinstance(self.parent, FreeGroup):
  582. return S.Infinity
  583. if self.C is None:
  584. C = self.parent.coset_enumeration(self.generators)
  585. self.C = C
  586. # This is valid because `len(self.C.table)` (the index of the subgroup)
  587. # will always be finite - otherwise coset enumeration doesn't terminate
  588. return self.parent.order()/len(self.C.table)
  589. def to_FpGroup(self):
  590. if isinstance(self.parent, FreeGroup):
  591. gen_syms = [('x_%d'%i) for i in range(len(self.generators))]
  592. return free_group(', '.join(gen_syms))[0]
  593. return self.parent.subgroup(C=self.C)
  594. def __str__(self):
  595. if len(self.generators) > 30:
  596. str_form = "<fp subgroup with %s generators>" % len(self.generators)
  597. else:
  598. str_form = "<fp subgroup on the generators %s>" % str(self.generators)
  599. return str_form
  600. __repr__ = __str__
  601. ###############################################################################
  602. # LOW INDEX SUBGROUPS #
  603. ###############################################################################
  604. def low_index_subgroups(G, N, Y=()):
  605. """
  606. Implements the Low Index Subgroups algorithm, i.e find all subgroups of
  607. ``G`` upto a given index ``N``. This implements the method described in
  608. [Sim94]. This procedure involves a backtrack search over incomplete Coset
  609. Tables, rather than over forced coincidences.
  610. Parameters
  611. ==========
  612. G: An FpGroup < X|R >
  613. N: positive integer, representing the maximum index value for subgroups
  614. Y: (an optional argument) specifying a list of subgroup generators, such
  615. that each of the resulting subgroup contains the subgroup generated by Y.
  616. Examples
  617. ========
  618. >>> from sympy.combinatorics import free_group
  619. >>> from sympy.combinatorics.fp_groups import FpGroup, low_index_subgroups
  620. >>> F, x, y = free_group("x, y")
  621. >>> f = FpGroup(F, [x**2, y**3, (x*y)**4])
  622. >>> L = low_index_subgroups(f, 4)
  623. >>> for coset_table in L:
  624. ... print(coset_table.table)
  625. [[0, 0, 0, 0]]
  626. [[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 3, 3]]
  627. [[0, 0, 1, 2], [2, 2, 2, 0], [1, 1, 0, 1]]
  628. [[1, 1, 0, 0], [0, 0, 1, 1]]
  629. References
  630. ==========
  631. .. [1] Holt, D., Eick, B., O'Brien, E.
  632. "Handbook of Computational Group Theory"
  633. Section 5.4
  634. .. [2] Marston Conder and Peter Dobcsanyi
  635. "Applications and Adaptions of the Low Index Subgroups Procedure"
  636. """
  637. C = CosetTable(G, [])
  638. R = G.relators
  639. # length chosen for the length of the short relators
  640. len_short_rel = 5
  641. # elements of R2 only checked at the last step for complete
  642. # coset tables
  643. R2 = {rel for rel in R if len(rel) > len_short_rel}
  644. # elements of R1 are used in inner parts of the process to prune
  645. # branches of the search tree,
  646. R1 = {rel.identity_cyclic_reduction() for rel in set(R) - R2}
  647. R1_c_list = C.conjugates(R1)
  648. S = []
  649. descendant_subgroups(S, C, R1_c_list, C.A[0], R2, N, Y)
  650. return S
  651. def descendant_subgroups(S, C, R1_c_list, x, R2, N, Y):
  652. A_dict = C.A_dict
  653. A_dict_inv = C.A_dict_inv
  654. if C.is_complete():
  655. # if C is complete then it only needs to test
  656. # whether the relators in R2 are satisfied
  657. for w, alpha in product(R2, C.omega):
  658. if not C.scan_check(alpha, w):
  659. return
  660. # relators in R2 are satisfied, append the table to list
  661. S.append(C)
  662. else:
  663. # find the first undefined entry in Coset Table
  664. for alpha, x in product(range(len(C.table)), C.A):
  665. if C.table[alpha][A_dict[x]] is None:
  666. # this is "x" in pseudo-code (using "y" makes it clear)
  667. undefined_coset, undefined_gen = alpha, x
  668. break
  669. # for filling up the undefine entry we try all possible values
  670. # of beta in Omega or beta = n where beta^(undefined_gen^-1) is undefined
  671. reach = C.omega + [C.n]
  672. for beta in reach:
  673. if beta < N:
  674. if beta == C.n or C.table[beta][A_dict_inv[undefined_gen]] is None:
  675. try_descendant(S, C, R1_c_list, R2, N, undefined_coset, \
  676. undefined_gen, beta, Y)
  677. def try_descendant(S, C, R1_c_list, R2, N, alpha, x, beta, Y):
  678. r"""
  679. Solves the problem of trying out each individual possibility
  680. for `\alpha^x.
  681. """
  682. D = C.copy()
  683. if beta == D.n and beta < N:
  684. D.table.append([None]*len(D.A))
  685. D.p.append(beta)
  686. D.table[alpha][D.A_dict[x]] = beta
  687. D.table[beta][D.A_dict_inv[x]] = alpha
  688. D.deduction_stack.append((alpha, x))
  689. if not D.process_deductions_check(R1_c_list[D.A_dict[x]], \
  690. R1_c_list[D.A_dict_inv[x]]):
  691. return
  692. for w in Y:
  693. if not D.scan_check(0, w):
  694. return
  695. if first_in_class(D, Y):
  696. descendant_subgroups(S, D, R1_c_list, x, R2, N, Y)
  697. def first_in_class(C, Y=()):
  698. """
  699. Checks whether the subgroup ``H=G1`` corresponding to the Coset Table
  700. could possibly be the canonical representative of its conjugacy class.
  701. Parameters
  702. ==========
  703. C: CosetTable
  704. Returns
  705. =======
  706. bool: True/False
  707. If this returns False, then no descendant of C can have that property, and
  708. so we can abandon C. If it returns True, then we need to process further
  709. the node of the search tree corresponding to C, and so we call
  710. ``descendant_subgroups`` recursively on C.
  711. Examples
  712. ========
  713. >>> from sympy.combinatorics import free_group
  714. >>> from sympy.combinatorics.fp_groups import FpGroup, CosetTable, first_in_class
  715. >>> F, x, y = free_group("x, y")
  716. >>> f = FpGroup(F, [x**2, y**3, (x*y)**4])
  717. >>> C = CosetTable(f, [])
  718. >>> C.table = [[0, 0, None, None]]
  719. >>> first_in_class(C)
  720. True
  721. >>> C.table = [[1, 1, 1, None], [0, 0, None, 1]]; C.p = [0, 1]
  722. >>> first_in_class(C)
  723. True
  724. >>> C.table = [[1, 1, 2, 1], [0, 0, 0, None], [None, None, None, 0]]
  725. >>> C.p = [0, 1, 2]
  726. >>> first_in_class(C)
  727. False
  728. >>> C.table = [[1, 1, 1, 2], [0, 0, 2, 0], [2, None, 0, 1]]
  729. >>> first_in_class(C)
  730. False
  731. # TODO:: Sims points out in [Sim94] that performance can be improved by
  732. # remembering some of the information computed by ``first_in_class``. If
  733. # the ``continue alpha`` statement is executed at line 14, then the same thing
  734. # will happen for that value of alpha in any descendant of the table C, and so
  735. # the values the values of alpha for which this occurs could profitably be
  736. # stored and passed through to the descendants of C. Of course this would
  737. # make the code more complicated.
  738. # The code below is taken directly from the function on page 208 of [Sim94]
  739. # nu[alpha]
  740. """
  741. n = C.n
  742. # lamda is the largest numbered point in Omega_c_alpha which is currently defined
  743. lamda = -1
  744. # for alpha in Omega_c, nu[alpha] is the point in Omega_c_alpha corresponding to alpha
  745. nu = [None]*n
  746. # for alpha in Omega_c_alpha, mu[alpha] is the point in Omega_c corresponding to alpha
  747. mu = [None]*n
  748. # mutually nu and mu are the mutually-inverse equivalence maps between
  749. # Omega_c_alpha and Omega_c
  750. next_alpha = False
  751. # For each 0!=alpha in [0 .. nc-1], we start by constructing the equivalent
  752. # standardized coset table C_alpha corresponding to H_alpha
  753. for alpha in range(1, n):
  754. # reset nu to "None" after previous value of alpha
  755. for beta in range(lamda+1):
  756. nu[mu[beta]] = None
  757. # we only want to reject our current table in favour of a preceding
  758. # table in the ordering in which 1 is replaced by alpha, if the subgroup
  759. # G_alpha corresponding to this preceding table definitely contains the
  760. # given subgroup
  761. for w in Y:
  762. # TODO: this should support input of a list of general words
  763. # not just the words which are in "A" (i.e gen and gen^-1)
  764. if C.table[alpha][C.A_dict[w]] != alpha:
  765. # continue with alpha
  766. next_alpha = True
  767. break
  768. if next_alpha:
  769. next_alpha = False
  770. continue
  771. # try alpha as the new point 0 in Omega_C_alpha
  772. mu[0] = alpha
  773. nu[alpha] = 0
  774. # compare corresponding entries in C and C_alpha
  775. lamda = 0
  776. for beta in range(n):
  777. for x in C.A:
  778. gamma = C.table[beta][C.A_dict[x]]
  779. delta = C.table[mu[beta]][C.A_dict[x]]
  780. # if either of the entries is undefined,
  781. # we move with next alpha
  782. if gamma is None or delta is None:
  783. # continue with alpha
  784. next_alpha = True
  785. break
  786. if nu[delta] is None:
  787. # delta becomes the next point in Omega_C_alpha
  788. lamda += 1
  789. nu[delta] = lamda
  790. mu[lamda] = delta
  791. if nu[delta] < gamma:
  792. return False
  793. if nu[delta] > gamma:
  794. # continue with alpha
  795. next_alpha = True
  796. break
  797. if next_alpha:
  798. next_alpha = False
  799. break
  800. return True
  801. #========================================================================
  802. # Simplifying Presentation
  803. #========================================================================
  804. def simplify_presentation(*args, change_gens=False):
  805. '''
  806. For an instance of `FpGroup`, return a simplified isomorphic copy of
  807. the group (e.g. remove redundant generators or relators). Alternatively,
  808. a list of generators and relators can be passed in which case the
  809. simplified lists will be returned.
  810. By default, the generators of the group are unchanged. If you would
  811. like to remove redundant generators, set the keyword argument
  812. `change_gens = True`.
  813. '''
  814. if len(args) == 1:
  815. if not isinstance(args[0], FpGroup):
  816. raise TypeError("The argument must be an instance of FpGroup")
  817. G = args[0]
  818. gens, rels = simplify_presentation(G.generators, G.relators,
  819. change_gens=change_gens)
  820. if gens:
  821. return FpGroup(gens[0].group, rels)
  822. return FpGroup(FreeGroup([]), [])
  823. elif len(args) == 2:
  824. gens, rels = args[0][:], args[1][:]
  825. if not gens:
  826. return gens, rels
  827. identity = gens[0].group.identity
  828. else:
  829. if len(args) == 0:
  830. m = "Not enough arguments"
  831. else:
  832. m = "Too many arguments"
  833. raise RuntimeError(m)
  834. prev_gens = []
  835. prev_rels = []
  836. while not set(prev_rels) == set(rels):
  837. prev_rels = rels
  838. while change_gens and not set(prev_gens) == set(gens):
  839. prev_gens = gens
  840. gens, rels = elimination_technique_1(gens, rels, identity)
  841. rels = _simplify_relators(rels, identity)
  842. if change_gens:
  843. syms = [g.array_form[0][0] for g in gens]
  844. F = free_group(syms)[0]
  845. identity = F.identity
  846. gens = F.generators
  847. subs = dict(zip(syms, gens))
  848. for j, r in enumerate(rels):
  849. a = r.array_form
  850. rel = identity
  851. for sym, p in a:
  852. rel = rel*subs[sym]**p
  853. rels[j] = rel
  854. return gens, rels
  855. def _simplify_relators(rels, identity):
  856. """Relies upon ``_simplification_technique_1`` for its functioning. """
  857. rels = rels[:]
  858. rels = list(set(_simplification_technique_1(rels)))
  859. rels.sort()
  860. rels = [r.identity_cyclic_reduction() for r in rels]
  861. try:
  862. rels.remove(identity)
  863. except ValueError:
  864. pass
  865. return rels
  866. # Pg 350, section 2.5.1 from [2]
  867. def elimination_technique_1(gens, rels, identity):
  868. rels = rels[:]
  869. # the shorter relators are examined first so that generators selected for
  870. # elimination will have shorter strings as equivalent
  871. rels.sort()
  872. gens = gens[:]
  873. redundant_gens = {}
  874. redundant_rels = []
  875. used_gens = set()
  876. # examine each relator in relator list for any generator occurring exactly
  877. # once
  878. for rel in rels:
  879. # don't look for a redundant generator in a relator which
  880. # depends on previously found ones
  881. contained_gens = rel.contains_generators()
  882. if any(g in contained_gens for g in redundant_gens):
  883. continue
  884. contained_gens = list(contained_gens)
  885. contained_gens.sort(reverse = True)
  886. for gen in contained_gens:
  887. if rel.generator_count(gen) == 1 and gen not in used_gens:
  888. k = rel.exponent_sum(gen)
  889. gen_index = rel.index(gen**k)
  890. bk = rel.subword(gen_index + 1, len(rel))
  891. fw = rel.subword(0, gen_index)
  892. chi = bk*fw
  893. redundant_gens[gen] = chi**(-1*k)
  894. used_gens.update(chi.contains_generators())
  895. redundant_rels.append(rel)
  896. break
  897. rels = [r for r in rels if r not in redundant_rels]
  898. # eliminate the redundant generators from remaining relators
  899. rels = [r.eliminate_words(redundant_gens, _all = True).identity_cyclic_reduction() for r in rels]
  900. rels = list(set(rels))
  901. try:
  902. rels.remove(identity)
  903. except ValueError:
  904. pass
  905. gens = [g for g in gens if g not in redundant_gens]
  906. return gens, rels
  907. def _simplification_technique_1(rels):
  908. """
  909. All relators are checked to see if they are of the form `gen^n`. If any
  910. such relators are found then all other relators are processed for strings
  911. in the `gen` known order.
  912. Examples
  913. ========
  914. >>> from sympy.combinatorics import free_group
  915. >>> from sympy.combinatorics.fp_groups import _simplification_technique_1
  916. >>> F, x, y = free_group("x, y")
  917. >>> w1 = [x**2*y**4, x**3]
  918. >>> _simplification_technique_1(w1)
  919. [x**-1*y**4, x**3]
  920. >>> w2 = [x**2*y**-4*x**5, x**3, x**2*y**8, y**5]
  921. >>> _simplification_technique_1(w2)
  922. [x**-1*y*x**-1, x**3, x**-1*y**-2, y**5]
  923. >>> w3 = [x**6*y**4, x**4]
  924. >>> _simplification_technique_1(w3)
  925. [x**2*y**4, x**4]
  926. """
  927. rels = rels[:]
  928. # dictionary with "gen: n" where gen^n is one of the relators
  929. exps = {}
  930. for i in range(len(rels)):
  931. rel = rels[i]
  932. if rel.number_syllables() == 1:
  933. g = rel[0]
  934. exp = abs(rel.array_form[0][1])
  935. if rel.array_form[0][1] < 0:
  936. rels[i] = rels[i]**-1
  937. g = g**-1
  938. if g in exps:
  939. exp = gcd(exp, exps[g].array_form[0][1])
  940. exps[g] = g**exp
  941. one_syllables_words = exps.values()
  942. # decrease some of the exponents in relators, making use of the single
  943. # syllable relators
  944. for i in range(len(rels)):
  945. rel = rels[i]
  946. if rel in one_syllables_words:
  947. continue
  948. rel = rel.eliminate_words(one_syllables_words, _all = True)
  949. # if rels[i] contains g**n where abs(n) is greater than half of the power p
  950. # of g in exps, g**n can be replaced by g**(n-p) (or g**(p-n) if n<0)
  951. for g in rel.contains_generators():
  952. if g in exps:
  953. exp = exps[g].array_form[0][1]
  954. max_exp = (exp + 1)//2
  955. rel = rel.eliminate_word(g**(max_exp), g**(max_exp-exp), _all = True)
  956. rel = rel.eliminate_word(g**(-max_exp), g**(-(max_exp-exp)), _all = True)
  957. rels[i] = rel
  958. rels = [r.identity_cyclic_reduction() for r in rels]
  959. return rels
  960. ###############################################################################
  961. # SUBGROUP PRESENTATIONS #
  962. ###############################################################################
  963. # Pg 175 [1]
  964. def define_schreier_generators(C, homomorphism=False):
  965. '''
  966. Parameters
  967. ==========
  968. C -- Coset table.
  969. homomorphism -- When set to True, return a dictionary containing the images
  970. of the presentation generators in the original group.
  971. '''
  972. y = []
  973. gamma = 1
  974. f = C.fp_group
  975. X = f.generators
  976. if homomorphism:
  977. # `_gens` stores the elements of the parent group to
  978. # to which the schreier generators correspond to.
  979. _gens = {}
  980. # compute the schreier Traversal
  981. tau = {}
  982. tau[0] = f.identity
  983. C.P = [[None]*len(C.A) for i in range(C.n)]
  984. for alpha, x in product(C.omega, C.A):
  985. beta = C.table[alpha][C.A_dict[x]]
  986. if beta == gamma:
  987. C.P[alpha][C.A_dict[x]] = "<identity>"
  988. C.P[beta][C.A_dict_inv[x]] = "<identity>"
  989. gamma += 1
  990. if homomorphism:
  991. tau[beta] = tau[alpha]*x
  992. elif x in X and C.P[alpha][C.A_dict[x]] is None:
  993. y_alpha_x = '%s_%s' % (x, alpha)
  994. y.append(y_alpha_x)
  995. C.P[alpha][C.A_dict[x]] = y_alpha_x
  996. if homomorphism:
  997. _gens[y_alpha_x] = tau[alpha]*x*tau[beta]**-1
  998. grp_gens = list(free_group(', '.join(y)))
  999. C._schreier_free_group = grp_gens.pop(0)
  1000. C._schreier_generators = grp_gens
  1001. if homomorphism:
  1002. C._schreier_gen_elem = _gens
  1003. # replace all elements of P by, free group elements
  1004. for i, j in product(range(len(C.P)), range(len(C.A))):
  1005. # if equals "<identity>", replace by identity element
  1006. if C.P[i][j] == "<identity>":
  1007. C.P[i][j] = C._schreier_free_group.identity
  1008. elif isinstance(C.P[i][j], str):
  1009. r = C._schreier_generators[y.index(C.P[i][j])]
  1010. C.P[i][j] = r
  1011. beta = C.table[i][j]
  1012. C.P[beta][j + 1] = r**-1
  1013. def reidemeister_relators(C):
  1014. R = C.fp_group.relators
  1015. rels = [rewrite(C, coset, word) for word in R for coset in range(C.n)]
  1016. order_1_gens = {i for i in rels if len(i) == 1}
  1017. # remove all the order 1 generators from relators
  1018. rels = list(filter(lambda rel: rel not in order_1_gens, rels))
  1019. # replace order 1 generators by identity element in reidemeister relators
  1020. for i in range(len(rels)):
  1021. w = rels[i]
  1022. w = w.eliminate_words(order_1_gens, _all=True)
  1023. rels[i] = w
  1024. C._schreier_generators = [i for i in C._schreier_generators
  1025. if not (i in order_1_gens or i**-1 in order_1_gens)]
  1026. # Tietze transformation 1 i.e TT_1
  1027. # remove cyclic conjugate elements from relators
  1028. i = 0
  1029. while i < len(rels):
  1030. w = rels[i]
  1031. j = i + 1
  1032. while j < len(rels):
  1033. if w.is_cyclic_conjugate(rels[j]):
  1034. del rels[j]
  1035. else:
  1036. j += 1
  1037. i += 1
  1038. C._reidemeister_relators = rels
  1039. def rewrite(C, alpha, w):
  1040. """
  1041. Parameters
  1042. ==========
  1043. C: CosetTable
  1044. alpha: A live coset
  1045. w: A word in `A*`
  1046. Returns
  1047. =======
  1048. rho(tau(alpha), w)
  1049. Examples
  1050. ========
  1051. >>> from sympy.combinatorics.fp_groups import FpGroup, CosetTable, define_schreier_generators, rewrite
  1052. >>> from sympy.combinatorics import free_group
  1053. >>> F, x, y = free_group("x, y")
  1054. >>> f = FpGroup(F, [x**2, y**3, (x*y)**6])
  1055. >>> C = CosetTable(f, [])
  1056. >>> C.table = [[1, 1, 2, 3], [0, 0, 4, 5], [4, 4, 3, 0], [5, 5, 0, 2], [2, 2, 5, 1], [3, 3, 1, 4]]
  1057. >>> C.p = [0, 1, 2, 3, 4, 5]
  1058. >>> define_schreier_generators(C)
  1059. >>> rewrite(C, 0, (x*y)**6)
  1060. x_4*y_2*x_3*x_1*x_2*y_4*x_5
  1061. """
  1062. v = C._schreier_free_group.identity
  1063. for i in range(len(w)):
  1064. x_i = w[i]
  1065. v = v*C.P[alpha][C.A_dict[x_i]]
  1066. alpha = C.table[alpha][C.A_dict[x_i]]
  1067. return v
  1068. # Pg 350, section 2.5.2 from [2]
  1069. def elimination_technique_2(C):
  1070. """
  1071. This technique eliminates one generator at a time. Heuristically this
  1072. seems superior in that we may select for elimination the generator with
  1073. shortest equivalent string at each stage.
  1074. >>> from sympy.combinatorics import free_group
  1075. >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r, \
  1076. reidemeister_relators, define_schreier_generators, elimination_technique_2
  1077. >>> F, x, y = free_group("x, y")
  1078. >>> f = FpGroup(F, [x**3, y**5, (x*y)**2]); H = [x*y, x**-1*y**-1*x*y*x]
  1079. >>> C = coset_enumeration_r(f, H)
  1080. >>> C.compress(); C.standardize()
  1081. >>> define_schreier_generators(C)
  1082. >>> reidemeister_relators(C)
  1083. >>> elimination_technique_2(C)
  1084. ([y_1, y_2], [y_2**-3, y_2*y_1*y_2*y_1*y_2*y_1, y_1**2])
  1085. """
  1086. rels = C._reidemeister_relators
  1087. rels.sort(reverse=True)
  1088. gens = C._schreier_generators
  1089. for i in range(len(gens) - 1, -1, -1):
  1090. rel = rels[i]
  1091. for j in range(len(gens) - 1, -1, -1):
  1092. gen = gens[j]
  1093. if rel.generator_count(gen) == 1:
  1094. k = rel.exponent_sum(gen)
  1095. gen_index = rel.index(gen**k)
  1096. bk = rel.subword(gen_index + 1, len(rel))
  1097. fw = rel.subword(0, gen_index)
  1098. rep_by = (bk*fw)**(-1*k)
  1099. del rels[i]; del gens[j]
  1100. for l in range(len(rels)):
  1101. rels[l] = rels[l].eliminate_word(gen, rep_by)
  1102. break
  1103. C._reidemeister_relators = rels
  1104. C._schreier_generators = gens
  1105. return C._schreier_generators, C._reidemeister_relators
  1106. def reidemeister_presentation(fp_grp, H, C=None, homomorphism=False):
  1107. """
  1108. Parameters
  1109. ==========
  1110. fp_group: A finitely presented group, an instance of FpGroup
  1111. H: A subgroup whose presentation is to be found, given as a list
  1112. of words in generators of `fp_grp`
  1113. homomorphism: When set to True, return a homomorphism from the subgroup
  1114. to the parent group
  1115. Examples
  1116. ========
  1117. >>> from sympy.combinatorics import free_group
  1118. >>> from sympy.combinatorics.fp_groups import FpGroup, reidemeister_presentation
  1119. >>> F, x, y = free_group("x, y")
  1120. Example 5.6 Pg. 177 from [1]
  1121. >>> f = FpGroup(F, [x**3, y**5, (x*y)**2])
  1122. >>> H = [x*y, x**-1*y**-1*x*y*x]
  1123. >>> reidemeister_presentation(f, H)
  1124. ((y_1, y_2), (y_1**2, y_2**3, y_2*y_1*y_2*y_1*y_2*y_1))
  1125. Example 5.8 Pg. 183 from [1]
  1126. >>> f = FpGroup(F, [x**3, y**3, (x*y)**3])
  1127. >>> H = [x*y, x*y**-1]
  1128. >>> reidemeister_presentation(f, H)
  1129. ((x_0, y_0), (x_0**3, y_0**3, x_0*y_0*x_0*y_0*x_0*y_0))
  1130. Exercises Q2. Pg 187 from [1]
  1131. >>> f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3])
  1132. >>> H = [x]
  1133. >>> reidemeister_presentation(f, H)
  1134. ((x_0,), (x_0**4,))
  1135. Example 5.9 Pg. 183 from [1]
  1136. >>> f = FpGroup(F, [x**3*y**-3, (x*y)**3, (x*y**-1)**2])
  1137. >>> H = [x]
  1138. >>> reidemeister_presentation(f, H)
  1139. ((x_0,), (x_0**6,))
  1140. """
  1141. if not C:
  1142. C = coset_enumeration_r(fp_grp, H)
  1143. C.compress(); C.standardize()
  1144. define_schreier_generators(C, homomorphism=homomorphism)
  1145. reidemeister_relators(C)
  1146. gens, rels = C._schreier_generators, C._reidemeister_relators
  1147. gens, rels = simplify_presentation(gens, rels, change_gens=True)
  1148. C.schreier_generators = tuple(gens)
  1149. C.reidemeister_relators = tuple(rels)
  1150. if homomorphism:
  1151. _gens = []
  1152. for gen in gens:
  1153. _gens.append(C._schreier_gen_elem[str(gen)])
  1154. return C.schreier_generators, C.reidemeister_relators, _gens
  1155. return C.schreier_generators, C.reidemeister_relators
  1156. FpGroupElement = FreeGroupElement