util.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  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 of length `k`, where `k` is
  107. the length of ``base``. The `i`-th entry contains those elements in
  108. ``gens`` which fix the first `i` elements of ``base`` (so that the
  109. `0`-th entry is equal to ``gens`` itself). If no element fixes the first
  110. `i` elements of ``base``, the `i`-th element is set to a list containing
  111. 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
  161. stabilizers
  162. Returns
  163. =======
  164. ``(transversals, basic_orbits, strong_gens_distr)`` where ``transversals``
  165. are the basic transversals, ``basic_orbits`` are the basic orbits, and
  166. ``strong_gens_distr`` are the strong generators distributed by membership
  167. in basic stabilizers.
  168. Examples
  169. ========
  170. >>> from sympy.combinatorics.named_groups import DihedralGroup
  171. >>> from sympy.combinatorics.util import _handle_precomputed_bsgs
  172. >>> D = DihedralGroup(3)
  173. >>> D.schreier_sims()
  174. >>> _handle_precomputed_bsgs(D.base, D.strong_gens,
  175. ... basic_orbits=D.basic_orbits)
  176. ([{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)]])
  177. See Also
  178. ========
  179. _orbits_transversals_from_bsgs, _distribute_gens_by_base
  180. """
  181. if strong_gens_distr is None:
  182. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  183. if transversals is None:
  184. if basic_orbits is None:
  185. basic_orbits, transversals = \
  186. _orbits_transversals_from_bsgs(base, strong_gens_distr)
  187. else:
  188. transversals = \
  189. _orbits_transversals_from_bsgs(base, strong_gens_distr,
  190. transversals_only=True)
  191. else:
  192. if basic_orbits is None:
  193. base_len = len(base)
  194. basic_orbits = [None]*base_len
  195. for i in range(base_len):
  196. basic_orbits[i] = list(transversals[i].keys())
  197. return transversals, basic_orbits, strong_gens_distr
  198. def _orbits_transversals_from_bsgs(base, strong_gens_distr,
  199. transversals_only=False, slp=False):
  200. """
  201. Compute basic orbits and transversals from a base and strong generating set.
  202. Explanation
  203. ===========
  204. The generators are provided as distributed across the basic stabilizers.
  205. If the optional argument ``transversals_only`` is set to True, only the
  206. transversals are returned.
  207. Parameters
  208. ==========
  209. ``base`` - The base.
  210. ``strong_gens_distr`` - Strong generators distributed by membership in basic
  211. stabilizers.
  212. ``transversals_only`` - bool
  213. A flag switching between returning only the
  214. transversals and both orbits and transversals.
  215. ``slp`` -
  216. If ``True``, return a list of dictionaries containing the
  217. generator presentations of the elements of the transversals,
  218. i.e. the list of indices of generators from ``strong_gens_distr[i]``
  219. such that their product is the relevant transversal element.
  220. Examples
  221. ========
  222. >>> from sympy.combinatorics import SymmetricGroup
  223. >>> from sympy.combinatorics.util import _distribute_gens_by_base
  224. >>> S = SymmetricGroup(3)
  225. >>> S.schreier_sims()
  226. >>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens)
  227. >>> (S.base, strong_gens_distr)
  228. ([0, 1], [[(0 1 2), (2)(0 1), (1 2)], [(1 2)]])
  229. See Also
  230. ========
  231. _distribute_gens_by_base, _handle_precomputed_bsgs
  232. """
  233. from sympy.combinatorics.perm_groups import _orbit_transversal
  234. base_len = len(base)
  235. degree = strong_gens_distr[0][0].size
  236. transversals = [None]*base_len
  237. slps = [None]*base_len
  238. if transversals_only is False:
  239. basic_orbits = [None]*base_len
  240. for i in range(base_len):
  241. transversals[i], slps[i] = _orbit_transversal(degree, strong_gens_distr[i],
  242. base[i], pairs=True, slp=True)
  243. transversals[i] = dict(transversals[i])
  244. if transversals_only is False:
  245. basic_orbits[i] = list(transversals[i].keys())
  246. if transversals_only:
  247. return transversals
  248. else:
  249. if not slp:
  250. return basic_orbits, transversals
  251. return basic_orbits, transversals, slps
  252. def _remove_gens(base, strong_gens, basic_orbits=None, strong_gens_distr=None):
  253. """
  254. Remove redundant generators from a strong generating set.
  255. Parameters
  256. ==========
  257. ``base`` - a base
  258. ``strong_gens`` - a strong generating set relative to ``base``
  259. ``basic_orbits`` - basic orbits
  260. ``strong_gens_distr`` - strong generators distributed by membership in basic
  261. stabilizers
  262. Returns
  263. =======
  264. A strong generating set with respect to ``base`` which is a subset of
  265. ``strong_gens``.
  266. Examples
  267. ========
  268. >>> from sympy.combinatorics import SymmetricGroup
  269. >>> from sympy.combinatorics.util import _remove_gens
  270. >>> from sympy.combinatorics.testutil import _verify_bsgs
  271. >>> S = SymmetricGroup(15)
  272. >>> base, strong_gens = S.schreier_sims_incremental()
  273. >>> new_gens = _remove_gens(base, strong_gens)
  274. >>> len(new_gens)
  275. 14
  276. >>> _verify_bsgs(S, base, new_gens)
  277. True
  278. Notes
  279. =====
  280. This procedure is outlined in [1],p.95.
  281. References
  282. ==========
  283. .. [1] Holt, D., Eick, B., O'Brien, E.
  284. "Handbook of computational group theory"
  285. """
  286. from sympy.combinatorics.perm_groups import _orbit
  287. base_len = len(base)
  288. degree = strong_gens[0].size
  289. if strong_gens_distr is None:
  290. strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
  291. if basic_orbits is None:
  292. basic_orbits = []
  293. for i in range(base_len):
  294. basic_orbit = _orbit(degree, strong_gens_distr[i], base[i])
  295. basic_orbits.append(basic_orbit)
  296. strong_gens_distr.append([])
  297. res = strong_gens[:]
  298. for i in range(base_len - 1, -1, -1):
  299. gens_copy = strong_gens_distr[i][:]
  300. for gen in strong_gens_distr[i]:
  301. if gen not in strong_gens_distr[i + 1]:
  302. temp_gens = gens_copy[:]
  303. temp_gens.remove(gen)
  304. if temp_gens == []:
  305. continue
  306. temp_orbit = _orbit(degree, temp_gens, base[i])
  307. if temp_orbit == basic_orbits[i]:
  308. gens_copy.remove(gen)
  309. res.remove(gen)
  310. return res
  311. def _strip(g, base, orbits, transversals):
  312. """
  313. Attempt to decompose a permutation using a (possibly partial) BSGS
  314. structure.
  315. Explanation
  316. ===========
  317. This is done by treating the sequence ``base`` as an actual base, and
  318. the orbits ``orbits`` and transversals ``transversals`` as basic orbits and
  319. transversals relative to it.
  320. This process is called "sifting". A sift is unsuccessful when a certain
  321. orbit element is not found or when after the sift the decomposition
  322. doesn't end with the identity element.
  323. The argument ``transversals`` is a list of dictionaries that provides
  324. transversal elements for the orbits ``orbits``.
  325. Parameters
  326. ==========
  327. ``g`` - permutation to be decomposed
  328. ``base`` - sequence of points
  329. ``orbits`` - a list in which the ``i``-th entry is an orbit of ``base[i]``
  330. under some subgroup of the pointwise stabilizer of `
  331. `base[0], base[1], ..., base[i - 1]``. The groups themselves are implicit
  332. in this function since the only information we need is encoded in the orbits
  333. and transversals
  334. ``transversals`` - a list of orbit transversals associated with the orbits
  335. ``orbits``.
  336. Examples
  337. ========
  338. >>> from sympy.combinatorics import Permutation, SymmetricGroup
  339. >>> from sympy.combinatorics.util import _strip
  340. >>> S = SymmetricGroup(5)
  341. >>> S.schreier_sims()
  342. >>> g = Permutation([0, 2, 3, 1, 4])
  343. >>> _strip(g, S.base, S.basic_orbits, S.basic_transversals)
  344. ((4), 5)
  345. Notes
  346. =====
  347. The algorithm is described in [1],pp.89-90. The reason for returning
  348. both the current state of the element being decomposed and the level
  349. at which the sifting ends is that they provide important information for
  350. the randomized version of the Schreier-Sims algorithm.
  351. References
  352. ==========
  353. .. [1] Holt, D., Eick, B., O'Brien, E."Handbook of computational group theory"
  354. See Also
  355. ========
  356. sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims
  357. sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims_random
  358. """
  359. h = g._array_form
  360. base_len = len(base)
  361. for i in range(base_len):
  362. beta = h[base[i]]
  363. if beta == base[i]:
  364. continue
  365. if beta not in orbits[i]:
  366. return _af_new(h), i + 1
  367. u = transversals[i][beta]._array_form
  368. h = _af_rmul(_af_invert(u), h)
  369. return _af_new(h), base_len + 1
  370. def _strip_af(h, base, orbits, transversals, j, slp=[], slps={}):
  371. """
  372. optimized _strip, with h, transversals and result in array form
  373. if the stripped elements is the identity, it returns False, base_len + 1
  374. j h[base[i]] == base[i] for i <= j
  375. """
  376. base_len = len(base)
  377. for i in range(j+1, base_len):
  378. beta = h[base[i]]
  379. if beta == base[i]:
  380. continue
  381. if beta not in orbits[i]:
  382. if not slp:
  383. return h, i + 1
  384. return h, i + 1, slp
  385. u = transversals[i][beta]
  386. if h == u:
  387. if not slp:
  388. return False, base_len + 1
  389. return False, base_len + 1, slp
  390. h = _af_rmul(_af_invert(u), h)
  391. if slp:
  392. u_slp = slps[i][beta][:]
  393. u_slp.reverse()
  394. u_slp = [(i, (g,)) for g in u_slp]
  395. slp = u_slp + slp
  396. if not slp:
  397. return h, base_len + 1
  398. return h, base_len + 1, slp
  399. def _strong_gens_from_distr(strong_gens_distr):
  400. """
  401. Retrieve strong generating set from generators of basic stabilizers.
  402. This is just the union of the generators of the first and second basic
  403. stabilizers.
  404. Parameters
  405. ==========
  406. ``strong_gens_distr`` - strong generators distributed by membership in basic
  407. stabilizers
  408. Examples
  409. ========
  410. >>> from sympy.combinatorics import SymmetricGroup
  411. >>> from sympy.combinatorics.util import (_strong_gens_from_distr,
  412. ... _distribute_gens_by_base)
  413. >>> S = SymmetricGroup(3)
  414. >>> S.schreier_sims()
  415. >>> S.strong_gens
  416. [(0 1 2), (2)(0 1), (1 2)]
  417. >>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens)
  418. >>> _strong_gens_from_distr(strong_gens_distr)
  419. [(0 1 2), (2)(0 1), (1 2)]
  420. See Also
  421. ========
  422. _distribute_gens_by_base
  423. """
  424. if len(strong_gens_distr) == 1:
  425. return strong_gens_distr[0][:]
  426. else:
  427. result = strong_gens_distr[0]
  428. for gen in strong_gens_distr[1]:
  429. if gen not in result:
  430. result.append(gen)
  431. return result