pc_groups.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. from sympy.ntheory.primetest import isprime
  2. from sympy.combinatorics.perm_groups import PermutationGroup
  3. from sympy.printing.defaults import DefaultPrinting
  4. from sympy.combinatorics.free_groups import free_group
  5. class PolycyclicGroup(DefaultPrinting):
  6. is_group = True
  7. is_solvable = True
  8. def __init__(self, pc_sequence, pc_series, relative_order, collector=None):
  9. """
  10. Parameters
  11. ==========
  12. pc_sequence : list
  13. A sequence of elements whose classes generate the cyclic factor
  14. groups of pc_series.
  15. pc_series : list
  16. A subnormal sequence of subgroups where each factor group is cyclic.
  17. relative_order : list
  18. The orders of factor groups of pc_series.
  19. collector : Collector
  20. By default, it is None. Collector class provides the
  21. polycyclic presentation with various other functionalities.
  22. """
  23. self.pcgs = pc_sequence
  24. self.pc_series = pc_series
  25. self.relative_order = relative_order
  26. self.collector = Collector(self.pcgs, pc_series, relative_order) if not collector else collector
  27. def is_prime_order(self):
  28. return all(isprime(order) for order in self.relative_order)
  29. def length(self):
  30. return len(self.pcgs)
  31. class Collector(DefaultPrinting):
  32. """
  33. References
  34. ==========
  35. .. [1] Holt, D., Eick, B., O'Brien, E.
  36. "Handbook of Computational Group Theory"
  37. Section 8.1.3
  38. """
  39. def __init__(self, pcgs, pc_series, relative_order, free_group_=None, pc_presentation=None):
  40. """
  41. Most of the parameters for the Collector class are the same as for PolycyclicGroup.
  42. Others are described below.
  43. Parameters
  44. ==========
  45. free_group_ : tuple
  46. free_group_ provides the mapping of polycyclic generating
  47. sequence with the free group elements.
  48. pc_presentation : dict
  49. Provides the presentation of polycyclic groups with the
  50. help of power and conjugate relators.
  51. See Also
  52. ========
  53. PolycyclicGroup
  54. """
  55. self.pcgs = pcgs
  56. self.pc_series = pc_series
  57. self.relative_order = relative_order
  58. self.free_group = free_group('x:{}'.format(len(pcgs)))[0] if not free_group_ else free_group_
  59. self.index = {s: i for i, s in enumerate(self.free_group.symbols)}
  60. self.pc_presentation = self.pc_relators()
  61. def minimal_uncollected_subword(self, word):
  62. r"""
  63. Returns the minimal uncollected subwords.
  64. Explanation
  65. ===========
  66. A word ``v`` defined on generators in ``X`` is a minimal
  67. uncollected subword of the word ``w`` if ``v`` is a subword
  68. of ``w`` and it has one of the following form
  69. * `v = {x_{i+1}}^{a_j}x_i`
  70. * `v = {x_{i+1}}^{a_j}{x_i}^{-1}`
  71. * `v = {x_i}^{a_j}`
  72. for `a_j` not in `\{1, \ldots, s-1\}`. Where, ``s`` is the power
  73. exponent of the corresponding generator.
  74. Examples
  75. ========
  76. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  77. >>> from sympy.combinatorics import free_group
  78. >>> G = SymmetricGroup(4)
  79. >>> PcGroup = G.polycyclic_group()
  80. >>> collector = PcGroup.collector
  81. >>> F, x1, x2 = free_group("x1, x2")
  82. >>> word = x2**2*x1**7
  83. >>> collector.minimal_uncollected_subword(word)
  84. ((x2, 2),)
  85. """
  86. # To handle the case word = <identity>
  87. if not word:
  88. return None
  89. array = word.array_form
  90. re = self.relative_order
  91. index = self.index
  92. for i in range(len(array)):
  93. s1, e1 = array[i]
  94. if re[index[s1]] and (e1 < 0 or e1 > re[index[s1]]-1):
  95. return ((s1, e1), )
  96. for i in range(len(array)-1):
  97. s1, e1 = array[i]
  98. s2, e2 = array[i+1]
  99. if index[s1] > index[s2]:
  100. e = 1 if e2 > 0 else -1
  101. return ((s1, e1), (s2, e))
  102. return None
  103. def relations(self):
  104. """
  105. Separates the given relators of pc presentation in power and
  106. conjugate relations.
  107. Returns
  108. =======
  109. (power_rel, conj_rel)
  110. Separates pc presentation into power and conjugate relations.
  111. Examples
  112. ========
  113. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  114. >>> G = SymmetricGroup(3)
  115. >>> PcGroup = G.polycyclic_group()
  116. >>> collector = PcGroup.collector
  117. >>> power_rel, conj_rel = collector.relations()
  118. >>> power_rel
  119. {x0**2: (), x1**3: ()}
  120. >>> conj_rel
  121. {x0**-1*x1*x0: x1**2}
  122. See Also
  123. ========
  124. pc_relators
  125. """
  126. power_relators = {}
  127. conjugate_relators = {}
  128. for key, value in self.pc_presentation.items():
  129. if len(key.array_form) == 1:
  130. power_relators[key] = value
  131. else:
  132. conjugate_relators[key] = value
  133. return power_relators, conjugate_relators
  134. def subword_index(self, word, w):
  135. """
  136. Returns the start and ending index of a given
  137. subword in a word.
  138. Parameters
  139. ==========
  140. word : FreeGroupElement
  141. word defined on free group elements for a
  142. polycyclic group.
  143. w : FreeGroupElement
  144. subword of a given word, whose starting and
  145. ending index to be computed.
  146. Returns
  147. =======
  148. (i, j)
  149. A tuple containing starting and ending index of ``w``
  150. in the given word. If not exists, (-1,-1) is returned.
  151. Examples
  152. ========
  153. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  154. >>> from sympy.combinatorics import free_group
  155. >>> G = SymmetricGroup(4)
  156. >>> PcGroup = G.polycyclic_group()
  157. >>> collector = PcGroup.collector
  158. >>> F, x1, x2 = free_group("x1, x2")
  159. >>> word = x2**2*x1**7
  160. >>> w = x2**2*x1
  161. >>> collector.subword_index(word, w)
  162. (0, 3)
  163. >>> w = x1**7
  164. >>> collector.subword_index(word, w)
  165. (2, 9)
  166. >>> w = x1**8
  167. >>> collector.subword_index(word, w)
  168. (-1, -1)
  169. """
  170. low = -1
  171. high = -1
  172. for i in range(len(word)-len(w)+1):
  173. if word.subword(i, i+len(w)) == w:
  174. low = i
  175. high = i+len(w)
  176. break
  177. return low, high
  178. def map_relation(self, w):
  179. """
  180. Return a conjugate relation.
  181. Explanation
  182. ===========
  183. Given a word formed by two free group elements, the
  184. corresponding conjugate relation with those free
  185. group elements is formed and mapped with the collected
  186. word in the polycyclic presentation.
  187. Examples
  188. ========
  189. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  190. >>> from sympy.combinatorics import free_group
  191. >>> G = SymmetricGroup(3)
  192. >>> PcGroup = G.polycyclic_group()
  193. >>> collector = PcGroup.collector
  194. >>> F, x0, x1 = free_group("x0, x1")
  195. >>> w = x1*x0
  196. >>> collector.map_relation(w)
  197. x1**2
  198. See Also
  199. ========
  200. pc_presentation
  201. """
  202. array = w.array_form
  203. s1 = array[0][0]
  204. s2 = array[1][0]
  205. key = ((s2, -1), (s1, 1), (s2, 1))
  206. key = self.free_group.dtype(key)
  207. return self.pc_presentation[key]
  208. def collected_word(self, word):
  209. r"""
  210. Return the collected form of a word.
  211. Explanation
  212. ===========
  213. A word ``w`` is called collected, if `w = {x_{i_1}}^{a_1} * \ldots *
  214. {x_{i_r}}^{a_r}` with `i_1 < i_2< \ldots < i_r` and `a_j` is in
  215. `\{1, \ldots, {s_j}-1\}`.
  216. Otherwise w is uncollected.
  217. Parameters
  218. ==========
  219. word : FreeGroupElement
  220. An uncollected word.
  221. Returns
  222. =======
  223. word
  224. A collected word of form `w = {x_{i_1}}^{a_1}, \ldots,
  225. {x_{i_r}}^{a_r}` with `i_1, i_2, \ldots, i_r` and `a_j \in
  226. \{1, \ldots, {s_j}-1\}`.
  227. Examples
  228. ========
  229. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  230. >>> from sympy.combinatorics.perm_groups import PermutationGroup
  231. >>> from sympy.combinatorics import free_group
  232. >>> G = SymmetricGroup(4)
  233. >>> PcGroup = G.polycyclic_group()
  234. >>> collector = PcGroup.collector
  235. >>> F, x0, x1, x2, x3 = free_group("x0, x1, x2, x3")
  236. >>> word = x3*x2*x1*x0
  237. >>> collected_word = collector.collected_word(word)
  238. >>> free_to_perm = {}
  239. >>> free_group = collector.free_group
  240. >>> for sym, gen in zip(free_group.symbols, collector.pcgs):
  241. ... free_to_perm[sym] = gen
  242. >>> G1 = PermutationGroup()
  243. >>> for w in word:
  244. ... sym = w[0]
  245. ... perm = free_to_perm[sym]
  246. ... G1 = PermutationGroup([perm] + G1.generators)
  247. >>> G2 = PermutationGroup()
  248. >>> for w in collected_word:
  249. ... sym = w[0]
  250. ... perm = free_to_perm[sym]
  251. ... G2 = PermutationGroup([perm] + G2.generators)
  252. The two are not identical, but they are equivalent:
  253. >>> G1.equals(G2), G1 == G2
  254. (True, False)
  255. See Also
  256. ========
  257. minimal_uncollected_subword
  258. """
  259. free_group = self.free_group
  260. while True:
  261. w = self.minimal_uncollected_subword(word)
  262. if not w:
  263. break
  264. low, high = self.subword_index(word, free_group.dtype(w))
  265. if low == -1:
  266. continue
  267. s1, e1 = w[0]
  268. if len(w) == 1:
  269. re = self.relative_order[self.index[s1]]
  270. q = e1 // re
  271. r = e1-q*re
  272. key = ((w[0][0], re), )
  273. key = free_group.dtype(key)
  274. if self.pc_presentation[key]:
  275. presentation = self.pc_presentation[key].array_form
  276. sym, exp = presentation[0]
  277. word_ = ((w[0][0], r), (sym, q*exp))
  278. word_ = free_group.dtype(word_)
  279. else:
  280. if r != 0:
  281. word_ = ((w[0][0], r), )
  282. word_ = free_group.dtype(word_)
  283. else:
  284. word_ = None
  285. word = word.eliminate_word(free_group.dtype(w), word_)
  286. if len(w) == 2 and w[1][1] > 0:
  287. s2, e2 = w[1]
  288. s2 = ((s2, 1), )
  289. s2 = free_group.dtype(s2)
  290. word_ = self.map_relation(free_group.dtype(w))
  291. word_ = s2*word_**e1
  292. word_ = free_group.dtype(word_)
  293. word = word.substituted_word(low, high, word_)
  294. elif len(w) == 2 and w[1][1] < 0:
  295. s2, e2 = w[1]
  296. s2 = ((s2, 1), )
  297. s2 = free_group.dtype(s2)
  298. word_ = self.map_relation(free_group.dtype(w))
  299. word_ = s2**-1*word_**e1
  300. word_ = free_group.dtype(word_)
  301. word = word.substituted_word(low, high, word_)
  302. return word
  303. def pc_relators(self):
  304. r"""
  305. Return the polycyclic presentation.
  306. Explanation
  307. ===========
  308. There are two types of relations used in polycyclic
  309. presentation.
  310. * Power relations : Power relators are of the form `x_i^{re_i}`,
  311. where `i \in \{0, \ldots, \mathrm{len(pcgs)}\}`, ``x`` represents polycyclic
  312. generator and ``re`` is the corresponding relative order.
  313. * Conjugate relations : Conjugate relators are of the form `x_j^-1x_ix_j`,
  314. where `j < i \in \{0, \ldots, \mathrm{len(pcgs)}\}`.
  315. Returns
  316. =======
  317. A dictionary with power and conjugate relations as key and
  318. their collected form as corresponding values.
  319. Notes
  320. =====
  321. Identity Permutation is mapped with empty ``()``.
  322. Examples
  323. ========
  324. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  325. >>> from sympy.combinatorics.permutations import Permutation
  326. >>> S = SymmetricGroup(49).sylow_subgroup(7)
  327. >>> der = S.derived_series()
  328. >>> G = der[len(der)-2]
  329. >>> PcGroup = G.polycyclic_group()
  330. >>> collector = PcGroup.collector
  331. >>> pcgs = PcGroup.pcgs
  332. >>> len(pcgs)
  333. 6
  334. >>> free_group = collector.free_group
  335. >>> pc_resentation = collector.pc_presentation
  336. >>> free_to_perm = {}
  337. >>> for s, g in zip(free_group.symbols, pcgs):
  338. ... free_to_perm[s] = g
  339. >>> for k, v in pc_resentation.items():
  340. ... k_array = k.array_form
  341. ... if v != ():
  342. ... v_array = v.array_form
  343. ... lhs = Permutation()
  344. ... for gen in k_array:
  345. ... s = gen[0]
  346. ... e = gen[1]
  347. ... lhs = lhs*free_to_perm[s]**e
  348. ... if v == ():
  349. ... assert lhs.is_identity
  350. ... continue
  351. ... rhs = Permutation()
  352. ... for gen in v_array:
  353. ... s = gen[0]
  354. ... e = gen[1]
  355. ... rhs = rhs*free_to_perm[s]**e
  356. ... assert lhs == rhs
  357. """
  358. free_group = self.free_group
  359. rel_order = self.relative_order
  360. pc_relators = {}
  361. perm_to_free = {}
  362. pcgs = self.pcgs
  363. for gen, s in zip(pcgs, free_group.generators):
  364. perm_to_free[gen**-1] = s**-1
  365. perm_to_free[gen] = s
  366. pcgs = pcgs[::-1]
  367. series = self.pc_series[::-1]
  368. rel_order = rel_order[::-1]
  369. collected_gens = []
  370. for i, gen in enumerate(pcgs):
  371. re = rel_order[i]
  372. relation = perm_to_free[gen]**re
  373. G = series[i]
  374. l = G.generator_product(gen**re, original = True)
  375. l.reverse()
  376. word = free_group.identity
  377. for g in l:
  378. word = word*perm_to_free[g]
  379. word = self.collected_word(word)
  380. pc_relators[relation] = word if word else ()
  381. self.pc_presentation = pc_relators
  382. collected_gens.append(gen)
  383. if len(collected_gens) > 1:
  384. conj = collected_gens[len(collected_gens)-1]
  385. conjugator = perm_to_free[conj]
  386. for j in range(len(collected_gens)-1):
  387. conjugated = perm_to_free[collected_gens[j]]
  388. relation = conjugator**-1*conjugated*conjugator
  389. gens = conj**-1*collected_gens[j]*conj
  390. l = G.generator_product(gens, original = True)
  391. l.reverse()
  392. word = free_group.identity
  393. for g in l:
  394. word = word*perm_to_free[g]
  395. word = self.collected_word(word)
  396. pc_relators[relation] = word if word else ()
  397. self.pc_presentation = pc_relators
  398. return pc_relators
  399. def exponent_vector(self, element):
  400. r"""
  401. Return the exponent vector of length equal to the
  402. length of polycyclic generating sequence.
  403. Explanation
  404. ===========
  405. For a given generator/element ``g`` of the polycyclic group,
  406. it can be represented as `g = {x_1}^{e_1}, \ldots, {x_n}^{e_n}`,
  407. where `x_i` represents polycyclic generators and ``n`` is
  408. the number of generators in the free_group equal to the length
  409. of pcgs.
  410. Parameters
  411. ==========
  412. element : Permutation
  413. Generator of a polycyclic group.
  414. Examples
  415. ========
  416. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  417. >>> from sympy.combinatorics.permutations import Permutation
  418. >>> G = SymmetricGroup(4)
  419. >>> PcGroup = G.polycyclic_group()
  420. >>> collector = PcGroup.collector
  421. >>> pcgs = PcGroup.pcgs
  422. >>> collector.exponent_vector(G[0])
  423. [1, 0, 0, 0]
  424. >>> exp = collector.exponent_vector(G[1])
  425. >>> g = Permutation()
  426. >>> for i in range(len(exp)):
  427. ... g = g*pcgs[i]**exp[i] if exp[i] else g
  428. >>> assert g == G[1]
  429. References
  430. ==========
  431. .. [1] Holt, D., Eick, B., O'Brien, E.
  432. "Handbook of Computational Group Theory"
  433. Section 8.1.1, Definition 8.4
  434. """
  435. free_group = self.free_group
  436. G = PermutationGroup()
  437. for g in self.pcgs:
  438. G = PermutationGroup([g] + G.generators)
  439. gens = G.generator_product(element, original = True)
  440. gens.reverse()
  441. perm_to_free = {}
  442. for sym, g in zip(free_group.generators, self.pcgs):
  443. perm_to_free[g**-1] = sym**-1
  444. perm_to_free[g] = sym
  445. w = free_group.identity
  446. for g in gens:
  447. w = w*perm_to_free[g]
  448. word = self.collected_word(w)
  449. index = self.index
  450. exp_vector = [0]*len(free_group)
  451. word = word.array_form
  452. for t in word:
  453. exp_vector[index[t[0]]] = t[1]
  454. return exp_vector
  455. def depth(self, element):
  456. r"""
  457. Return the depth of a given element.
  458. Explanation
  459. ===========
  460. The depth of a given element ``g`` is defined by
  461. `\mathrm{dep}[g] = i` if `e_1 = e_2 = \ldots = e_{i-1} = 0`
  462. and `e_i != 0`, where ``e`` represents the exponent-vector.
  463. Examples
  464. ========
  465. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  466. >>> G = SymmetricGroup(3)
  467. >>> PcGroup = G.polycyclic_group()
  468. >>> collector = PcGroup.collector
  469. >>> collector.depth(G[0])
  470. 2
  471. >>> collector.depth(G[1])
  472. 1
  473. References
  474. ==========
  475. .. [1] Holt, D., Eick, B., O'Brien, E.
  476. "Handbook of Computational Group Theory"
  477. Section 8.1.1, Definition 8.5
  478. """
  479. exp_vector = self.exponent_vector(element)
  480. return next((i+1 for i, x in enumerate(exp_vector) if x), len(self.pcgs)+1)
  481. def leading_exponent(self, element):
  482. r"""
  483. Return the leading non-zero exponent.
  484. Explanation
  485. ===========
  486. The leading exponent for a given element `g` is defined
  487. by `\mathrm{leading\_exponent}[g]` `= e_i`, if `\mathrm{depth}[g] = i`.
  488. Examples
  489. ========
  490. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  491. >>> G = SymmetricGroup(3)
  492. >>> PcGroup = G.polycyclic_group()
  493. >>> collector = PcGroup.collector
  494. >>> collector.leading_exponent(G[1])
  495. 1
  496. """
  497. exp_vector = self.exponent_vector(element)
  498. depth = self.depth(element)
  499. if depth != len(self.pcgs)+1:
  500. return exp_vector[depth-1]
  501. return None
  502. def _sift(self, z, g):
  503. h = g
  504. d = self.depth(h)
  505. while d < len(self.pcgs) and z[d-1] != 1:
  506. k = z[d-1]
  507. e = self.leading_exponent(h)*(self.leading_exponent(k))**-1
  508. e = e % self.relative_order[d-1]
  509. h = k**-e*h
  510. d = self.depth(h)
  511. return h
  512. def induced_pcgs(self, gens):
  513. """
  514. Parameters
  515. ==========
  516. gens : list
  517. A list of generators on which polycyclic subgroup
  518. is to be defined.
  519. Examples
  520. ========
  521. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  522. >>> S = SymmetricGroup(8)
  523. >>> G = S.sylow_subgroup(2)
  524. >>> PcGroup = G.polycyclic_group()
  525. >>> collector = PcGroup.collector
  526. >>> gens = [G[0], G[1]]
  527. >>> ipcgs = collector.induced_pcgs(gens)
  528. >>> [gen.order() for gen in ipcgs]
  529. [2, 2, 2]
  530. >>> G = S.sylow_subgroup(3)
  531. >>> PcGroup = G.polycyclic_group()
  532. >>> collector = PcGroup.collector
  533. >>> gens = [G[0], G[1]]
  534. >>> ipcgs = collector.induced_pcgs(gens)
  535. >>> [gen.order() for gen in ipcgs]
  536. [3]
  537. """
  538. z = [1]*len(self.pcgs)
  539. G = gens
  540. while G:
  541. g = G.pop(0)
  542. h = self._sift(z, g)
  543. d = self.depth(h)
  544. if d < len(self.pcgs):
  545. for gen in z:
  546. if gen != 1:
  547. G.append(h**-1*gen**-1*h*gen)
  548. z[d-1] = h
  549. z = [gen for gen in z if gen != 1]
  550. return z
  551. def constructive_membership_test(self, ipcgs, g):
  552. """
  553. Return the exponent vector for induced pcgs.
  554. """
  555. e = [0]*len(ipcgs)
  556. h = g
  557. d = self.depth(h)
  558. for i, gen in enumerate(ipcgs):
  559. while self.depth(gen) == d:
  560. f = self.leading_exponent(h)*self.leading_exponent(gen)
  561. f = f % self.relative_order[d-1]
  562. h = gen**(-f)*h
  563. e[i] = f
  564. d = self.depth(h)
  565. if h == 1:
  566. return e
  567. return False