m2m模型翻译
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5383 lines
178 KiB

6 months ago
  1. from sympy.core.random import randrange, choice
  2. from math import log
  3. from sympy.ntheory import primefactors
  4. from sympy.core.symbol import Symbol
  5. from sympy.ntheory.factor_ import (factorint, multiplicity)
  6. from sympy.combinatorics import Permutation
  7. from sympy.combinatorics.permutations import (_af_commutes_with, _af_invert,
  8. _af_rmul, _af_rmuln, _af_pow, Cycle)
  9. from sympy.combinatorics.util import (_check_cycles_alt_sym,
  10. _distribute_gens_by_base, _orbits_transversals_from_bsgs,
  11. _handle_precomputed_bsgs, _base_ordering, _strong_gens_from_distr,
  12. _strip, _strip_af)
  13. from sympy.core import Basic
  14. from sympy.functions.combinatorial.factorials import factorial
  15. from sympy.ntheory import sieve
  16. from sympy.utilities.iterables import has_variety, is_sequence, uniq
  17. from sympy.core.random import _randrange
  18. from itertools import islice
  19. from sympy.core.sympify import _sympify
  20. rmul = Permutation.rmul_with_af
  21. _af_new = Permutation._af_new
  22. class PermutationGroup(Basic):
  23. r"""The class defining a Permutation group.
  24. Explanation
  25. ===========
  26. ``PermutationGroup([p1, p2, ..., pn])`` returns the permutation group
  27. generated by the list of permutations. This group can be supplied
  28. to Polyhedron if one desires to decorate the elements to which the
  29. indices of the permutation refer.
  30. Examples
  31. ========
  32. >>> from sympy.combinatorics import Permutation, PermutationGroup
  33. >>> from sympy.combinatorics import Polyhedron
  34. The permutations corresponding to motion of the front, right and
  35. bottom face of a $2 \times 2$ Rubik's cube are defined:
  36. >>> F = Permutation(2, 19, 21, 8)(3, 17, 20, 10)(4, 6, 7, 5)
  37. >>> R = Permutation(1, 5, 21, 14)(3, 7, 23, 12)(8, 10, 11, 9)
  38. >>> D = Permutation(6, 18, 14, 10)(7, 19, 15, 11)(20, 22, 23, 21)
  39. These are passed as permutations to PermutationGroup:
  40. >>> G = PermutationGroup(F, R, D)
  41. >>> G.order()
  42. 3674160
  43. The group can be supplied to a Polyhedron in order to track the
  44. objects being moved. An example involving the $2 \times 2$ Rubik's cube is
  45. given there, but here is a simple demonstration:
  46. >>> a = Permutation(2, 1)
  47. >>> b = Permutation(1, 0)
  48. >>> G = PermutationGroup(a, b)
  49. >>> P = Polyhedron(list('ABC'), pgroup=G)
  50. >>> P.corners
  51. (A, B, C)
  52. >>> P.rotate(0) # apply permutation 0
  53. >>> P.corners
  54. (A, C, B)
  55. >>> P.reset()
  56. >>> P.corners
  57. (A, B, C)
  58. Or one can make a permutation as a product of selected permutations
  59. and apply them to an iterable directly:
  60. >>> P10 = G.make_perm([0, 1])
  61. >>> P10('ABC')
  62. ['C', 'A', 'B']
  63. See Also
  64. ========
  65. sympy.combinatorics.polyhedron.Polyhedron,
  66. sympy.combinatorics.permutations.Permutation
  67. References
  68. ==========
  69. .. [1] Holt, D., Eick, B., O'Brien, E.
  70. "Handbook of Computational Group Theory"
  71. .. [2] Seress, A.
  72. "Permutation Group Algorithms"
  73. .. [3] https://en.wikipedia.org/wiki/Schreier_vector
  74. .. [4] https://en.wikipedia.org/wiki/Nielsen_transformation#Product_replacement_algorithm
  75. .. [5] Frank Celler, Charles R.Leedham-Green, Scott H.Murray,
  76. Alice C.Niemeyer, and E.A.O'Brien. "Generating Random
  77. Elements of a Finite Group"
  78. .. [6] https://en.wikipedia.org/wiki/Block_%28permutation_group_theory%29
  79. .. [7] http://www.algorithmist.com/index.php/Union_Find
  80. .. [8] https://en.wikipedia.org/wiki/Multiply_transitive_group#Multiply_transitive_groups
  81. .. [9] https://en.wikipedia.org/wiki/Center_%28group_theory%29
  82. .. [10] https://en.wikipedia.org/wiki/Centralizer_and_normalizer
  83. .. [11] http://groupprops.subwiki.org/wiki/Derived_subgroup
  84. .. [12] https://en.wikipedia.org/wiki/Nilpotent_group
  85. .. [13] http://www.math.colostate.edu/~hulpke/CGT/cgtnotes.pdf
  86. .. [14] https://www.gap-system.org/Manuals/doc/ref/manual.pdf
  87. """
  88. is_group = True
  89. def __new__(cls, *args, dups=True, **kwargs):
  90. """The default constructor. Accepts Cycle and Permutation forms.
  91. Removes duplicates unless ``dups`` keyword is ``False``.
  92. """
  93. if not args:
  94. args = [Permutation()]
  95. else:
  96. args = list(args[0] if is_sequence(args[0]) else args)
  97. if not args:
  98. args = [Permutation()]
  99. if any(isinstance(a, Cycle) for a in args):
  100. args = [Permutation(a) for a in args]
  101. if has_variety(a.size for a in args):
  102. degree = kwargs.pop('degree', None)
  103. if degree is None:
  104. degree = max(a.size for a in args)
  105. for i in range(len(args)):
  106. if args[i].size != degree:
  107. args[i] = Permutation(args[i], size=degree)
  108. if dups:
  109. args = list(uniq([_af_new(list(a)) for a in args]))
  110. if len(args) > 1:
  111. args = [g for g in args if not g.is_identity]
  112. return Basic.__new__(cls, *args, **kwargs)
  113. def __init__(self, *args, **kwargs):
  114. self._generators = list(self.args)
  115. self._order = None
  116. self._center = []
  117. self._is_abelian = None
  118. self._is_transitive = None
  119. self._is_sym = None
  120. self._is_alt = None
  121. self._is_primitive = None
  122. self._is_nilpotent = None
  123. self._is_solvable = None
  124. self._is_trivial = None
  125. self._transitivity_degree = None
  126. self._max_div = None
  127. self._is_perfect = None
  128. self._is_cyclic = None
  129. self._r = len(self._generators)
  130. self._degree = self._generators[0].size
  131. # these attributes are assigned after running schreier_sims
  132. self._base = []
  133. self._strong_gens = []
  134. self._strong_gens_slp = []
  135. self._basic_orbits = []
  136. self._transversals = []
  137. self._transversal_slp = []
  138. # these attributes are assigned after running _random_pr_init
  139. self._random_gens = []
  140. # finite presentation of the group as an instance of `FpGroup`
  141. self._fp_presentation = None
  142. def __getitem__(self, i):
  143. return self._generators[i]
  144. def __contains__(self, i):
  145. """Return ``True`` if *i* is contained in PermutationGroup.
  146. Examples
  147. ========
  148. >>> from sympy.combinatorics import Permutation, PermutationGroup
  149. >>> p = Permutation(1, 2, 3)
  150. >>> Permutation(3) in PermutationGroup(p)
  151. True
  152. """
  153. if not isinstance(i, Permutation):
  154. raise TypeError("A PermutationGroup contains only Permutations as "
  155. "elements, not elements of type %s" % type(i))
  156. return self.contains(i)
  157. def __len__(self):
  158. return len(self._generators)
  159. def equals(self, other):
  160. """Return ``True`` if PermutationGroup generated by elements in the
  161. group are same i.e they represent the same PermutationGroup.
  162. Examples
  163. ========
  164. >>> from sympy.combinatorics import Permutation, PermutationGroup
  165. >>> p = Permutation(0, 1, 2, 3, 4, 5)
  166. >>> G = PermutationGroup([p, p**2])
  167. >>> H = PermutationGroup([p**2, p])
  168. >>> G.generators == H.generators
  169. False
  170. >>> G.equals(H)
  171. True
  172. """
  173. if not isinstance(other, PermutationGroup):
  174. return False
  175. set_self_gens = set(self.generators)
  176. set_other_gens = set(other.generators)
  177. # before reaching the general case there are also certain
  178. # optimisation and obvious cases requiring less or no actual
  179. # computation.
  180. if set_self_gens == set_other_gens:
  181. return True
  182. # in the most general case it will check that each generator of
  183. # one group belongs to the other PermutationGroup and vice-versa
  184. for gen1 in set_self_gens:
  185. if not other.contains(gen1):
  186. return False
  187. for gen2 in set_other_gens:
  188. if not self.contains(gen2):
  189. return False
  190. return True
  191. def __mul__(self, other):
  192. """
  193. Return the direct product of two permutation groups as a permutation
  194. group.
  195. Explanation
  196. ===========
  197. This implementation realizes the direct product by shifting the index
  198. set for the generators of the second group: so if we have ``G`` acting
  199. on ``n1`` points and ``H`` acting on ``n2`` points, ``G*H`` acts on
  200. ``n1 + n2`` points.
  201. Examples
  202. ========
  203. >>> from sympy.combinatorics.named_groups import CyclicGroup
  204. >>> G = CyclicGroup(5)
  205. >>> H = G*G
  206. >>> H
  207. PermutationGroup([
  208. (9)(0 1 2 3 4),
  209. (5 6 7 8 9)])
  210. >>> H.order()
  211. 25
  212. """
  213. if isinstance(other, Permutation):
  214. return Coset(other, self, dir='+')
  215. gens1 = [perm._array_form for perm in self.generators]
  216. gens2 = [perm._array_form for perm in other.generators]
  217. n1 = self._degree
  218. n2 = other._degree
  219. start = list(range(n1))
  220. end = list(range(n1, n1 + n2))
  221. for i in range(len(gens2)):
  222. gens2[i] = [x + n1 for x in gens2[i]]
  223. gens2 = [start + gen for gen in gens2]
  224. gens1 = [gen + end for gen in gens1]
  225. together = gens1 + gens2
  226. gens = [_af_new(x) for x in together]
  227. return PermutationGroup(gens)
  228. def _random_pr_init(self, r, n, _random_prec_n=None):
  229. r"""Initialize random generators for the product replacement algorithm.
  230. Explanation
  231. ===========
  232. The implementation uses a modification of the original product
  233. replacement algorithm due to Leedham-Green, as described in [1],
  234. pp. 69-71; also, see [2], pp. 27-29 for a detailed theoretical
  235. analysis of the original product replacement algorithm, and [4].
  236. The product replacement algorithm is used for producing random,
  237. uniformly distributed elements of a group `G` with a set of generators
  238. `S`. For the initialization ``_random_pr_init``, a list ``R`` of
  239. `\max\{r, |S|\}` group generators is created as the attribute
  240. ``G._random_gens``, repeating elements of `S` if necessary, and the
  241. identity element of `G` is appended to ``R`` - we shall refer to this
  242. last element as the accumulator. Then the function ``random_pr()``
  243. is called ``n`` times, randomizing the list ``R`` while preserving
  244. the generation of `G` by ``R``. The function ``random_pr()`` itself
  245. takes two random elements ``g, h`` among all elements of ``R`` but
  246. the accumulator and replaces ``g`` with a randomly chosen element
  247. from `\{gh, g(~h), hg, (~h)g\}`. Then the accumulator is multiplied
  248. by whatever ``g`` was replaced by. The new value of the accumulator is
  249. then returned by ``random_pr()``.
  250. The elements returned will eventually (for ``n`` large enough) become
  251. uniformly distributed across `G` ([5]). For practical purposes however,
  252. the values ``n = 50, r = 11`` are suggested in [1].
  253. Notes
  254. =====
  255. THIS FUNCTION HAS SIDE EFFECTS: it changes the attribute
  256. self._random_gens
  257. See Also
  258. ========
  259. random_pr
  260. """
  261. deg = self.degree
  262. random_gens = [x._array_form for x in self.generators]
  263. k = len(random_gens)
  264. if k < r:
  265. for i in range(k, r):
  266. random_gens.append(random_gens[i - k])
  267. acc = list(range(deg))
  268. random_gens.append(acc)
  269. self._random_gens = random_gens
  270. # handle randomized input for testing purposes
  271. if _random_prec_n is None:
  272. for i in range(n):
  273. self.random_pr()
  274. else:
  275. for i in range(n):
  276. self.random_pr(_random_prec=_random_prec_n[i])
  277. def _union_find_merge(self, first, second, ranks, parents, not_rep):
  278. """Merges two classes in a union-find data structure.
  279. Explanation
  280. ===========
  281. Used in the implementation of Atkinson's algorithm as suggested in [1],
  282. pp. 83-87. The class merging process uses union by rank as an
  283. optimization. ([7])
  284. Notes
  285. =====
  286. THIS FUNCTION HAS SIDE EFFECTS: the list of class representatives,
  287. ``parents``, the list of class sizes, ``ranks``, and the list of
  288. elements that are not representatives, ``not_rep``, are changed due to
  289. class merging.
  290. See Also
  291. ========
  292. minimal_block, _union_find_rep
  293. References
  294. ==========
  295. .. [1] Holt, D., Eick, B., O'Brien, E.
  296. "Handbook of computational group theory"
  297. .. [7] http://www.algorithmist.com/index.php/Union_Find
  298. """
  299. rep_first = self._union_find_rep(first, parents)
  300. rep_second = self._union_find_rep(second, parents)
  301. if rep_first != rep_second:
  302. # union by rank
  303. if ranks[rep_first] >= ranks[rep_second]:
  304. new_1, new_2 = rep_first, rep_second
  305. else:
  306. new_1, new_2 = rep_second, rep_first
  307. total_rank = ranks[new_1] + ranks[new_2]
  308. if total_rank > self.max_div:
  309. return -1
  310. parents[new_2] = new_1
  311. ranks[new_1] = total_rank
  312. not_rep.append(new_2)
  313. return 1
  314. return 0
  315. def _union_find_rep(self, num, parents):
  316. """Find representative of a class in a union-find data structure.
  317. Explanation
  318. ===========
  319. Used in the implementation of Atkinson's algorithm as suggested in [1],
  320. pp. 83-87. After the representative of the class to which ``num``
  321. belongs is found, path compression is performed as an optimization
  322. ([7]).
  323. Notes
  324. =====
  325. THIS FUNCTION HAS SIDE EFFECTS: the list of class representatives,
  326. ``parents``, is altered due to path compression.
  327. See Also
  328. ========
  329. minimal_block, _union_find_merge
  330. References
  331. ==========
  332. .. [1] Holt, D., Eick, B., O'Brien, E.
  333. "Handbook of computational group theory"
  334. .. [7] http://www.algorithmist.com/index.php/Union_Find
  335. """
  336. rep, parent = num, parents[num]
  337. while parent != rep:
  338. rep = parent
  339. parent = parents[rep]
  340. # path compression
  341. temp, parent = num, parents[num]
  342. while parent != rep:
  343. parents[temp] = rep
  344. temp = parent
  345. parent = parents[temp]
  346. return rep
  347. @property
  348. def base(self):
  349. r"""Return a base from the Schreier-Sims algorithm.
  350. Explanation
  351. ===========
  352. For a permutation group `G`, a base is a sequence of points
  353. `B = (b_1, b_2, \dots, b_k)` such that no element of `G` apart
  354. from the identity fixes all the points in `B`. The concepts of
  355. a base and strong generating set and their applications are
  356. discussed in depth in [1], pp. 87-89 and [2], pp. 55-57.
  357. An alternative way to think of `B` is that it gives the
  358. indices of the stabilizer cosets that contain more than the
  359. identity permutation.
  360. Examples
  361. ========
  362. >>> from sympy.combinatorics import Permutation, PermutationGroup
  363. >>> G = PermutationGroup([Permutation(0, 1, 3)(2, 4)])
  364. >>> G.base
  365. [0, 2]
  366. See Also
  367. ========
  368. strong_gens, basic_transversals, basic_orbits, basic_stabilizers
  369. """
  370. if self._base == []:
  371. self.schreier_sims()
  372. return self._base
  373. def baseswap(self, base, strong_gens, pos, randomized=False,
  374. transversals=None, basic_orbits=None, strong_gens_distr=None):
  375. r"""Swap two consecutive base points in base and strong generating set.
  376. Explanation
  377. ===========
  378. If a base for a group `G` is given by `(b_1, b_2, \dots, b_k)`, this
  379. function returns a base `(b_1, b_2, \dots, b_{i+1}, b_i, \dots, b_k)`,
  380. where `i` is given by ``pos``, and a strong generating set relative
  381. to that base. The original base and strong generating set are not
  382. modified.
  383. The randomized version (default) is of Las Vegas type.
  384. Parameters
  385. ==========
  386. base, strong_gens
  387. The base and strong generating set.
  388. pos
  389. The position at which swapping is performed.
  390. randomized
  391. A switch between randomized and deterministic version.
  392. transversals
  393. The transversals for the basic orbits, if known.
  394. basic_orbits
  395. The basic orbits, if known.
  396. strong_gens_distr
  397. The strong generators distributed by basic stabilizers, if known.
  398. Returns
  399. =======
  400. (base, strong_gens)
  401. ``base`` is the new base, and ``strong_gens`` is a generating set
  402. relative to it.
  403. Examples
  404. ========
  405. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  406. >>> from sympy.combinatorics.testutil import _verify_bsgs
  407. >>> from sympy.combinatorics.perm_groups import PermutationGroup
  408. >>> S = SymmetricGroup(4)
  409. >>> S.schreier_sims()
  410. >>> S.base
  411. [0, 1, 2]
  412. >>> base, gens = S.baseswap(S.base, S.strong_gens, 1, randomized=False)
  413. >>> base, gens
  414. ([0, 2, 1],
  415. [(0 1 2 3), (3)(0 1), (1 3 2),
  416. (2 3), (1 3)])
  417. check that base, gens is a BSGS
  418. >>> S1 = PermutationGroup(gens)
  419. >>> _verify_bsgs(S1, base, gens)
  420. True
  421. See Also
  422. ========
  423. schreier_sims
  424. Notes
  425. =====
  426. The deterministic version of the algorithm is discussed in
  427. [1], pp. 102-103; the randomized version is discussed in [1], p.103, and
  428. [2], p.98. It is of Las Vegas type.
  429. Notice that [1] contains a mistake in the pseudocode and
  430. discussion of BASESWAP: on line 3 of the pseudocode,
  431. `|\beta_{i+1}^{\left\langle T\right\rangle}|` should be replaced by
  432. `|\beta_{i}^{\left\langle T\right\rangle}|`, and the same for the
  433. discussion of the algorithm.
  434. """
  435. # construct the basic orbits, generators for the stabilizer chain
  436. # and transversal elements from whatever was provided
  437. transversals, basic_orbits, strong_gens_distr = \
  438. _handle_precomputed_bsgs(base, strong_gens, transversals,
  439. basic_orbits, strong_gens_distr)
  440. base_len = len(base)
  441. degree = self.degree
  442. # size of orbit of base[pos] under the stabilizer we seek to insert
  443. # in the stabilizer chain at position pos + 1
  444. size = len(basic_orbits[pos])*len(basic_orbits[pos + 1]) \
  445. //len(_orbit(degree, strong_gens_distr[pos], base[pos + 1]))
  446. # initialize the wanted stabilizer by a subgroup
  447. if pos + 2 > base_len - 1:
  448. T = []
  449. else:
  450. T = strong_gens_distr[pos + 2][:]
  451. # randomized version
  452. if randomized is True:
  453. stab_pos = PermutationGroup(strong_gens_distr[pos])
  454. schreier_vector = stab_pos.schreier_vector(base[pos + 1])
  455. # add random elements of the stabilizer until they generate it
  456. while len(_orbit(degree, T, base[pos])) != size:
  457. new = stab_pos.random_stab(base[pos + 1],
  458. schreier_vector=schreier_vector)
  459. T.append(new)
  460. # deterministic version
  461. else:
  462. Gamma = set(basic_orbits[pos])
  463. Gamma.remove(base[pos])
  464. if base[pos + 1] in Gamma:
  465. Gamma.remove(base[pos + 1])
  466. # add elements of the stabilizer until they generate it by
  467. # ruling out member of the basic orbit of base[pos] along the way
  468. while len(_orbit(degree, T, base[pos])) != size:
  469. gamma = next(iter(Gamma))
  470. x = transversals[pos][gamma]
  471. temp = x._array_form.index(base[pos + 1]) # (~x)(base[pos + 1])
  472. if temp not in basic_orbits[pos + 1]:
  473. Gamma = Gamma - _orbit(degree, T, gamma)
  474. else:
  475. y = transversals[pos + 1][temp]
  476. el = rmul(x, y)
  477. if el(base[pos]) not in _orbit(degree, T, base[pos]):
  478. T.append(el)
  479. Gamma = Gamma - _orbit(degree, T, base[pos])
  480. # build the new base and strong generating set
  481. strong_gens_new_distr = strong_gens_distr[:]
  482. strong_gens_new_distr[pos + 1] = T
  483. base_new = base[:]
  484. base_new[pos], base_new[pos + 1] = base_new[pos + 1], base_new[pos]
  485. strong_gens_new = _strong_gens_from_distr(strong_gens_new_distr)
  486. for gen in T:
  487. if gen not in strong_gens_new:
  488. strong_gens_new.append(gen)
  489. return base_new, strong_gens_new
  490. @property
  491. def basic_orbits(self):
  492. r"""
  493. Return the basic orbits relative to a base and strong generating set.
  494. Explanation
  495. ===========
  496. If `(b_1, b_2, \dots, b_k)` is a base for a group `G`, and
  497. `G^{(i)} = G_{b_1, b_2, \dots, b_{i-1}}` is the ``i``-th basic stabilizer
  498. (so that `G^{(1)} = G`), the ``i``-th basic orbit relative to this base
  499. is the orbit of `b_i` under `G^{(i)}`. See [1], pp. 87-89 for more
  500. information.
  501. Examples
  502. ========
  503. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  504. >>> S = SymmetricGroup(4)
  505. >>> S.basic_orbits
  506. [[0, 1, 2, 3], [1, 2, 3], [2, 3]]
  507. See Also
  508. ========
  509. base, strong_gens, basic_transversals, basic_stabilizers
  510. """
  511. if self._basic_orbits == []:
  512. self.schreier_sims()
  513. return self._basic_orbits
  514. @property
  515. def basic_stabilizers(self):
  516. r"""
  517. Return a chain of stabilizers relative to a base and strong generating
  518. set.
  519. Explanation
  520. ===========
  521. The ``i``-th basic stabilizer `G^{(i)}` relative to a base
  522. `(b_1, b_2, \dots, b_k)` is `G_{b_1, b_2, \dots, b_{i-1}}`. For more
  523. information, see [1], pp. 87-89.
  524. Examples
  525. ========
  526. >>> from sympy.combinatorics.named_groups import AlternatingGroup
  527. >>> A = AlternatingGroup(4)
  528. >>> A.schreier_sims()
  529. >>> A.base
  530. [0, 1]
  531. >>> for g in A.basic_stabilizers:
  532. ... print(g)
  533. ...
  534. PermutationGroup([
  535. (3)(0 1 2),
  536. (1 2 3)])
  537. PermutationGroup([
  538. (1 2 3)])
  539. See Also
  540. ========
  541. base, strong_gens, basic_orbits, basic_transversals
  542. """
  543. if self._transversals == []:
  544. self.schreier_sims()
  545. strong_gens = self._strong_gens
  546. base = self._base
  547. if not base: # e.g. if self is trivial
  548. return []
  549. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  550. basic_stabilizers = []
  551. for gens in strong_gens_distr:
  552. basic_stabilizers.append(PermutationGroup(gens))
  553. return basic_stabilizers
  554. @property
  555. def basic_transversals(self):
  556. """
  557. Return basic transversals relative to a base and strong generating set.
  558. Explanation
  559. ===========
  560. The basic transversals are transversals of the basic orbits. They
  561. are provided as a list of dictionaries, each dictionary having
  562. keys - the elements of one of the basic orbits, and values - the
  563. corresponding transversal elements. See [1], pp. 87-89 for more
  564. information.
  565. Examples
  566. ========
  567. >>> from sympy.combinatorics.named_groups import AlternatingGroup
  568. >>> A = AlternatingGroup(4)
  569. >>> A.basic_transversals
  570. [{0: (3), 1: (3)(0 1 2), 2: (3)(0 2 1), 3: (0 3 1)}, {1: (3), 2: (1 2 3), 3: (1 3 2)}]
  571. See Also
  572. ========
  573. strong_gens, base, basic_orbits, basic_stabilizers
  574. """
  575. if self._transversals == []:
  576. self.schreier_sims()
  577. return self._transversals
  578. def composition_series(self):
  579. r"""
  580. Return the composition series for a group as a list
  581. of permutation groups.
  582. Explanation
  583. ===========
  584. The composition series for a group `G` is defined as a
  585. subnormal series `G = H_0 > H_1 > H_2 \ldots` A composition
  586. series is a subnormal series such that each factor group
  587. `H(i+1) / H(i)` is simple.
  588. A subnormal series is a composition series only if it is of
  589. maximum length.
  590. The algorithm works as follows:
  591. Starting with the derived series the idea is to fill
  592. the gap between `G = der[i]` and `H = der[i+1]` for each
  593. `i` independently. Since, all subgroups of the abelian group
  594. `G/H` are normal so, first step is to take the generators
  595. `g` of `G` and add them to generators of `H` one by one.
  596. The factor groups formed are not simple in general. Each
  597. group is obtained from the previous one by adding one
  598. generator `g`, if the previous group is denoted by `H`
  599. then the next group `K` is generated by `g` and `H`.
  600. The factor group `K/H` is cyclic and it's order is
  601. `K.order()//G.order()`. The series is then extended between
  602. `K` and `H` by groups generated by powers of `g` and `H`.
  603. The series formed is then prepended to the already existing
  604. series.
  605. Examples
  606. ========
  607. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  608. >>> from sympy.combinatorics.named_groups import CyclicGroup
  609. >>> S = SymmetricGroup(12)
  610. >>> G = S.sylow_subgroup(2)
  611. >>> C = G.composition_series()
  612. >>> [H.order() for H in C]
  613. [1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1]
  614. >>> G = S.sylow_subgroup(3)
  615. >>> C = G.composition_series()
  616. >>> [H.order() for H in C]
  617. [243, 81, 27, 9, 3, 1]
  618. >>> G = CyclicGroup(12)
  619. >>> C = G.composition_series()
  620. >>> [H.order() for H in C]
  621. [12, 6, 3, 1]
  622. """
  623. der = self.derived_series()
  624. if not all(g.is_identity for g in der[-1].generators):
  625. raise NotImplementedError('Group should be solvable')
  626. series = []
  627. for i in range(len(der)-1):
  628. H = der[i+1]
  629. up_seg = []
  630. for g in der[i].generators:
  631. K = PermutationGroup([g] + H.generators)
  632. order = K.order() // H.order()
  633. down_seg = []
  634. for p, e in factorint(order).items():
  635. for _ in range(e):
  636. down_seg.append(PermutationGroup([g] + H.generators))
  637. g = g**p
  638. up_seg = down_seg + up_seg
  639. H = K
  640. up_seg[0] = der[i]
  641. series.extend(up_seg)
  642. series.append(der[-1])
  643. return series
  644. def coset_transversal(self, H):
  645. """Return a transversal of the right cosets of self by its subgroup H
  646. using the second method described in [1], Subsection 4.6.7
  647. """
  648. if not H.is_subgroup(self):
  649. raise ValueError("The argument must be a subgroup")
  650. if H.order() == 1:
  651. return self._elements
  652. self._schreier_sims(base=H.base) # make G.base an extension of H.base
  653. base = self.base
  654. base_ordering = _base_ordering(base, self.degree)
  655. identity = Permutation(self.degree - 1)
  656. transversals = self.basic_transversals[:]
  657. # transversals is a list of dictionaries. Get rid of the keys
  658. # so that it is a list of lists and sort each list in
  659. # the increasing order of base[l]^x
  660. for l, t in enumerate(transversals):
  661. transversals[l] = sorted(t.values(),
  662. key = lambda x: base_ordering[base[l]^x])
  663. orbits = H.basic_orbits
  664. h_stabs = H.basic_stabilizers
  665. g_stabs = self.basic_stabilizers
  666. indices = [x.order()//y.order() for x, y in zip(g_stabs, h_stabs)]
  667. # T^(l) should be a right transversal of H^(l) in G^(l) for
  668. # 1<=l<=len(base). While H^(l) is the trivial group, T^(l)
  669. # contains all the elements of G^(l) so we might just as well
  670. # start with l = len(h_stabs)-1
  671. if len(g_stabs) > len(h_stabs):
  672. T = g_stabs[len(h_stabs)]._elements
  673. else:
  674. T = [identity]
  675. l = len(h_stabs)-1
  676. t_len = len(T)
  677. while l > -1:
  678. T_next = []
  679. for u in transversals[l]:
  680. if u == identity:
  681. continue
  682. b = base_ordering[base[l]^u]
  683. for t in T:
  684. p = t*u
  685. if all(base_ordering[h^p] >= b for h in orbits[l]):
  686. T_next.append(p)
  687. if t_len + len(T_next) == indices[l]:
  688. break
  689. if t_len + len(T_next) == indices[l]:
  690. break
  691. T += T_next
  692. t_len += len(T_next)
  693. l -= 1
  694. T.remove(identity)
  695. T = [identity] + T
  696. return T
  697. def _coset_representative(self, g, H):
  698. """Return the representative of Hg from the transversal that
  699. would be computed by ``self.coset_transversal(H)``.
  700. """
  701. if H.order() == 1:
  702. return g
  703. # The base of self must be an extension of H.base.
  704. if not(self.base[:len(H.base)] == H.base):
  705. self._schreier_sims(base=H.base)
  706. orbits = H.basic_orbits[:]
  707. h_transversals = [list(_.values()) for _ in H.basic_transversals]
  708. transversals = [list(_.values()) for _ in self.basic_transversals]
  709. base = self.base
  710. base_ordering = _base_ordering(base, self.degree)
  711. def step(l, x):
  712. gamma = sorted(orbits[l], key = lambda y: base_ordering[y^x])[0]
  713. i = [base[l]^h for h in h_transversals[l]].index(gamma)
  714. x = h_transversals[l][i]*x
  715. if l < len(orbits)-1:
  716. for u in transversals[l]:
  717. if base[l]^u == base[l]^x:
  718. break
  719. x = step(l+1, x*u**-1)*u
  720. return x
  721. return step(0, g)
  722. def coset_table(self, H):
  723. """Return the standardised (right) coset table of self in H as
  724. a list of lists.
  725. """
  726. # Maybe this should be made to return an instance of CosetTable
  727. # from fp_groups.py but the class would need to be changed first
  728. # to be compatible with PermutationGroups
  729. from itertools import chain, product
  730. if not H.is_subgroup(self):
  731. raise ValueError("The argument must be a subgroup")
  732. T = self.coset_transversal(H)
  733. n = len(T)
  734. A = list(chain.from_iterable((gen, gen**-1)
  735. for gen in self.generators))
  736. table = []
  737. for i in range(n):
  738. row = [self._coset_representative(T[i]*x, H) for x in A]
  739. row = [T.index(r) for r in row]
  740. table.append(row)
  741. # standardize (this is the same as the algorithm used in coset_table)
  742. # If CosetTable is made compatible with PermutationGroups, this
  743. # should be replaced by table.standardize()
  744. A = range(len(A))
  745. gamma = 1
  746. for alpha, a in product(range(n), A):
  747. beta = table[alpha][a]
  748. if beta >= gamma:
  749. if beta > gamma:
  750. for x in A:
  751. z = table[gamma][x]
  752. table[gamma][x] = table[beta][x]
  753. table[beta][x] = z
  754. for i in range(n):
  755. if table[i][x] == beta:
  756. table[i][x] = gamma
  757. elif table[i][x] == gamma:
  758. table[i][x] = beta
  759. gamma += 1
  760. if gamma >= n-1:
  761. return table
  762. def center(self):
  763. r"""
  764. Return the center of a permutation group.
  765. Explanation
  766. ===========
  767. The center for a group `G` is defined as
  768. `Z(G) = \{z\in G | \forall g\in G, zg = gz \}`,
  769. the set of elements of `G` that commute with all elements of `G`.
  770. It is equal to the centralizer of `G` inside `G`, and is naturally a
  771. subgroup of `G` ([9]).
  772. Examples
  773. ========
  774. >>> from sympy.combinatorics.named_groups import DihedralGroup
  775. >>> D = DihedralGroup(4)
  776. >>> G = D.center()
  777. >>> G.order()
  778. 2
  779. See Also
  780. ========
  781. centralizer
  782. Notes
  783. =====
  784. This is a naive implementation that is a straightforward application
  785. of ``.centralizer()``
  786. """
  787. return self.centralizer(self)
  788. def centralizer(self, other):
  789. r"""
  790. Return the centralizer of a group/set/element.
  791. Explanation
  792. ===========
  793. The centralizer of a set of permutations ``S`` inside
  794. a group ``G`` is the set of elements of ``G`` that commute with all
  795. elements of ``S``::
  796. `C_G(S) = \{ g \in G | gs = sg \forall s \in S\}` ([10])
  797. Usually, ``S`` is a subset of ``G``, but if ``G`` is a proper subgroup of
  798. the full symmetric group, we allow for ``S`` to have elements outside
  799. ``G``.
  800. It is naturally a subgroup of ``G``; the centralizer of a permutation
  801. group is equal to the centralizer of any set of generators for that
  802. group, since any element commuting with the generators commutes with
  803. any product of the generators.
  804. Parameters
  805. ==========
  806. other
  807. a permutation group/list of permutations/single permutation
  808. Examples
  809. ========
  810. >>> from sympy.combinatorics.named_groups import (SymmetricGroup,
  811. ... CyclicGroup)
  812. >>> S = SymmetricGroup(6)
  813. >>> C = CyclicGroup(6)
  814. >>> H = S.centralizer(C)
  815. >>> H.is_subgroup(C)
  816. True
  817. See Also
  818. ========
  819. subgroup_search
  820. Notes
  821. =====
  822. The implementation is an application of ``.subgroup_search()`` with
  823. tests using a specific base for the group ``G``.
  824. """
  825. if hasattr(other, 'generators'):
  826. if other.is_trivial or self.is_trivial:
  827. return self
  828. degree = self.degree
  829. identity = _af_new(list(range(degree)))
  830. orbits = other.orbits()
  831. num_orbits = len(orbits)
  832. orbits.sort(key=lambda x: -len(x))
  833. long_base = []
  834. orbit_reps = [None]*num_orbits
  835. orbit_reps_indices = [None]*num_orbits
  836. orbit_descr = [None]*degree
  837. for i in range(num_orbits):
  838. orbit = list(orbits[i])
  839. orbit_reps[i] = orbit[0]
  840. orbit_reps_indices[i] = len(long_base)
  841. for point in orbit:
  842. orbit_descr[point] = i
  843. long_base = long_base + orbit
  844. base, strong_gens = self.schreier_sims_incremental(base=long_base)
  845. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  846. i = 0
  847. for i in range(len(base)):
  848. if strong_gens_distr[i] == [identity]:
  849. break
  850. base = base[:i]
  851. base_len = i
  852. for j in range(num_orbits):
  853. if base[base_len - 1] in orbits[j]:
  854. break
  855. rel_orbits = orbits[: j + 1]
  856. num_rel_orbits = len(rel_orbits)
  857. transversals = [None]*num_rel_orbits
  858. for j in range(num_rel_orbits):
  859. rep = orbit_reps[j]
  860. transversals[j] = dict(
  861. other.orbit_transversal(rep, pairs=True))
  862. trivial_test = lambda x: True
  863. tests = [None]*base_len
  864. for l in range(base_len):
  865. if base[l] in orbit_reps:
  866. tests[l] = trivial_test
  867. else:
  868. def test(computed_words, l=l):
  869. g = computed_words[l]
  870. rep_orb_index = orbit_descr[base[l]]
  871. rep = orbit_reps[rep_orb_index]
  872. im = g._array_form[base[l]]
  873. im_rep = g._array_form[rep]
  874. tr_el = transversals[rep_orb_index][base[l]]
  875. # using the definition of transversal,
  876. # base[l]^g = rep^(tr_el*g);
  877. # if g belongs to the centralizer, then
  878. # base[l]^g = (rep^g)^tr_el
  879. return im == tr_el._array_form[im_rep]
  880. tests[l] = test
  881. def prop(g):
  882. return [rmul(g, gen) for gen in other.generators] == \
  883. [rmul(gen, g) for gen in other.generators]
  884. return self.subgroup_search(prop, base=base,
  885. strong_gens=strong_gens, tests=tests)
  886. elif hasattr(other, '__getitem__'):
  887. gens = list(other)
  888. return self.centralizer(PermutationGroup(gens))
  889. elif hasattr(other, 'array_form'):
  890. return self.centralizer(PermutationGroup([other]))
  891. def commutator(self, G, H):
  892. """
  893. Return the commutator of two subgroups.
  894. Explanation
  895. ===========
  896. For a permutation group ``K`` and subgroups ``G``, ``H``, the
  897. commutator of ``G`` and ``H`` is defined as the group generated
  898. by all the commutators `[g, h] = hgh^{-1}g^{-1}` for ``g`` in ``G`` and
  899. ``h`` in ``H``. It is naturally a subgroup of ``K`` ([1], p.27).
  900. Examples
  901. ========
  902. >>> from sympy.combinatorics.named_groups import (SymmetricGroup,
  903. ... AlternatingGroup)
  904. >>> S = SymmetricGroup(5)
  905. >>> A = AlternatingGroup(5)
  906. >>> G = S.commutator(S, A)
  907. >>> G.is_subgroup(A)
  908. True
  909. See Also
  910. ========
  911. derived_subgroup
  912. Notes
  913. =====
  914. The commutator of two subgroups `H, G` is equal to the normal closure
  915. of the commutators of all the generators, i.e. `hgh^{-1}g^{-1}` for `h`
  916. a generator of `H` and `g` a generator of `G` ([1], p.28)
  917. """
  918. ggens = G.generators
  919. hgens = H.generators
  920. commutators = []
  921. for ggen in ggens:
  922. for hgen in hgens:
  923. commutator = rmul(hgen, ggen, ~hgen, ~ggen)
  924. if commutator not in commutators:
  925. commutators.append(commutator)
  926. res = self.normal_closure(commutators)
  927. return res
  928. def coset_factor(self, g, factor_index=False):
  929. """Return ``G``'s (self's) coset factorization of ``g``
  930. Explanation
  931. ===========
  932. If ``g`` is an element of ``G`` then it can be written as the product
  933. of permutations drawn from the Schreier-Sims coset decomposition,
  934. The permutations returned in ``f`` are those for which
  935. the product gives ``g``: ``g = f[n]*...f[1]*f[0]`` where ``n = len(B)``
  936. and ``B = G.base``. f[i] is one of the permutations in
  937. ``self._basic_orbits[i]``.
  938. If factor_index==True,
  939. returns a tuple ``[b[0],..,b[n]]``, where ``b[i]``
  940. belongs to ``self._basic_orbits[i]``
  941. Examples
  942. ========
  943. >>> from sympy.combinatorics import Permutation, PermutationGroup
  944. >>> a = Permutation(0, 1, 3, 7, 6, 4)(2, 5)
  945. >>> b = Permutation(0, 1, 3, 2)(4, 5, 7, 6)
  946. >>> G = PermutationGroup([a, b])
  947. Define g:
  948. >>> g = Permutation(7)(1, 2, 4)(3, 6, 5)
  949. Confirm that it is an element of G:
  950. >>> G.contains(g)
  951. True
  952. Thus, it can be written as a product of factors (up to
  953. 3) drawn from u. See below that a factor from u1 and u2
  954. and the Identity permutation have been used:
  955. >>> f = G.coset_factor(g)
  956. >>> f[2]*f[1]*f[0] == g
  957. True
  958. >>> f1 = G.coset_factor(g, True); f1
  959. [0, 4, 4]
  960. >>> tr = G.basic_transversals
  961. >>> f[0] == tr[0][f1[0]]
  962. True
  963. If g is not an element of G then [] is returned:
  964. >>> c = Permutation(5, 6, 7)
  965. >>> G.coset_factor(c)
  966. []
  967. See Also
  968. ========
  969. sympy.combinatorics.util._strip
  970. """
  971. if isinstance(g, (Cycle, Permutation)):
  972. g = g.list()
  973. if len(g) != self._degree:
  974. # this could either adjust the size or return [] immediately
  975. # but we don't choose between the two and just signal a possible
  976. # error
  977. raise ValueError('g should be the same size as permutations of G')
  978. I = list(range(self._degree))
  979. basic_orbits = self.basic_orbits
  980. transversals = self._transversals
  981. factors = []
  982. base = self.base
  983. h = g
  984. for i in range(len(base)):
  985. beta = h[base[i]]
  986. if beta == base[i]:
  987. factors.append(beta)
  988. continue
  989. if beta not in basic_orbits[i]:
  990. return []
  991. u = transversals[i][beta]._array_form
  992. h = _af_rmul(_af_invert(u), h)
  993. factors.append(beta)
  994. if h != I:
  995. return []
  996. if factor_index:
  997. return factors
  998. tr = self.basic_transversals
  999. factors = [tr[i][factors[i]] for i in range(len(base))]
  1000. return factors
  1001. def generator_product(self, g, original=False):
  1002. r'''
  1003. Return a list of strong generators `[s1, \dots, sn]`
  1004. s.t `g = sn \times \dots \times s1`. If ``original=True``, make the
  1005. list contain only the original group generators
  1006. '''
  1007. product = []
  1008. if g.is_identity:
  1009. return []
  1010. if g in self.strong_gens:
  1011. if not original or g in self.generators:
  1012. return [g]
  1013. else:
  1014. slp = self._strong_gens_slp[g]
  1015. for s in slp:
  1016. product.extend(self.generator_product(s, original=True))
  1017. return product
  1018. elif g**-1 in self.strong_gens:
  1019. g = g**-1
  1020. if not original or g in self.generators:
  1021. return [g**-1]
  1022. else:
  1023. slp = self._strong_gens_slp[g]
  1024. for s in slp:
  1025. product.extend(self.generator_product(s, original=True))
  1026. l = len(product)
  1027. product = [product[l-i-1]**-1 for i in range(l)]
  1028. return product
  1029. f = self.coset_factor(g, True)
  1030. for i, j in enumerate(f):
  1031. slp = self._transversal_slp[i][j]
  1032. for s in slp:
  1033. if not original:
  1034. product.append(self.strong_gens[s])
  1035. else:
  1036. s = self.strong_gens[s]
  1037. product.extend(self.generator_product(s, original=True))
  1038. return product
  1039. def coset_rank(self, g):
  1040. """rank using Schreier-Sims representation.
  1041. Explanation
  1042. ===========
  1043. The coset rank of ``g`` is the ordering number in which
  1044. it appears in the lexicographic listing according to the
  1045. coset decomposition
  1046. The ordering is the same as in G.generate(method='coset').
  1047. If ``g`` does not belong to the group it returns None.
  1048. Examples
  1049. ========
  1050. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1051. >>> a = Permutation(0, 1, 3, 7, 6, 4)(2, 5)
  1052. >>> b = Permutation(0, 1, 3, 2)(4, 5, 7, 6)
  1053. >>> G = PermutationGroup([a, b])
  1054. >>> c = Permutation(7)(2, 4)(3, 5)
  1055. >>> G.coset_rank(c)
  1056. 16
  1057. >>> G.coset_unrank(16)
  1058. (7)(2 4)(3 5)
  1059. See Also
  1060. ========
  1061. coset_factor
  1062. """
  1063. factors = self.coset_factor(g, True)
  1064. if not factors:
  1065. return None
  1066. rank = 0
  1067. b = 1
  1068. transversals = self._transversals
  1069. base = self._base
  1070. basic_orbits = self._basic_orbits
  1071. for i in range(len(base)):
  1072. k = factors[i]
  1073. j = basic_orbits[i].index(k)
  1074. rank += b*j
  1075. b = b*len(transversals[i])
  1076. return rank
  1077. def coset_unrank(self, rank, af=False):
  1078. """unrank using Schreier-Sims representation
  1079. coset_unrank is the inverse operation of coset_rank
  1080. if 0 <= rank < order; otherwise it returns None.
  1081. """
  1082. if rank < 0 or rank >= self.order():
  1083. return None
  1084. base = self.base
  1085. transversals = self.basic_transversals
  1086. basic_orbits = self.basic_orbits
  1087. m = len(base)
  1088. v = [0]*m
  1089. for i in range(m):
  1090. rank, c = divmod(rank, len(transversals[i]))
  1091. v[i] = basic_orbits[i][c]
  1092. a = [transversals[i][v[i]]._array_form for i in range(m)]
  1093. h = _af_rmuln(*a)
  1094. if af:
  1095. return h
  1096. else:
  1097. return _af_new(h)
  1098. @property
  1099. def degree(self):
  1100. """Returns the size of the permutations in the group.
  1101. Explanation
  1102. ===========
  1103. The number of permutations comprising the group is given by
  1104. ``len(group)``; the number of permutations that can be generated
  1105. by the group is given by ``group.order()``.
  1106. Examples
  1107. ========
  1108. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1109. >>> a = Permutation([1, 0, 2])
  1110. >>> G = PermutationGroup([a])
  1111. >>> G.degree
  1112. 3
  1113. >>> len(G)
  1114. 1
  1115. >>> G.order()
  1116. 2
  1117. >>> list(G.generate())
  1118. [(2), (2)(0 1)]
  1119. See Also
  1120. ========
  1121. order
  1122. """
  1123. return self._degree
  1124. @property
  1125. def identity(self):
  1126. '''
  1127. Return the identity element of the permutation group.
  1128. '''
  1129. return _af_new(list(range(self.degree)))
  1130. @property
  1131. def elements(self):
  1132. """Returns all the elements of the permutation group as a set
  1133. Examples
  1134. ========
  1135. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1136. >>> p = PermutationGroup(Permutation(1, 3), Permutation(1, 2))
  1137. >>> p.elements
  1138. {(1 2 3), (1 3 2), (1 3), (2 3), (3), (3)(1 2)}
  1139. """
  1140. return set(self._elements)
  1141. @property
  1142. def _elements(self):
  1143. """Returns all the elements of the permutation group as a list
  1144. Examples
  1145. ========
  1146. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1147. >>> p = PermutationGroup(Permutation(1, 3), Permutation(1, 2))
  1148. >>> p._elements
  1149. [(3), (3)(1 2), (1 3), (2 3), (1 2 3), (1 3 2)]
  1150. """
  1151. return list(islice(self.generate(), None))
  1152. def derived_series(self):
  1153. r"""Return the derived series for the group.
  1154. Explanation
  1155. ===========
  1156. The derived series for a group `G` is defined as
  1157. `G = G_0 > G_1 > G_2 > \ldots` where `G_i = [G_{i-1}, G_{i-1}]`,
  1158. i.e. `G_i` is the derived subgroup of `G_{i-1}`, for
  1159. `i\in\mathbb{N}`. When we have `G_k = G_{k-1}` for some
  1160. `k\in\mathbb{N}`, the series terminates.
  1161. Returns
  1162. =======
  1163. A list of permutation groups containing the members of the derived
  1164. series in the order `G = G_0, G_1, G_2, \ldots`.
  1165. Examples
  1166. ========
  1167. >>> from sympy.combinatorics.named_groups import (SymmetricGroup,
  1168. ... AlternatingGroup, DihedralGroup)
  1169. >>> A = AlternatingGroup(5)
  1170. >>> len(A.derived_series())
  1171. 1
  1172. >>> S = SymmetricGroup(4)
  1173. >>> len(S.derived_series())
  1174. 4
  1175. >>> S.derived_series()[1].is_subgroup(AlternatingGroup(4))
  1176. True
  1177. >>> S.derived_series()[2].is_subgroup(DihedralGroup(2))
  1178. True
  1179. See Also
  1180. ========
  1181. derived_subgroup
  1182. """
  1183. res = [self]
  1184. current = self
  1185. nxt = self.derived_subgroup()
  1186. while not current.is_subgroup(nxt):
  1187. res.append(nxt)
  1188. current = nxt
  1189. nxt = nxt.derived_subgroup()
  1190. return res
  1191. def derived_subgroup(self):
  1192. r"""Compute the derived subgroup.
  1193. Explanation
  1194. ===========
  1195. The derived subgroup, or commutator subgroup is the subgroup generated
  1196. by all commutators `[g, h] = hgh^{-1}g^{-1}` for `g, h\in G` ; it is
  1197. equal to the normal closure of the set of commutators of the generators
  1198. ([1], p.28, [11]).
  1199. Examples
  1200. ========
  1201. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1202. >>> a = Permutation([1, 0, 2, 4, 3])
  1203. >>> b = Permutation([0, 1, 3, 2, 4])
  1204. >>> G = PermutationGroup([a, b])
  1205. >>> C = G.derived_subgroup()
  1206. >>> list(C.generate(af=True))
  1207. [[0, 1, 2, 3, 4], [0, 1, 3, 4, 2], [0, 1, 4, 2, 3]]
  1208. See Also
  1209. ========
  1210. derived_series
  1211. """
  1212. r = self._r
  1213. gens = [p._array_form for p in self.generators]
  1214. set_commutators = set()
  1215. degree = self._degree
  1216. rng = list(range(degree))
  1217. for i in range(r):
  1218. for j in range(r):
  1219. p1 = gens[i]
  1220. p2 = gens[j]
  1221. c = list(range(degree))
  1222. for k in rng:
  1223. c[p2[p1[k]]] = p1[p2[k]]
  1224. ct = tuple(c)
  1225. if ct not in set_commutators:
  1226. set_commutators.add(ct)
  1227. cms = [_af_new(p) for p in set_commutators]
  1228. G2 = self.normal_closure(cms)
  1229. return G2
  1230. def generate(self, method="coset", af=False):
  1231. """Return iterator to generate the elements of the group.
  1232. Explanation
  1233. ===========
  1234. Iteration is done with one of these methods::
  1235. method='coset' using the Schreier-Sims coset representation
  1236. method='dimino' using the Dimino method
  1237. If ``af = True`` it yields the array form of the permutations
  1238. Examples
  1239. ========
  1240. >>> from sympy.combinatorics import PermutationGroup
  1241. >>> from sympy.combinatorics.polyhedron import tetrahedron
  1242. The permutation group given in the tetrahedron object is also
  1243. true groups:
  1244. >>> G = tetrahedron.pgroup
  1245. >>> G.is_group
  1246. True
  1247. Also the group generated by the permutations in the tetrahedron
  1248. pgroup -- even the first two -- is a proper group:
  1249. >>> H = PermutationGroup(G[0], G[1])
  1250. >>> J = PermutationGroup(list(H.generate())); J
  1251. PermutationGroup([
  1252. (0 1)(2 3),
  1253. (1 2 3),
  1254. (1 3 2),
  1255. (0 3 1),
  1256. (0 2 3),
  1257. (0 3)(1 2),
  1258. (0 1 3),
  1259. (3)(0 2 1),
  1260. (0 3 2),
  1261. (3)(0 1 2),
  1262. (0 2)(1 3)])
  1263. >>> _.is_group
  1264. True
  1265. """
  1266. if method == "coset":
  1267. return self.generate_schreier_sims(af)
  1268. elif method == "dimino":
  1269. return self.generate_dimino(af)
  1270. else:
  1271. raise NotImplementedError('No generation defined for %s' % method)
  1272. def generate_dimino(self, af=False):
  1273. """Yield group elements using Dimino's algorithm.
  1274. If ``af == True`` it yields the array form of the permutations.
  1275. Examples
  1276. ========
  1277. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1278. >>> a = Permutation([0, 2, 1, 3])
  1279. >>> b = Permutation([0, 2, 3, 1])
  1280. >>> g = PermutationGroup([a, b])
  1281. >>> list(g.generate_dimino(af=True))
  1282. [[0, 1, 2, 3], [0, 2, 1, 3], [0, 2, 3, 1],
  1283. [0, 1, 3, 2], [0, 3, 2, 1], [0, 3, 1, 2]]
  1284. References
  1285. ==========
  1286. .. [1] The Implementation of Various Algorithms for Permutation Groups in
  1287. the Computer Algebra System: AXIOM, N.J. Doye, M.Sc. Thesis
  1288. """
  1289. idn = list(range(self.degree))
  1290. order = 0
  1291. element_list = [idn]
  1292. set_element_list = {tuple(idn)}
  1293. if af:
  1294. yield idn
  1295. else:
  1296. yield _af_new(idn)
  1297. gens = [p._array_form for p in self.generators]
  1298. for i in range(len(gens)):
  1299. # D elements of the subgroup G_i generated by gens[:i]
  1300. D = element_list[:]
  1301. N = [idn]
  1302. while N:
  1303. A = N
  1304. N = []
  1305. for a in A:
  1306. for g in gens[:i + 1]:
  1307. ag = _af_rmul(a, g)
  1308. if tuple(ag) not in set_element_list:
  1309. # produce G_i*g
  1310. for d in D:
  1311. order += 1
  1312. ap = _af_rmul(d, ag)
  1313. if af:
  1314. yield ap
  1315. else:
  1316. p = _af_new(ap)
  1317. yield p
  1318. element_list.append(ap)
  1319. set_element_list.add(tuple(ap))
  1320. N.append(ap)
  1321. self._order = len(element_list)
  1322. def generate_schreier_sims(self, af=False):
  1323. """Yield group elements using the Schreier-Sims representation
  1324. in coset_rank order
  1325. If ``af = True`` it yields the array form of the permutations
  1326. Examples
  1327. ========
  1328. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1329. >>> a = Permutation([0, 2, 1, 3])
  1330. >>> b = Permutation([0, 2, 3, 1])
  1331. >>> g = PermutationGroup([a, b])
  1332. >>> list(g.generate_schreier_sims(af=True))
  1333. [[0, 1, 2, 3], [0, 2, 1, 3], [0, 3, 2, 1],
  1334. [0, 1, 3, 2], [0, 2, 3, 1], [0, 3, 1, 2]]
  1335. """
  1336. n = self._degree
  1337. u = self.basic_transversals
  1338. basic_orbits = self._basic_orbits
  1339. if len(u) == 0:
  1340. for x in self.generators:
  1341. if af:
  1342. yield x._array_form
  1343. else:
  1344. yield x
  1345. return
  1346. if len(u) == 1:
  1347. for i in basic_orbits[0]:
  1348. if af:
  1349. yield u[0][i]._array_form
  1350. else:
  1351. yield u[0][i]
  1352. return
  1353. u = list(reversed(u))
  1354. basic_orbits = basic_orbits[::-1]
  1355. # stg stack of group elements
  1356. stg = [list(range(n))]
  1357. posmax = [len(x) for x in u]
  1358. n1 = len(posmax) - 1
  1359. pos = [0]*n1
  1360. h = 0
  1361. while 1:
  1362. # backtrack when finished iterating over coset
  1363. if pos[h] >= posmax[h]:
  1364. if h == 0:
  1365. return
  1366. pos[h] = 0
  1367. h -= 1
  1368. stg.pop()
  1369. continue
  1370. p = _af_rmul(u[h][basic_orbits[h][pos[h]]]._array_form, stg[-1])
  1371. pos[h] += 1
  1372. stg.append(p)
  1373. h += 1
  1374. if h == n1:
  1375. if af:
  1376. for i in basic_orbits[-1]:
  1377. p = _af_rmul(u[-1][i]._array_form, stg[-1])
  1378. yield p
  1379. else:
  1380. for i in basic_orbits[-1]:
  1381. p = _af_rmul(u[-1][i]._array_form, stg[-1])
  1382. p1 = _af_new(p)
  1383. yield p1
  1384. stg.pop()
  1385. h -= 1
  1386. @property
  1387. def generators(self):
  1388. """Returns the generators of the group.
  1389. Examples
  1390. ========
  1391. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1392. >>> a = Permutation([0, 2, 1])
  1393. >>> b = Permutation([1, 0, 2])
  1394. >>> G = PermutationGroup([a, b])
  1395. >>> G.generators
  1396. [(1 2), (2)(0 1)]
  1397. """
  1398. return self._generators
  1399. def contains(self, g, strict=True):
  1400. """Test if permutation ``g`` belong to self, ``G``.
  1401. Explanation
  1402. ===========
  1403. If ``g`` is an element of ``G`` it can be written as a product
  1404. of factors drawn from the cosets of ``G``'s stabilizers. To see
  1405. if ``g`` is one of the actual generators defining the group use
  1406. ``G.has(g)``.
  1407. If ``strict`` is not ``True``, ``g`` will be resized, if necessary,
  1408. to match the size of permutations in ``self``.
  1409. Examples
  1410. ========
  1411. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1412. >>> a = Permutation(1, 2)
  1413. >>> b = Permutation(2, 3, 1)
  1414. >>> G = PermutationGroup(a, b, degree=5)
  1415. >>> G.contains(G[0]) # trivial check
  1416. True
  1417. >>> elem = Permutation([[2, 3]], size=5)
  1418. >>> G.contains(elem)
  1419. True
  1420. >>> G.contains(Permutation(4)(0, 1, 2, 3))
  1421. False
  1422. If strict is False, a permutation will be resized, if
  1423. necessary:
  1424. >>> H = PermutationGroup(Permutation(5))
  1425. >>> H.contains(Permutation(3))
  1426. False
  1427. >>> H.contains(Permutation(3), strict=False)
  1428. True
  1429. To test if a given permutation is present in the group:
  1430. >>> elem in G.generators
  1431. False
  1432. >>> G.has(elem)
  1433. False
  1434. See Also
  1435. ========
  1436. coset_factor, sympy.core.basic.Basic.has, __contains__
  1437. """
  1438. if not isinstance(g, Permutation):
  1439. return False
  1440. if g.size != self.degree:
  1441. if strict:
  1442. return False
  1443. g = Permutation(g, size=self.degree)
  1444. if g in self.generators:
  1445. return True
  1446. return bool(self.coset_factor(g.array_form, True))
  1447. @property
  1448. def is_perfect(self):
  1449. """Return ``True`` if the group is perfect.
  1450. A group is perfect if it equals to its derived subgroup.
  1451. Examples
  1452. ========
  1453. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1454. >>> a = Permutation(1,2,3)(4,5)
  1455. >>> b = Permutation(1,2,3,4,5)
  1456. >>> G = PermutationGroup([a, b])
  1457. >>> G.is_perfect
  1458. False
  1459. """
  1460. if self._is_perfect is None:
  1461. self._is_perfect = self.equals(self.derived_subgroup())
  1462. return self._is_perfect
  1463. @property
  1464. def is_abelian(self):
  1465. """Test if the group is Abelian.
  1466. Examples
  1467. ========
  1468. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1469. >>> a = Permutation([0, 2, 1])
  1470. >>> b = Permutation([1, 0, 2])
  1471. >>> G = PermutationGroup([a, b])
  1472. >>> G.is_abelian
  1473. False
  1474. >>> a = Permutation([0, 2, 1])
  1475. >>> G = PermutationGroup([a])
  1476. >>> G.is_abelian
  1477. True
  1478. """
  1479. if self._is_abelian is not None:
  1480. return self._is_abelian
  1481. self._is_abelian = True
  1482. gens = [p._array_form for p in self.generators]
  1483. for x in gens:
  1484. for y in gens:
  1485. if y <= x:
  1486. continue
  1487. if not _af_commutes_with(x, y):
  1488. self._is_abelian = False
  1489. return False
  1490. return True
  1491. def abelian_invariants(self):
  1492. """
  1493. Returns the abelian invariants for the given group.
  1494. Let ``G`` be a nontrivial finite abelian group. Then G is isomorphic to
  1495. the direct product of finitely many nontrivial cyclic groups of
  1496. prime-power order.
  1497. Explanation
  1498. ===========
  1499. The prime-powers that occur as the orders of the factors are uniquely
  1500. determined by G. More precisely, the primes that occur in the orders of the
  1501. factors in any such decomposition of ``G`` are exactly the primes that divide
  1502. ``|G|`` and for any such prime ``p``, if the orders of the factors that are
  1503. p-groups in one such decomposition of ``G`` are ``p^{t_1} >= p^{t_2} >= ... p^{t_r}``,
  1504. then the orders of the factors that are p-groups in any such decomposition of ``G``
  1505. are ``p^{t_1} >= p^{t_2} >= ... p^{t_r}``.
  1506. The uniquely determined integers ``p^{t_1} >= p^{t_2} >= ... p^{t_r}``, taken
  1507. for all primes that divide ``|G|`` are called the invariants of the nontrivial
  1508. group ``G`` as suggested in ([14], p. 542).
  1509. Notes
  1510. =====
  1511. We adopt the convention that the invariants of a trivial group are [].
  1512. Examples
  1513. ========
  1514. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1515. >>> a = Permutation([0, 2, 1])
  1516. >>> b = Permutation([1, 0, 2])
  1517. >>> G = PermutationGroup([a, b])
  1518. >>> G.abelian_invariants()
  1519. [2]
  1520. >>> from sympy.combinatorics import CyclicGroup
  1521. >>> G = CyclicGroup(7)
  1522. >>> G.abelian_invariants()
  1523. [7]
  1524. """
  1525. if self.is_trivial:
  1526. return []
  1527. gns = self.generators
  1528. inv = []
  1529. G = self
  1530. H = G.derived_subgroup()
  1531. Hgens = H.generators
  1532. for p in primefactors(G.order()):
  1533. ranks = []
  1534. while True:
  1535. pows = []
  1536. for g in gns:
  1537. elm = g**p
  1538. if not H.contains(elm):
  1539. pows.append(elm)
  1540. K = PermutationGroup(Hgens + pows) if pows else H
  1541. r = G.order()//K.order()
  1542. G = K
  1543. gns = pows
  1544. if r == 1:
  1545. break
  1546. ranks.append(multiplicity(p, r))
  1547. if ranks:
  1548. pows = [1]*ranks[0]
  1549. for i in ranks:
  1550. for j in range(0, i):
  1551. pows[j] = pows[j]*p
  1552. inv.extend(pows)
  1553. inv.sort()
  1554. return inv
  1555. def is_elementary(self, p):
  1556. """Return ``True`` if the group is elementary abelian. An elementary
  1557. abelian group is a finite abelian group, where every nontrivial
  1558. element has order `p`, where `p` is a prime.
  1559. Examples
  1560. ========
  1561. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1562. >>> a = Permutation([0, 2, 1])
  1563. >>> G = PermutationGroup([a])
  1564. >>> G.is_elementary(2)
  1565. True
  1566. >>> a = Permutation([0, 2, 1, 3])
  1567. >>> b = Permutation([3, 1, 2, 0])
  1568. >>> G = PermutationGroup([a, b])
  1569. >>> G.is_elementary(2)
  1570. True
  1571. >>> G.is_elementary(3)
  1572. False
  1573. """
  1574. return self.is_abelian and all(g.order() == p for g in self.generators)
  1575. def _eval_is_alt_sym_naive(self, only_sym=False, only_alt=False):
  1576. """A naive test using the group order."""
  1577. if only_sym and only_alt:
  1578. raise ValueError(
  1579. "Both {} and {} cannot be set to True"
  1580. .format(only_sym, only_alt))
  1581. n = self.degree
  1582. sym_order = 1
  1583. for i in range(2, n+1):
  1584. sym_order *= i
  1585. order = self.order()
  1586. if order == sym_order:
  1587. self._is_sym = True
  1588. self._is_alt = False
  1589. if only_alt:
  1590. return False
  1591. return True
  1592. elif 2*order == sym_order:
  1593. self._is_sym = False
  1594. self._is_alt = True
  1595. if only_sym:
  1596. return False
  1597. return True
  1598. return False
  1599. def _eval_is_alt_sym_monte_carlo(self, eps=0.05, perms=None):
  1600. """A test using monte-carlo algorithm.
  1601. Parameters
  1602. ==========
  1603. eps : float, optional
  1604. The criterion for the incorrect ``False`` return.
  1605. perms : list[Permutation], optional
  1606. If explicitly given, it tests over the given candidats
  1607. for testing.
  1608. If ``None``, it randomly computes ``N_eps`` and chooses
  1609. ``N_eps`` sample of the permutation from the group.
  1610. See Also
  1611. ========
  1612. _check_cycles_alt_sym
  1613. """
  1614. if perms is None:
  1615. n = self.degree
  1616. if n < 17:
  1617. c_n = 0.34
  1618. else:
  1619. c_n = 0.57
  1620. d_n = (c_n*log(2))/log(n)
  1621. N_eps = int(-log(eps)/d_n)
  1622. perms = (self.random_pr() for i in range(N_eps))
  1623. return self._eval_is_alt_sym_monte_carlo(perms=perms)
  1624. for perm in perms:
  1625. if _check_cycles_alt_sym(perm):
  1626. return True
  1627. return False
  1628. def is_alt_sym(self, eps=0.05, _random_prec=None):
  1629. r"""Monte Carlo test for the symmetric/alternating group for degrees
  1630. >= 8.
  1631. Explanation
  1632. ===========
  1633. More specifically, it is one-sided Monte Carlo with the
  1634. answer True (i.e., G is symmetric/alternating) guaranteed to be
  1635. correct, and the answer False being incorrect with probability eps.
  1636. For degree < 8, the order of the group is checked so the test
  1637. is deterministic.
  1638. Notes
  1639. =====
  1640. The algorithm itself uses some nontrivial results from group theory and
  1641. number theory:
  1642. 1) If a transitive group ``G`` of degree ``n`` contains an element
  1643. with a cycle of length ``n/2 < p < n-2`` for ``p`` a prime, ``G`` is the
  1644. symmetric or alternating group ([1], pp. 81-82)
  1645. 2) The proportion of elements in the symmetric/alternating group having
  1646. the property described in 1) is approximately `\log(2)/\log(n)`
  1647. ([1], p.82; [2], pp. 226-227).
  1648. The helper function ``_check_cycles_alt_sym`` is used to
  1649. go over the cycles in a permutation and look for ones satisfying 1).
  1650. Examples
  1651. ========
  1652. >>> from sympy.combinatorics.named_groups import DihedralGroup
  1653. >>> D = DihedralGroup(10)
  1654. >>> D.is_alt_sym()
  1655. False
  1656. See Also
  1657. ========
  1658. _check_cycles_alt_sym
  1659. """
  1660. if _random_prec is not None:
  1661. N_eps = _random_prec['N_eps']
  1662. perms= (_random_prec[i] for i in range(N_eps))
  1663. return self._eval_is_alt_sym_monte_carlo(perms=perms)
  1664. if self._is_sym or self._is_alt:
  1665. return True
  1666. if self._is_sym is False and self._is_alt is False:
  1667. return False
  1668. n = self.degree
  1669. if n < 8:
  1670. return self._eval_is_alt_sym_naive()
  1671. elif self.is_transitive():
  1672. return self._eval_is_alt_sym_monte_carlo(eps=eps)
  1673. self._is_sym, self._is_alt = False, False
  1674. return False
  1675. @property
  1676. def is_nilpotent(self):
  1677. """Test if the group is nilpotent.
  1678. Explanation
  1679. ===========
  1680. A group `G` is nilpotent if it has a central series of finite length.
  1681. Alternatively, `G` is nilpotent if its lower central series terminates
  1682. with the trivial group. Every nilpotent group is also solvable
  1683. ([1], p.29, [12]).
  1684. Examples
  1685. ========
  1686. >>> from sympy.combinatorics.named_groups import (SymmetricGroup,
  1687. ... CyclicGroup)
  1688. >>> C = CyclicGroup(6)
  1689. >>> C.is_nilpotent
  1690. True
  1691. >>> S = SymmetricGroup(5)
  1692. >>> S.is_nilpotent
  1693. False
  1694. See Also
  1695. ========
  1696. lower_central_series, is_solvable
  1697. """
  1698. if self._is_nilpotent is None:
  1699. lcs = self.lower_central_series()
  1700. terminator = lcs[len(lcs) - 1]
  1701. gens = terminator.generators
  1702. degree = self.degree
  1703. identity = _af_new(list(range(degree)))
  1704. if all(g == identity for g in gens):
  1705. self._is_solvable = True
  1706. self._is_nilpotent = True
  1707. return True
  1708. else:
  1709. self._is_nilpotent = False
  1710. return False
  1711. else:
  1712. return self._is_nilpotent
  1713. def is_normal(self, gr, strict=True):
  1714. """Test if ``G=self`` is a normal subgroup of ``gr``.
  1715. Explanation
  1716. ===========
  1717. G is normal in gr if
  1718. for each g2 in G, g1 in gr, ``g = g1*g2*g1**-1`` belongs to G
  1719. It is sufficient to check this for each g1 in gr.generators and
  1720. g2 in G.generators.
  1721. Examples
  1722. ========
  1723. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1724. >>> a = Permutation([1, 2, 0])
  1725. >>> b = Permutation([1, 0, 2])
  1726. >>> G = PermutationGroup([a, b])
  1727. >>> G1 = PermutationGroup([a, Permutation([2, 0, 1])])
  1728. >>> G1.is_normal(G)
  1729. True
  1730. """
  1731. if not self.is_subgroup(gr, strict=strict):
  1732. return False
  1733. d_self = self.degree
  1734. d_gr = gr.degree
  1735. if self.is_trivial and (d_self == d_gr or not strict):
  1736. return True
  1737. if self._is_abelian:
  1738. return True
  1739. new_self = self.copy()
  1740. if not strict and d_self != d_gr:
  1741. if d_self < d_gr:
  1742. new_self = PermGroup(new_self.generators + [Permutation(d_gr - 1)])
  1743. else:
  1744. gr = PermGroup(gr.generators + [Permutation(d_self - 1)])
  1745. gens2 = [p._array_form for p in new_self.generators]
  1746. gens1 = [p._array_form for p in gr.generators]
  1747. for g1 in gens1:
  1748. for g2 in gens2:
  1749. p = _af_rmuln(g1, g2, _af_invert(g1))
  1750. if not new_self.coset_factor(p, True):
  1751. return False
  1752. return True
  1753. def is_primitive(self, randomized=True):
  1754. r"""Test if a group is primitive.
  1755. Explanation
  1756. ===========
  1757. A permutation group ``G`` acting on a set ``S`` is called primitive if
  1758. ``S`` contains no nontrivial block under the action of ``G``
  1759. (a block is nontrivial if its cardinality is more than ``1``).
  1760. Notes
  1761. =====
  1762. The algorithm is described in [1], p.83, and uses the function
  1763. minimal_block to search for blocks of the form `\{0, k\}` for ``k``
  1764. ranging over representatives for the orbits of `G_0`, the stabilizer of
  1765. ``0``. This algorithm has complexity `O(n^2)` where ``n`` is the degree
  1766. of the group, and will perform badly if `G_0` is small.
  1767. There are two implementations offered: one finds `G_0`
  1768. deterministically using the function ``stabilizer``, and the other
  1769. (default) produces random elements of `G_0` using ``random_stab``,
  1770. hoping that they generate a subgroup of `G_0` with not too many more
  1771. orbits than `G_0` (this is suggested in [1], p.83). Behavior is changed
  1772. by the ``randomized`` flag.
  1773. Examples
  1774. ========
  1775. >>> from sympy.combinatorics.named_groups import DihedralGroup
  1776. >>> D = DihedralGroup(10)
  1777. >>> D.is_primitive()
  1778. False
  1779. See Also
  1780. ========
  1781. minimal_block, random_stab
  1782. """
  1783. if self._is_primitive is not None:
  1784. return self._is_primitive
  1785. if self.is_transitive() is False:
  1786. return False
  1787. if randomized:
  1788. random_stab_gens = []
  1789. v = self.schreier_vector(0)
  1790. for _ in range(len(self)):
  1791. random_stab_gens.append(self.random_stab(0, v))
  1792. stab = PermutationGroup(random_stab_gens)
  1793. else:
  1794. stab = self.stabilizer(0)
  1795. orbits = stab.orbits()
  1796. for orb in orbits:
  1797. x = orb.pop()
  1798. if x != 0 and any(e != 0 for e in self.minimal_block([0, x])):
  1799. self._is_primitive = False
  1800. return False
  1801. self._is_primitive = True
  1802. return True
  1803. def minimal_blocks(self, randomized=True):
  1804. '''
  1805. For a transitive group, return the list of all minimal
  1806. block systems. If a group is intransitive, return `False`.
  1807. Examples
  1808. ========
  1809. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1810. >>> from sympy.combinatorics.named_groups import DihedralGroup
  1811. >>> DihedralGroup(6).minimal_blocks()
  1812. [[0, 1, 0, 1, 0, 1], [0, 1, 2, 0, 1, 2]]
  1813. >>> G = PermutationGroup(Permutation(1,2,5))
  1814. >>> G.minimal_blocks()
  1815. False
  1816. See Also
  1817. ========
  1818. minimal_block, is_transitive, is_primitive
  1819. '''
  1820. def _number_blocks(blocks):
  1821. # number the blocks of a block system
  1822. # in order and return the number of
  1823. # blocks and the tuple with the
  1824. # reordering
  1825. n = len(blocks)
  1826. appeared = {}
  1827. m = 0
  1828. b = [None]*n
  1829. for i in range(n):
  1830. if blocks[i] not in appeared:
  1831. appeared[blocks[i]] = m
  1832. b[i] = m
  1833. m += 1
  1834. else:
  1835. b[i] = appeared[blocks[i]]
  1836. return tuple(b), m
  1837. if not self.is_transitive():
  1838. return False
  1839. blocks = []
  1840. num_blocks = []
  1841. rep_blocks = []
  1842. if randomized:
  1843. random_stab_gens = []
  1844. v = self.schreier_vector(0)
  1845. for i in range(len(self)):
  1846. random_stab_gens.append(self.random_stab(0, v))
  1847. stab = PermutationGroup(random_stab_gens)
  1848. else:
  1849. stab = self.stabilizer(0)
  1850. orbits = stab.orbits()
  1851. for orb in orbits:
  1852. x = orb.pop()
  1853. if x != 0:
  1854. block = self.minimal_block([0, x])
  1855. num_block, _ = _number_blocks(block)
  1856. # a representative block (containing 0)
  1857. rep = {j for j in range(self.degree) if num_block[j] == 0}
  1858. # check if the system is minimal with
  1859. # respect to the already discovere ones
  1860. minimal = True
  1861. blocks_remove_mask = [False] * len(blocks)
  1862. for i, r in enumerate(rep_blocks):
  1863. if len(r) > len(rep) and rep.issubset(r):
  1864. # i-th block system is not minimal
  1865. blocks_remove_mask[i] = True
  1866. elif len(r) < len(rep) and r.issubset(rep):
  1867. # the system being checked is not minimal
  1868. minimal = False
  1869. break
  1870. # remove non-minimal representative blocks
  1871. blocks = [b for i, b in enumerate(blocks) if not blocks_remove_mask[i]]
  1872. num_blocks = [n for i, n in enumerate(num_blocks) if not blocks_remove_mask[i]]
  1873. rep_blocks = [r for i, r in enumerate(rep_blocks) if not blocks_remove_mask[i]]
  1874. if minimal and num_block not in num_blocks:
  1875. blocks.append(block)
  1876. num_blocks.append(num_block)
  1877. rep_blocks.append(rep)
  1878. return blocks
  1879. @property
  1880. def is_solvable(self):
  1881. """Test if the group is solvable.
  1882. ``G`` is solvable if its derived series terminates with the trivial
  1883. group ([1], p.29).
  1884. Examples
  1885. ========
  1886. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  1887. >>> S = SymmetricGroup(3)
  1888. >>> S.is_solvable
  1889. True
  1890. See Also
  1891. ========
  1892. is_nilpotent, derived_series
  1893. """
  1894. if self._is_solvable is None:
  1895. if self.order() % 2 != 0:
  1896. return True
  1897. ds = self.derived_series()
  1898. terminator = ds[len(ds) - 1]
  1899. gens = terminator.generators
  1900. degree = self.degree
  1901. identity = _af_new(list(range(degree)))
  1902. if all(g == identity for g in gens):
  1903. self._is_solvable = True
  1904. return True
  1905. else:
  1906. self._is_solvable = False
  1907. return False
  1908. else:
  1909. return self._is_solvable
  1910. def is_subgroup(self, G, strict=True):
  1911. """Return ``True`` if all elements of ``self`` belong to ``G``.
  1912. If ``strict`` is ``False`` then if ``self``'s degree is smaller
  1913. than ``G``'s, the elements will be resized to have the same degree.
  1914. Examples
  1915. ========
  1916. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1917. >>> from sympy.combinatorics import SymmetricGroup, CyclicGroup
  1918. Testing is strict by default: the degree of each group must be the
  1919. same:
  1920. >>> p = Permutation(0, 1, 2, 3, 4, 5)
  1921. >>> G1 = PermutationGroup([Permutation(0, 1, 2), Permutation(0, 1)])
  1922. >>> G2 = PermutationGroup([Permutation(0, 2), Permutation(0, 1, 2)])
  1923. >>> G3 = PermutationGroup([p, p**2])
  1924. >>> assert G1.order() == G2.order() == G3.order() == 6
  1925. >>> G1.is_subgroup(G2)
  1926. True
  1927. >>> G1.is_subgroup(G3)
  1928. False
  1929. >>> G3.is_subgroup(PermutationGroup(G3[1]))
  1930. False
  1931. >>> G3.is_subgroup(PermutationGroup(G3[0]))
  1932. True
  1933. To ignore the size, set ``strict`` to ``False``:
  1934. >>> S3 = SymmetricGroup(3)
  1935. >>> S5 = SymmetricGroup(5)
  1936. >>> S3.is_subgroup(S5, strict=False)
  1937. True
  1938. >>> C7 = CyclicGroup(7)
  1939. >>> G = S5*C7
  1940. >>> S5.is_subgroup(G, False)
  1941. True
  1942. >>> C7.is_subgroup(G, 0)
  1943. False
  1944. """
  1945. if isinstance(G, SymmetricPermutationGroup):
  1946. if self.degree != G.degree:
  1947. return False
  1948. return True
  1949. if not isinstance(G, PermutationGroup):
  1950. return False
  1951. if self == G or self.generators[0]==Permutation():
  1952. return True
  1953. if G.order() % self.order() != 0:
  1954. return False
  1955. if self.degree == G.degree or \
  1956. (self.degree < G.degree and not strict):
  1957. gens = self.generators
  1958. else:
  1959. return False
  1960. return all(G.contains(g, strict=strict) for g in gens)
  1961. @property
  1962. def is_polycyclic(self):
  1963. """Return ``True`` if a group is polycyclic. A group is polycyclic if
  1964. it has a subnormal series with cyclic factors. For finite groups,
  1965. this is the same as if the group is solvable.
  1966. Examples
  1967. ========
  1968. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1969. >>> a = Permutation([0, 2, 1, 3])
  1970. >>> b = Permutation([2, 0, 1, 3])
  1971. >>> G = PermutationGroup([a, b])
  1972. >>> G.is_polycyclic
  1973. True
  1974. """
  1975. return self.is_solvable
  1976. def is_transitive(self, strict=True):
  1977. """Test if the group is transitive.
  1978. Explanation
  1979. ===========
  1980. A group is transitive if it has a single orbit.
  1981. If ``strict`` is ``False`` the group is transitive if it has
  1982. a single orbit of length different from 1.
  1983. Examples
  1984. ========
  1985. >>> from sympy.combinatorics import Permutation, PermutationGroup
  1986. >>> a = Permutation([0, 2, 1, 3])
  1987. >>> b = Permutation([2, 0, 1, 3])
  1988. >>> G1 = PermutationGroup([a, b])
  1989. >>> G1.is_transitive()
  1990. False
  1991. >>> G1.is_transitive(strict=False)
  1992. True
  1993. >>> c = Permutation([2, 3, 0, 1])
  1994. >>> G2 = PermutationGroup([a, c])
  1995. >>> G2.is_transitive()
  1996. True
  1997. >>> d = Permutation([1, 0, 2, 3])
  1998. >>> e = Permutation([0, 1, 3, 2])
  1999. >>> G3 = PermutationGroup([d, e])
  2000. >>> G3.is_transitive() or G3.is_transitive(strict=False)
  2001. False
  2002. """
  2003. if self._is_transitive: # strict or not, if True then True
  2004. return self._is_transitive
  2005. if strict:
  2006. if self._is_transitive is not None: # we only store strict=True
  2007. return self._is_transitive
  2008. ans = len(self.orbit(0)) == self.degree
  2009. self._is_transitive = ans
  2010. return ans
  2011. got_orb = False
  2012. for x in self.orbits():
  2013. if len(x) > 1:
  2014. if got_orb:
  2015. return False
  2016. got_orb = True
  2017. return got_orb
  2018. @property
  2019. def is_trivial(self):
  2020. """Test if the group is the trivial group.
  2021. This is true if the group contains only the identity permutation.
  2022. Examples
  2023. ========
  2024. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2025. >>> G = PermutationGroup([Permutation([0, 1, 2])])
  2026. >>> G.is_trivial
  2027. True
  2028. """
  2029. if self._is_trivial is None:
  2030. self._is_trivial = len(self) == 1 and self[0].is_Identity
  2031. return self._is_trivial
  2032. def lower_central_series(self):
  2033. r"""Return the lower central series for the group.
  2034. The lower central series for a group `G` is the series
  2035. `G = G_0 > G_1 > G_2 > \ldots` where
  2036. `G_k = [G, G_{k-1}]`, i.e. every term after the first is equal to the
  2037. commutator of `G` and the previous term in `G1` ([1], p.29).
  2038. Returns
  2039. =======
  2040. A list of permutation groups in the order `G = G_0, G_1, G_2, \ldots`
  2041. Examples
  2042. ========
  2043. >>> from sympy.combinatorics.named_groups import (AlternatingGroup,
  2044. ... DihedralGroup)
  2045. >>> A = AlternatingGroup(4)
  2046. >>> len(A.lower_central_series())
  2047. 2
  2048. >>> A.lower_central_series()[1].is_subgroup(DihedralGroup(2))
  2049. True
  2050. See Also
  2051. ========
  2052. commutator, derived_series
  2053. """
  2054. res = [self]
  2055. current = self
  2056. nxt = self.commutator(self, current)
  2057. while not current.is_subgroup(nxt):
  2058. res.append(nxt)
  2059. current = nxt
  2060. nxt = self.commutator(self, current)
  2061. return res
  2062. @property
  2063. def max_div(self):
  2064. """Maximum proper divisor of the degree of a permutation group.
  2065. Explanation
  2066. ===========
  2067. Obviously, this is the degree divided by its minimal proper divisor
  2068. (larger than ``1``, if one exists). As it is guaranteed to be prime,
  2069. the ``sieve`` from ``sympy.ntheory`` is used.
  2070. This function is also used as an optimization tool for the functions
  2071. ``minimal_block`` and ``_union_find_merge``.
  2072. Examples
  2073. ========
  2074. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2075. >>> G = PermutationGroup([Permutation([0, 2, 1, 3])])
  2076. >>> G.max_div
  2077. 2
  2078. See Also
  2079. ========
  2080. minimal_block, _union_find_merge
  2081. """
  2082. if self._max_div is not None:
  2083. return self._max_div
  2084. n = self.degree
  2085. if n == 1:
  2086. return 1
  2087. for x in sieve:
  2088. if n % x == 0:
  2089. d = n//x
  2090. self._max_div = d
  2091. return d
  2092. def minimal_block(self, points):
  2093. r"""For a transitive group, finds the block system generated by
  2094. ``points``.
  2095. Explanation
  2096. ===========
  2097. If a group ``G`` acts on a set ``S``, a nonempty subset ``B`` of ``S``
  2098. is called a block under the action of ``G`` if for all ``g`` in ``G``
  2099. we have ``gB = B`` (``g`` fixes ``B``) or ``gB`` and ``B`` have no
  2100. common points (``g`` moves ``B`` entirely). ([1], p.23; [6]).
  2101. The distinct translates ``gB`` of a block ``B`` for ``g`` in ``G``
  2102. partition the set ``S`` and this set of translates is known as a block
  2103. system. Moreover, we obviously have that all blocks in the partition
  2104. have the same size, hence the block size divides ``|S|`` ([1], p.23).
  2105. A ``G``-congruence is an equivalence relation ``~`` on the set ``S``
  2106. such that ``a ~ b`` implies ``g(a) ~ g(b)`` for all ``g`` in ``G``.
  2107. For a transitive group, the equivalence classes of a ``G``-congruence
  2108. and the blocks of a block system are the same thing ([1], p.23).
  2109. The algorithm below checks the group for transitivity, and then finds
  2110. the ``G``-congruence generated by the pairs ``(p_0, p_1), (p_0, p_2),
  2111. ..., (p_0,p_{k-1})`` which is the same as finding the maximal block
  2112. system (i.e., the one with minimum block size) such that
  2113. ``p_0, ..., p_{k-1}`` are in the same block ([1], p.83).
  2114. It is an implementation of Atkinson's algorithm, as suggested in [1],
  2115. and manipulates an equivalence relation on the set ``S`` using a
  2116. union-find data structure. The running time is just above
  2117. `O(|points||S|)`. ([1], pp. 83-87; [7]).
  2118. Examples
  2119. ========
  2120. >>> from sympy.combinatorics.named_groups import DihedralGroup
  2121. >>> D = DihedralGroup(10)
  2122. >>> D.minimal_block([0, 5])
  2123. [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
  2124. >>> D.minimal_block([0, 1])
  2125. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  2126. See Also
  2127. ========
  2128. _union_find_rep, _union_find_merge, is_transitive, is_primitive
  2129. """
  2130. if not self.is_transitive():
  2131. return False
  2132. n = self.degree
  2133. gens = self.generators
  2134. # initialize the list of equivalence class representatives
  2135. parents = list(range(n))
  2136. ranks = [1]*n
  2137. not_rep = []
  2138. k = len(points)
  2139. # the block size must divide the degree of the group
  2140. if k > self.max_div:
  2141. return [0]*n
  2142. for i in range(k - 1):
  2143. parents[points[i + 1]] = points[0]
  2144. not_rep.append(points[i + 1])
  2145. ranks[points[0]] = k
  2146. i = 0
  2147. len_not_rep = k - 1
  2148. while i < len_not_rep:
  2149. gamma = not_rep[i]
  2150. i += 1
  2151. for gen in gens:
  2152. # find has side effects: performs path compression on the list
  2153. # of representatives
  2154. delta = self._union_find_rep(gamma, parents)
  2155. # union has side effects: performs union by rank on the list
  2156. # of representatives
  2157. temp = self._union_find_merge(gen(gamma), gen(delta), ranks,
  2158. parents, not_rep)
  2159. if temp == -1:
  2160. return [0]*n
  2161. len_not_rep += temp
  2162. for i in range(n):
  2163. # force path compression to get the final state of the equivalence
  2164. # relation
  2165. self._union_find_rep(i, parents)
  2166. # rewrite result so that block representatives are minimal
  2167. new_reps = {}
  2168. return [new_reps.setdefault(r, i) for i, r in enumerate(parents)]
  2169. def conjugacy_class(self, x):
  2170. r"""Return the conjugacy class of an element in the group.
  2171. Explanation
  2172. ===========
  2173. The conjugacy class of an element ``g`` in a group ``G`` is the set of
  2174. elements ``x`` in ``G`` that are conjugate with ``g``, i.e. for which
  2175. ``g = xax^{-1}``
  2176. for some ``a`` in ``G``.
  2177. Note that conjugacy is an equivalence relation, and therefore that
  2178. conjugacy classes are partitions of ``G``. For a list of all the
  2179. conjugacy classes of the group, use the conjugacy_classes() method.
  2180. In a permutation group, each conjugacy class corresponds to a particular
  2181. `cycle structure': for example, in ``S_3``, the conjugacy classes are:
  2182. * the identity class, ``{()}``
  2183. * all transpositions, ``{(1 2), (1 3), (2 3)}``
  2184. * all 3-cycles, ``{(1 2 3), (1 3 2)}``
  2185. Examples
  2186. ========
  2187. >>> from sympy.combinatorics import Permutation, SymmetricGroup
  2188. >>> S3 = SymmetricGroup(3)
  2189. >>> S3.conjugacy_class(Permutation(0, 1, 2))
  2190. {(0 1 2), (0 2 1)}
  2191. Notes
  2192. =====
  2193. This procedure computes the conjugacy class directly by finding the
  2194. orbit of the element under conjugation in G. This algorithm is only
  2195. feasible for permutation groups of relatively small order, but is like
  2196. the orbit() function itself in that respect.
  2197. """
  2198. # Ref: "Computing the conjugacy classes of finite groups"; Butler, G.
  2199. # Groups '93 Galway/St Andrews; edited by Campbell, C. M.
  2200. new_class = {x}
  2201. last_iteration = new_class
  2202. while len(last_iteration) > 0:
  2203. this_iteration = set()
  2204. for y in last_iteration:
  2205. for s in self.generators:
  2206. conjugated = s * y * (~s)
  2207. if conjugated not in new_class:
  2208. this_iteration.add(conjugated)
  2209. new_class.update(last_iteration)
  2210. last_iteration = this_iteration
  2211. return new_class
  2212. def conjugacy_classes(self):
  2213. r"""Return the conjugacy classes of the group.
  2214. Explanation
  2215. ===========
  2216. As described in the documentation for the .conjugacy_class() function,
  2217. conjugacy is an equivalence relation on a group G which partitions the
  2218. set of elements. This method returns a list of all these conjugacy
  2219. classes of G.
  2220. Examples
  2221. ========
  2222. >>> from sympy.combinatorics import SymmetricGroup
  2223. >>> SymmetricGroup(3).conjugacy_classes()
  2224. [{(2)}, {(0 1 2), (0 2 1)}, {(0 2), (1 2), (2)(0 1)}]
  2225. """
  2226. identity = _af_new(list(range(self.degree)))
  2227. known_elements = {identity}
  2228. classes = [known_elements.copy()]
  2229. for x in self.generate():
  2230. if x not in known_elements:
  2231. new_class = self.conjugacy_class(x)
  2232. classes.append(new_class)
  2233. known_elements.update(new_class)
  2234. return classes
  2235. def normal_closure(self, other, k=10):
  2236. r"""Return the normal closure of a subgroup/set of permutations.
  2237. Explanation
  2238. ===========
  2239. If ``S`` is a subset of a group ``G``, the normal closure of ``A`` in ``G``
  2240. is defined as the intersection of all normal subgroups of ``G`` that
  2241. contain ``A`` ([1], p.14). Alternatively, it is the group generated by
  2242. the conjugates ``x^{-1}yx`` for ``x`` a generator of ``G`` and ``y`` a
  2243. generator of the subgroup ``\left\langle S\right\rangle`` generated by
  2244. ``S`` (for some chosen generating set for ``\left\langle S\right\rangle``)
  2245. ([1], p.73).
  2246. Parameters
  2247. ==========
  2248. other
  2249. a subgroup/list of permutations/single permutation
  2250. k
  2251. an implementation-specific parameter that determines the number
  2252. of conjugates that are adjoined to ``other`` at once
  2253. Examples
  2254. ========
  2255. >>> from sympy.combinatorics.named_groups import (SymmetricGroup,
  2256. ... CyclicGroup, AlternatingGroup)
  2257. >>> S = SymmetricGroup(5)
  2258. >>> C = CyclicGroup(5)
  2259. >>> G = S.normal_closure(C)
  2260. >>> G.order()
  2261. 60
  2262. >>> G.is_subgroup(AlternatingGroup(5))
  2263. True
  2264. See Also
  2265. ========
  2266. commutator, derived_subgroup, random_pr
  2267. Notes
  2268. =====
  2269. The algorithm is described in [1], pp. 73-74; it makes use of the
  2270. generation of random elements for permutation groups by the product
  2271. replacement algorithm.
  2272. """
  2273. if hasattr(other, 'generators'):
  2274. degree = self.degree
  2275. identity = _af_new(list(range(degree)))
  2276. if all(g == identity for g in other.generators):
  2277. return other
  2278. Z = PermutationGroup(other.generators[:])
  2279. base, strong_gens = Z.schreier_sims_incremental()
  2280. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  2281. basic_orbits, basic_transversals = \
  2282. _orbits_transversals_from_bsgs(base, strong_gens_distr)
  2283. self._random_pr_init(r=10, n=20)
  2284. _loop = True
  2285. while _loop:
  2286. Z._random_pr_init(r=10, n=10)
  2287. for _ in range(k):
  2288. g = self.random_pr()
  2289. h = Z.random_pr()
  2290. conj = h^g
  2291. res = _strip(conj, base, basic_orbits, basic_transversals)
  2292. if res[0] != identity or res[1] != len(base) + 1:
  2293. gens = Z.generators
  2294. gens.append(conj)
  2295. Z = PermutationGroup(gens)
  2296. strong_gens.append(conj)
  2297. temp_base, temp_strong_gens = \
  2298. Z.schreier_sims_incremental(base, strong_gens)
  2299. base, strong_gens = temp_base, temp_strong_gens
  2300. strong_gens_distr = \
  2301. _distribute_gens_by_base(base, strong_gens)
  2302. basic_orbits, basic_transversals = \
  2303. _orbits_transversals_from_bsgs(base,
  2304. strong_gens_distr)
  2305. _loop = False
  2306. for g in self.generators:
  2307. for h in Z.generators:
  2308. conj = h^g
  2309. res = _strip(conj, base, basic_orbits,
  2310. basic_transversals)
  2311. if res[0] != identity or res[1] != len(base) + 1:
  2312. _loop = True
  2313. break
  2314. if _loop:
  2315. break
  2316. return Z
  2317. elif hasattr(other, '__getitem__'):
  2318. return self.normal_closure(PermutationGroup(other))
  2319. elif hasattr(other, 'array_form'):
  2320. return self.normal_closure(PermutationGroup([other]))
  2321. def orbit(self, alpha, action='tuples'):
  2322. r"""Compute the orbit of alpha `\{g(\alpha) | g \in G\}` as a set.
  2323. Explanation
  2324. ===========
  2325. The time complexity of the algorithm used here is `O(|Orb|*r)` where
  2326. `|Orb|` is the size of the orbit and ``r`` is the number of generators of
  2327. the group. For a more detailed analysis, see [1], p.78, [2], pp. 19-21.
  2328. Here alpha can be a single point, or a list of points.
  2329. If alpha is a single point, the ordinary orbit is computed.
  2330. if alpha is a list of points, there are three available options:
  2331. 'union' - computes the union of the orbits of the points in the list
  2332. 'tuples' - computes the orbit of the list interpreted as an ordered
  2333. tuple under the group action ( i.e., g((1,2,3)) = (g(1), g(2), g(3)) )
  2334. 'sets' - computes the orbit of the list interpreted as a sets
  2335. Examples
  2336. ========
  2337. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2338. >>> a = Permutation([1, 2, 0, 4, 5, 6, 3])
  2339. >>> G = PermutationGroup([a])
  2340. >>> G.orbit(0)
  2341. {0, 1, 2}
  2342. >>> G.orbit([0, 4], 'union')
  2343. {0, 1, 2, 3, 4, 5, 6}
  2344. See Also
  2345. ========
  2346. orbit_transversal
  2347. """
  2348. return _orbit(self.degree, self.generators, alpha, action)
  2349. def orbit_rep(self, alpha, beta, schreier_vector=None):
  2350. """Return a group element which sends ``alpha`` to ``beta``.
  2351. Explanation
  2352. ===========
  2353. If ``beta`` is not in the orbit of ``alpha``, the function returns
  2354. ``False``. This implementation makes use of the schreier vector.
  2355. For a proof of correctness, see [1], p.80
  2356. Examples
  2357. ========
  2358. >>> from sympy.combinatorics.named_groups import AlternatingGroup
  2359. >>> G = AlternatingGroup(5)
  2360. >>> G.orbit_rep(0, 4)
  2361. (0 4 1 2 3)
  2362. See Also
  2363. ========
  2364. schreier_vector
  2365. """
  2366. if schreier_vector is None:
  2367. schreier_vector = self.schreier_vector(alpha)
  2368. if schreier_vector[beta] is None:
  2369. return False
  2370. k = schreier_vector[beta]
  2371. gens = [x._array_form for x in self.generators]
  2372. a = []
  2373. while k != -1:
  2374. a.append(gens[k])
  2375. beta = gens[k].index(beta) # beta = (~gens[k])(beta)
  2376. k = schreier_vector[beta]
  2377. if a:
  2378. return _af_new(_af_rmuln(*a))
  2379. else:
  2380. return _af_new(list(range(self._degree)))
  2381. def orbit_transversal(self, alpha, pairs=False):
  2382. r"""Computes a transversal for the orbit of ``alpha`` as a set.
  2383. Explanation
  2384. ===========
  2385. For a permutation group `G`, a transversal for the orbit
  2386. `Orb = \{g(\alpha) | g \in G\}` is a set
  2387. `\{g_\beta | g_\beta(\alpha) = \beta\}` for `\beta \in Orb`.
  2388. Note that there may be more than one possible transversal.
  2389. If ``pairs`` is set to ``True``, it returns the list of pairs
  2390. `(\beta, g_\beta)`. For a proof of correctness, see [1], p.79
  2391. Examples
  2392. ========
  2393. >>> from sympy.combinatorics.named_groups import DihedralGroup
  2394. >>> G = DihedralGroup(6)
  2395. >>> G.orbit_transversal(0)
  2396. [(5), (0 1 2 3 4 5), (0 5)(1 4)(2 3), (0 2 4)(1 3 5), (5)(0 4)(1 3), (0 3)(1 4)(2 5)]
  2397. See Also
  2398. ========
  2399. orbit
  2400. """
  2401. return _orbit_transversal(self._degree, self.generators, alpha, pairs)
  2402. def orbits(self, rep=False):
  2403. """Return the orbits of ``self``, ordered according to lowest element
  2404. in each orbit.
  2405. Examples
  2406. ========
  2407. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2408. >>> a = Permutation(1, 5)(2, 3)(4, 0, 6)
  2409. >>> b = Permutation(1, 5)(3, 4)(2, 6, 0)
  2410. >>> G = PermutationGroup([a, b])
  2411. >>> G.orbits()
  2412. [{0, 2, 3, 4, 6}, {1, 5}]
  2413. """
  2414. return _orbits(self._degree, self._generators)
  2415. def order(self):
  2416. """Return the order of the group: the number of permutations that
  2417. can be generated from elements of the group.
  2418. The number of permutations comprising the group is given by
  2419. ``len(group)``; the length of each permutation in the group is
  2420. given by ``group.size``.
  2421. Examples
  2422. ========
  2423. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2424. >>> a = Permutation([1, 0, 2])
  2425. >>> G = PermutationGroup([a])
  2426. >>> G.degree
  2427. 3
  2428. >>> len(G)
  2429. 1
  2430. >>> G.order()
  2431. 2
  2432. >>> list(G.generate())
  2433. [(2), (2)(0 1)]
  2434. >>> a = Permutation([0, 2, 1])
  2435. >>> b = Permutation([1, 0, 2])
  2436. >>> G = PermutationGroup([a, b])
  2437. >>> G.order()
  2438. 6
  2439. See Also
  2440. ========
  2441. degree
  2442. """
  2443. if self._order is not None:
  2444. return self._order
  2445. if self._is_sym:
  2446. n = self._degree
  2447. self._order = factorial(n)
  2448. return self._order
  2449. if self._is_alt:
  2450. n = self._degree
  2451. self._order = factorial(n)/2
  2452. return self._order
  2453. basic_transversals = self.basic_transversals
  2454. m = 1
  2455. for x in basic_transversals:
  2456. m *= len(x)
  2457. self._order = m
  2458. return m
  2459. def index(self, H):
  2460. """
  2461. Returns the index of a permutation group.
  2462. Examples
  2463. ========
  2464. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2465. >>> a = Permutation(1,2,3)
  2466. >>> b =Permutation(3)
  2467. >>> G = PermutationGroup([a])
  2468. >>> H = PermutationGroup([b])
  2469. >>> G.index(H)
  2470. 3
  2471. """
  2472. if H.is_subgroup(self):
  2473. return self.order()//H.order()
  2474. @property
  2475. def is_symmetric(self):
  2476. """Return ``True`` if the group is symmetric.
  2477. Examples
  2478. ========
  2479. >>> from sympy.combinatorics import SymmetricGroup
  2480. >>> g = SymmetricGroup(5)
  2481. >>> g.is_symmetric
  2482. True
  2483. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2484. >>> g = PermutationGroup(
  2485. ... Permutation(0, 1, 2, 3, 4),
  2486. ... Permutation(2, 3))
  2487. >>> g.is_symmetric
  2488. True
  2489. Notes
  2490. =====
  2491. This uses a naive test involving the computation of the full
  2492. group order.
  2493. If you need more quicker taxonomy for large groups, you can use
  2494. :meth:`PermutationGroup.is_alt_sym`.
  2495. However, :meth:`PermutationGroup.is_alt_sym` may not be accurate
  2496. and is not able to distinguish between an alternating group and
  2497. a symmetric group.
  2498. See Also
  2499. ========
  2500. is_alt_sym
  2501. """
  2502. _is_sym = self._is_sym
  2503. if _is_sym is not None:
  2504. return _is_sym
  2505. n = self.degree
  2506. if n >= 8:
  2507. if self.is_transitive():
  2508. _is_alt_sym = self._eval_is_alt_sym_monte_carlo()
  2509. if _is_alt_sym:
  2510. if any(g.is_odd for g in self.generators):
  2511. self._is_sym, self._is_alt = True, False
  2512. return True
  2513. self._is_sym, self._is_alt = False, True
  2514. return False
  2515. return self._eval_is_alt_sym_naive(only_sym=True)
  2516. self._is_sym, self._is_alt = False, False
  2517. return False
  2518. return self._eval_is_alt_sym_naive(only_sym=True)
  2519. @property
  2520. def is_alternating(self):
  2521. """Return ``True`` if the group is alternating.
  2522. Examples
  2523. ========
  2524. >>> from sympy.combinatorics import AlternatingGroup
  2525. >>> g = AlternatingGroup(5)
  2526. >>> g.is_alternating
  2527. True
  2528. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2529. >>> g = PermutationGroup(
  2530. ... Permutation(0, 1, 2, 3, 4),
  2531. ... Permutation(2, 3, 4))
  2532. >>> g.is_alternating
  2533. True
  2534. Notes
  2535. =====
  2536. This uses a naive test involving the computation of the full
  2537. group order.
  2538. If you need more quicker taxonomy for large groups, you can use
  2539. :meth:`PermutationGroup.is_alt_sym`.
  2540. However, :meth:`PermutationGroup.is_alt_sym` may not be accurate
  2541. and is not able to distinguish between an alternating group and
  2542. a symmetric group.
  2543. See Also
  2544. ========
  2545. is_alt_sym
  2546. """
  2547. _is_alt = self._is_alt
  2548. if _is_alt is not None:
  2549. return _is_alt
  2550. n = self.degree
  2551. if n >= 8:
  2552. if self.is_transitive():
  2553. _is_alt_sym = self._eval_is_alt_sym_monte_carlo()
  2554. if _is_alt_sym:
  2555. if all(g.is_even for g in self.generators):
  2556. self._is_sym, self._is_alt = False, True
  2557. return True
  2558. self._is_sym, self._is_alt = True, False
  2559. return False
  2560. return self._eval_is_alt_sym_naive(only_alt=True)
  2561. self._is_sym, self._is_alt = False, False
  2562. return False
  2563. return self._eval_is_alt_sym_naive(only_alt=True)
  2564. @classmethod
  2565. def _distinct_primes_lemma(cls, primes):
  2566. """Subroutine to test if there is only one cyclic group for the
  2567. order."""
  2568. primes = sorted(primes)
  2569. l = len(primes)
  2570. for i in range(l):
  2571. for j in range(i+1, l):
  2572. if primes[j] % primes[i] == 1:
  2573. return None
  2574. return True
  2575. @property
  2576. def is_cyclic(self):
  2577. r"""
  2578. Return ``True`` if the group is Cyclic.
  2579. Examples
  2580. ========
  2581. >>> from sympy.combinatorics.named_groups import AbelianGroup
  2582. >>> G = AbelianGroup(3, 4)
  2583. >>> G.is_cyclic
  2584. True
  2585. >>> G = AbelianGroup(4, 4)
  2586. >>> G.is_cyclic
  2587. False
  2588. Notes
  2589. =====
  2590. If the order of a group $n$ can be factored into the distinct
  2591. primes $p_1, p_2, \dots , p_s$ and if
  2592. .. math::
  2593. \forall i, j \in \{1, 2, \dots, s \}:
  2594. p_i \not \equiv 1 \pmod {p_j}
  2595. holds true, there is only one group of the order $n$ which
  2596. is a cyclic group [1]_. This is a generalization of the lemma
  2597. that the group of order $15, 35, \dots$ are cyclic.
  2598. And also, these additional lemmas can be used to test if a
  2599. group is cyclic if the order of the group is already found.
  2600. - If the group is abelian and the order of the group is
  2601. square-free, the group is cyclic.
  2602. - If the order of the group is less than $6$ and is not $4$, the
  2603. group is cyclic.
  2604. - If the order of the group is prime, the group is cyclic.
  2605. References
  2606. ==========
  2607. .. [1] 1978: John S. Rose: A Course on Group Theory,
  2608. Introduction to Finite Group Theory: 1.4
  2609. """
  2610. if self._is_cyclic is not None:
  2611. return self._is_cyclic
  2612. if len(self.generators) == 1:
  2613. self._is_cyclic = True
  2614. self._is_abelian = True
  2615. return True
  2616. if self._is_abelian is False:
  2617. self._is_cyclic = False
  2618. return False
  2619. order = self.order()
  2620. if order < 6:
  2621. self._is_abelian = True
  2622. if order != 4:
  2623. self._is_cyclic = True
  2624. return True
  2625. factors = factorint(order)
  2626. if all(v == 1 for v in factors.values()):
  2627. if self._is_abelian:
  2628. self._is_cyclic = True
  2629. return True
  2630. primes = list(factors.keys())
  2631. if PermutationGroup._distinct_primes_lemma(primes) is True:
  2632. self._is_cyclic = True
  2633. self._is_abelian = True
  2634. return True
  2635. for p in factors:
  2636. pgens = []
  2637. for g in self.generators:
  2638. pgens.append(g**p)
  2639. if self.index(self.subgroup(pgens)) != p:
  2640. self._is_cyclic = False
  2641. return False
  2642. self._is_cyclic = True
  2643. self._is_abelian = True
  2644. return True
  2645. def pointwise_stabilizer(self, points, incremental=True):
  2646. r"""Return the pointwise stabilizer for a set of points.
  2647. Explanation
  2648. ===========
  2649. For a permutation group `G` and a set of points
  2650. `\{p_1, p_2,\ldots, p_k\}`, the pointwise stabilizer of
  2651. `p_1, p_2, \ldots, p_k` is defined as
  2652. `G_{p_1,\ldots, p_k} =
  2653. \{g\in G | g(p_i) = p_i \forall i\in\{1, 2,\ldots,k\}\}` ([1],p20).
  2654. It is a subgroup of `G`.
  2655. Examples
  2656. ========
  2657. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  2658. >>> S = SymmetricGroup(7)
  2659. >>> Stab = S.pointwise_stabilizer([2, 3, 5])
  2660. >>> Stab.is_subgroup(S.stabilizer(2).stabilizer(3).stabilizer(5))
  2661. True
  2662. See Also
  2663. ========
  2664. stabilizer, schreier_sims_incremental
  2665. Notes
  2666. =====
  2667. When incremental == True,
  2668. rather than the obvious implementation using successive calls to
  2669. ``.stabilizer()``, this uses the incremental Schreier-Sims algorithm
  2670. to obtain a base with starting segment - the given points.
  2671. """
  2672. if incremental:
  2673. base, strong_gens = self.schreier_sims_incremental(base=points)
  2674. stab_gens = []
  2675. degree = self.degree
  2676. for gen in strong_gens:
  2677. if [gen(point) for point in points] == points:
  2678. stab_gens.append(gen)
  2679. if not stab_gens:
  2680. stab_gens = _af_new(list(range(degree)))
  2681. return PermutationGroup(stab_gens)
  2682. else:
  2683. gens = self._generators
  2684. degree = self.degree
  2685. for x in points:
  2686. gens = _stabilizer(degree, gens, x)
  2687. return PermutationGroup(gens)
  2688. def make_perm(self, n, seed=None):
  2689. """
  2690. Multiply ``n`` randomly selected permutations from
  2691. pgroup together, starting with the identity
  2692. permutation. If ``n`` is a list of integers, those
  2693. integers will be used to select the permutations and they
  2694. will be applied in L to R order: make_perm((A, B, C)) will
  2695. give CBA(I) where I is the identity permutation.
  2696. ``seed`` is used to set the seed for the random selection
  2697. of permutations from pgroup. If this is a list of integers,
  2698. the corresponding permutations from pgroup will be selected
  2699. in the order give. This is mainly used for testing purposes.
  2700. Examples
  2701. ========
  2702. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2703. >>> a, b = [Permutation([1, 0, 3, 2]), Permutation([1, 3, 0, 2])]
  2704. >>> G = PermutationGroup([a, b])
  2705. >>> G.make_perm(1, [0])
  2706. (0 1)(2 3)
  2707. >>> G.make_perm(3, [0, 1, 0])
  2708. (0 2 3 1)
  2709. >>> G.make_perm([0, 1, 0])
  2710. (0 2 3 1)
  2711. See Also
  2712. ========
  2713. random
  2714. """
  2715. if is_sequence(n):
  2716. if seed is not None:
  2717. raise ValueError('If n is a sequence, seed should be None')
  2718. n, seed = len(n), n
  2719. else:
  2720. try:
  2721. n = int(n)
  2722. except TypeError:
  2723. raise ValueError('n must be an integer or a sequence.')
  2724. randomrange = _randrange(seed)
  2725. # start with the identity permutation
  2726. result = Permutation(list(range(self.degree)))
  2727. m = len(self)
  2728. for _ in range(n):
  2729. p = self[randomrange(m)]
  2730. result = rmul(result, p)
  2731. return result
  2732. def random(self, af=False):
  2733. """Return a random group element
  2734. """
  2735. rank = randrange(self.order())
  2736. return self.coset_unrank(rank, af)
  2737. def random_pr(self, gen_count=11, iterations=50, _random_prec=None):
  2738. """Return a random group element using product replacement.
  2739. Explanation
  2740. ===========
  2741. For the details of the product replacement algorithm, see
  2742. ``_random_pr_init`` In ``random_pr`` the actual 'product replacement'
  2743. is performed. Notice that if the attribute ``_random_gens``
  2744. is empty, it needs to be initialized by ``_random_pr_init``.
  2745. See Also
  2746. ========
  2747. _random_pr_init
  2748. """
  2749. if self._random_gens == []:
  2750. self._random_pr_init(gen_count, iterations)
  2751. random_gens = self._random_gens
  2752. r = len(random_gens) - 1
  2753. # handle randomized input for testing purposes
  2754. if _random_prec is None:
  2755. s = randrange(r)
  2756. t = randrange(r - 1)
  2757. if t == s:
  2758. t = r - 1
  2759. x = choice([1, 2])
  2760. e = choice([-1, 1])
  2761. else:
  2762. s = _random_prec['s']
  2763. t = _random_prec['t']
  2764. if t == s:
  2765. t = r - 1
  2766. x = _random_prec['x']
  2767. e = _random_prec['e']
  2768. if x == 1:
  2769. random_gens[s] = _af_rmul(random_gens[s], _af_pow(random_gens[t], e))
  2770. random_gens[r] = _af_rmul(random_gens[r], random_gens[s])
  2771. else:
  2772. random_gens[s] = _af_rmul(_af_pow(random_gens[t], e), random_gens[s])
  2773. random_gens[r] = _af_rmul(random_gens[s], random_gens[r])
  2774. return _af_new(random_gens[r])
  2775. def random_stab(self, alpha, schreier_vector=None, _random_prec=None):
  2776. """Random element from the stabilizer of ``alpha``.
  2777. The schreier vector for ``alpha`` is an optional argument used
  2778. for speeding up repeated calls. The algorithm is described in [1], p.81
  2779. See Also
  2780. ========
  2781. random_pr, orbit_rep
  2782. """
  2783. if schreier_vector is None:
  2784. schreier_vector = self.schreier_vector(alpha)
  2785. if _random_prec is None:
  2786. rand = self.random_pr()
  2787. else:
  2788. rand = _random_prec['rand']
  2789. beta = rand(alpha)
  2790. h = self.orbit_rep(alpha, beta, schreier_vector)
  2791. return rmul(~h, rand)
  2792. def schreier_sims(self):
  2793. """Schreier-Sims algorithm.
  2794. Explanation
  2795. ===========
  2796. It computes the generators of the chain of stabilizers
  2797. `G > G_{b_1} > .. > G_{b1,..,b_r} > 1`
  2798. in which `G_{b_1,..,b_i}` stabilizes `b_1,..,b_i`,
  2799. and the corresponding ``s`` cosets.
  2800. An element of the group can be written as the product
  2801. `h_1*..*h_s`.
  2802. We use the incremental Schreier-Sims algorithm.
  2803. Examples
  2804. ========
  2805. >>> from sympy.combinatorics import Permutation, PermutationGroup
  2806. >>> a = Permutation([0, 2, 1])
  2807. >>> b = Permutation([1, 0, 2])
  2808. >>> G = PermutationGroup([a, b])
  2809. >>> G.schreier_sims()
  2810. >>> G.basic_transversals
  2811. [{0: (2)(0 1), 1: (2), 2: (1 2)},
  2812. {0: (2), 2: (0 2)}]
  2813. """
  2814. if self._transversals:
  2815. return
  2816. self._schreier_sims()
  2817. return
  2818. def _schreier_sims(self, base=None):
  2819. schreier = self.schreier_sims_incremental(base=base, slp_dict=True)
  2820. base, strong_gens = schreier[:2]
  2821. self._base = base
  2822. self._strong_gens = strong_gens
  2823. self._strong_gens_slp = schreier[2]
  2824. if not base:
  2825. self._transversals = []
  2826. self._basic_orbits = []
  2827. return
  2828. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  2829. basic_orbits, transversals, slps = _orbits_transversals_from_bsgs(base,\
  2830. strong_gens_distr, slp=True)
  2831. # rewrite the indices stored in slps in terms of strong_gens
  2832. for i, slp in enumerate(slps):
  2833. gens = strong_gens_distr[i]
  2834. for k in slp:
  2835. slp[k] = [strong_gens.index(gens[s]) for s in slp[k]]
  2836. self._transversals = transversals
  2837. self._basic_orbits = [sorted(x) for x in basic_orbits]
  2838. self._transversal_slp = slps
  2839. def schreier_sims_incremental(self, base=None, gens=None, slp_dict=False):
  2840. """Extend a sequence of points and generating set to a base and strong
  2841. generating set.
  2842. Parameters
  2843. ==========
  2844. base
  2845. The sequence of points to be extended to a base. Optional
  2846. parameter with default value ``[]``.
  2847. gens
  2848. The generating set to be extended to a strong generating set
  2849. relative to the base obtained. Optional parameter with default
  2850. value ``self.generators``.
  2851. slp_dict
  2852. If `True`, return a dictionary `{g: gens}` for each strong
  2853. generator `g` where `gens` is a list of strong generators
  2854. coming before `g` in `strong_gens`, such that the product
  2855. of the elements of `gens` is equal to `g`.
  2856. Returns
  2857. =======
  2858. (base, strong_gens)
  2859. ``base`` is the base obtained, and ``strong_gens`` is the strong
  2860. generating set relative to it. The original parameters ``base``,
  2861. ``gens`` remain unchanged.
  2862. Examples
  2863. ========
  2864. >>> from sympy.combinatorics.named_groups import AlternatingGroup
  2865. >>> from sympy.combinatorics.testutil import _verify_bsgs
  2866. >>> A = AlternatingGroup(7)
  2867. >>> base = [2, 3]
  2868. >>> seq = [2, 3]
  2869. >>> base, strong_gens = A.schreier_sims_incremental(base=seq)
  2870. >>> _verify_bsgs(A, base, strong_gens)
  2871. True
  2872. >>> base[:2]
  2873. [2, 3]
  2874. Notes
  2875. =====
  2876. This version of the Schreier-Sims algorithm runs in polynomial time.
  2877. There are certain assumptions in the implementation - if the trivial
  2878. group is provided, ``base`` and ``gens`` are returned immediately,
  2879. as any sequence of points is a base for the trivial group. If the
  2880. identity is present in the generators ``gens``, it is removed as
  2881. it is a redundant generator.
  2882. The implementation is described in [1], pp. 90-93.
  2883. See Also
  2884. ========
  2885. schreier_sims, schreier_sims_random
  2886. """
  2887. if base is None:
  2888. base = []
  2889. if gens is None:
  2890. gens = self.generators[:]
  2891. degree = self.degree
  2892. id_af = list(range(degree))
  2893. # handle the trivial group
  2894. if len(gens) == 1 and gens[0].is_Identity:
  2895. if slp_dict:
  2896. return base, gens, {gens[0]: [gens[0]]}
  2897. return base, gens
  2898. # prevent side effects
  2899. _base, _gens = base[:], gens[:]
  2900. # remove the identity as a generator
  2901. _gens = [x for x in _gens if not x.is_Identity]
  2902. # make sure no generator fixes all base points
  2903. for gen in _gens:
  2904. if all(x == gen._array_form[x] for x in _base):
  2905. for new in id_af:
  2906. if gen._array_form[new] != new:
  2907. break
  2908. else:
  2909. assert None # can this ever happen?
  2910. _base.append(new)
  2911. # distribute generators according to basic stabilizers
  2912. strong_gens_distr = _distribute_gens_by_base(_base, _gens)
  2913. strong_gens_slp = []
  2914. # initialize the basic stabilizers, basic orbits and basic transversals
  2915. orbs = {}
  2916. transversals = {}
  2917. slps = {}
  2918. base_len = len(_base)
  2919. for i in range(base_len):
  2920. transversals[i], slps[i] = _orbit_transversal(degree, strong_gens_distr[i],
  2921. _base[i], pairs=True, af=True, slp=True)
  2922. transversals[i] = dict(transversals[i])
  2923. orbs[i] = list(transversals[i].keys())
  2924. # main loop: amend the stabilizer chain until we have generators
  2925. # for all stabilizers
  2926. i = base_len - 1
  2927. while i >= 0:
  2928. # this flag is used to continue with the main loop from inside
  2929. # a nested loop
  2930. continue_i = False
  2931. # test the generators for being a strong generating set
  2932. db = {}
  2933. for beta, u_beta in list(transversals[i].items()):
  2934. for j, gen in enumerate(strong_gens_distr[i]):
  2935. gb = gen._array_form[beta]
  2936. u1 = transversals[i][gb]
  2937. g1 = _af_rmul(gen._array_form, u_beta)
  2938. slp = [(i, g) for g in slps[i][beta]]
  2939. slp = [(i, j)] + slp
  2940. if g1 != u1:
  2941. # test if the schreier generator is in the i+1-th
  2942. # would-be basic stabilizer
  2943. y = True
  2944. try:
  2945. u1_inv = db[gb]
  2946. except KeyError:
  2947. u1_inv = db[gb] = _af_invert(u1)
  2948. schreier_gen = _af_rmul(u1_inv, g1)
  2949. u1_inv_slp = slps[i][gb][:]
  2950. u1_inv_slp.reverse()
  2951. u1_inv_slp = [(i, (g,)) for g in u1_inv_slp]
  2952. slp = u1_inv_slp + slp
  2953. h, j, slp = _strip_af(schreier_gen, _base, orbs, transversals, i, slp=slp, slps=slps)
  2954. if j <= base_len:
  2955. # new strong generator h at level j
  2956. y = False
  2957. elif h:
  2958. # h fixes all base points
  2959. y = False
  2960. moved = 0
  2961. while h[moved] == moved:
  2962. moved += 1
  2963. _base.append(moved)
  2964. base_len += 1
  2965. strong_gens_distr.append([])
  2966. if y is False:
  2967. # if a new strong generator is found, update the
  2968. # data structures and start over
  2969. h = _af_new(h)
  2970. strong_gens_slp.append((h, slp))
  2971. for l in range(i + 1, j):
  2972. strong_gens_distr[l].append(h)
  2973. transversals[l], slps[l] =\
  2974. _orbit_transversal(degree, strong_gens_distr[l],
  2975. _base[l], pairs=True, af=True, slp=True)
  2976. transversals[l] = dict(transversals[l])
  2977. orbs[l] = list(transversals[l].keys())
  2978. i = j - 1
  2979. # continue main loop using the flag
  2980. continue_i = True
  2981. if continue_i is True:
  2982. break
  2983. if continue_i is True:
  2984. break
  2985. if continue_i is True:
  2986. continue
  2987. i -= 1
  2988. strong_gens = _gens[:]
  2989. if slp_dict:
  2990. # create the list of the strong generators strong_gens and
  2991. # rewrite the indices of strong_gens_slp in terms of the
  2992. # elements of strong_gens
  2993. for k, slp in strong_gens_slp:
  2994. strong_gens.append(k)
  2995. for i in range(len(slp)):
  2996. s = slp[i]
  2997. if isinstance(s[1], tuple):
  2998. slp[i] = strong_gens_distr[s[0]][s[1][0]]**-1
  2999. else:
  3000. slp[i] = strong_gens_distr[s[0]][s[1]]
  3001. strong_gens_slp = dict(strong_gens_slp)
  3002. # add the original generators
  3003. for g in _gens:
  3004. strong_gens_slp[g] = [g]
  3005. return (_base, strong_gens, strong_gens_slp)
  3006. strong_gens.extend([k for k, _ in strong_gens_slp])
  3007. return _base, strong_gens
  3008. def schreier_sims_random(self, base=None, gens=None, consec_succ=10,
  3009. _random_prec=None):
  3010. r"""Randomized Schreier-Sims algorithm.
  3011. Explanation
  3012. ===========
  3013. The randomized Schreier-Sims algorithm takes the sequence ``base``
  3014. and the generating set ``gens``, and extends ``base`` to a base, and
  3015. ``gens`` to a strong generating set relative to that base with
  3016. probability of a wrong answer at most `2^{-consec\_succ}`,
  3017. provided the random generators are sufficiently random.
  3018. Parameters
  3019. ==========
  3020. base
  3021. The sequence to be extended to a base.
  3022. gens
  3023. The generating set to be extended to a strong generating set.
  3024. consec_succ
  3025. The parameter defining the probability of a wrong answer.
  3026. _random_prec
  3027. An internal parameter used for testing purposes.
  3028. Returns
  3029. =======
  3030. (base, strong_gens)
  3031. ``base`` is the base and ``strong_gens`` is the strong generating
  3032. set relative to it.
  3033. Examples
  3034. ========
  3035. >>> from sympy.combinatorics.testutil import _verify_bsgs
  3036. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  3037. >>> S = SymmetricGroup(5)
  3038. >>> base, strong_gens = S.schreier_sims_random(consec_succ=5)
  3039. >>> _verify_bsgs(S, base, strong_gens) #doctest: +SKIP
  3040. True
  3041. Notes
  3042. =====
  3043. The algorithm is described in detail in [1], pp. 97-98. It extends
  3044. the orbits ``orbs`` and the permutation groups ``stabs`` to
  3045. basic orbits and basic stabilizers for the base and strong generating
  3046. set produced in the end.
  3047. The idea of the extension process
  3048. is to "sift" random group elements through the stabilizer chain
  3049. and amend the stabilizers/orbits along the way when a sift
  3050. is not successful.
  3051. The helper function ``_strip`` is used to attempt
  3052. to decompose a random group element according to the current
  3053. state of the stabilizer chain and report whether the element was
  3054. fully decomposed (successful sift) or not (unsuccessful sift). In
  3055. the latter case, the level at which the sift failed is reported and
  3056. used to amend ``stabs``, ``base``, ``gens`` and ``orbs`` accordingly.
  3057. The halting condition is for ``consec_succ`` consecutive successful
  3058. sifts to pass. This makes sure that the current ``base`` and ``gens``
  3059. form a BSGS with probability at least `1 - 1/\text{consec\_succ}`.
  3060. See Also
  3061. ========
  3062. schreier_sims
  3063. """
  3064. if base is None:
  3065. base = []
  3066. if gens is None:
  3067. gens = self.generators
  3068. base_len = len(base)
  3069. n = self.degree
  3070. # make sure no generator fixes all base points
  3071. for gen in gens:
  3072. if all(gen(x) == x for x in base):
  3073. new = 0
  3074. while gen._array_form[new] == new:
  3075. new += 1
  3076. base.append(new)
  3077. base_len += 1
  3078. # distribute generators according to basic stabilizers
  3079. strong_gens_distr = _distribute_gens_by_base(base, gens)
  3080. # initialize the basic stabilizers, basic transversals and basic orbits
  3081. transversals = {}
  3082. orbs = {}
  3083. for i in range(base_len):
  3084. transversals[i] = dict(_orbit_transversal(n, strong_gens_distr[i],
  3085. base[i], pairs=True))
  3086. orbs[i] = list(transversals[i].keys())
  3087. # initialize the number of consecutive elements sifted
  3088. c = 0
  3089. # start sifting random elements while the number of consecutive sifts
  3090. # is less than consec_succ
  3091. while c < consec_succ:
  3092. if _random_prec is None:
  3093. g = self.random_pr()
  3094. else:
  3095. g = _random_prec['g'].pop()
  3096. h, j = _strip(g, base, orbs, transversals)
  3097. y = True
  3098. # determine whether a new base point is needed
  3099. if j <= base_len:
  3100. y = False
  3101. elif not h.is_Identity:
  3102. y = False
  3103. moved = 0
  3104. while h(moved) == moved:
  3105. moved += 1
  3106. base.append(moved)
  3107. base_len += 1
  3108. strong_gens_distr.append([])
  3109. # if the element doesn't sift, amend the strong generators and
  3110. # associated stabilizers and orbits
  3111. if y is False:
  3112. for l in range(1, j):
  3113. strong_gens_distr[l].append(h)
  3114. transversals[l] = dict(_orbit_transversal(n,
  3115. strong_gens_distr[l], base[l], pairs=True))
  3116. orbs[l] = list(transversals[l].keys())
  3117. c = 0
  3118. else:
  3119. c += 1
  3120. # build the strong generating set
  3121. strong_gens = strong_gens_distr[0][:]
  3122. for gen in strong_gens_distr[1]:
  3123. if gen not in strong_gens:
  3124. strong_gens.append(gen)
  3125. return base, strong_gens
  3126. def schreier_vector(self, alpha):
  3127. """Computes the schreier vector for ``alpha``.
  3128. Explanation
  3129. ===========
  3130. The Schreier vector efficiently stores information
  3131. about the orbit of ``alpha``. It can later be used to quickly obtain
  3132. elements of the group that send ``alpha`` to a particular element
  3133. in the orbit. Notice that the Schreier vector depends on the order
  3134. in which the group generators are listed. For a definition, see [3].
  3135. Since list indices start from zero, we adopt the convention to use
  3136. "None" instead of 0 to signify that an element doesn't belong
  3137. to the orbit.
  3138. For the algorithm and its correctness, see [2], pp.78-80.
  3139. Examples
  3140. ========
  3141. >>> from sympy.combinatorics import Permutation, PermutationGroup
  3142. >>> a = Permutation([2, 4, 6, 3, 1, 5, 0])
  3143. >>> b = Permutation([0, 1, 3, 5, 4, 6, 2])
  3144. >>> G = PermutationGroup([a, b])
  3145. >>> G.schreier_vector(0)
  3146. [-1, None, 0, 1, None, 1, 0]
  3147. See Also
  3148. ========
  3149. orbit
  3150. """
  3151. n = self.degree
  3152. v = [None]*n
  3153. v[alpha] = -1
  3154. orb = [alpha]
  3155. used = [False]*n
  3156. used[alpha] = True
  3157. gens = self.generators
  3158. r = len(gens)
  3159. for b in orb:
  3160. for i in range(r):
  3161. temp = gens[i]._array_form[b]
  3162. if used[temp] is False:
  3163. orb.append(temp)
  3164. used[temp] = True
  3165. v[temp] = i
  3166. return v
  3167. def stabilizer(self, alpha):
  3168. r"""Return the stabilizer subgroup of ``alpha``.
  3169. Explanation
  3170. ===========
  3171. The stabilizer of `\alpha` is the group `G_\alpha =
  3172. \{g \in G | g(\alpha) = \alpha\}`.
  3173. For a proof of correctness, see [1], p.79.
  3174. Examples
  3175. ========
  3176. >>> from sympy.combinatorics.named_groups import DihedralGroup
  3177. >>> G = DihedralGroup(6)
  3178. >>> G.stabilizer(5)
  3179. PermutationGroup([
  3180. (5)(0 4)(1 3)])
  3181. See Also
  3182. ========
  3183. orbit
  3184. """
  3185. return PermGroup(_stabilizer(self._degree, self._generators, alpha))
  3186. @property
  3187. def strong_gens(self):
  3188. r"""Return a strong generating set from the Schreier-Sims algorithm.
  3189. Explanation
  3190. ===========
  3191. A generating set `S = \{g_1, g_2, \dots, g_t\}` for a permutation group
  3192. `G` is a strong generating set relative to the sequence of points
  3193. (referred to as a "base") `(b_1, b_2, \dots, b_k)` if, for
  3194. `1 \leq i \leq k` we have that the intersection of the pointwise
  3195. stabilizer `G^{(i+1)} := G_{b_1, b_2, \dots, b_i}` with `S` generates
  3196. the pointwise stabilizer `G^{(i+1)}`. The concepts of a base and
  3197. strong generating set and their applications are discussed in depth
  3198. in [1], pp. 87-89 and [2], pp. 55-57.
  3199. Examples
  3200. ========
  3201. >>> from sympy.combinatorics.named_groups import DihedralGroup
  3202. >>> D = DihedralGroup(4)
  3203. >>> D.strong_gens
  3204. [(0 1 2 3), (0 3)(1 2), (1 3)]
  3205. >>> D.base
  3206. [0, 1]
  3207. See Also
  3208. ========
  3209. base, basic_transversals, basic_orbits, basic_stabilizers
  3210. """
  3211. if self._strong_gens == []:
  3212. self.schreier_sims()
  3213. return self._strong_gens
  3214. def subgroup(self, gens):
  3215. """
  3216. Return the subgroup generated by `gens` which is a list of
  3217. elements of the group
  3218. """
  3219. if not all(g in self for g in gens):
  3220. raise ValueError("The group doesn't contain the supplied generators")
  3221. G = PermutationGroup(gens)
  3222. return G
  3223. def subgroup_search(self, prop, base=None, strong_gens=None, tests=None,
  3224. init_subgroup=None):
  3225. """Find the subgroup of all elements satisfying the property ``prop``.
  3226. Explanation
  3227. ===========
  3228. This is done by a depth-first search with respect to base images that
  3229. uses several tests to prune the search tree.
  3230. Parameters
  3231. ==========
  3232. prop
  3233. The property to be used. Has to be callable on group elements
  3234. and always return ``True`` or ``False``. It is assumed that
  3235. all group elements satisfying ``prop`` indeed form a subgroup.
  3236. base
  3237. A base for the supergroup.
  3238. strong_gens
  3239. A strong generating set for the supergroup.
  3240. tests
  3241. A list of callables of length equal to the length of ``base``.
  3242. These are used to rule out group elements by partial base images,
  3243. so that ``tests[l](g)`` returns False if the element ``g`` is known
  3244. not to satisfy prop base on where g sends the first ``l + 1`` base
  3245. points.
  3246. init_subgroup
  3247. if a subgroup of the sought group is
  3248. known in advance, it can be passed to the function as this
  3249. parameter.
  3250. Returns
  3251. =======
  3252. res
  3253. The subgroup of all elements satisfying ``prop``. The generating
  3254. set for this group is guaranteed to be a strong generating set
  3255. relative to the base ``base``.
  3256. Examples
  3257. ========
  3258. >>> from sympy.combinatorics.named_groups import (SymmetricGroup,
  3259. ... AlternatingGroup)
  3260. >>> from sympy.combinatorics.testutil import _verify_bsgs
  3261. >>> S = SymmetricGroup(7)
  3262. >>> prop_even = lambda x: x.is_even
  3263. >>> base, strong_gens = S.schreier_sims_incremental()
  3264. >>> G = S.subgroup_search(prop_even, base=base, strong_gens=strong_gens)
  3265. >>> G.is_subgroup(AlternatingGroup(7))
  3266. True
  3267. >>> _verify_bsgs(G, base, G.generators)
  3268. True
  3269. Notes
  3270. =====
  3271. This function is extremely lengthy and complicated and will require
  3272. some careful attention. The implementation is described in
  3273. [1], pp. 114-117, and the comments for the code here follow the lines
  3274. of the pseudocode in the book for clarity.
  3275. The complexity is exponential in general, since the search process by
  3276. itself visits all members of the supergroup. However, there are a lot
  3277. of tests which are used to prune the search tree, and users can define
  3278. their own tests via the ``tests`` parameter, so in practice, and for
  3279. some computations, it's not terrible.
  3280. A crucial part in the procedure is the frequent base change performed
  3281. (this is line 11 in the pseudocode) in order to obtain a new basic
  3282. stabilizer. The book mentiones that this can be done by using
  3283. ``.baseswap(...)``, however the current implementation uses a more
  3284. straightforward way to find the next basic stabilizer - calling the
  3285. function ``.stabilizer(...)`` on the previous basic stabilizer.
  3286. """
  3287. # initialize BSGS and basic group properties
  3288. def get_reps(orbits):
  3289. # get the minimal element in the base ordering
  3290. return [min(orbit, key = lambda x: base_ordering[x]) \
  3291. for orbit in orbits]
  3292. def update_nu(l):
  3293. temp_index = len(basic_orbits[l]) + 1 -\
  3294. len(res_basic_orbits_init_base[l])
  3295. # this corresponds to the element larger than all points
  3296. if temp_index >= len(sorted_orbits[l]):
  3297. nu[l] = base_ordering[degree]
  3298. else:
  3299. nu[l] = sorted_orbits[l][temp_index]
  3300. if base is None:
  3301. base, strong_gens = self.schreier_sims_incremental()
  3302. base_len = len(base)
  3303. degree = self.degree
  3304. identity = _af_new(list(range(degree)))
  3305. base_ordering = _base_ordering(base, degree)
  3306. # add an element larger than all points
  3307. base_ordering.append(degree)
  3308. # add an element smaller than all points
  3309. base_ordering.append(-1)
  3310. # compute BSGS-related structures
  3311. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  3312. basic_orbits, transversals = _orbits_transversals_from_bsgs(base,
  3313. strong_gens_distr)
  3314. # handle subgroup initialization and tests
  3315. if init_subgroup is None:
  3316. init_subgroup = PermutationGroup([identity])
  3317. if tests is None:
  3318. trivial_test = lambda x: True
  3319. tests = []
  3320. for i in range(base_len):
  3321. tests.append(trivial_test)
  3322. # line 1: more initializations.
  3323. res = init_subgroup
  3324. f = base_len - 1
  3325. l = base_len - 1
  3326. # line 2: set the base for K to the base for G
  3327. res_base = base[:]
  3328. # line 3: compute BSGS and related structures for K
  3329. res_base, res_strong_gens = res.schreier_sims_incremental(
  3330. base=res_base)
  3331. res_strong_gens_distr = _distribute_gens_by_base(res_base,
  3332. res_strong_gens)
  3333. res_generators = res.generators
  3334. res_basic_orbits_init_base = \
  3335. [_orbit(degree, res_strong_gens_distr[i], res_base[i])\
  3336. for i in range(base_len)]
  3337. # initialize orbit representatives
  3338. orbit_reps = [None]*base_len
  3339. # line 4: orbit representatives for f-th basic stabilizer of K
  3340. orbits = _orbits(degree, res_strong_gens_distr[f])
  3341. orbit_reps[f] = get_reps(orbits)
  3342. # line 5: remove the base point from the representatives to avoid
  3343. # getting the identity element as a generator for K
  3344. orbit_reps[f].remove(base[f])
  3345. # line 6: more initializations
  3346. c = [0]*base_len
  3347. u = [identity]*base_len
  3348. sorted_orbits = [None]*base_len
  3349. for i in range(base_len):
  3350. sorted_orbits[i] = basic_orbits[i][:]
  3351. sorted_orbits[i].sort(key=lambda point: base_ordering[point])
  3352. # line 7: initializations
  3353. mu = [None]*base_len
  3354. nu = [None]*base_len
  3355. # this corresponds to the element smaller than all points
  3356. mu[l] = degree + 1
  3357. update_nu(l)
  3358. # initialize computed words
  3359. computed_words = [identity]*base_len
  3360. # line 8: main loop
  3361. while True:
  3362. # apply all the tests
  3363. while l < base_len - 1 and \
  3364. computed_words[l](base[l]) in orbit_reps[l] and \
  3365. base_ordering[mu[l]] < \
  3366. base_ordering[computed_words[l](base[l])] < \
  3367. base_ordering[nu[l]] and \
  3368. tests[l](computed_words):
  3369. # line 11: change the (partial) base of K
  3370. new_point = computed_words[l](base[l])
  3371. res_base[l] = new_point
  3372. new_stab_gens = _stabilizer(degree, res_strong_gens_distr[l],
  3373. new_point)
  3374. res_strong_gens_distr[l + 1] = new_stab_gens
  3375. # line 12: calculate minimal orbit representatives for the
  3376. # l+1-th basic stabilizer
  3377. orbits = _orbits(degree, new_stab_gens)
  3378. orbit_reps[l + 1] = get_reps(orbits)
  3379. # line 13: amend sorted orbits
  3380. l += 1
  3381. temp_orbit = [computed_words[l - 1](point) for point
  3382. in basic_orbits[l]]
  3383. temp_orbit.sort(key=lambda point: base_ordering[point])
  3384. sorted_orbits[l] = temp_orbit
  3385. # lines 14 and 15: update variables used minimality tests
  3386. new_mu = degree + 1
  3387. for i in range(l):
  3388. if base[l] in res_basic_orbits_init_base[i]:
  3389. candidate = computed_words[i](base[i])
  3390. if base_ordering[candidate] > base_ordering[new_mu]:
  3391. new_mu = candidate
  3392. mu[l] = new_mu
  3393. update_nu(l)
  3394. # line 16: determine the new transversal element
  3395. c[l] = 0
  3396. temp_point = sorted_orbits[l][c[l]]
  3397. gamma = computed_words[l - 1]._array_form.index(temp_point)
  3398. u[l] = transversals[l][gamma]
  3399. # update computed words
  3400. computed_words[l] = rmul(computed_words[l - 1], u[l])
  3401. # lines 17 & 18: apply the tests to the group element found
  3402. g = computed_words[l]
  3403. temp_point = g(base[l])
  3404. if l == base_len - 1 and \
  3405. base_ordering[mu[l]] < \
  3406. base_ordering[temp_point] < base_ordering[nu[l]] and \
  3407. temp_point in orbit_reps[l] and \
  3408. tests[l](computed_words) and \
  3409. prop(g):
  3410. # line 19: reset the base of K
  3411. res_generators.append(g)
  3412. res_base = base[:]
  3413. # line 20: recalculate basic orbits (and transversals)
  3414. res_strong_gens.append(g)
  3415. res_strong_gens_distr = _distribute_gens_by_base(res_base,
  3416. res_strong_gens)
  3417. res_basic_orbits_init_base = \
  3418. [_orbit(degree, res_strong_gens_distr[i], res_base[i]) \
  3419. for i in range(base_len)]
  3420. # line 21: recalculate orbit representatives
  3421. # line 22: reset the search depth
  3422. orbit_reps[f] = get_reps(orbits)
  3423. l = f
  3424. # line 23: go up the tree until in the first branch not fully
  3425. # searched
  3426. while l >= 0 and c[l] == len(basic_orbits[l]) - 1:
  3427. l = l - 1
  3428. # line 24: if the entire tree is traversed, return K
  3429. if l == -1:
  3430. return PermutationGroup(res_generators)
  3431. # lines 25-27: update orbit representatives
  3432. if l < f:
  3433. # line 26
  3434. f = l
  3435. c[l] = 0
  3436. # line 27
  3437. temp_orbits = _orbits(degree, res_strong_gens_distr[f])
  3438. orbit_reps[f] = get_reps(temp_orbits)
  3439. # line 28: update variables used for minimality testing
  3440. mu[l] = degree + 1
  3441. temp_index = len(basic_orbits[l]) + 1 - \
  3442. len(res_basic_orbits_init_base[l])
  3443. if temp_index >= len(sorted_orbits[l]):
  3444. nu[l] = base_ordering[degree]
  3445. else:
  3446. nu[l] = sorted_orbits[l][temp_index]
  3447. # line 29: set the next element from the current branch and update
  3448. # accordingly
  3449. c[l] += 1
  3450. if l == 0:
  3451. gamma = sorted_orbits[l][c[l]]
  3452. else:
  3453. gamma = computed_words[l - 1]._array_form.index(sorted_orbits[l][c[l]])
  3454. u[l] = transversals[l][gamma]
  3455. if l == 0:
  3456. computed_words[l] = u[l]
  3457. else:
  3458. computed_words[l] = rmul(computed_words[l - 1], u[l])
  3459. @property
  3460. def transitivity_degree(self):
  3461. r"""Compute the degree of transitivity of the group.
  3462. Explanation
  3463. ===========
  3464. A permutation group `G` acting on `\Omega = \{0, 1, \dots, n-1\}` is
  3465. ``k``-fold transitive, if, for any `k` points
  3466. `(a_1, a_2, \dots, a_k) \in \Omega` and any `k` points
  3467. `(b_1, b_2, \dots, b_k) \in \Omega` there exists `g \in G` such that
  3468. `g(a_1) = b_1, g(a_2) = b_2, \dots, g(a_k) = b_k`
  3469. The degree of transitivity of `G` is the maximum ``k`` such that
  3470. `G` is ``k``-fold transitive. ([8])
  3471. Examples
  3472. ========
  3473. >>> from sympy.combinatorics import Permutation, PermutationGroup
  3474. >>> a = Permutation([1, 2, 0])
  3475. >>> b = Permutation([1, 0, 2])
  3476. >>> G = PermutationGroup([a, b])
  3477. >>> G.transitivity_degree
  3478. 3
  3479. See Also
  3480. ========
  3481. is_transitive, orbit
  3482. """
  3483. if self._transitivity_degree is None:
  3484. n = self.degree
  3485. G = self
  3486. # if G is k-transitive, a tuple (a_0,..,a_k)
  3487. # can be brought to (b_0,...,b_(k-1), b_k)
  3488. # where b_0,...,b_(k-1) are fixed points;
  3489. # consider the group G_k which stabilizes b_0,...,b_(k-1)
  3490. # if G_k is transitive on the subset excluding b_0,...,b_(k-1)
  3491. # then G is (k+1)-transitive
  3492. for i in range(n):
  3493. orb = G.orbit(i)
  3494. if len(orb) != n - i:
  3495. self._transitivity_degree = i
  3496. return i
  3497. G = G.stabilizer(i)
  3498. self._transitivity_degree = n
  3499. return n
  3500. else:
  3501. return self._transitivity_degree
  3502. def _p_elements_group(self, p):
  3503. '''
  3504. For an abelian p-group, return the subgroup consisting of
  3505. all elements of order p (and the identity)
  3506. '''
  3507. gens = self.generators[:]
  3508. gens = sorted(gens, key=lambda x: x.order(), reverse=True)
  3509. gens_p = [g**(g.order()/p) for g in gens]
  3510. gens_r = []
  3511. for i in range(len(gens)):
  3512. x = gens[i]
  3513. x_order = x.order()
  3514. # x_p has order p
  3515. x_p = x**(x_order/p)
  3516. if i > 0:
  3517. P = PermutationGroup(gens_p[:i])
  3518. else:
  3519. P = PermutationGroup(self.identity)
  3520. if x**(x_order/p) not in P:
  3521. gens_r.append(x**(x_order/p))
  3522. else:
  3523. # replace x by an element of order (x.order()/p)
  3524. # so that gens still generates G
  3525. g = P.generator_product(x_p, original=True)
  3526. for s in g:
  3527. x = x*s**-1
  3528. x_order = x_order/p
  3529. # insert x to gens so that the sorting is preserved
  3530. del gens[i]
  3531. del gens_p[i]
  3532. j = i - 1
  3533. while j < len(gens) and gens[j].order() >= x_order:
  3534. j += 1
  3535. gens = gens[:j] + [x] + gens[j:]
  3536. gens_p = gens_p[:j] + [x] + gens_p[j:]
  3537. return PermutationGroup(gens_r)
  3538. def _sylow_alt_sym(self, p):
  3539. '''
  3540. Return a p-Sylow subgroup of a symmetric or an
  3541. alternating group.
  3542. Explanation
  3543. ===========
  3544. The algorithm for this is hinted at in [1], Chapter 4,
  3545. Exercise 4.
  3546. For Sym(n) with n = p^i, the idea is as follows. Partition
  3547. the interval [0..n-1] into p equal parts, each of length p^(i-1):
  3548. [0..p^(i-1)-1], [p^(i-1)..2*p^(i-1)-1]...[(p-1)*p^(i-1)..p^i-1].
  3549. Find a p-Sylow subgroup of Sym(p^(i-1)) (treated as a subgroup
  3550. of ``self``) acting on each of the parts. Call the subgroups
  3551. P_1, P_2...P_p. The generators for the subgroups P_2...P_p
  3552. can be obtained from those of P_1 by applying a "shifting"
  3553. permutation to them, that is, a permutation mapping [0..p^(i-1)-1]
  3554. to the second part (the other parts are obtained by using the shift
  3555. multiple times). The union of this permutation and the generators
  3556. of P_1 is a p-Sylow subgroup of ``self``.
  3557. For n not equal to a power of p, partition
  3558. [0..n-1] in accordance with how n would be written in base p.
  3559. E.g. for p=2 and n=11, 11 = 2^3 + 2^2 + 1 so the partition
  3560. is [[0..7], [8..9], {10}]. To generate a p-Sylow subgroup,
  3561. take the union of the generators for each of the parts.
  3562. For the above example, {(0 1), (0 2)(1 3), (0 4), (1 5)(2 7)}
  3563. from the first part, {(8 9)} from the second part and
  3564. nothing from the third. This gives 4 generators in total, and
  3565. the subgroup they generate is p-Sylow.
  3566. Alternating groups are treated the same except when p=2. In this
  3567. case, (0 1)(s s+1) should be added for an appropriate s (the start
  3568. of a part) for each part in the partitions.
  3569. See Also
  3570. ========
  3571. sylow_subgroup, is_alt_sym
  3572. '''
  3573. n = self.degree
  3574. gens = []
  3575. identity = Permutation(n-1)
  3576. # the case of 2-sylow subgroups of alternating groups
  3577. # needs special treatment
  3578. alt = p == 2 and all(g.is_even for g in self.generators)
  3579. # find the presentation of n in base p
  3580. coeffs = []
  3581. m = n
  3582. while m > 0:
  3583. coeffs.append(m % p)
  3584. m = m // p
  3585. power = len(coeffs)-1
  3586. # for a symmetric group, gens[:i] is the generating
  3587. # set for a p-Sylow subgroup on [0..p**(i-1)-1]. For
  3588. # alternating groups, the same is given by gens[:2*(i-1)]
  3589. for i in range(1, power+1):
  3590. if i == 1 and alt:
  3591. # (0 1) shouldn't be added for alternating groups
  3592. continue
  3593. gen = Permutation([(j + p**(i-1)) % p**i for j in range(p**i)])
  3594. gens.append(identity*gen)
  3595. if alt:
  3596. gen = Permutation(0, 1)*gen*Permutation(0, 1)*gen
  3597. gens.append(gen)
  3598. # the first point in the current part (see the algorithm
  3599. # description in the docstring)
  3600. start = 0
  3601. while power > 0:
  3602. a = coeffs[power]
  3603. # make the permutation shifting the start of the first
  3604. # part ([0..p^i-1] for some i) to the current one
  3605. for _ in range(a):
  3606. shift = Permutation()
  3607. if start > 0:
  3608. for i in range(p**power):
  3609. shift = shift(i, start + i)
  3610. if alt:
  3611. gen = Permutation(0, 1)*shift*Permutation(0, 1)*shift
  3612. gens.append(gen)
  3613. j = 2*(power - 1)
  3614. else:
  3615. j = power
  3616. for i, gen in enumerate(gens[:j]):
  3617. if alt and i % 2 == 1:
  3618. continue
  3619. # shift the generator to the start of the
  3620. # partition part
  3621. gen = shift*gen*shift
  3622. gens.append(gen)
  3623. start += p**power
  3624. power = power-1
  3625. return gens
  3626. def sylow_subgroup(self, p):
  3627. '''
  3628. Return a p-Sylow subgroup of the group.
  3629. The algorithm is described in [1], Chapter 4, Section 7
  3630. Examples
  3631. ========
  3632. >>> from sympy.combinatorics.named_groups import DihedralGroup
  3633. >>> from sympy.combinatorics.named_groups import SymmetricGroup
  3634. >>> from sympy.combinatorics.named_groups import AlternatingGroup
  3635. >>> D = DihedralGroup(6)
  3636. >>> S = D.sylow_subgroup(2)
  3637. >>> S.order()
  3638. 4
  3639. >>> G = SymmetricGroup(6)
  3640. >>> S = G.sylow_subgroup(5)
  3641. >>> S.order()
  3642. 5
  3643. >>> G1 = AlternatingGroup(3)
  3644. >>> G2 = AlternatingGroup(5)
  3645. >>> G3 = AlternatingGroup(9)
  3646. >>> S1 = G1.sylow_subgroup(3)
  3647. >>> S2 = G2.sylow_subgroup(3)
  3648. >>> S3 = G3.sylow_subgroup(3)
  3649. >>> len1 = len(S1.lower_central_series())
  3650. >>> len2 = len(S2.lower_central_series())
  3651. >>> len3 = len(S3.lower_central_series())
  3652. >>> len1 == len2
  3653. True
  3654. >>> len1 < len3
  3655. True
  3656. '''
  3657. from sympy.combinatorics.homomorphisms import (
  3658. orbit_homomorphism, block_homomorphism)
  3659. from sympy.ntheory.primetest import isprime
  3660. if not isprime(p):
  3661. raise ValueError("p must be a prime")
  3662. def is_p_group(G):
  3663. # check if the order of G is a power of p
  3664. # and return the power
  3665. m = G.order()
  3666. n = 0
  3667. while m % p == 0:
  3668. m = m/p
  3669. n += 1
  3670. if m == 1:
  3671. return True, n
  3672. return False, n
  3673. def _sylow_reduce(mu, nu):
  3674. # reduction based on two homomorphisms
  3675. # mu and nu with trivially intersecting
  3676. # kernels
  3677. Q = mu.image().sylow_subgroup(p)
  3678. Q = mu.invert_subgroup(Q)
  3679. nu = nu.restrict_to(Q)
  3680. R = nu.image().sylow_subgroup(p)
  3681. return nu.invert_subgroup(R)
  3682. order = self.order()
  3683. if order % p != 0:
  3684. return PermutationGroup([self.identity])
  3685. p_group, n = is_p_group(self)
  3686. if p_group:
  3687. return self
  3688. if self.is_alt_sym():
  3689. return PermutationGroup(self._sylow_alt_sym(p))
  3690. # if there is a non-trivial orbit with size not divisible
  3691. # by p, the sylow subgroup is contained in its stabilizer
  3692. # (by orbit-stabilizer theorem)
  3693. orbits = self.orbits()
  3694. non_p_orbits = [o for o in orbits if len(o) % p != 0 and len(o) != 1]
  3695. if non_p_orbits:
  3696. G = self.stabilizer(list(non_p_orbits[0]).pop())
  3697. return G.sylow_subgroup(p)
  3698. if not self.is_transitive():
  3699. # apply _sylow_reduce to orbit actions
  3700. orbits = sorted(orbits, key=len)
  3701. omega1 = orbits.pop()
  3702. omega2 = orbits[0].union(*orbits)
  3703. mu = orbit_homomorphism(self, omega1)
  3704. nu = orbit_homomorphism(self, omega2)
  3705. return _sylow_reduce(mu, nu)
  3706. blocks = self.minimal_blocks()
  3707. if len(blocks) > 1:
  3708. # apply _sylow_reduce to block system actions
  3709. mu = block_homomorphism(self, blocks[0])
  3710. nu = block_homomorphism(self, blocks[1])
  3711. return _sylow_reduce(mu, nu)
  3712. elif len(blocks) == 1:
  3713. block = list(blocks)[0]
  3714. if any(e != 0 for e in block):
  3715. # self is imprimitive
  3716. mu = block_homomorphism(self, block)
  3717. if not is_p_group(mu.image())[0]:
  3718. S = mu.image().sylow_subgroup(p)
  3719. return mu.invert_subgroup(S).sylow_subgroup(p)
  3720. # find an element of order p
  3721. g = self.random()
  3722. g_order = g.order()
  3723. while g_order % p != 0 or g_order == 0:
  3724. g = self.random()
  3725. g_order = g.order()
  3726. g = g**(g_order // p)
  3727. if order % p**2 != 0:
  3728. return PermutationGroup(g)
  3729. C = self.centralizer(g)
  3730. while C.order() % p**n != 0:
  3731. S = C.sylow_subgroup(p)
  3732. s_order = S.order()
  3733. Z = S.center()
  3734. P = Z._p_elements_group(p)
  3735. h = P.random()
  3736. C_h = self.centralizer(h)
  3737. while C_h.order() % p*s_order != 0:
  3738. h = P.random()
  3739. C_h = self.centralizer(h)
  3740. C = C_h
  3741. return C.sylow_subgroup(p)
  3742. def _block_verify(self, L, alpha):
  3743. delta = sorted(list(self.orbit(alpha)))
  3744. # p[i] will be the number of the block
  3745. # delta[i] belongs to
  3746. p = [-1]*len(delta)
  3747. blocks = [-1]*len(delta)
  3748. B = [[]] # future list of blocks
  3749. u = [0]*len(delta) # u[i] in L s.t. alpha^u[i] = B[0][i]
  3750. t = L.orbit_transversal(alpha, pairs=True)
  3751. for a, beta in t:
  3752. B[0].append(a)
  3753. i_a = delta.index(a)
  3754. p[i_a] = 0
  3755. blocks[i_a] = alpha
  3756. u[i_a] = beta
  3757. rho = 0
  3758. m = 0 # number of blocks - 1
  3759. while rho <= m:
  3760. beta = B[rho][0]
  3761. for g in self.generators:
  3762. d = beta^g
  3763. i_d = delta.index(d)
  3764. sigma = p[i_d]
  3765. if sigma < 0:
  3766. # define a new block
  3767. m += 1
  3768. sigma = m
  3769. u[i_d] = u[delta.index(beta)]*g
  3770. p[i_d] = sigma
  3771. rep = d
  3772. blocks[i_d] = rep
  3773. newb = [rep]
  3774. for gamma in B[rho][1:]:
  3775. i_gamma = delta.index(gamma)
  3776. d = gamma^g
  3777. i_d = delta.index(d)
  3778. if p[i_d] < 0:
  3779. u[i_d] = u[i_gamma]*g
  3780. p[i_d] = sigma
  3781. blocks[i_d] = rep
  3782. newb.append(d)
  3783. else:
  3784. # B[rho] is not a block
  3785. s = u[i_gamma]*g*u[i_d]**(-1)
  3786. return False, s
  3787. B.append(newb)
  3788. else:
  3789. for h in B[rho][1:]:
  3790. if h^g not in B[sigma]:
  3791. # B[rho] is not a block
  3792. s = u[delta.index(beta)]*g*u[i_d]**(-1)
  3793. return False, s
  3794. rho += 1
  3795. return True, blocks
  3796. def _verify(H, K, phi, z, alpha):
  3797. '''
  3798. Return a list of relators ``rels`` in generators ``gens`_h` that
  3799. are mapped to ``H.generators`` by ``phi`` so that given a finite
  3800. presentation <gens_k | rels_k> of ``K`` on a subset of ``gens_h``
  3801. <gens_h | rels_k + rels> is a finite presentation of ``H``.
  3802. Explanation
  3803. ===========
  3804. ``H`` should be generated by the union of ``K.generators`` and ``z``
  3805. (a single generator), and ``H.stabilizer(alpha) == K``; ``phi`` is a
  3806. canonical injection from a free group into a permutation group
  3807. containing ``H``.
  3808. The algorithm is described in [1], Chapter 6.
  3809. Examples
  3810. ========
  3811. >>> from sympy.combinatorics import free_group, Permutation, PermutationGroup
  3812. >>> from sympy.combinatorics.homomorphisms import homomorphism
  3813. >>> from sympy.combinatorics.fp_groups import FpGroup
  3814. >>> H = PermutationGroup(Permutation(0, 2), Permutation (1, 5))
  3815. >>> K = PermutationGroup(Permutation(5)(0, 2))
  3816. >>> F = free_group("x_0 x_1")[0]
  3817. >>> gens = F.generators
  3818. >>> phi = homomorphism(F, H, F.generators, H.generators)
  3819. >>> rels_k = [gens[0]**2] # relators for presentation of K
  3820. >>> z= Permutation(1, 5)
  3821. >>> check, rels_h = H._verify(K, phi, z, 1)
  3822. >>> check
  3823. True
  3824. >>> rels = rels_k + rels_h
  3825. >>> G = FpGroup(F, rels) # presentation of H
  3826. >>> G.order() == H.order()
  3827. True
  3828. See also
  3829. ========
  3830. strong_presentation, presentation, stabilizer
  3831. '''
  3832. orbit = H.orbit(alpha)
  3833. beta = alpha^(z**-1)
  3834. K_beta = K.stabilizer(beta)
  3835. # orbit representatives of K_beta
  3836. gammas = [alpha, beta]
  3837. orbits = list({tuple(K_beta.orbit(o)) for o in orbit})
  3838. orbit_reps = [orb[0] for orb in orbits]
  3839. for rep in orbit_reps:
  3840. if rep not in gammas:
  3841. gammas.append(rep)
  3842. # orbit transversal of K
  3843. betas = [alpha, beta]
  3844. transversal = {alpha: phi.invert(H.identity), beta: phi.invert(z**-1)}
  3845. for s, g in K.orbit_transversal(beta, pairs=True):
  3846. if s not in transversal:
  3847. transversal[s] = transversal[beta]*phi.invert(g)
  3848. union = K.orbit(alpha).union(K.orbit(beta))
  3849. while (len(union) < len(orbit)):
  3850. for gamma in gammas:
  3851. if gamma in union:
  3852. r = gamma^z
  3853. if r not in union:
  3854. betas.append(r)
  3855. transversal[r] = transversal[gamma]*phi.invert(z)
  3856. for s, g in K.orbit_transversal(r, pairs=True):
  3857. if s not in transversal:
  3858. transversal[s] = transversal[r]*phi.invert(g)
  3859. union = union.union(K.orbit(r))
  3860. break
  3861. # compute relators
  3862. rels = []
  3863. for b in betas:
  3864. k_gens = K.stabilizer(b).generators
  3865. for y in k_gens:
  3866. new_rel = transversal[b]
  3867. gens = K.generator_product(y, original=True)
  3868. for g in gens[::-1]:
  3869. new_rel = new_rel*phi.invert(g)
  3870. new_rel = new_rel*transversal[b]**-1
  3871. perm = phi(new_rel)
  3872. try:
  3873. gens = K.generator_product(perm, original=True)
  3874. except ValueError:
  3875. return False, perm
  3876. for g in gens:
  3877. new_rel = new_rel*phi.invert(g)**-1
  3878. if new_rel not in rels:
  3879. rels.append(new_rel)
  3880. for gamma in gammas:
  3881. new_rel = transversal[gamma]*phi.invert(z)*transversal[gamma^z]**-1
  3882. perm = phi(new_rel)
  3883. try:
  3884. gens = K.generator_product(perm, original=True)
  3885. except ValueError:
  3886. return False, perm
  3887. for g in gens:
  3888. new_rel = new_rel*phi.invert(g)**-1
  3889. if new_rel not in rels:
  3890. rels.append(new_rel)
  3891. return True, rels
  3892. def strong_presentation(self):
  3893. '''
  3894. Return a strong finite presentation of group. The generators
  3895. of the returned group are in the same order as the strong
  3896. generators of group.
  3897. The algorithm is based on Sims' Verify algorithm described
  3898. in [1], Chapter 6.
  3899. Examples
  3900. ========
  3901. >>> from sympy.combinatorics.named_groups import DihedralGroup
  3902. >>> P = DihedralGroup(4)
  3903. >>> G = P.strong_presentation()
  3904. >>> P.order() == G.order()
  3905. True
  3906. See Also
  3907. ========
  3908. presentation, _verify
  3909. '''
  3910. from sympy.combinatorics.fp_groups import (FpGroup,
  3911. simplify_presentation)
  3912. from sympy.combinatorics.free_groups import free_group
  3913. from sympy.combinatorics.homomorphisms import (block_homomorphism,
  3914. homomorphism, GroupHomomorphism)
  3915. strong_gens = self.strong_gens[:]
  3916. stabs = self.basic_stabilizers[:]
  3917. base = self.base[:]
  3918. # injection from a free group on len(strong_gens)
  3919. # generators into G
  3920. gen_syms = [('x_%d'%i) for i in range(len(strong_gens))]
  3921. F = free_group(', '.join(gen_syms))[0]
  3922. phi = homomorphism(F, self, F.generators, strong_gens)
  3923. H = PermutationGroup(self.identity)
  3924. while stabs:
  3925. alpha = base.pop()
  3926. K = H
  3927. H = stabs.pop()
  3928. new_gens = [g for g in H.generators if g not in K]
  3929. if K.order() == 1:
  3930. z = new_gens.pop()
  3931. rels = [F.generators[-1]**z.order()]
  3932. intermediate_gens = [z]
  3933. K = PermutationGroup(intermediate_gens)
  3934. # add generators one at a time building up from K to H
  3935. while new_gens:
  3936. z = new_gens.pop()
  3937. intermediate_gens = [z] + intermediate_gens
  3938. K_s = PermutationGroup(intermediate_gens)
  3939. orbit = K_s.orbit(alpha)
  3940. orbit_k = K.orbit(alpha)
  3941. # split into cases based on the orbit of K_s
  3942. if orbit_k == orbit:
  3943. if z in K:
  3944. rel = phi.invert(z)
  3945. perm = z
  3946. else:
  3947. t = K.orbit_rep(alpha, alpha^z)
  3948. rel = phi.invert(z)*phi.invert(t)**-1
  3949. perm = z*t**-1
  3950. for g in K.generator_product(perm, original=True):
  3951. rel = rel*phi.invert(g)**-1
  3952. new_rels = [rel]
  3953. elif len(orbit_k) == 1:
  3954. # `success` is always true because `strong_gens`
  3955. # and `base` are already a verified BSGS. Later
  3956. # this could be changed to start with a randomly
  3957. # generated (potential) BSGS, and then new elements
  3958. # would have to be appended to it when `success`
  3959. # is false.
  3960. success, new_rels = K_s._verify(K, phi, z, alpha)
  3961. else:
  3962. # K.orbit(alpha) should be a block
  3963. # under the action of K_s on K_s.orbit(alpha)
  3964. check, block = K_s._block_verify(K, alpha)
  3965. if check:
  3966. # apply _verify to the action of K_s
  3967. # on the block system; for convenience,
  3968. # add the blocks as additional points
  3969. # that K_s should act on
  3970. t = block_homomorphism(K_s, block)
  3971. m = t.codomain.degree # number of blocks
  3972. d = K_s.degree
  3973. # conjugating with p will shift
  3974. # permutations in t.image() to
  3975. # higher numbers, e.g.
  3976. # p*(0 1)*p = (m m+1)
  3977. p = Permutation()
  3978. for i in range(m):
  3979. p *= Permutation(i, i+d)
  3980. t_img = t.images
  3981. # combine generators of K_s with their
  3982. # action on the block system
  3983. images = {g: g*p*t_img[g]*p for g in t_img}
  3984. for g in self.strong_gens[:-len(K_s.generators)]:
  3985. images[g] = g
  3986. K_s_act = PermutationGroup(list(images.values()))
  3987. f = GroupHomomorphism(self, K_s_act, images)
  3988. K_act = PermutationGroup([f(g) for g in K.generators])
  3989. success, new_rels = K_s_act._verify(K_act, f.compose(phi), f(z), d)
  3990. for n in new_rels:
  3991. if n not in rels:
  3992. rels.append(n)
  3993. K = K_s
  3994. group = FpGroup(F, rels)
  3995. return simplify_presentation(group)
  3996. def presentation(self, eliminate_gens=True):
  3997. '''
  3998. Return an `FpGroup` presentation of the group.
  3999. The algorithm is described in [1], Chapter 6.1.
  4000. '''
  4001. from sympy.combinatorics.fp_groups import (FpGroup,
  4002. simplify_presentation)
  4003. from sympy.combinatorics.coset_table import CosetTable
  4004. from sympy.combinatorics.free_groups import free_group
  4005. from sympy.combinatorics.homomorphisms import homomorphism
  4006. from itertools import product
  4007. if self._fp_presentation:
  4008. return self._fp_presentation
  4009. def _factor_group_by_rels(G, rels):
  4010. if isinstance(G, FpGroup):
  4011. rels.extend(G.relators)
  4012. return FpGroup(G.free_group, list(set(rels)))
  4013. return FpGroup(G, rels)
  4014. gens = self.generators
  4015. len_g = len(gens)
  4016. if len_g == 1:
  4017. order = gens[0].order()
  4018. # handle the trivial group
  4019. if order == 1:
  4020. return free_group([])[0]
  4021. F, x = free_group('x')
  4022. return FpGroup(F, [x**order])
  4023. if self.order() > 20:
  4024. half_gens = self.generators[0:(len_g+1)//2]
  4025. else:
  4026. half_gens = []
  4027. H = PermutationGroup(half_gens)
  4028. H_p = H.presentation()
  4029. len_h = len(H_p.generators)
  4030. C = self.coset_table(H)
  4031. n = len(C) # subgroup index
  4032. gen_syms = [('x_%d'%i) for i in range(len(gens))]
  4033. F = free_group(', '.join(gen_syms))[0]
  4034. # mapping generators of H_p to those of F
  4035. images = [F.generators[i] for i in range(len_h)]
  4036. R = homomorphism(H_p, F, H_p.generators, images, check=False)
  4037. # rewrite relators
  4038. rels = R(H_p.relators)
  4039. G_p = FpGroup(F, rels)
  4040. # injective homomorphism from G_p into self
  4041. T = homomorphism(G_p, self, G_p.generators, gens)
  4042. C_p = CosetTable(G_p, [])
  4043. C_p.table = [[None]*(2*len_g) for i in range(n)]
  4044. # initiate the coset transversal
  4045. transversal = [None]*n
  4046. transversal[0] = G_p.identity
  4047. # fill in the coset table as much as possible
  4048. for i in range(2*len_h):
  4049. C_p.table[0][i] = 0
  4050. gamma = 1
  4051. for alpha, x in product(range(0, n), range(2*len_g)):
  4052. beta = C[alpha][x]
  4053. if beta == gamma:
  4054. gen = G_p.generators[x//2]**((-1)**(x % 2))
  4055. transversal[beta] = transversal[alpha]*gen
  4056. C_p.table[alpha][x] = beta
  4057. C_p.table[beta][x + (-1)**(x % 2)] = alpha
  4058. gamma += 1
  4059. if gamma == n:
  4060. break
  4061. C_p.p = list(range(n))
  4062. beta = x = 0
  4063. while not C_p.is_complete():
  4064. # find the first undefined entry
  4065. while C_p.table[beta][x] == C[beta][x]:
  4066. x = (x + 1) % (2*len_g)
  4067. if x == 0:
  4068. beta = (beta + 1) % n
  4069. # define a new relator
  4070. gen = G_p.generators[x//2]**((-1)**(x % 2))
  4071. new_rel = transversal[beta]*gen*transversal[C[beta][x]]**-1
  4072. perm = T(new_rel)
  4073. nxt = G_p.identity
  4074. for s in H.generator_product(perm, original=True):
  4075. nxt = nxt*T.invert(s)**-1
  4076. new_rel = new_rel*nxt
  4077. # continue coset enumeration
  4078. G_p = _factor_group_by_rels(G_p, [new_rel])
  4079. C_p.scan_and_fill(0, new_rel)
  4080. C_p = G_p.coset_enumeration([], strategy="coset_table",
  4081. draft=C_p, max_cosets=n, incomplete=True)
  4082. self._fp_presentation = simplify_presentation(G_p)
  4083. return self._fp_presentation
  4084. def polycyclic_group(self):
  4085. """
  4086. Return the PolycyclicGroup instance with below parameters:
  4087. Explanation
  4088. ===========
  4089. * ``pc_sequence`` : Polycyclic sequence is formed by collecting all
  4090. the missing generators between the adjacent groups in the
  4091. derived series of given permutation group.
  4092. * ``pc_series`` : Polycyclic series is formed by adding all the missing
  4093. generators of ``der[i+1]`` in ``der[i]``, where ``der`` represents
  4094. the derived series.
  4095. * ``relative_order`` : A list, computed by the ratio of adjacent groups in
  4096. pc_series.
  4097. """
  4098. from sympy.combinatorics.pc_groups import PolycyclicGroup
  4099. if not self.is_polycyclic:
  4100. raise ValueError("The group must be solvable")
  4101. der = self.derived_series()
  4102. pc_series = []
  4103. pc_sequence = []
  4104. relative_order = []
  4105. pc_series.append(der[-1])
  4106. der.reverse()
  4107. for i in range(len(der)-1):
  4108. H = der[i]
  4109. for g in der[i+1].generators:
  4110. if g not in H:
  4111. H = PermutationGroup([g] + H.generators)
  4112. pc_series.insert(0, H)
  4113. pc_sequence.insert(0, g)
  4114. G1 = pc_series[0].order()
  4115. G2 = pc_series[1].order()
  4116. relative_order.insert(0, G1 // G2)
  4117. return PolycyclicGroup(pc_sequence, pc_series, relative_order, collector=None)
  4118. def _orbit(degree, generators, alpha, action='tuples'):
  4119. r"""Compute the orbit of alpha `\{g(\alpha) | g \in G\}` as a set.
  4120. Explanation
  4121. ===========
  4122. The time complexity of the algorithm used here is `O(|Orb|*r)` where
  4123. `|Orb|` is the size of the orbit and ``r`` is the number of generators of
  4124. the group. For a more detailed analysis, see [1], p.78, [2], pp. 19-21.
  4125. Here alpha can be a single point, or a list of points.
  4126. If alpha is a single point, the ordinary orbit is computed.
  4127. if alpha is a list of points, there are three available options:
  4128. 'union' - computes the union of the orbits of the points in the list
  4129. 'tuples' - computes the orbit of the list interpreted as an ordered
  4130. tuple under the group action ( i.e., g((1, 2, 3)) = (g(1), g(2), g(3)) )
  4131. 'sets' - computes the orbit of the list interpreted as a sets
  4132. Examples
  4133. ========
  4134. >>> from sympy.combinatorics import Permutation, PermutationGroup
  4135. >>> from sympy.combinatorics.perm_groups import _orbit
  4136. >>> a = Permutation([1, 2, 0, 4, 5, 6, 3])
  4137. >>> G = PermutationGroup([a])
  4138. >>> _orbit(G.degree, G.generators, 0)
  4139. {0, 1, 2}
  4140. >>> _orbit(G.degree, G.generators, [0, 4], 'union')
  4141. {0, 1, 2, 3, 4, 5, 6}
  4142. See Also
  4143. ========
  4144. orbit, orbit_transversal
  4145. """
  4146. if not hasattr(alpha, '__getitem__'):
  4147. alpha = [alpha]
  4148. gens = [x._array_form for x in generators]
  4149. if len(alpha) == 1 or action == 'union':
  4150. orb = alpha
  4151. used = [False]*degree
  4152. for el in alpha:
  4153. used[el] = True
  4154. for b in orb:
  4155. for gen in gens:
  4156. temp = gen[b]
  4157. if used[temp] == False:
  4158. orb.append(temp)
  4159. used[temp] = True
  4160. return set(orb)
  4161. elif action == 'tuples':
  4162. alpha = tuple(alpha)
  4163. orb = [alpha]
  4164. used = {alpha}
  4165. for b in orb:
  4166. for gen in gens:
  4167. temp = tuple([gen[x] for x in b])
  4168. if temp not in used:
  4169. orb.append(temp)
  4170. used.add(temp)
  4171. return set(orb)
  4172. elif action == 'sets':
  4173. alpha = frozenset(alpha)
  4174. orb = [alpha]
  4175. used = {alpha}
  4176. for b in orb:
  4177. for gen in gens:
  4178. temp = frozenset([gen[x] for x in b])
  4179. if temp not in used:
  4180. orb.append(temp)
  4181. used.add(temp)
  4182. return {tuple(x) for x in orb}
  4183. def _orbits(degree, generators):
  4184. """Compute the orbits of G.
  4185. If ``rep=False`` it returns a list of sets else it returns a list of
  4186. representatives of the orbits
  4187. Examples
  4188. ========
  4189. >>> from sympy.combinatorics import Permutation
  4190. >>> from sympy.combinatorics.perm_groups import _orbits
  4191. >>> a = Permutation([0, 2, 1])
  4192. >>> b = Permutation([1, 0, 2])
  4193. >>> _orbits(a.size, [a, b])
  4194. [{0, 1, 2}]
  4195. """
  4196. orbs = []
  4197. sorted_I = list(range(degree))
  4198. I = set(sorted_I)
  4199. while I:
  4200. i = sorted_I[0]
  4201. orb = _orbit(degree, generators, i)
  4202. orbs.append(orb)
  4203. # remove all indices that are in this orbit
  4204. I -= orb
  4205. sorted_I = [i for i in sorted_I if i not in orb]
  4206. return orbs
  4207. def _orbit_transversal(degree, generators, alpha, pairs, af=False, slp=False):
  4208. r"""Computes a transversal for the orbit of ``alpha`` as a set.
  4209. Explanation
  4210. ===========
  4211. generators generators of the group ``G``
  4212. For a permutation group ``G``, a transversal for the orbit
  4213. `Orb = \{g(\alpha) | g \in G\}` is a set
  4214. `\{g_\beta | g_\beta(\alpha) = \beta\}` for `\beta \in Orb`.
  4215. Note that there may be more than one possible transversal.
  4216. If ``pairs`` is set to ``True``, it returns the list of pairs
  4217. `(\beta, g_\beta)`. For a proof of correctness, see [1], p.79
  4218. if ``af`` is ``True``, the transversal elements are given in
  4219. array form.
  4220. If `slp` is `True`, a dictionary `{beta: slp_beta}` is returned
  4221. for `\beta \in Orb` where `slp_beta` is a list of indices of the
  4222. generators in `generators` s.t. if `slp_beta = [i_1 \dots i_n]`
  4223. `g_\beta = generators[i_n] \times \dots \times generators[i_1]`.
  4224. Examples
  4225. ========
  4226. >>> from sympy.combinatorics.named_groups import DihedralGroup
  4227. >>> from sympy.combinatorics.perm_groups import _orbit_transversal
  4228. >>> G = DihedralGroup(6)
  4229. >>> _orbit_transversal(G.degree, G.generators, 0, False)
  4230. [(5), (0 1 2 3 4 5), (0 5)(1 4)(2 3), (0 2 4)(1 3 5), (5)(0 4)(1 3), (0 3)(1 4)(2 5)]
  4231. """
  4232. tr = [(alpha, list(range(degree)))]
  4233. slp_dict = {alpha: []}
  4234. used = [False]*degree
  4235. used[alpha] = True
  4236. gens = [x._array_form for x in generators]
  4237. for x, px in tr:
  4238. px_slp = slp_dict[x]
  4239. for gen in gens:
  4240. temp = gen[x]
  4241. if used[temp] == False:
  4242. slp_dict[temp] = [gens.index(gen)] + px_slp
  4243. tr.append((temp, _af_rmul(gen, px)))
  4244. used[temp] = True
  4245. if pairs:
  4246. if not af:
  4247. tr = [(x, _af_new(y)) for x, y in tr]
  4248. if not slp:
  4249. return tr
  4250. return tr, slp_dict
  4251. if af:
  4252. tr = [y for _, y in tr]
  4253. if not slp:
  4254. return tr
  4255. return tr, slp_dict
  4256. tr = [_af_new(y) for _, y in tr]
  4257. if not slp:
  4258. return tr
  4259. return tr, slp_dict
  4260. def _stabilizer(degree, generators, alpha):
  4261. r"""Return the stabilizer subgroup of ``alpha``.
  4262. Explanation
  4263. ===========
  4264. The stabilizer of `\alpha` is the group `G_\alpha =
  4265. \{g \in G | g(\alpha) = \alpha\}`.
  4266. For a proof of correctness, see [1], p.79.
  4267. degree : degree of G
  4268. generators : generators of G
  4269. Examples
  4270. ========
  4271. >>> from sympy.combinatorics.perm_groups import _stabilizer
  4272. >>> from sympy.combinatorics.named_groups import DihedralGroup
  4273. >>> G = DihedralGroup(6)
  4274. >>> _stabilizer(G.degree, G.generators, 5)
  4275. [(5)(0 4)(1 3), (5)]
  4276. See Also
  4277. ========
  4278. orbit
  4279. """
  4280. orb = [alpha]
  4281. table = {alpha: list(range(degree))}
  4282. table_inv = {alpha: list(range(degree))}
  4283. used = [False]*degree
  4284. used[alpha] = True
  4285. gens = [x._array_form for x in generators]
  4286. stab_gens = []
  4287. for b in orb:
  4288. for gen in gens:
  4289. temp = gen[b]
  4290. if used[temp] is False:
  4291. gen_temp = _af_rmul(gen, table[b])
  4292. orb.append(temp)
  4293. table[temp] = gen_temp
  4294. table_inv[temp] = _af_invert(gen_temp)
  4295. used[temp] = True
  4296. else:
  4297. schreier_gen = _af_rmuln(table_inv[temp], gen, table[b])
  4298. if schreier_gen not in stab_gens:
  4299. stab_gens.append(schreier_gen)
  4300. return [_af_new(x) for x in stab_gens]
  4301. PermGroup = PermutationGroup
  4302. class SymmetricPermutationGroup(Basic):
  4303. """
  4304. The class defining the lazy form of SymmetricGroup.
  4305. deg : int
  4306. """
  4307. def __new__(cls, deg):
  4308. deg = _sympify(deg)
  4309. obj = Basic.__new__(cls, deg)
  4310. return obj
  4311. def __init__(self, *args, **kwargs):
  4312. self._deg = self.args[0]
  4313. self._order = None
  4314. def __contains__(self, i):
  4315. """Return ``True`` if *i* is contained in SymmetricPermutationGroup.
  4316. Examples
  4317. ========
  4318. >>> from sympy.combinatorics import Permutation, SymmetricPermutationGroup
  4319. >>> G = SymmetricPermutationGroup(4)
  4320. >>> Permutation(1, 2, 3) in G
  4321. True
  4322. """
  4323. if not isinstance(i, Permutation):
  4324. raise TypeError("A SymmetricPermutationGroup contains only Permutations as "
  4325. "elements, not elements of type %s" % type(i))
  4326. return i.size == self.degree
  4327. def order(self):
  4328. """
  4329. Return the order of the SymmetricPermutationGroup.
  4330. Examples
  4331. ========
  4332. >>> from sympy.combinatorics import SymmetricPermutationGroup
  4333. >>> G = SymmetricPermutationGroup(4)
  4334. >>> G.order()
  4335. 24
  4336. """
  4337. if self._order is not None:
  4338. return self._order
  4339. n = self._deg
  4340. self._order = factorial(n)
  4341. return self._order
  4342. @property
  4343. def degree(self):
  4344. """
  4345. Return the degree of the SymmetricPermutationGroup.
  4346. Examples
  4347. ========
  4348. >>> from sympy.combinatorics import SymmetricPermutationGroup
  4349. >>> G = SymmetricPermutationGroup(4)
  4350. >>> G.degree
  4351. 4
  4352. """
  4353. return self._deg
  4354. @property
  4355. def identity(self):
  4356. '''
  4357. Return the identity element of the SymmetricPermutationGroup.
  4358. Examples
  4359. ========
  4360. >>> from sympy.combinatorics import SymmetricPermutationGroup
  4361. >>> G = SymmetricPermutationGroup(4)
  4362. >>> G.identity()
  4363. (3)
  4364. '''
  4365. return _af_new(list(range(self._deg)))
  4366. class Coset(Basic):
  4367. """A left coset of a permutation group with respect to an element.
  4368. Parameters
  4369. ==========
  4370. g : Permutation
  4371. H : PermutationGroup
  4372. dir : "+" or "-", If not specified by default it will be "+"
  4373. here ``dir`` specified the type of coset "+" represent the
  4374. right coset and "-" represent the left coset.
  4375. G : PermutationGroup, optional
  4376. The group which contains *H* as its subgroup and *g* as its
  4377. element.
  4378. If not specified, it would automatically become a symmetric
  4379. group ``SymmetricPermutationGroup(g.size)`` and
  4380. ``SymmetricPermutationGroup(H.degree)`` if ``g.size`` and ``H.degree``
  4381. are matching.``SymmetricPermutationGroup`` is a lazy form of SymmetricGroup
  4382. used for representation purpose.
  4383. """
  4384. def __new__(cls, g, H, G=None, dir="+"):
  4385. g = _sympify(g)
  4386. if not isinstance(g, Permutation):
  4387. raise NotImplementedError
  4388. H = _sympify(H)
  4389. if not isinstance(H, PermutationGroup):
  4390. raise NotImplementedError
  4391. if G is not None:
  4392. G = _sympify(G)
  4393. if not isinstance(G, (PermutationGroup, SymmetricPermutationGroup)):
  4394. raise NotImplementedError
  4395. if not H.is_subgroup(G):
  4396. raise ValueError("{} must be a subgroup of {}.".format(H, G))
  4397. if g not in G:
  4398. raise ValueError("{} must be an element of {}.".format(g, G))
  4399. else:
  4400. g_size = g.size
  4401. h_degree = H.degree
  4402. if g_size != h_degree:
  4403. raise ValueError(
  4404. "The size of the permutation {} and the degree of "
  4405. "the permutation group {} should be matching "
  4406. .format(g, H))
  4407. G = SymmetricPermutationGroup(g.size)
  4408. if isinstance(dir, str):
  4409. dir = Symbol(dir)
  4410. elif not isinstance(dir, Symbol):
  4411. raise TypeError("dir must be of type basestring or "
  4412. "Symbol, not %s" % type(dir))
  4413. if str(dir) not in ('+', '-'):
  4414. raise ValueError("dir must be one of '+' or '-' not %s" % dir)
  4415. obj = Basic.__new__(cls, g, H, G, dir)
  4416. return obj
  4417. def __init__(self, *args, **kwargs):
  4418. self._dir = self.args[3]
  4419. @property
  4420. def is_left_coset(self):
  4421. """
  4422. Check if the coset is left coset that is ``gH``.
  4423. Examples
  4424. ========
  4425. >>> from sympy.combinatorics import Permutation, PermutationGroup, Coset
  4426. >>> a = Permutation(1, 2)
  4427. >>> b = Permutation(0, 1)
  4428. >>> G = PermutationGroup([a, b])
  4429. >>> cst = Coset(a, G, dir="-")
  4430. >>> cst.is_left_coset
  4431. True
  4432. """
  4433. return str(self._dir) == '-'
  4434. @property
  4435. def is_right_coset(self):
  4436. """
  4437. Check if the coset is right coset that is ``Hg``.
  4438. Examples
  4439. ========
  4440. >>> from sympy.combinatorics import Permutation, PermutationGroup, Coset
  4441. >>> a = Permutation(1, 2)
  4442. >>> b = Permutation(0, 1)
  4443. >>> G = PermutationGroup([a, b])
  4444. >>> cst = Coset(a, G, dir="+")
  4445. >>> cst.is_right_coset
  4446. True
  4447. """
  4448. return str(self._dir) == '+'
  4449. def as_list(self):
  4450. """
  4451. Return all the elements of coset in the form of list.
  4452. """
  4453. g = self.args[0]
  4454. H = self.args[1]
  4455. cst = []
  4456. if str(self._dir) == '+':
  4457. for h in H.elements:
  4458. cst.append(h*g)
  4459. else:
  4460. for h in H.elements:
  4461. cst.append(g*h)
  4462. return cst