util.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. from sympy.combinatorics.permutations import Permutation, _af_invert, _af_rmul
  2. from sympy.ntheory import isprime
  3. rmul = Permutation.rmul
  4. _af_new = Permutation._af_new
  5. ############################################
  6. #
  7. # Utilities for computational group theory
  8. #
  9. ############################################
  10. def _base_ordering(base, degree):
  11. r"""
  12. Order `\{0, 1, \dots, n-1\}` so that base points come first and in order.
  13. Parameters
  14. ==========
  15. base : the base
  16. degree : the degree of the associated permutation group
  17. Returns
  18. =======
  19. A list ``base_ordering`` such that ``base_ordering[point]`` is the
  20. number of ``point`` in the ordering.
  21. Examples
  22. ========
  23. >>> from sympy.combinatorics import SymmetricGroup
  24. >>> from sympy.combinatorics.util import _base_ordering
  25. >>> S = SymmetricGroup(4)
  26. >>> S.schreier_sims()
  27. >>> _base_ordering(S.base, S.degree)
  28. [0, 1, 2, 3]
  29. Notes
  30. =====
  31. This is used in backtrack searches, when we define a relation `\ll` on
  32. the underlying set for a permutation group of degree `n`,
  33. `\{0, 1, \dots, n-1\}`, so that if `(b_1, b_2, \dots, b_k)` is a base we
  34. have `b_i \ll b_j` whenever `i<j` and `b_i \ll a` for all
  35. `i\in\{1,2, \dots, k\}` and `a` is not in the base. The idea is developed
  36. and applied to backtracking algorithms in [1], pp.108-132. The points
  37. that are not in the base are taken in increasing order.
  38. References
  39. ==========
  40. .. [1] Holt, D., Eick, B., O'Brien, E.
  41. "Handbook of computational group theory"
  42. """
  43. base_len = len(base)
  44. ordering = [0]*degree
  45. for i in range(base_len):
  46. ordering[base[i]] = i
  47. current = base_len
  48. for i in range(degree):
  49. if i not in base:
  50. ordering[i] = current
  51. current += 1
  52. return ordering
  53. def _check_cycles_alt_sym(perm):
  54. """
  55. Checks for cycles of prime length p with n/2 < p < n-2.
  56. Explanation
  57. ===========
  58. Here `n` is the degree of the permutation. This is a helper function for
  59. the function is_alt_sym from sympy.combinatorics.perm_groups.
  60. Examples
  61. ========
  62. >>> from sympy.combinatorics.util import _check_cycles_alt_sym
  63. >>> from sympy.combinatorics import Permutation
  64. >>> a = Permutation([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [11, 12]])
  65. >>> _check_cycles_alt_sym(a)
  66. False
  67. >>> b = Permutation([[0, 1, 2, 3, 4, 5, 6], [7, 8, 9, 10]])
  68. >>> _check_cycles_alt_sym(b)
  69. True
  70. See Also
  71. ========
  72. sympy.combinatorics.perm_groups.PermutationGroup.is_alt_sym
  73. """
  74. n = perm.size
  75. af = perm.array_form
  76. current_len = 0
  77. total_len = 0
  78. used = set()
  79. for i in range(n//2):
  80. if i not in used and i < n//2 - total_len:
  81. current_len = 1
  82. used.add(i)
  83. j = i
  84. while af[j] != i:
  85. current_len += 1
  86. j = af[j]
  87. used.add(j)
  88. total_len += current_len
  89. if current_len > n//2 and current_len < n - 2 and isprime(current_len):
  90. return True
  91. return False
  92. def _distribute_gens_by_base(base, gens):
  93. r"""
  94. Distribute the group elements ``gens`` by membership in basic stabilizers.
  95. Explanation
  96. ===========
  97. Notice that for a base `(b_1, b_2, \dots, b_k)`, the basic stabilizers
  98. are defined as `G^{(i)} = G_{b_1, \dots, b_{i-1}}` for
  99. `i \in\{1, 2, \dots, k\}`.
  100. Parameters
  101. ==========
  102. base : a sequence of points in `\{0, 1, \dots, n-1\}`
  103. gens : a list of elements of a permutation group of degree `n`.
  104. Returns
  105. =======
  106. list
  107. List of length `k`, where `k` is the length of *base*. The `i`-th entry
  108. contains those elements in *gens* which fix the first `i` elements of
  109. *base* (so that the `0`-th entry is equal to *gens* itself). If no
  110. element fixes the first `i` elements of *base*, the `i`-th element is
  111. set to a list containing the identity element.
  112. Examples
  113. ========
  114. >>> from sympy.combinatorics.named_groups import DihedralGroup
  115. >>> from sympy.combinatorics.util import _distribute_gens_by_base
  116. >>> D = DihedralGroup(3)
  117. >>> D.schreier_sims()
  118. >>> D.strong_gens
  119. [(0 1 2), (0 2), (1 2)]
  120. >>> D.base
  121. [0, 1]
  122. >>> _distribute_gens_by_base(D.base, D.strong_gens)
  123. [[(0 1 2), (0 2), (1 2)],
  124. [(1 2)]]
  125. See Also
  126. ========
  127. _strong_gens_from_distr, _orbits_transversals_from_bsgs,
  128. _handle_precomputed_bsgs
  129. """
  130. base_len = len(base)
  131. degree = gens[0].size
  132. stabs = [[] for _ in range(base_len)]
  133. max_stab_index = 0
  134. for gen in gens:
  135. j = 0
  136. while j < base_len - 1 and gen._array_form[base[j]] == base[j]:
  137. j += 1
  138. if j > max_stab_index:
  139. max_stab_index = j
  140. for k in range(j + 1):
  141. stabs[k].append(gen)
  142. for i in range(max_stab_index + 1, base_len):
  143. stabs[i].append(_af_new(list(range(degree))))
  144. return stabs
  145. def _handle_precomputed_bsgs(base, strong_gens, transversals=None,
  146. basic_orbits=None, strong_gens_distr=None):
  147. """
  148. Calculate BSGS-related structures from those present.
  149. Explanation
  150. ===========
  151. The base and strong generating set must be provided; if any of the
  152. transversals, basic orbits or distributed strong generators are not
  153. provided, they will be calculated from the base and strong generating set.
  154. Parameters
  155. ==========
  156. base : the base
  157. strong_gens : the strong generators
  158. transversals : basic transversals
  159. basic_orbits : basic orbits
  160. strong_gens_distr : strong generators distributed by membership in basic stabilizers
  161. Returns
  162. =======
  163. (transversals, basic_orbits, strong_gens_distr)
  164. where *transversals* are the basic transversals, *basic_orbits* are the
  165. basic orbits, and *strong_gens_distr* are the strong generators distributed
  166. by membership in basic stabilizers.
  167. Examples
  168. ========
  169. >>> from sympy.combinatorics.named_groups import DihedralGroup
  170. >>> from sympy.combinatorics.util import _handle_precomputed_bsgs
  171. >>> D = DihedralGroup(3)
  172. >>> D.schreier_sims()
  173. >>> _handle_precomputed_bsgs(D.base, D.strong_gens,
  174. ... basic_orbits=D.basic_orbits)
  175. ([{0: (2), 1: (0 1 2), 2: (0 2)}, {1: (2), 2: (1 2)}], [[0, 1, 2], [1, 2]], [[(0 1 2), (0 2), (1 2)], [(1 2)]])
  176. See Also
  177. ========
  178. _orbits_transversals_from_bsgs, _distribute_gens_by_base
  179. """
  180. if strong_gens_distr is None:
  181. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  182. if transversals is None:
  183. if basic_orbits is None:
  184. basic_orbits, transversals = \
  185. _orbits_transversals_from_bsgs(base, strong_gens_distr)
  186. else:
  187. transversals = \
  188. _orbits_transversals_from_bsgs(base, strong_gens_distr,
  189. transversals_only=True)
  190. else:
  191. if basic_orbits is None:
  192. base_len = len(base)
  193. basic_orbits = [None]*base_len
  194. for i in range(base_len):
  195. basic_orbits[i] = list(transversals[i].keys())
  196. return transversals, basic_orbits, strong_gens_distr
  197. def _orbits_transversals_from_bsgs(base, strong_gens_distr,
  198. transversals_only=False, slp=False):
  199. """
  200. Compute basic orbits and transversals from a base and strong generating set.
  201. Explanation
  202. ===========
  203. The generators are provided as distributed across the basic stabilizers.
  204. If the optional argument ``transversals_only`` is set to True, only the
  205. transversals are returned.
  206. Parameters
  207. ==========
  208. base : The base.
  209. strong_gens_distr : Strong generators distributed by membership in basic stabilizers.
  210. transversals_only : bool, default: False
  211. A flag switching between returning only the
  212. transversals and both orbits and transversals.
  213. slp : bool, default: False
  214. If ``True``, return a list of dictionaries containing the
  215. generator presentations of the elements of the transversals,
  216. i.e. the list of indices of generators from ``strong_gens_distr[i]``
  217. such that their product is the relevant transversal element.
  218. Examples
  219. ========
  220. >>> from sympy.combinatorics import SymmetricGroup
  221. >>> from sympy.combinatorics.util import _distribute_gens_by_base
  222. >>> S = SymmetricGroup(3)
  223. >>> S.schreier_sims()
  224. >>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens)
  225. >>> (S.base, strong_gens_distr)
  226. ([0, 1], [[(0 1 2), (2)(0 1), (1 2)], [(1 2)]])
  227. See Also
  228. ========
  229. _distribute_gens_by_base, _handle_precomputed_bsgs
  230. """
  231. from sympy.combinatorics.perm_groups import _orbit_transversal
  232. base_len = len(base)
  233. degree = strong_gens_distr[0][0].size
  234. transversals = [None]*base_len
  235. slps = [None]*base_len
  236. if transversals_only is False:
  237. basic_orbits = [None]*base_len
  238. for i in range(base_len):
  239. transversals[i], slps[i] = _orbit_transversal(degree, strong_gens_distr[i],
  240. base[i], pairs=True, slp=True)
  241. transversals[i] = dict(transversals[i])
  242. if transversals_only is False:
  243. basic_orbits[i] = list(transversals[i].keys())
  244. if transversals_only:
  245. return transversals
  246. else:
  247. if not slp:
  248. return basic_orbits, transversals
  249. return basic_orbits, transversals, slps
  250. def _remove_gens(base, strong_gens, basic_orbits=None, strong_gens_distr=None):
  251. """
  252. Remove redundant generators from a strong generating set.
  253. Parameters
  254. ==========
  255. base : a base
  256. strong_gens : a strong generating set relative to *base*
  257. basic_orbits : basic orbits
  258. strong_gens_distr : strong generators distributed by membership in basic stabilizers
  259. Returns
  260. =======
  261. A strong generating set with respect to ``base`` which is a subset of
  262. ``strong_gens``.
  263. Examples
  264. ========
  265. >>> from sympy.combinatorics import SymmetricGroup
  266. >>> from sympy.combinatorics.util import _remove_gens
  267. >>> from sympy.combinatorics.testutil import _verify_bsgs
  268. >>> S = SymmetricGroup(15)
  269. >>> base, strong_gens = S.schreier_sims_incremental()
  270. >>> new_gens = _remove_gens(base, strong_gens)
  271. >>> len(new_gens)
  272. 14
  273. >>> _verify_bsgs(S, base, new_gens)
  274. True
  275. Notes
  276. =====
  277. This procedure is outlined in [1],p.95.
  278. References
  279. ==========
  280. .. [1] Holt, D., Eick, B., O'Brien, E.
  281. "Handbook of computational group theory"
  282. """
  283. from sympy.combinatorics.perm_groups import _orbit
  284. base_len = len(base)
  285. degree = strong_gens[0].size
  286. if strong_gens_distr is None:
  287. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  288. if basic_orbits is None:
  289. basic_orbits = []
  290. for i in range(base_len):
  291. basic_orbit = _orbit(degree, strong_gens_distr[i], base[i])
  292. basic_orbits.append(basic_orbit)
  293. strong_gens_distr.append([])
  294. res = strong_gens[:]
  295. for i in range(base_len - 1, -1, -1):
  296. gens_copy = strong_gens_distr[i][:]
  297. for gen in strong_gens_distr[i]:
  298. if gen not in strong_gens_distr[i + 1]:
  299. temp_gens = gens_copy[:]
  300. temp_gens.remove(gen)
  301. if temp_gens == []:
  302. continue
  303. temp_orbit = _orbit(degree, temp_gens, base[i])
  304. if temp_orbit == basic_orbits[i]:
  305. gens_copy.remove(gen)
  306. res.remove(gen)
  307. return res
  308. def _strip(g, base, orbits, transversals):
  309. """
  310. Attempt to decompose a permutation using a (possibly partial) BSGS
  311. structure.
  312. Explanation
  313. ===========
  314. This is done by treating the sequence ``base`` as an actual base, and
  315. the orbits ``orbits`` and transversals ``transversals`` as basic orbits and
  316. transversals relative to it.
  317. This process is called "sifting". A sift is unsuccessful when a certain
  318. orbit element is not found or when after the sift the decomposition
  319. does not end with the identity element.
  320. The argument ``transversals`` is a list of dictionaries that provides
  321. transversal elements for the orbits ``orbits``.
  322. Parameters
  323. ==========
  324. g : permutation to be decomposed
  325. base : sequence of points
  326. orbits : list
  327. A list in which the ``i``-th entry is an orbit of ``base[i]``
  328. under some subgroup of the pointwise stabilizer of `
  329. `base[0], base[1], ..., base[i - 1]``. The groups themselves are implicit
  330. in this function since the only information we need is encoded in the orbits
  331. and transversals
  332. transversals : list
  333. A list of orbit transversals associated with the orbits *orbits*.
  334. Examples
  335. ========
  336. >>> from sympy.combinatorics import Permutation, SymmetricGroup
  337. >>> from sympy.combinatorics.util import _strip
  338. >>> S = SymmetricGroup(5)
  339. >>> S.schreier_sims()
  340. >>> g = Permutation([0, 2, 3, 1, 4])
  341. >>> _strip(g, S.base, S.basic_orbits, S.basic_transversals)
  342. ((4), 5)
  343. Notes
  344. =====
  345. The algorithm is described in [1],pp.89-90. The reason for returning
  346. both the current state of the element being decomposed and the level
  347. at which the sifting ends is that they provide important information for
  348. the randomized version of the Schreier-Sims algorithm.
  349. References
  350. ==========
  351. .. [1] Holt, D., Eick, B., O'Brien, E."Handbook of computational group theory"
  352. See Also
  353. ========
  354. sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims
  355. sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims_random
  356. """
  357. h = g._array_form
  358. base_len = len(base)
  359. for i in range(base_len):
  360. beta = h[base[i]]
  361. if beta == base[i]:
  362. continue
  363. if beta not in orbits[i]:
  364. return _af_new(h), i + 1
  365. u = transversals[i][beta]._array_form
  366. h = _af_rmul(_af_invert(u), h)
  367. return _af_new(h), base_len + 1
  368. def _strip_af(h, base, orbits, transversals, j, slp=[], slps={}):
  369. """
  370. optimized _strip, with h, transversals and result in array form
  371. if the stripped elements is the identity, it returns False, base_len + 1
  372. j h[base[i]] == base[i] for i <= j
  373. """
  374. base_len = len(base)
  375. for i in range(j+1, base_len):
  376. beta = h[base[i]]
  377. if beta == base[i]:
  378. continue
  379. if beta not in orbits[i]:
  380. if not slp:
  381. return h, i + 1
  382. return h, i + 1, slp
  383. u = transversals[i][beta]
  384. if h == u:
  385. if not slp:
  386. return False, base_len + 1
  387. return False, base_len + 1, slp
  388. h = _af_rmul(_af_invert(u), h)
  389. if slp:
  390. u_slp = slps[i][beta][:]
  391. u_slp.reverse()
  392. u_slp = [(i, (g,)) for g in u_slp]
  393. slp = u_slp + slp
  394. if not slp:
  395. return h, base_len + 1
  396. return h, base_len + 1, slp
  397. def _strong_gens_from_distr(strong_gens_distr):
  398. """
  399. Retrieve strong generating set from generators of basic stabilizers.
  400. This is just the union of the generators of the first and second basic
  401. stabilizers.
  402. Parameters
  403. ==========
  404. strong_gens_distr : strong generators distributed by membership in basic stabilizers
  405. Examples
  406. ========
  407. >>> from sympy.combinatorics import SymmetricGroup
  408. >>> from sympy.combinatorics.util import (_strong_gens_from_distr,
  409. ... _distribute_gens_by_base)
  410. >>> S = SymmetricGroup(3)
  411. >>> S.schreier_sims()
  412. >>> S.strong_gens
  413. [(0 1 2), (2)(0 1), (1 2)]
  414. >>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens)
  415. >>> _strong_gens_from_distr(strong_gens_distr)
  416. [(0 1 2), (2)(0 1), (1 2)]
  417. See Also
  418. ========
  419. _distribute_gens_by_base
  420. """
  421. if len(strong_gens_distr) == 1:
  422. return strong_gens_distr[0][:]
  423. else:
  424. result = strong_gens_distr[0]
  425. for gen in strong_gens_distr[1]:
  426. if gen not in result:
  427. result.append(gen)
  428. return result