pc_groups.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  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.
  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. """
  167. low = -1
  168. high = -1
  169. for i in range(len(word)-len(w)+1):
  170. if word.subword(i, i+len(w)) == w:
  171. low = i
  172. high = i+len(w)
  173. break
  174. if low == high == -1:
  175. return -1, -1
  176. return low, high
  177. def map_relation(self, w):
  178. """
  179. Return a conjugate relation.
  180. Explanation
  181. ===========
  182. Given a word formed by two free group elements, the
  183. corresponding conjugate relation with those free
  184. group elements is formed and mapped with the collected
  185. word in the polycyclic presentation.
  186. Examples
  187. ========
  188. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  189. >>> from sympy.combinatorics import free_group
  190. >>> G = SymmetricGroup(3)
  191. >>> PcGroup = G.polycyclic_group()
  192. >>> collector = PcGroup.collector
  193. >>> F, x0, x1 = free_group("x0, x1")
  194. >>> w = x1*x0
  195. >>> collector.map_relation(w)
  196. x1**2
  197. See Also
  198. ========
  199. pc_presentation
  200. """
  201. array = w.array_form
  202. s1 = array[0][0]
  203. s2 = array[1][0]
  204. key = ((s2, -1), (s1, 1), (s2, 1))
  205. key = self.free_group.dtype(key)
  206. return self.pc_presentation[key]
  207. def collected_word(self, word):
  208. r"""
  209. Return the collected form of a word.
  210. Explanation
  211. ===========
  212. A word ``w`` is called collected, if `w = {x_{i_1}}^{a_1} * \ldots *
  213. {x_{i_r}}^{a_r}` with `i_1 < i_2< \ldots < i_r` and `a_j` is in
  214. `\{1, \ldots, {s_j}-1\}`.
  215. Otherwise w is uncollected.
  216. Parameters
  217. ==========
  218. word : FreeGroupElement
  219. An uncollected word.
  220. Returns
  221. =======
  222. word
  223. A collected word of form `w = {x_{i_1}}^{a_1}, \ldots,
  224. {x_{i_r}}^{a_r}` with `i_1, i_2, \ldots, i_r` and `a_j \in
  225. \{1, \ldots, {s_j}-1\}`.
  226. Examples
  227. ========
  228. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  229. >>> from sympy.combinatorics.perm_groups import PermutationGroup
  230. >>> from sympy.combinatorics import free_group
  231. >>> G = SymmetricGroup(4)
  232. >>> PcGroup = G.polycyclic_group()
  233. >>> collector = PcGroup.collector
  234. >>> F, x0, x1, x2, x3 = free_group("x0, x1, x2, x3")
  235. >>> word = x3*x2*x1*x0
  236. >>> collected_word = collector.collected_word(word)
  237. >>> free_to_perm = {}
  238. >>> free_group = collector.free_group
  239. >>> for sym, gen in zip(free_group.symbols, collector.pcgs):
  240. ... free_to_perm[sym] = gen
  241. >>> G1 = PermutationGroup()
  242. >>> for w in word:
  243. ... sym = w[0]
  244. ... perm = free_to_perm[sym]
  245. ... G1 = PermutationGroup([perm] + G1.generators)
  246. >>> G2 = PermutationGroup()
  247. >>> for w in collected_word:
  248. ... sym = w[0]
  249. ... perm = free_to_perm[sym]
  250. ... G2 = PermutationGroup([perm] + G2.generators)
  251. The two are not identical, but they are equivalent:
  252. >>> G1.equals(G2), G1 == G2
  253. (True, False)
  254. See Also
  255. ========
  256. minimal_uncollected_subword
  257. """
  258. free_group = self.free_group
  259. while True:
  260. w = self.minimal_uncollected_subword(word)
  261. if not w:
  262. break
  263. low, high = self.subword_index(word, free_group.dtype(w))
  264. if low == -1:
  265. continue
  266. s1, e1 = w[0]
  267. if len(w) == 1:
  268. re = self.relative_order[self.index[s1]]
  269. q = e1 // re
  270. r = e1-q*re
  271. key = ((w[0][0], re), )
  272. key = free_group.dtype(key)
  273. if self.pc_presentation[key]:
  274. presentation = self.pc_presentation[key].array_form
  275. sym, exp = presentation[0]
  276. word_ = ((w[0][0], r), (sym, q*exp))
  277. word_ = free_group.dtype(word_)
  278. else:
  279. if r != 0:
  280. word_ = ((w[0][0], r), )
  281. word_ = free_group.dtype(word_)
  282. else:
  283. word_ = None
  284. word = word.eliminate_word(free_group.dtype(w), word_)
  285. if len(w) == 2 and w[1][1] > 0:
  286. s2, e2 = w[1]
  287. s2 = ((s2, 1), )
  288. s2 = free_group.dtype(s2)
  289. word_ = self.map_relation(free_group.dtype(w))
  290. word_ = s2*word_**e1
  291. word_ = free_group.dtype(word_)
  292. word = word.substituted_word(low, high, word_)
  293. elif len(w) == 2 and w[1][1] < 0:
  294. s2, e2 = w[1]
  295. s2 = ((s2, 1), )
  296. s2 = free_group.dtype(s2)
  297. word_ = self.map_relation(free_group.dtype(w))
  298. word_ = s2**-1*word_**e1
  299. word_ = free_group.dtype(word_)
  300. word = word.substituted_word(low, high, word_)
  301. return word
  302. def pc_relators(self):
  303. r"""
  304. Return the polycyclic presentation.
  305. Explanation
  306. ===========
  307. There are two types of relations used in polycyclic
  308. presentation.
  309. * Power relations : Power relators are of the form `x_i^{re_i}`,
  310. where `i \in \{0, \ldots, \mathrm{len(pcgs)}\}`, ``x`` represents polycyclic
  311. generator and ``re`` is the corresponding relative order.
  312. * Conjugate relations : Conjugate relators are of the form `x_j^-1x_ix_j`,
  313. where `j < i \in \{0, \ldots, \mathrm{len(pcgs)}\}`.
  314. Returns
  315. =======
  316. A dictionary with power and conjugate relations as key and
  317. their collected form as corresponding values.
  318. Notes
  319. =====
  320. Identity Permutation is mapped with empty ``()``.
  321. Examples
  322. ========
  323. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  324. >>> from sympy.combinatorics.permutations import Permutation
  325. >>> S = SymmetricGroup(49).sylow_subgroup(7)
  326. >>> der = S.derived_series()
  327. >>> G = der[len(der)-2]
  328. >>> PcGroup = G.polycyclic_group()
  329. >>> collector = PcGroup.collector
  330. >>> pcgs = PcGroup.pcgs
  331. >>> len(pcgs)
  332. 6
  333. >>> free_group = collector.free_group
  334. >>> pc_resentation = collector.pc_presentation
  335. >>> free_to_perm = {}
  336. >>> for s, g in zip(free_group.symbols, pcgs):
  337. ... free_to_perm[s] = g
  338. >>> for k, v in pc_resentation.items():
  339. ... k_array = k.array_form
  340. ... if v != ():
  341. ... v_array = v.array_form
  342. ... lhs = Permutation()
  343. ... for gen in k_array:
  344. ... s = gen[0]
  345. ... e = gen[1]
  346. ... lhs = lhs*free_to_perm[s]**e
  347. ... if v == ():
  348. ... assert lhs.is_identity
  349. ... continue
  350. ... rhs = Permutation()
  351. ... for gen in v_array:
  352. ... s = gen[0]
  353. ... e = gen[1]
  354. ... rhs = rhs*free_to_perm[s]**e
  355. ... assert lhs == rhs
  356. """
  357. free_group = self.free_group
  358. rel_order = self.relative_order
  359. pc_relators = {}
  360. perm_to_free = {}
  361. pcgs = self.pcgs
  362. for gen, s in zip(pcgs, free_group.generators):
  363. perm_to_free[gen**-1] = s**-1
  364. perm_to_free[gen] = s
  365. pcgs = pcgs[::-1]
  366. series = self.pc_series[::-1]
  367. rel_order = rel_order[::-1]
  368. collected_gens = []
  369. for i, gen in enumerate(pcgs):
  370. re = rel_order[i]
  371. relation = perm_to_free[gen]**re
  372. G = series[i]
  373. l = G.generator_product(gen**re, original = True)
  374. l.reverse()
  375. word = free_group.identity
  376. for g in l:
  377. word = word*perm_to_free[g]
  378. word = self.collected_word(word)
  379. pc_relators[relation] = word if word else ()
  380. self.pc_presentation = pc_relators
  381. collected_gens.append(gen)
  382. if len(collected_gens) > 1:
  383. conj = collected_gens[len(collected_gens)-1]
  384. conjugator = perm_to_free[conj]
  385. for j in range(len(collected_gens)-1):
  386. conjugated = perm_to_free[collected_gens[j]]
  387. relation = conjugator**-1*conjugated*conjugator
  388. gens = conj**-1*collected_gens[j]*conj
  389. l = G.generator_product(gens, original = True)
  390. l.reverse()
  391. word = free_group.identity
  392. for g in l:
  393. word = word*perm_to_free[g]
  394. word = self.collected_word(word)
  395. pc_relators[relation] = word if word else ()
  396. self.pc_presentation = pc_relators
  397. return pc_relators
  398. def exponent_vector(self, element):
  399. r"""
  400. Return the exponent vector of length equal to the
  401. length of polycyclic generating sequence.
  402. Explanation
  403. ===========
  404. For a given generator/element ``g`` of the polycyclic group,
  405. it can be represented as `g = {x_1}^{e_1}, \ldots, {x_n}^{e_n}`,
  406. where `x_i` represents polycyclic generators and ``n`` is
  407. the number of generators in the free_group equal to the length
  408. of pcgs.
  409. Parameters
  410. ==========
  411. element : Permutation
  412. Generator of a polycyclic group.
  413. Examples
  414. ========
  415. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  416. >>> from sympy.combinatorics.permutations import Permutation
  417. >>> G = SymmetricGroup(4)
  418. >>> PcGroup = G.polycyclic_group()
  419. >>> collector = PcGroup.collector
  420. >>> pcgs = PcGroup.pcgs
  421. >>> collector.exponent_vector(G[0])
  422. [1, 0, 0, 0]
  423. >>> exp = collector.exponent_vector(G[1])
  424. >>> g = Permutation()
  425. >>> for i in range(len(exp)):
  426. ... g = g*pcgs[i]**exp[i] if exp[i] else g
  427. >>> assert g == G[1]
  428. References
  429. ==========
  430. .. [1] Holt, D., Eick, B., O'Brien, E.
  431. "Handbook of Computational Group Theory"
  432. Section 8.1.1, Definition 8.4
  433. """
  434. free_group = self.free_group
  435. G = PermutationGroup()
  436. for g in self.pcgs:
  437. G = PermutationGroup([g] + G.generators)
  438. gens = G.generator_product(element, original = True)
  439. gens.reverse()
  440. perm_to_free = {}
  441. for sym, g in zip(free_group.generators, self.pcgs):
  442. perm_to_free[g**-1] = sym**-1
  443. perm_to_free[g] = sym
  444. w = free_group.identity
  445. for g in gens:
  446. w = w*perm_to_free[g]
  447. word = self.collected_word(w)
  448. index = self.index
  449. exp_vector = [0]*len(free_group)
  450. word = word.array_form
  451. for t in word:
  452. exp_vector[index[t[0]]] = t[1]
  453. return exp_vector
  454. def depth(self, element):
  455. r"""
  456. Return the depth of a given element.
  457. Explanation
  458. ===========
  459. The depth of a given element ``g`` is defined by
  460. `\mathrm{dep}[g] = i` if `e_1 = e_2 = \ldots = e_{i-1} = 0`
  461. and `e_i != 0`, where ``e`` represents the exponent-vector.
  462. Examples
  463. ========
  464. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  465. >>> G = SymmetricGroup(3)
  466. >>> PcGroup = G.polycyclic_group()
  467. >>> collector = PcGroup.collector
  468. >>> collector.depth(G[0])
  469. 2
  470. >>> collector.depth(G[1])
  471. 1
  472. References
  473. ==========
  474. .. [1] Holt, D., Eick, B., O'Brien, E.
  475. "Handbook of Computational Group Theory"
  476. Section 8.1.1, Definition 8.5
  477. """
  478. exp_vector = self.exponent_vector(element)
  479. return next((i+1 for i, x in enumerate(exp_vector) if x), len(self.pcgs)+1)
  480. def leading_exponent(self, element):
  481. r"""
  482. Return the leading non-zero exponent.
  483. Explanation
  484. ===========
  485. The leading exponent for a given element `g` is defined
  486. by `\mathrm{leading\_exponent}[g]` `= e_i`, if `\mathrm{depth}[g] = i`.
  487. Examples
  488. ========
  489. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  490. >>> G = SymmetricGroup(3)
  491. >>> PcGroup = G.polycyclic_group()
  492. >>> collector = PcGroup.collector
  493. >>> collector.leading_exponent(G[1])
  494. 1
  495. """
  496. exp_vector = self.exponent_vector(element)
  497. depth = self.depth(element)
  498. if depth != len(self.pcgs)+1:
  499. return exp_vector[depth-1]
  500. return None
  501. def _sift(self, z, g):
  502. h = g
  503. d = self.depth(h)
  504. while d < len(self.pcgs) and z[d-1] != 1:
  505. k = z[d-1]
  506. e = self.leading_exponent(h)*(self.leading_exponent(k))**-1
  507. e = e % self.relative_order[d-1]
  508. h = k**-e*h
  509. d = self.depth(h)
  510. return h
  511. def induced_pcgs(self, gens):
  512. """
  513. Parameters
  514. ==========
  515. gens : list
  516. A list of generators on which polycyclic subgroup
  517. is to be defined.
  518. Examples
  519. ========
  520. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  521. >>> S = SymmetricGroup(8)
  522. >>> G = S.sylow_subgroup(2)
  523. >>> PcGroup = G.polycyclic_group()
  524. >>> collector = PcGroup.collector
  525. >>> gens = [G[0], G[1]]
  526. >>> ipcgs = collector.induced_pcgs(gens)
  527. >>> [gen.order() for gen in ipcgs]
  528. [2, 2, 2]
  529. >>> G = S.sylow_subgroup(3)
  530. >>> PcGroup = G.polycyclic_group()
  531. >>> collector = PcGroup.collector
  532. >>> gens = [G[0], G[1]]
  533. >>> ipcgs = collector.induced_pcgs(gens)
  534. >>> [gen.order() for gen in ipcgs]
  535. [3]
  536. """
  537. z = [1]*len(self.pcgs)
  538. G = gens
  539. while G:
  540. g = G.pop(0)
  541. h = self._sift(z, g)
  542. d = self.depth(h)
  543. if d < len(self.pcgs):
  544. for gen in z:
  545. if gen != 1:
  546. G.append(h**-1*gen**-1*h*gen)
  547. z[d-1] = h;
  548. z = [gen for gen in z if gen != 1]
  549. return z
  550. def constructive_membership_test(self, ipcgs, g):
  551. """
  552. Return the exponent vector for induced pcgs.
  553. """
  554. e = [0]*len(ipcgs)
  555. h = g
  556. d = self.depth(h)
  557. for i, gen in enumerate(ipcgs):
  558. while self.depth(gen) == d:
  559. f = self.leading_exponent(h)*self.leading_exponent(gen)
  560. f = f % self.relative_order[d-1]
  561. h = gen**(-f)*h
  562. e[i] = f
  563. d = self.depth(h)
  564. if h == 1:
  565. return e
  566. return False