tensor_can.py 40 KB

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