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.

1197 lines
40 KiB

6 months ago
  1. from sympy.combinatorics.permutations import Permutation, _af_rmul, \
  2. _af_invert, _af_new
  3. from sympy.combinatorics.perm_groups import PermutationGroup, _orbit, \
  4. _orbit_transversal
  5. from sympy.combinatorics.util import _distribute_gens_by_base, \
  6. _orbits_transversals_from_bsgs
  7. """
  8. References for tensor canonicalization:
  9. [1] R. Portugal "Algorithmic simplification of tensor expressions",
  10. J. Phys. A 32 (1999) 7779-7789
  11. [2] R. Portugal, B.F. Svaiter "Group-theoretic Approach for Symbolic
  12. Tensor Manipulation: I. Free Indices"
  13. arXiv:math-ph/0107031v1
  14. [3] L.R.U. Manssur, R. Portugal "Group-theoretic Approach for Symbolic
  15. Tensor Manipulation: II. Dummy Indices"
  16. arXiv:math-ph/0107032v1
  17. [4] xperm.c part of XPerm written by J. M. Martin-Garcia
  18. http://www.xact.es/index.html
  19. """
  20. def dummy_sgs(dummies, sym, n):
  21. """
  22. Return the strong generators for dummy indices.
  23. Parameters
  24. ==========
  25. dummies : List of dummy indices.
  26. `dummies[2k], dummies[2k+1]` are paired indices.
  27. In base form, the dummy indices are always in
  28. consecutive positions.
  29. sym : symmetry under interchange of contracted dummies::
  30. * None no symmetry
  31. * 0 commuting
  32. * 1 anticommuting
  33. n : number of indices
  34. Examples
  35. ========
  36. >>> from sympy.combinatorics.tensor_can import dummy_sgs
  37. >>> dummy_sgs(list(range(2, 8)), 0, 8)
  38. [[0, 1, 3, 2, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 5, 4, 6, 7, 8, 9],
  39. [0, 1, 2, 3, 4, 5, 7, 6, 8, 9], [0, 1, 4, 5, 2, 3, 6, 7, 8, 9],
  40. [0, 1, 2, 3, 6, 7, 4, 5, 8, 9]]
  41. """
  42. if len(dummies) > n:
  43. raise ValueError("List too large")
  44. res = []
  45. # exchange of contravariant and covariant indices
  46. if sym is not None:
  47. for j in dummies[::2]:
  48. a = list(range(n + 2))
  49. if sym == 1:
  50. a[n] = n + 1
  51. a[n + 1] = n
  52. a[j], a[j + 1] = a[j + 1], a[j]
  53. res.append(a)
  54. # rename dummy indices
  55. for j in dummies[:-3:2]:
  56. a = list(range(n + 2))
  57. a[j:j + 4] = a[j + 2], a[j + 3], a[j], a[j + 1]
  58. res.append(a)
  59. return res
  60. def _min_dummies(dummies, sym, indices):
  61. """
  62. Return list of minima of the orbits of indices in group of dummies.
  63. See ``double_coset_can_rep`` for the description of ``dummies`` and ``sym``.
  64. ``indices`` is the initial list of dummy indices.
  65. Examples
  66. ========
  67. >>> from sympy.combinatorics.tensor_can import _min_dummies
  68. >>> _min_dummies([list(range(2, 8))], [0], list(range(10)))
  69. [0, 1, 2, 2, 2, 2, 2, 2, 8, 9]
  70. """
  71. num_types = len(sym)
  72. m = []
  73. for dx in dummies:
  74. if dx:
  75. m.append(min(dx))
  76. else:
  77. m.append(None)
  78. res = indices[:]
  79. for i in range(num_types):
  80. for c, i in enumerate(indices):
  81. for j in range(num_types):
  82. if i in dummies[j]:
  83. res[c] = m[j]
  84. break
  85. return res
  86. def _trace_S(s, j, b, S_cosets):
  87. """
  88. Return the representative h satisfying s[h[b]] == j
  89. If there is not such a representative return None
  90. """
  91. for h in S_cosets[b]:
  92. if s[h[b]] == j:
  93. return h
  94. return None
  95. def _trace_D(gj, p_i, Dxtrav):
  96. """
  97. Return the representative h satisfying h[gj] == p_i
  98. If there is not such a representative return None
  99. """
  100. for h in Dxtrav:
  101. if h[gj] == p_i:
  102. return h
  103. return None
  104. def _dumx_remove(dumx, dumx_flat, p0):
  105. """
  106. remove p0 from dumx
  107. """
  108. res = []
  109. for dx in dumx:
  110. if p0 not in dx:
  111. res.append(dx)
  112. continue
  113. k = dx.index(p0)
  114. if k % 2 == 0:
  115. p0_paired = dx[k + 1]
  116. else:
  117. p0_paired = dx[k - 1]
  118. dx.remove(p0)
  119. dx.remove(p0_paired)
  120. dumx_flat.remove(p0)
  121. dumx_flat.remove(p0_paired)
  122. res.append(dx)
  123. def transversal2coset(size, base, transversal):
  124. a = []
  125. j = 0
  126. for i in range(size):
  127. if i in base:
  128. a.append(sorted(transversal[j].values()))
  129. j += 1
  130. else:
  131. a.append([list(range(size))])
  132. j = len(a) - 1
  133. while a[j] == [list(range(size))]:
  134. j -= 1
  135. return a[:j + 1]
  136. def double_coset_can_rep(dummies, sym, b_S, sgens, S_transversals, g):
  137. r"""
  138. Butler-Portugal algorithm for tensor canonicalization with dummy indices.
  139. Parameters
  140. ==========
  141. dummies
  142. list of lists of dummy indices,
  143. one list for each type of index;
  144. the dummy indices are put in order contravariant, covariant
  145. [d0, -d0, d1, -d1, ...].
  146. sym
  147. list of the symmetries of the index metric for each type.
  148. possible symmetries of the metrics
  149. * 0 symmetric
  150. * 1 antisymmetric
  151. * None no symmetry
  152. b_S
  153. base of a minimal slot symmetry BSGS.
  154. sgens
  155. generators of the slot symmetry BSGS.
  156. S_transversals
  157. transversals for the slot BSGS.
  158. g
  159. permutation representing the tensor.
  160. Returns
  161. =======
  162. Return 0 if the tensor is zero, else return the array form of
  163. the permutation representing the canonical form of the tensor.
  164. Notes
  165. =====
  166. A tensor with dummy indices can be represented in a number
  167. of equivalent ways which typically grows exponentially with
  168. the number of indices. To be able to establish if two tensors
  169. with many indices are equal becomes computationally very slow
  170. in absence of an efficient algorithm.
  171. The Butler-Portugal algorithm [3] is an efficient algorithm to
  172. put tensors in canonical form, solving the above problem.
  173. Portugal observed that a tensor can be represented by a permutation,
  174. and that the class of tensors equivalent to it under slot and dummy
  175. symmetries is equivalent to the double coset `D*g*S`
  176. (Note: in this documentation we use the conventions for multiplication
  177. of permutations p, q with (p*q)(i) = p[q[i]] which is opposite
  178. to the one used in the Permutation class)
  179. Using the algorithm by Butler to find a representative of the
  180. double coset one can find a canonical form for the tensor.
  181. To see this correspondence,
  182. let `g` be a permutation in array form; a tensor with indices `ind`
  183. (the indices including both the contravariant and the covariant ones)
  184. can be written as
  185. `t = T(ind[g[0]], \dots, ind[g[n-1]])`,
  186. where `n = len(ind)`;
  187. `g` has size `n + 2`, the last two indices for the sign of the tensor
  188. (trick introduced in [4]).
  189. A slot symmetry transformation `s` is a permutation acting on the slots
  190. `t \rightarrow T(ind[(g*s)[0]], \dots, ind[(g*s)[n-1]])`
  191. A dummy symmetry transformation acts on `ind`
  192. `t \rightarrow T(ind[(d*g)[0]], \dots, ind[(d*g)[n-1]])`
  193. Being interested only in the transformations of the tensor under
  194. these symmetries, one can represent the tensor by `g`, which transforms
  195. as
  196. `g -> d*g*s`, so it belongs to the coset `D*g*S`, or in other words
  197. to the set of all permutations allowed by the slot and dummy symmetries.
  198. Let us explain the conventions by an example.
  199. Given a tensor `T^{d3 d2 d1}{}_{d1 d2 d3}` with the slot symmetries
  200. `T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}`
  201. `T^{a0 a1 a2 a3 a4 a5} = -T^{a4 a1 a2 a3 a0 a5}`
  202. and symmetric metric, find the tensor equivalent to it which
  203. is the lowest under the ordering of indices:
  204. lexicographic ordering `d1, d2, d3` and then contravariant
  205. before covariant index; that is the canonical form of the tensor.
  206. The canonical form is `-T^{d1 d2 d3}{}_{d1 d2 d3}`
  207. obtained using `T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}`.
  208. To convert this problem in the input for this function,
  209. use the following ordering of the index names
  210. (- for covariant for short) `d1, -d1, d2, -d2, d3, -d3`
  211. `T^{d3 d2 d1}{}_{d1 d2 d3}` corresponds to `g = [4, 2, 0, 1, 3, 5, 6, 7]`
  212. where the last two indices are for the sign
  213. `sgens = [Permutation(0, 2)(6, 7), Permutation(0, 4)(6, 7)]`
  214. sgens[0] is the slot symmetry `-(0, 2)`
  215. `T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}`
  216. sgens[1] is the slot symmetry `-(0, 4)`
  217. `T^{a0 a1 a2 a3 a4 a5} = -T^{a4 a1 a2 a3 a0 a5}`
  218. The dummy symmetry group D is generated by the strong base generators
  219. `[(0, 1), (2, 3), (4, 5), (0, 2)(1, 3), (0, 4)(1, 5)]`
  220. where the first three interchange covariant and contravariant
  221. positions of the same index (d1 <-> -d1) and the last two interchange
  222. the dummy indices themselves (d1 <-> d2).
  223. The dummy symmetry acts from the left
  224. `d = [1, 0, 2, 3, 4, 5, 6, 7]` exchange `d1 \leftrightarrow -d1`
  225. `T^{d3 d2 d1}{}_{d1 d2 d3} == T^{d3 d2}{}_{d1}{}^{d1}{}_{d2 d3}`
  226. `g=[4, 2, 0, 1, 3, 5, 6, 7] -> [4, 2, 1, 0, 3, 5, 6, 7] = _af_rmul(d, g)`
  227. which differs from `_af_rmul(g, d)`.
  228. The slot symmetry acts from the right
  229. `s = [2, 1, 0, 3, 4, 5, 7, 6]` exchanges slots 0 and 2 and changes sign
  230. `T^{d3 d2 d1}{}_{d1 d2 d3} == -T^{d1 d2 d3}{}_{d1 d2 d3}`
  231. `g=[4,2,0,1,3,5,6,7] -> [0, 2, 4, 1, 3, 5, 7, 6] = _af_rmul(g, s)`
  232. Example in which the tensor is zero, same slot symmetries as above:
  233. `T^{d2}{}_{d1 d3}{}^{d1 d3}{}_{d2}`
  234. `= -T^{d3}{}_{d1 d3}{}^{d1 d2}{}_{d2}` under slot symmetry `-(0,4)`;
  235. `= T_{d3 d1}{}^{d3}{}^{d1 d2}{}_{d2}` under slot symmetry `-(0,2)`;
  236. `= T^{d3}{}_{d1 d3}{}^{d1 d2}{}_{d2}` symmetric metric;
  237. `= 0` since two of these lines have tensors differ only for the sign.
  238. The double coset D*g*S consists of permutations `h = d*g*s` corresponding
  239. to equivalent tensors; if there are two `h` which are the same apart
  240. from the sign, return zero; otherwise
  241. choose as representative the tensor with indices
  242. ordered lexicographically according to `[d1, -d1, d2, -d2, d3, -d3]`
  243. that is ``rep = min(D*g*S) = min([d*g*s for d in D for s in S])``
  244. The indices are fixed one by one; first choose the lowest index
  245. for slot 0, then the lowest remaining index for slot 1, etc.
  246. Doing this one obtains a chain of stabilizers
  247. `S \rightarrow S_{b0} \rightarrow S_{b0,b1} \rightarrow \dots` and
  248. `D \rightarrow D_{p0} \rightarrow D_{p0,p1} \rightarrow \dots`
  249. where ``[b0, b1, ...] = range(b)`` is a base of the symmetric group;
  250. the strong base `b_S` of S is an ordered sublist of it;
  251. therefore it is sufficient to compute once the
  252. strong base generators of S using the Schreier-Sims algorithm;
  253. the stabilizers of the strong base generators are the
  254. strong base generators of the stabilizer subgroup.
  255. ``dbase = [p0, p1, ...]`` is not in general in lexicographic order,
  256. so that one must recompute the strong base generators each time;
  257. however this is trivial, there is no need to use the Schreier-Sims
  258. algorithm for D.
  259. The algorithm keeps a TAB of elements `(s_i, d_i, h_i)`
  260. where `h_i = d_i \times g \times s_i` satisfying `h_i[j] = p_j` for `0 \le j < i`
  261. starting from `s_0 = id, d_0 = id, h_0 = g`.
  262. The equations `h_0[0] = p_0, h_1[1] = p_1, \dots` are solved in this order,
  263. choosing each time the lowest possible value of p_i
  264. For `j < i`
  265. `d_i*g*s_i*S_{b_0, \dots, b_{i-1}}*b_j = D_{p_0, \dots, p_{i-1}}*p_j`
  266. so that for dx in `D_{p_0,\dots,p_{i-1}}` and sx in
  267. `S_{base[0], \dots, base[i-1]}` one has `dx*d_i*g*s_i*sx*b_j = p_j`
  268. Search for dx, sx such that this equation holds for `j = i`;
  269. it can be written as `s_i*sx*b_j = J, dx*d_i*g*J = p_j`
  270. `sx*b_j = s_i**-1*J; sx = trace(s_i**-1, S_{b_0,...,b_{i-1}})`
  271. `dx**-1*p_j = d_i*g*J; dx = trace(d_i*g*J, D_{p_0,...,p_{i-1}})`
  272. `s_{i+1} = s_i*trace(s_i**-1*J, S_{b_0,...,b_{i-1}})`
  273. `d_{i+1} = trace(d_i*g*J, D_{p_0,...,p_{i-1}})**-1*d_i`
  274. `h_{i+1}*b_i = d_{i+1}*g*s_{i+1}*b_i = p_i`
  275. `h_n*b_j = p_j` for all j, so that `h_n` is the solution.
  276. Add the found `(s, d, h)` to TAB1.
  277. At the end of the iteration sort TAB1 with respect to the `h`;
  278. if there are two consecutive `h` in TAB1 which differ only for the
  279. sign, the tensor is zero, so return 0;
  280. if there are two consecutive `h` which are equal, keep only one.
  281. Then stabilize the slot generators under `i` and the dummy generators
  282. under `p_i`.
  283. Assign `TAB = TAB1` at the end of the iteration step.
  284. At the end `TAB` contains a unique `(s, d, h)`, since all the slots
  285. of the tensor `h` have been fixed to have the minimum value according
  286. to the symmetries. The algorithm returns `h`.
  287. It is important that the slot BSGS has lexicographic minimal base,
  288. otherwise there is an `i` which does not belong to the slot base
  289. for which `p_i` is fixed by the dummy symmetry only, while `i`
  290. is not invariant from the slot stabilizer, so `p_i` is not in
  291. general the minimal value.
  292. This algorithm differs slightly from the original algorithm [3]:
  293. the canonical form is minimal lexicographically, and
  294. the BSGS has minimal base under lexicographic order.
  295. Equal tensors `h` are eliminated from TAB.
  296. Examples
  297. ========
  298. >>> from sympy.combinatorics.permutations import Permutation
  299. >>> from sympy.combinatorics.tensor_can import double_coset_can_rep, get_transversals
  300. >>> gens = [Permutation(x) for x in [[2, 1, 0, 3, 4, 5, 7, 6], [4, 1, 2, 3, 0, 5, 7, 6]]]
  301. >>> base = [0, 2]
  302. >>> g = Permutation([4, 2, 0, 1, 3, 5, 6, 7])
  303. >>> transversals = get_transversals(base, gens)
  304. >>> double_coset_can_rep([list(range(6))], [0], base, gens, transversals, g)
  305. [0, 1, 2, 3, 4, 5, 7, 6]
  306. >>> g = Permutation([4, 1, 3, 0, 5, 2, 6, 7])
  307. >>> double_coset_can_rep([list(range(6))], [0], base, gens, transversals, g)
  308. 0
  309. """
  310. size = g.size
  311. g = g.array_form
  312. num_dummies = size - 2
  313. indices = list(range(num_dummies))
  314. all_metrics_with_sym = not any(_ is None for _ in sym)
  315. num_types = len(sym)
  316. dumx = dummies[:]
  317. dumx_flat = []
  318. for dx in dumx:
  319. dumx_flat.extend(dx)
  320. b_S = b_S[:]
  321. sgensx = [h._array_form for h in sgens]
  322. if b_S:
  323. S_transversals = transversal2coset(size, b_S, S_transversals)
  324. # strong generating set for D
  325. dsgsx = []
  326. for i in range(num_types):
  327. dsgsx.extend(dummy_sgs(dumx[i], sym[i], num_dummies))
  328. idn = list(range(size))
  329. # TAB = list of entries (s, d, h) where h = _af_rmuln(d,g,s)
  330. # for short, in the following d*g*s means _af_rmuln(d,g,s)
  331. TAB = [(idn, idn, g)]
  332. for i in range(size - 2):
  333. b = i
  334. testb = b in b_S and sgensx
  335. if testb:
  336. sgensx1 = [_af_new(_) for _ in sgensx]
  337. deltab = _orbit(size, sgensx1, b)
  338. else:
  339. deltab = {b}
  340. # p1 = min(IMAGES) = min(Union D_p*h*deltab for h in TAB)
  341. if all_metrics_with_sym:
  342. md = _min_dummies(dumx, sym, indices)
  343. else:
  344. md = [min(_orbit(size, [_af_new(
  345. ddx) for ddx in dsgsx], ii)) for ii in range(size - 2)]
  346. p_i = min([min([md[h[x]] for x in deltab]) for s, d, h in TAB])
  347. dsgsx1 = [_af_new(_) for _ in dsgsx]
  348. Dxtrav = _orbit_transversal(size, dsgsx1, p_i, False, af=True) \
  349. if dsgsx else None
  350. if Dxtrav:
  351. Dxtrav = [_af_invert(x) for x in Dxtrav]
  352. # compute the orbit of p_i
  353. for ii in range(num_types):
  354. if p_i in dumx[ii]:
  355. # the orbit is made by all the indices in dum[ii]
  356. if sym[ii] is not None:
  357. deltap = dumx[ii]
  358. else:
  359. # the orbit is made by all the even indices if p_i
  360. # is even, by all the odd indices if p_i is odd
  361. p_i_index = dumx[ii].index(p_i) % 2
  362. deltap = dumx[ii][p_i_index::2]
  363. break
  364. else:
  365. deltap = [p_i]
  366. TAB1 = []
  367. while TAB:
  368. s, d, h = TAB.pop()
  369. if min([md[h[x]] for x in deltab]) != p_i:
  370. continue
  371. deltab1 = [x for x in deltab if md[h[x]] == p_i]
  372. # NEXT = s*deltab1 intersection (d*g)**-1*deltap
  373. dg = _af_rmul(d, g)
  374. dginv = _af_invert(dg)
  375. sdeltab = [s[x] for x in deltab1]
  376. gdeltap = [dginv[x] for x in deltap]
  377. NEXT = [x for x in sdeltab if x in gdeltap]
  378. # d, s satisfy
  379. # d*g*s*base[i-1] = p_{i-1}; using the stabilizers
  380. # d*g*s*S_{base[0],...,base[i-1]}*base[i-1] =
  381. # D_{p_0,...,p_{i-1}}*p_{i-1}
  382. # so that to find d1, s1 satisfying d1*g*s1*b = p_i
  383. # one can look for dx in D_{p_0,...,p_{i-1}} and
  384. # sx in S_{base[0],...,base[i-1]}
  385. # d1 = dx*d; s1 = s*sx
  386. # d1*g*s1*b = dx*d*g*s*sx*b = p_i
  387. for j in NEXT:
  388. if testb:
  389. # solve s1*b = j with s1 = s*sx for some element sx
  390. # of the stabilizer of ..., base[i-1]
  391. # sx*b = s**-1*j; sx = _trace_S(s, j,...)
  392. # s1 = s*trace_S(s**-1*j,...)
  393. s1 = _trace_S(s, j, b, S_transversals)
  394. if not s1:
  395. continue
  396. else:
  397. s1 = [s[ix] for ix in s1]
  398. else:
  399. s1 = s
  400. # assert s1[b] == j # invariant
  401. # solve d1*g*j = p_i with d1 = dx*d for some element dg
  402. # of the stabilizer of ..., p_{i-1}
  403. # dx**-1*p_i = d*g*j; dx**-1 = trace_D(d*g*j,...)
  404. # d1 = trace_D(d*g*j,...)**-1*d
  405. # to save an inversion in the inner loop; notice we did
  406. # Dxtrav = [perm_af_invert(x) for x in Dxtrav] out of the loop
  407. if Dxtrav:
  408. d1 = _trace_D(dg[j], p_i, Dxtrav)
  409. if not d1:
  410. continue
  411. else:
  412. if p_i != dg[j]:
  413. continue
  414. d1 = idn
  415. assert d1[dg[j]] == p_i # invariant
  416. d1 = [d1[ix] for ix in d]
  417. h1 = [d1[g[ix]] for ix in s1]
  418. # assert h1[b] == p_i # invariant
  419. TAB1.append((s1, d1, h1))
  420. # if TAB contains equal permutations, keep only one of them;
  421. # if TAB contains equal permutations up to the sign, return 0
  422. TAB1.sort(key=lambda x: x[-1])
  423. prev = [0] * size
  424. while TAB1:
  425. s, d, h = TAB1.pop()
  426. if h[:-2] == prev[:-2]:
  427. if h[-1] != prev[-1]:
  428. return 0
  429. else:
  430. TAB.append((s, d, h))
  431. prev = h
  432. # stabilize the SGS
  433. sgensx = [h for h in sgensx if h[b] == b]
  434. if b in b_S:
  435. b_S.remove(b)
  436. _dumx_remove(dumx, dumx_flat, p_i)
  437. dsgsx = []
  438. for i in range(num_types):
  439. dsgsx.extend(dummy_sgs(dumx[i], sym[i], num_dummies))
  440. return TAB[0][-1]
  441. def canonical_free(base, gens, g, num_free):
  442. """
  443. Canonicalization of a tensor with respect to free indices
  444. choosing the minimum with respect to lexicographical ordering
  445. in the free indices.
  446. Explanation
  447. ===========
  448. ``base``, ``gens`` BSGS for slot permutation group
  449. ``g`` permutation representing the tensor
  450. ``num_free`` number of free indices
  451. The indices must be ordered with first the free indices
  452. See explanation in double_coset_can_rep
  453. The algorithm is a variation of the one given in [2].
  454. Examples
  455. ========
  456. >>> from sympy.combinatorics import Permutation
  457. >>> from sympy.combinatorics.tensor_can import canonical_free
  458. >>> gens = [[1, 0, 2, 3, 5, 4], [2, 3, 0, 1, 4, 5],[0, 1, 3, 2, 5, 4]]
  459. >>> gens = [Permutation(h) for h in gens]
  460. >>> base = [0, 2]
  461. >>> g = Permutation([2, 1, 0, 3, 4, 5])
  462. >>> canonical_free(base, gens, g, 4)
  463. [0, 3, 1, 2, 5, 4]
  464. Consider the product of Riemann tensors
  465. ``T = R^{a}_{d0}^{d1,d2}*R_{d2,d1}^{d0,b}``
  466. The order of the indices is ``[a, b, d0, -d0, d1, -d1, d2, -d2]``
  467. The permutation corresponding to the tensor is
  468. ``g = [0, 3, 4, 6, 7, 5, 2, 1, 8, 9]``
  469. In particular ``a`` is position ``0``, ``b`` is in position ``9``.
  470. Use the slot symmetries to get `T` is a form which is the minimal
  471. in lexicographic order in the free indices ``a`` and ``b``, e.g.
  472. ``-R^{a}_{d0}^{d1,d2}*R^{b,d0}_{d2,d1}`` corresponding to
  473. ``[0, 3, 4, 6, 1, 2, 7, 5, 9, 8]``
  474. >>> from sympy.combinatorics.tensor_can import riemann_bsgs, tensor_gens
  475. >>> base, gens = riemann_bsgs
  476. >>> size, sbase, sgens = tensor_gens(base, gens, [[], []], 0)
  477. >>> g = Permutation([0, 3, 4, 6, 7, 5, 2, 1, 8, 9])
  478. >>> canonical_free(sbase, [Permutation(h) for h in sgens], g, 2)
  479. [0, 3, 4, 6, 1, 2, 7, 5, 9, 8]
  480. """
  481. g = g.array_form
  482. size = len(g)
  483. if not base:
  484. return g[:]
  485. transversals = get_transversals(base, gens)
  486. for x in sorted(g[:-2]):
  487. if x not in base:
  488. base.append(x)
  489. h = g
  490. for i, transv in enumerate(transversals):
  491. h_i = [size]*num_free
  492. # find the element s in transversals[i] such that
  493. # _af_rmul(h, s) has its free elements with the lowest position in h
  494. s = None
  495. for sk in transv.values():
  496. h1 = _af_rmul(h, sk)
  497. hi = [h1.index(ix) for ix in range(num_free)]
  498. if hi < h_i:
  499. h_i = hi
  500. s = sk
  501. if s:
  502. h = _af_rmul(h, s)
  503. return h
  504. def _get_map_slots(size, fixed_slots):
  505. res = list(range(size))
  506. pos = 0
  507. for i in range(size):
  508. if i in fixed_slots:
  509. continue
  510. res[i] = pos
  511. pos += 1
  512. return res
  513. def _lift_sgens(size, fixed_slots, free, s):
  514. a = []
  515. j = k = 0
  516. fd = list(zip(fixed_slots, free))
  517. fd = [y for x, y in sorted(fd)]
  518. num_free = len(free)
  519. for i in range(size):
  520. if i in fixed_slots:
  521. a.append(fd[k])
  522. k += 1
  523. else:
  524. a.append(s[j] + num_free)
  525. j += 1
  526. return a
  527. def canonicalize(g, dummies, msym, *v):
  528. """
  529. canonicalize tensor formed by tensors
  530. Parameters
  531. ==========
  532. g : permutation representing the tensor
  533. dummies : list representing the dummy indices
  534. it can be a list of dummy indices of the same type
  535. or a list of lists of dummy indices, one list for each
  536. type of index;
  537. the dummy indices must come after the free indices,
  538. and put in order contravariant, covariant
  539. [d0, -d0, d1,-d1,...]
  540. msym : symmetry of the metric(s)
  541. it can be an integer or a list;
  542. in the first case it is the symmetry of the dummy index metric;
  543. in the second case it is the list of the symmetries of the
  544. index metric for each type
  545. v : list, (base_i, gens_i, n_i, sym_i) for tensors of type `i`
  546. base_i, gens_i : BSGS for tensors of this type.
  547. The BSGS should have minimal base under lexicographic ordering;
  548. if not, an attempt is made do get the minimal BSGS;
  549. in case of failure,
  550. canonicalize_naive is used, which is much slower.
  551. n_i : number of tensors of type `i`.
  552. sym_i : symmetry under exchange of component tensors of type `i`.
  553. Both for msym and sym_i the cases are
  554. * None no symmetry
  555. * 0 commuting
  556. * 1 anticommuting
  557. Returns
  558. =======
  559. 0 if the tensor is zero, else return the array form of
  560. the permutation representing the canonical form of the tensor.
  561. Algorithm
  562. =========
  563. First one uses canonical_free to get the minimum tensor under
  564. lexicographic order, using only the slot symmetries.
  565. If the component tensors have not minimal BSGS, it is attempted
  566. to find it; if the attempt fails canonicalize_naive
  567. is used instead.
  568. Compute the residual slot symmetry keeping fixed the free indices
  569. using tensor_gens(base, gens, list_free_indices, sym).
  570. Reduce the problem eliminating the free indices.
  571. Then use double_coset_can_rep and lift back the result reintroducing
  572. the free indices.
  573. Examples
  574. ========
  575. one type of index with commuting metric;
  576. `A_{a b}` and `B_{a b}` antisymmetric and commuting
  577. `T = A_{d0 d1} * B^{d0}{}_{d2} * B^{d2 d1}`
  578. `ord = [d0,-d0,d1,-d1,d2,-d2]` order of the indices
  579. g = [1, 3, 0, 5, 4, 2, 6, 7]
  580. `T_c = 0`
  581. >>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, canonicalize, bsgs_direct_product
  582. >>> from sympy.combinatorics import Permutation
  583. >>> base2a, gens2a = get_symmetric_group_sgs(2, 1)
  584. >>> t0 = (base2a, gens2a, 1, 0)
  585. >>> t1 = (base2a, gens2a, 2, 0)
  586. >>> g = Permutation([1, 3, 0, 5, 4, 2, 6, 7])
  587. >>> canonicalize(g, range(6), 0, t0, t1)
  588. 0
  589. same as above, but with `B_{a b}` anticommuting
  590. `T_c = -A^{d0 d1} * B_{d0}{}^{d2} * B_{d1 d2}`
  591. can = [0,2,1,4,3,5,7,6]
  592. >>> t1 = (base2a, gens2a, 2, 1)
  593. >>> canonicalize(g, range(6), 0, t0, t1)
  594. [0, 2, 1, 4, 3, 5, 7, 6]
  595. two types of indices `[a,b,c,d,e,f]` and `[m,n]`, in this order,
  596. both with commuting metric
  597. `f^{a b c}` antisymmetric, commuting
  598. `A_{m a}` no symmetry, commuting
  599. `T = f^c{}_{d a} * f^f{}_{e b} * A_m{}^d * A^{m b} * A_n{}^a * A^{n e}`
  600. ord = [c,f,a,-a,b,-b,d,-d,e,-e,m,-m,n,-n]
  601. g = [0,7,3, 1,9,5, 11,6, 10,4, 13,2, 12,8, 14,15]
  602. The canonical tensor is
  603. `T_c = -f^{c a b} * f^{f d e} * A^m{}_a * A_{m d} * A^n{}_b * A_{n e}`
  604. can = [0,2,4, 1,6,8, 10,3, 11,7, 12,5, 13,9, 15,14]
  605. >>> base_f, gens_f = get_symmetric_group_sgs(3, 1)
  606. >>> base1, gens1 = get_symmetric_group_sgs(1)
  607. >>> base_A, gens_A = bsgs_direct_product(base1, gens1, base1, gens1)
  608. >>> t0 = (base_f, gens_f, 2, 0)
  609. >>> t1 = (base_A, gens_A, 4, 0)
  610. >>> dummies = [range(2, 10), range(10, 14)]
  611. >>> g = Permutation([0, 7, 3, 1, 9, 5, 11, 6, 10, 4, 13, 2, 12, 8, 14, 15])
  612. >>> canonicalize(g, dummies, [0, 0], t0, t1)
  613. [0, 2, 4, 1, 6, 8, 10, 3, 11, 7, 12, 5, 13, 9, 15, 14]
  614. """
  615. from sympy.combinatorics.testutil import canonicalize_naive
  616. if not isinstance(msym, list):
  617. if msym not in (0, 1, None):
  618. raise ValueError('msym must be 0, 1 or None')
  619. num_types = 1
  620. else:
  621. num_types = len(msym)
  622. if not all(msymx in (0, 1, None) for msymx in msym):
  623. raise ValueError('msym entries must be 0, 1 or None')
  624. if len(dummies) != num_types:
  625. raise ValueError(
  626. 'dummies and msym must have the same number of elements')
  627. size = g.size
  628. num_tensors = 0
  629. v1 = []
  630. for i in range(len(v)):
  631. base_i, gens_i, n_i, sym_i = v[i]
  632. # check that the BSGS is minimal;
  633. # this property is used in double_coset_can_rep;
  634. # if it is not minimal use canonicalize_naive
  635. if not _is_minimal_bsgs(base_i, gens_i):
  636. mbsgs = get_minimal_bsgs(base_i, gens_i)
  637. if not mbsgs:
  638. can = canonicalize_naive(g, dummies, msym, *v)
  639. return can
  640. base_i, gens_i = mbsgs
  641. v1.append((base_i, gens_i, [[]] * n_i, sym_i))
  642. num_tensors += n_i
  643. if num_types == 1 and not isinstance(msym, list):
  644. dummies = [dummies]
  645. msym = [msym]
  646. flat_dummies = []
  647. for dumx in dummies:
  648. flat_dummies.extend(dumx)
  649. if flat_dummies and flat_dummies != list(range(flat_dummies[0], flat_dummies[-1] + 1)):
  650. raise ValueError('dummies is not valid')
  651. # slot symmetry of the tensor
  652. size1, sbase, sgens = gens_products(*v1)
  653. if size != size1:
  654. raise ValueError(
  655. 'g has size %d, generators have size %d' % (size, size1))
  656. free = [i for i in range(size - 2) if i not in flat_dummies]
  657. num_free = len(free)
  658. # g1 minimal tensor under slot symmetry
  659. g1 = canonical_free(sbase, sgens, g, num_free)
  660. if not flat_dummies:
  661. return g1
  662. # save the sign of g1
  663. sign = 0 if g1[-1] == size - 1 else 1
  664. # the free indices are kept fixed.
  665. # Determine free_i, the list of slots of tensors which are fixed
  666. # since they are occupied by free indices, which are fixed.
  667. start = 0
  668. for i in range(len(v)):
  669. free_i = []
  670. base_i, gens_i, n_i, sym_i = v[i]
  671. len_tens = gens_i[0].size - 2
  672. # for each component tensor get a list od fixed islots
  673. for j in range(n_i):
  674. # get the elements corresponding to the component tensor
  675. h = g1[start:(start + len_tens)]
  676. fr = []
  677. # get the positions of the fixed elements in h
  678. for k in free:
  679. if k in h:
  680. fr.append(h.index(k))
  681. free_i.append(fr)
  682. start += len_tens
  683. v1[i] = (base_i, gens_i, free_i, sym_i)
  684. # BSGS of the tensor with fixed free indices
  685. # if tensor_gens fails in gens_product, use canonicalize_naive
  686. size, sbase, sgens = gens_products(*v1)
  687. # reduce the permutations getting rid of the free indices
  688. pos_free = [g1.index(x) for x in range(num_free)]
  689. size_red = size - num_free
  690. g1_red = [x - num_free for x in g1 if x in flat_dummies]
  691. if sign:
  692. g1_red.extend([size_red - 1, size_red - 2])
  693. else:
  694. g1_red.extend([size_red - 2, size_red - 1])
  695. map_slots = _get_map_slots(size, pos_free)
  696. sbase_red = [map_slots[i] for i in sbase if i not in pos_free]
  697. sgens_red = [_af_new([map_slots[i] for i in y._array_form if i not in pos_free]) for y in sgens]
  698. dummies_red = [[x - num_free for x in y] for y in dummies]
  699. transv_red = get_transversals(sbase_red, sgens_red)
  700. g1_red = _af_new(g1_red)
  701. g2 = double_coset_can_rep(
  702. dummies_red, msym, sbase_red, sgens_red, transv_red, g1_red)
  703. if g2 == 0:
  704. return 0
  705. # lift to the case with the free indices
  706. g3 = _lift_sgens(size, pos_free, free, g2)
  707. return g3
  708. def perm_af_direct_product(gens1, gens2, signed=True):
  709. """
  710. Direct products of the generators gens1 and gens2.
  711. Examples
  712. ========
  713. >>> from sympy.combinatorics.tensor_can import perm_af_direct_product
  714. >>> gens1 = [[1, 0, 2, 3], [0, 1, 3, 2]]
  715. >>> gens2 = [[1, 0]]
  716. >>> perm_af_direct_product(gens1, gens2, False)
  717. [[1, 0, 2, 3, 4, 5], [0, 1, 3, 2, 4, 5], [0, 1, 2, 3, 5, 4]]
  718. >>> gens1 = [[1, 0, 2, 3, 5, 4], [0, 1, 3, 2, 4, 5]]
  719. >>> gens2 = [[1, 0, 2, 3]]
  720. >>> perm_af_direct_product(gens1, gens2, True)
  721. [[1, 0, 2, 3, 4, 5, 7, 6], [0, 1, 3, 2, 4, 5, 6, 7], [0, 1, 2, 3, 5, 4, 6, 7]]
  722. """
  723. gens1 = [list(x) for x in gens1]
  724. gens2 = [list(x) for x in gens2]
  725. s = 2 if signed else 0
  726. n1 = len(gens1[0]) - s
  727. n2 = len(gens2[0]) - s
  728. start = list(range(n1))
  729. end = list(range(n1, n1 + n2))
  730. if signed:
  731. gens1 = [gen[:-2] + end + [gen[-2] + n2, gen[-1] + n2]
  732. for gen in gens1]
  733. gens2 = [start + [x + n1 for x in gen] for gen in gens2]
  734. else:
  735. gens1 = [gen + end for gen in gens1]
  736. gens2 = [start + [x + n1 for x in gen] for gen in gens2]
  737. res = gens1 + gens2
  738. return res
  739. def bsgs_direct_product(base1, gens1, base2, gens2, signed=True):
  740. """
  741. Direct product of two BSGS.
  742. Parameters
  743. ==========
  744. base1 : base of the first BSGS.
  745. gens1 : strong generating sequence of the first BSGS.
  746. base2, gens2 : similarly for the second BSGS.
  747. signed : flag for signed permutations.
  748. Examples
  749. ========
  750. >>> from sympy.combinatorics.tensor_can import (get_symmetric_group_sgs, bsgs_direct_product)
  751. >>> base1, gens1 = get_symmetric_group_sgs(1)
  752. >>> base2, gens2 = get_symmetric_group_sgs(2)
  753. >>> bsgs_direct_product(base1, gens1, base2, gens2)
  754. ([1], [(4)(1 2)])
  755. """
  756. s = 2 if signed else 0
  757. n1 = gens1[0].size - s
  758. base = list(base1)
  759. base += [x + n1 for x in base2]
  760. gens1 = [h._array_form for h in gens1]
  761. gens2 = [h._array_form for h in gens2]
  762. gens = perm_af_direct_product(gens1, gens2, signed)
  763. size = len(gens[0])
  764. id_af = list(range(size))
  765. gens = [h for h in gens if h != id_af]
  766. if not gens:
  767. gens = [id_af]
  768. return base, [_af_new(h) for h in gens]
  769. def get_symmetric_group_sgs(n, antisym=False):
  770. """
  771. Return base, gens of the minimal BSGS for (anti)symmetric tensor
  772. Parameters
  773. ==========
  774. ``n``: rank of the tensor
  775. ``antisym`` : bool
  776. ``antisym = False`` symmetric tensor
  777. ``antisym = True`` antisymmetric tensor
  778. Examples
  779. ========
  780. >>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs
  781. >>> get_symmetric_group_sgs(3)
  782. ([0, 1], [(4)(0 1), (4)(1 2)])
  783. """
  784. if n == 1:
  785. return [], [_af_new(list(range(3)))]
  786. gens = [Permutation(n - 1)(i, i + 1)._array_form for i in range(n - 1)]
  787. if antisym == 0:
  788. gens = [x + [n, n + 1] for x in gens]
  789. else:
  790. gens = [x + [n + 1, n] for x in gens]
  791. base = list(range(n - 1))
  792. return base, [_af_new(h) for h in gens]
  793. riemann_bsgs = [0, 2], [Permutation(0, 1)(4, 5), Permutation(2, 3)(4, 5),
  794. Permutation(5)(0, 2)(1, 3)]
  795. def get_transversals(base, gens):
  796. """
  797. Return transversals for the group with BSGS base, gens
  798. """
  799. if not base:
  800. return []
  801. stabs = _distribute_gens_by_base(base, gens)
  802. orbits, transversals = _orbits_transversals_from_bsgs(base, stabs)
  803. transversals = [{x: h._array_form for x, h in y.items()} for y in
  804. transversals]
  805. return transversals
  806. def _is_minimal_bsgs(base, gens):
  807. """
  808. Check if the BSGS has minimal base under lexigographic order.
  809. base, gens BSGS
  810. Examples
  811. ========
  812. >>> from sympy.combinatorics import Permutation
  813. >>> from sympy.combinatorics.tensor_can import riemann_bsgs, _is_minimal_bsgs
  814. >>> _is_minimal_bsgs(*riemann_bsgs)
  815. True
  816. >>> riemann_bsgs1 = ([2, 0], ([Permutation(5)(0, 1)(4, 5), Permutation(5)(0, 2)(1, 3)]))
  817. >>> _is_minimal_bsgs(*riemann_bsgs1)
  818. False
  819. """
  820. base1 = []
  821. sgs1 = gens[:]
  822. size = gens[0].size
  823. for i in range(size):
  824. if not all(h._array_form[i] == i for h in sgs1):
  825. base1.append(i)
  826. sgs1 = [h for h in sgs1 if h._array_form[i] == i]
  827. return base1 == base
  828. def get_minimal_bsgs(base, gens):
  829. """
  830. Compute a minimal GSGS
  831. base, gens BSGS
  832. If base, gens is a minimal BSGS return it; else return a minimal BSGS
  833. if it fails in finding one, it returns None
  834. TODO: use baseswap in the case in which if it fails in finding a
  835. minimal BSGS
  836. Examples
  837. ========
  838. >>> from sympy.combinatorics import Permutation
  839. >>> from sympy.combinatorics.tensor_can import get_minimal_bsgs
  840. >>> riemann_bsgs1 = ([2, 0], ([Permutation(5)(0, 1)(4, 5), Permutation(5)(0, 2)(1, 3)]))
  841. >>> get_minimal_bsgs(*riemann_bsgs1)
  842. ([0, 2], [(0 1)(4 5), (5)(0 2)(1 3), (2 3)(4 5)])
  843. """
  844. G = PermutationGroup(gens)
  845. base, gens = G.schreier_sims_incremental()
  846. if not _is_minimal_bsgs(base, gens):
  847. return None
  848. return base, gens
  849. def tensor_gens(base, gens, list_free_indices, sym=0):
  850. """
  851. Returns size, res_base, res_gens BSGS for n tensors of the
  852. same type.
  853. Explanation
  854. ===========
  855. base, gens BSGS for tensors of this type
  856. list_free_indices list of the slots occupied by fixed indices
  857. for each of the tensors
  858. sym symmetry under commutation of two tensors
  859. sym None no symmetry
  860. sym 0 commuting
  861. sym 1 anticommuting
  862. Examples
  863. ========
  864. >>> from sympy.combinatorics.tensor_can import tensor_gens, get_symmetric_group_sgs
  865. two symmetric tensors with 3 indices without free indices
  866. >>> base, gens = get_symmetric_group_sgs(3)
  867. >>> tensor_gens(base, gens, [[], []])
  868. (8, [0, 1, 3, 4], [(7)(0 1), (7)(1 2), (7)(3 4), (7)(4 5), (7)(0 3)(1 4)(2 5)])
  869. two symmetric tensors with 3 indices with free indices in slot 1 and 0
  870. >>> tensor_gens(base, gens, [[1], [0]])
  871. (8, [0, 4], [(7)(0 2), (7)(4 5)])
  872. four symmetric tensors with 3 indices, two of which with free indices
  873. """
  874. def _get_bsgs(G, base, gens, free_indices):
  875. """
  876. return the BSGS for G.pointwise_stabilizer(free_indices)
  877. """
  878. if not free_indices:
  879. return base[:], gens[:]
  880. else:
  881. H = G.pointwise_stabilizer(free_indices)
  882. base, sgs = H.schreier_sims_incremental()
  883. return base, sgs
  884. # if not base there is no slot symmetry for the component tensors
  885. # if list_free_indices.count([]) < 2 there is no commutation symmetry
  886. # so there is no resulting slot symmetry
  887. if not base and list_free_indices.count([]) < 2:
  888. n = len(list_free_indices)
  889. size = gens[0].size
  890. size = n * (size - 2) + 2
  891. return size, [], [_af_new(list(range(size)))]
  892. # if any(list_free_indices) one needs to compute the pointwise
  893. # stabilizer, so G is needed
  894. if any(list_free_indices):
  895. G = PermutationGroup(gens)
  896. else:
  897. G = None
  898. # no_free list of lists of indices for component tensors without fixed
  899. # indices
  900. no_free = []
  901. size = gens[0].size
  902. id_af = list(range(size))
  903. num_indices = size - 2
  904. if not list_free_indices[0]:
  905. no_free.append(list(range(num_indices)))
  906. res_base, res_gens = _get_bsgs(G, base, gens, list_free_indices[0])
  907. for i in range(1, len(list_free_indices)):
  908. base1, gens1 = _get_bsgs(G, base, gens, list_free_indices[i])
  909. res_base, res_gens = bsgs_direct_product(res_base, res_gens,
  910. base1, gens1, 1)
  911. if not list_free_indices[i]:
  912. no_free.append(list(range(size - 2, size - 2 + num_indices)))
  913. size += num_indices
  914. nr = size - 2
  915. res_gens = [h for h in res_gens if h._array_form != id_af]
  916. # if sym there are no commuting tensors stop here
  917. if sym is None or not no_free:
  918. if not res_gens:
  919. res_gens = [_af_new(id_af)]
  920. return size, res_base, res_gens
  921. # if the component tensors have moinimal BSGS, so is their direct
  922. # product P; the slot symmetry group is S = P*C, where C is the group
  923. # to (anti)commute the component tensors with no free indices
  924. # a stabilizer has the property S_i = P_i*C_i;
  925. # the BSGS of P*C has SGS_P + SGS_C and the base is
  926. # the ordered union of the bases of P and C.
  927. # If P has minimal BSGS, so has S with this base.
  928. base_comm = []
  929. for i in range(len(no_free) - 1):
  930. ind1 = no_free[i]
  931. ind2 = no_free[i + 1]
  932. a = list(range(ind1[0]))
  933. a.extend(ind2)
  934. a.extend(ind1)
  935. base_comm.append(ind1[0])
  936. a.extend(list(range(ind2[-1] + 1, nr)))
  937. if sym == 0:
  938. a.extend([nr, nr + 1])
  939. else:
  940. a.extend([nr + 1, nr])
  941. res_gens.append(_af_new(a))
  942. res_base = list(res_base)
  943. # each base is ordered; order the union of the two bases
  944. for i in base_comm:
  945. if i not in res_base:
  946. res_base.append(i)
  947. res_base.sort()
  948. if not res_gens:
  949. res_gens = [_af_new(id_af)]
  950. return size, res_base, res_gens
  951. def gens_products(*v):
  952. """
  953. Returns size, res_base, res_gens BSGS for n tensors of different types.
  954. Explanation
  955. ===========
  956. v is a sequence of (base_i, gens_i, free_i, sym_i)
  957. where
  958. base_i, gens_i BSGS of tensor of type `i`
  959. free_i list of the fixed slots for each of the tensors
  960. of type `i`; if there are `n_i` tensors of type `i`
  961. and none of them have fixed slots, `free = [[]]*n_i`
  962. sym 0 (1) if the tensors of type `i` (anti)commute among themselves
  963. Examples
  964. ========
  965. >>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, gens_products
  966. >>> base, gens = get_symmetric_group_sgs(2)
  967. >>> gens_products((base, gens, [[], []], 0))
  968. (6, [0, 2], [(5)(0 1), (5)(2 3), (5)(0 2)(1 3)])
  969. >>> gens_products((base, gens, [[1], []], 0))
  970. (6, [2], [(5)(2 3)])
  971. """
  972. res_size, res_base, res_gens = tensor_gens(*v[0])
  973. for i in range(1, len(v)):
  974. size, base, gens = tensor_gens(*v[i])
  975. res_base, res_gens = bsgs_direct_product(res_base, res_gens, base,
  976. gens, 1)
  977. res_size = res_gens[0].size
  978. id_af = list(range(res_size))
  979. res_gens = [h for h in res_gens if h != id_af]
  980. if not res_gens:
  981. res_gens = [id_af]
  982. return res_size, res_base, res_gens