12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190 |
- from sympy.combinatorics.permutations import Permutation, _af_rmul, \
- _af_invert, _af_new
- from sympy.combinatorics.perm_groups import PermutationGroup, _orbit, \
- _orbit_transversal
- from sympy.combinatorics.util import _distribute_gens_by_base, \
- _orbits_transversals_from_bsgs
- """
- References for tensor canonicalization:
- [1] R. Portugal "Algorithmic simplification of tensor expressions",
- J. Phys. A 32 (1999) 7779-7789
- [2] R. Portugal, B.F. Svaiter "Group-theoretic Approach for Symbolic
- Tensor Manipulation: I. Free Indices"
- arXiv:math-ph/0107031v1
- [3] L.R.U. Manssur, R. Portugal "Group-theoretic Approach for Symbolic
- Tensor Manipulation: II. Dummy Indices"
- arXiv:math-ph/0107032v1
- [4] xperm.c part of XPerm written by J. M. Martin-Garcia
- http://www.xact.es/index.html
- """
- def dummy_sgs(dummies, sym, n):
- """
- Return the strong generators for dummy indices.
- Parameters
- ==========
- dummies : List of dummy indices.
- `dummies[2k], dummies[2k+1]` are paired indices.
- In base form, the dummy indices are always in
- consecutive positions.
- sym : symmetry under interchange of contracted dummies::
- * None no symmetry
- * 0 commuting
- * 1 anticommuting
- n : number of indices
- Examples
- ========
- >>> from sympy.combinatorics.tensor_can import dummy_sgs
- >>> dummy_sgs(list(range(2, 8)), 0, 8)
- [[0, 1, 3, 2, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 5, 4, 6, 7, 8, 9],
- [0, 1, 2, 3, 4, 5, 7, 6, 8, 9], [0, 1, 4, 5, 2, 3, 6, 7, 8, 9],
- [0, 1, 2, 3, 6, 7, 4, 5, 8, 9]]
- """
- if len(dummies) > n:
- raise ValueError("List too large")
- res = []
- # exchange of contravariant and covariant indices
- if sym is not None:
- for j in dummies[::2]:
- a = list(range(n + 2))
- if sym == 1:
- a[n] = n + 1
- a[n + 1] = n
- a[j], a[j + 1] = a[j + 1], a[j]
- res.append(a)
- # rename dummy indices
- for j in dummies[:-3:2]:
- a = list(range(n + 2))
- a[j:j + 4] = a[j + 2], a[j + 3], a[j], a[j + 1]
- res.append(a)
- return res
- def _min_dummies(dummies, sym, indices):
- """
- Return list of minima of the orbits of indices in group of dummies.
- See ``double_coset_can_rep`` for the description of ``dummies`` and ``sym``.
- ``indices`` is the initial list of dummy indices.
- Examples
- ========
- >>> from sympy.combinatorics.tensor_can import _min_dummies
- >>> _min_dummies([list(range(2, 8))], [0], list(range(10)))
- [0, 1, 2, 2, 2, 2, 2, 2, 8, 9]
- """
- num_types = len(sym)
- m = [min(dx) if dx else None for dx in dummies]
- res = indices[:]
- for i in range(num_types):
- for c, i in enumerate(indices):
- for j in range(num_types):
- if i in dummies[j]:
- res[c] = m[j]
- break
- return res
- def _trace_S(s, j, b, S_cosets):
- """
- Return the representative h satisfying s[h[b]] == j
- If there is not such a representative return None
- """
- for h in S_cosets[b]:
- if s[h[b]] == j:
- return h
- return None
- def _trace_D(gj, p_i, Dxtrav):
- """
- Return the representative h satisfying h[gj] == p_i
- If there is not such a representative return None
- """
- for h in Dxtrav:
- if h[gj] == p_i:
- return h
- return None
- def _dumx_remove(dumx, dumx_flat, p0):
- """
- remove p0 from dumx
- """
- res = []
- for dx in dumx:
- if p0 not in dx:
- res.append(dx)
- continue
- k = dx.index(p0)
- if k % 2 == 0:
- p0_paired = dx[k + 1]
- else:
- p0_paired = dx[k - 1]
- dx.remove(p0)
- dx.remove(p0_paired)
- dumx_flat.remove(p0)
- dumx_flat.remove(p0_paired)
- res.append(dx)
- def transversal2coset(size, base, transversal):
- a = []
- j = 0
- for i in range(size):
- if i in base:
- a.append(sorted(transversal[j].values()))
- j += 1
- else:
- a.append([list(range(size))])
- j = len(a) - 1
- while a[j] == [list(range(size))]:
- j -= 1
- return a[:j + 1]
- def double_coset_can_rep(dummies, sym, b_S, sgens, S_transversals, g):
- r"""
- Butler-Portugal algorithm for tensor canonicalization with dummy indices.
- Parameters
- ==========
- dummies
- list of lists of dummy indices,
- one list for each type of index;
- the dummy indices are put in order contravariant, covariant
- [d0, -d0, d1, -d1, ...].
- sym
- list of the symmetries of the index metric for each type.
- possible symmetries of the metrics
- * 0 symmetric
- * 1 antisymmetric
- * None no symmetry
- b_S
- base of a minimal slot symmetry BSGS.
- sgens
- generators of the slot symmetry BSGS.
- S_transversals
- transversals for the slot BSGS.
- g
- permutation representing the tensor.
- Returns
- =======
- Return 0 if the tensor is zero, else return the array form of
- the permutation representing the canonical form of the tensor.
- Notes
- =====
- A tensor with dummy indices can be represented in a number
- of equivalent ways which typically grows exponentially with
- the number of indices. To be able to establish if two tensors
- with many indices are equal becomes computationally very slow
- in absence of an efficient algorithm.
- The Butler-Portugal algorithm [3] is an efficient algorithm to
- put tensors in canonical form, solving the above problem.
- Portugal observed that a tensor can be represented by a permutation,
- and that the class of tensors equivalent to it under slot and dummy
- symmetries is equivalent to the double coset `D*g*S`
- (Note: in this documentation we use the conventions for multiplication
- of permutations p, q with (p*q)(i) = p[q[i]] which is opposite
- to the one used in the Permutation class)
- Using the algorithm by Butler to find a representative of the
- double coset one can find a canonical form for the tensor.
- To see this correspondence,
- let `g` be a permutation in array form; a tensor with indices `ind`
- (the indices including both the contravariant and the covariant ones)
- can be written as
- `t = T(ind[g[0]], \dots, ind[g[n-1]])`,
- where `n = len(ind)`;
- `g` has size `n + 2`, the last two indices for the sign of the tensor
- (trick introduced in [4]).
- A slot symmetry transformation `s` is a permutation acting on the slots
- `t \rightarrow T(ind[(g*s)[0]], \dots, ind[(g*s)[n-1]])`
- A dummy symmetry transformation acts on `ind`
- `t \rightarrow T(ind[(d*g)[0]], \dots, ind[(d*g)[n-1]])`
- Being interested only in the transformations of the tensor under
- these symmetries, one can represent the tensor by `g`, which transforms
- as
- `g -> d*g*s`, so it belongs to the coset `D*g*S`, or in other words
- to the set of all permutations allowed by the slot and dummy symmetries.
- Let us explain the conventions by an example.
- Given a tensor `T^{d3 d2 d1}{}_{d1 d2 d3}` with the slot symmetries
- `T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}`
- `T^{a0 a1 a2 a3 a4 a5} = -T^{a4 a1 a2 a3 a0 a5}`
- and symmetric metric, find the tensor equivalent to it which
- is the lowest under the ordering of indices:
- lexicographic ordering `d1, d2, d3` and then contravariant
- before covariant index; that is the canonical form of the tensor.
- The canonical form is `-T^{d1 d2 d3}{}_{d1 d2 d3}`
- obtained using `T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}`.
- To convert this problem in the input for this function,
- use the following ordering of the index names
- (- for covariant for short) `d1, -d1, d2, -d2, d3, -d3`
- `T^{d3 d2 d1}{}_{d1 d2 d3}` corresponds to `g = [4, 2, 0, 1, 3, 5, 6, 7]`
- where the last two indices are for the sign
- `sgens = [Permutation(0, 2)(6, 7), Permutation(0, 4)(6, 7)]`
- sgens[0] is the slot symmetry `-(0, 2)`
- `T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}`
- sgens[1] is the slot symmetry `-(0, 4)`
- `T^{a0 a1 a2 a3 a4 a5} = -T^{a4 a1 a2 a3 a0 a5}`
- The dummy symmetry group D is generated by the strong base generators
- `[(0, 1), (2, 3), (4, 5), (0, 2)(1, 3), (0, 4)(1, 5)]`
- where the first three interchange covariant and contravariant
- positions of the same index (d1 <-> -d1) and the last two interchange
- the dummy indices themselves (d1 <-> d2).
- The dummy symmetry acts from the left
- `d = [1, 0, 2, 3, 4, 5, 6, 7]` exchange `d1 \leftrightarrow -d1`
- `T^{d3 d2 d1}{}_{d1 d2 d3} == T^{d3 d2}{}_{d1}{}^{d1}{}_{d2 d3}`
- `g=[4, 2, 0, 1, 3, 5, 6, 7] -> [4, 2, 1, 0, 3, 5, 6, 7] = _af_rmul(d, g)`
- which differs from `_af_rmul(g, d)`.
- The slot symmetry acts from the right
- `s = [2, 1, 0, 3, 4, 5, 7, 6]` exchanges slots 0 and 2 and changes sign
- `T^{d3 d2 d1}{}_{d1 d2 d3} == -T^{d1 d2 d3}{}_{d1 d2 d3}`
- `g=[4,2,0,1,3,5,6,7] -> [0, 2, 4, 1, 3, 5, 7, 6] = _af_rmul(g, s)`
- Example in which the tensor is zero, same slot symmetries as above:
- `T^{d2}{}_{d1 d3}{}^{d1 d3}{}_{d2}`
- `= -T^{d3}{}_{d1 d3}{}^{d1 d2}{}_{d2}` under slot symmetry `-(0,4)`;
- `= T_{d3 d1}{}^{d3}{}^{d1 d2}{}_{d2}` under slot symmetry `-(0,2)`;
- `= T^{d3}{}_{d1 d3}{}^{d1 d2}{}_{d2}` symmetric metric;
- `= 0` since two of these lines have tensors differ only for the sign.
- The double coset D*g*S consists of permutations `h = d*g*s` corresponding
- to equivalent tensors; if there are two `h` which are the same apart
- from the sign, return zero; otherwise
- choose as representative the tensor with indices
- ordered lexicographically according to `[d1, -d1, d2, -d2, d3, -d3]`
- that is ``rep = min(D*g*S) = min([d*g*s for d in D for s in S])``
- The indices are fixed one by one; first choose the lowest index
- for slot 0, then the lowest remaining index for slot 1, etc.
- Doing this one obtains a chain of stabilizers
- `S \rightarrow S_{b0} \rightarrow S_{b0,b1} \rightarrow \dots` and
- `D \rightarrow D_{p0} \rightarrow D_{p0,p1} \rightarrow \dots`
- where ``[b0, b1, ...] = range(b)`` is a base of the symmetric group;
- the strong base `b_S` of S is an ordered sublist of it;
- therefore it is sufficient to compute once the
- strong base generators of S using the Schreier-Sims algorithm;
- the stabilizers of the strong base generators are the
- strong base generators of the stabilizer subgroup.
- ``dbase = [p0, p1, ...]`` is not in general in lexicographic order,
- so that one must recompute the strong base generators each time;
- however this is trivial, there is no need to use the Schreier-Sims
- algorithm for D.
- The algorithm keeps a TAB of elements `(s_i, d_i, h_i)`
- where `h_i = d_i \times g \times s_i` satisfying `h_i[j] = p_j` for `0 \le j < i`
- starting from `s_0 = id, d_0 = id, h_0 = g`.
- The equations `h_0[0] = p_0, h_1[1] = p_1, \dots` are solved in this order,
- choosing each time the lowest possible value of p_i
- For `j < i`
- `d_i*g*s_i*S_{b_0, \dots, b_{i-1}}*b_j = D_{p_0, \dots, p_{i-1}}*p_j`
- so that for dx in `D_{p_0,\dots,p_{i-1}}` and sx in
- `S_{base[0], \dots, base[i-1]}` one has `dx*d_i*g*s_i*sx*b_j = p_j`
- Search for dx, sx such that this equation holds for `j = i`;
- it can be written as `s_i*sx*b_j = J, dx*d_i*g*J = p_j`
- `sx*b_j = s_i**-1*J; sx = trace(s_i**-1, S_{b_0,...,b_{i-1}})`
- `dx**-1*p_j = d_i*g*J; dx = trace(d_i*g*J, D_{p_0,...,p_{i-1}})`
- `s_{i+1} = s_i*trace(s_i**-1*J, S_{b_0,...,b_{i-1}})`
- `d_{i+1} = trace(d_i*g*J, D_{p_0,...,p_{i-1}})**-1*d_i`
- `h_{i+1}*b_i = d_{i+1}*g*s_{i+1}*b_i = p_i`
- `h_n*b_j = p_j` for all j, so that `h_n` is the solution.
- Add the found `(s, d, h)` to TAB1.
- At the end of the iteration sort TAB1 with respect to the `h`;
- if there are two consecutive `h` in TAB1 which differ only for the
- sign, the tensor is zero, so return 0;
- if there are two consecutive `h` which are equal, keep only one.
- Then stabilize the slot generators under `i` and the dummy generators
- under `p_i`.
- Assign `TAB = TAB1` at the end of the iteration step.
- At the end `TAB` contains a unique `(s, d, h)`, since all the slots
- of the tensor `h` have been fixed to have the minimum value according
- to the symmetries. The algorithm returns `h`.
- It is important that the slot BSGS has lexicographic minimal base,
- otherwise there is an `i` which does not belong to the slot base
- for which `p_i` is fixed by the dummy symmetry only, while `i`
- is not invariant from the slot stabilizer, so `p_i` is not in
- general the minimal value.
- This algorithm differs slightly from the original algorithm [3]:
- the canonical form is minimal lexicographically, and
- the BSGS has minimal base under lexicographic order.
- Equal tensors `h` are eliminated from TAB.
- Examples
- ========
- >>> from sympy.combinatorics.permutations import Permutation
- >>> from sympy.combinatorics.tensor_can import double_coset_can_rep, get_transversals
- >>> gens = [Permutation(x) for x in [[2, 1, 0, 3, 4, 5, 7, 6], [4, 1, 2, 3, 0, 5, 7, 6]]]
- >>> base = [0, 2]
- >>> g = Permutation([4, 2, 0, 1, 3, 5, 6, 7])
- >>> transversals = get_transversals(base, gens)
- >>> double_coset_can_rep([list(range(6))], [0], base, gens, transversals, g)
- [0, 1, 2, 3, 4, 5, 7, 6]
- >>> g = Permutation([4, 1, 3, 0, 5, 2, 6, 7])
- >>> double_coset_can_rep([list(range(6))], [0], base, gens, transversals, g)
- 0
- """
- size = g.size
- g = g.array_form
- num_dummies = size - 2
- indices = list(range(num_dummies))
- all_metrics_with_sym = not any(_ is None for _ in sym)
- num_types = len(sym)
- dumx = dummies[:]
- dumx_flat = []
- for dx in dumx:
- dumx_flat.extend(dx)
- b_S = b_S[:]
- sgensx = [h._array_form for h in sgens]
- if b_S:
- S_transversals = transversal2coset(size, b_S, S_transversals)
- # strong generating set for D
- dsgsx = []
- for i in range(num_types):
- dsgsx.extend(dummy_sgs(dumx[i], sym[i], num_dummies))
- idn = list(range(size))
- # TAB = list of entries (s, d, h) where h = _af_rmuln(d,g,s)
- # for short, in the following d*g*s means _af_rmuln(d,g,s)
- TAB = [(idn, idn, g)]
- for i in range(size - 2):
- b = i
- testb = b in b_S and sgensx
- if testb:
- sgensx1 = [_af_new(_) for _ in sgensx]
- deltab = _orbit(size, sgensx1, b)
- else:
- deltab = {b}
- # p1 = min(IMAGES) = min(Union D_p*h*deltab for h in TAB)
- if all_metrics_with_sym:
- md = _min_dummies(dumx, sym, indices)
- else:
- md = [min(_orbit(size, [_af_new(
- ddx) for ddx in dsgsx], ii)) for ii in range(size - 2)]
- p_i = min([min([md[h[x]] for x in deltab]) for s, d, h in TAB])
- dsgsx1 = [_af_new(_) for _ in dsgsx]
- Dxtrav = _orbit_transversal(size, dsgsx1, p_i, False, af=True) \
- if dsgsx else None
- if Dxtrav:
- Dxtrav = [_af_invert(x) for x in Dxtrav]
- # compute the orbit of p_i
- for ii in range(num_types):
- if p_i in dumx[ii]:
- # the orbit is made by all the indices in dum[ii]
- if sym[ii] is not None:
- deltap = dumx[ii]
- else:
- # the orbit is made by all the even indices if p_i
- # is even, by all the odd indices if p_i is odd
- p_i_index = dumx[ii].index(p_i) % 2
- deltap = dumx[ii][p_i_index::2]
- break
- else:
- deltap = [p_i]
- TAB1 = []
- while TAB:
- s, d, h = TAB.pop()
- if min([md[h[x]] for x in deltab]) != p_i:
- continue
- deltab1 = [x for x in deltab if md[h[x]] == p_i]
- # NEXT = s*deltab1 intersection (d*g)**-1*deltap
- dg = _af_rmul(d, g)
- dginv = _af_invert(dg)
- sdeltab = [s[x] for x in deltab1]
- gdeltap = [dginv[x] for x in deltap]
- NEXT = [x for x in sdeltab if x in gdeltap]
- # d, s satisfy
- # d*g*s*base[i-1] = p_{i-1}; using the stabilizers
- # d*g*s*S_{base[0],...,base[i-1]}*base[i-1] =
- # D_{p_0,...,p_{i-1}}*p_{i-1}
- # so that to find d1, s1 satisfying d1*g*s1*b = p_i
- # one can look for dx in D_{p_0,...,p_{i-1}} and
- # sx in S_{base[0],...,base[i-1]}
- # d1 = dx*d; s1 = s*sx
- # d1*g*s1*b = dx*d*g*s*sx*b = p_i
- for j in NEXT:
- if testb:
- # solve s1*b = j with s1 = s*sx for some element sx
- # of the stabilizer of ..., base[i-1]
- # sx*b = s**-1*j; sx = _trace_S(s, j,...)
- # s1 = s*trace_S(s**-1*j,...)
- s1 = _trace_S(s, j, b, S_transversals)
- if not s1:
- continue
- else:
- s1 = [s[ix] for ix in s1]
- else:
- s1 = s
- # assert s1[b] == j # invariant
- # solve d1*g*j = p_i with d1 = dx*d for some element dg
- # of the stabilizer of ..., p_{i-1}
- # dx**-1*p_i = d*g*j; dx**-1 = trace_D(d*g*j,...)
- # d1 = trace_D(d*g*j,...)**-1*d
- # to save an inversion in the inner loop; notice we did
- # Dxtrav = [perm_af_invert(x) for x in Dxtrav] out of the loop
- if Dxtrav:
- d1 = _trace_D(dg[j], p_i, Dxtrav)
- if not d1:
- continue
- else:
- if p_i != dg[j]:
- continue
- d1 = idn
- assert d1[dg[j]] == p_i # invariant
- d1 = [d1[ix] for ix in d]
- h1 = [d1[g[ix]] for ix in s1]
- # assert h1[b] == p_i # invariant
- TAB1.append((s1, d1, h1))
- # if TAB contains equal permutations, keep only one of them;
- # if TAB contains equal permutations up to the sign, return 0
- TAB1.sort(key=lambda x: x[-1])
- prev = [0] * size
- while TAB1:
- s, d, h = TAB1.pop()
- if h[:-2] == prev[:-2]:
- if h[-1] != prev[-1]:
- return 0
- else:
- TAB.append((s, d, h))
- prev = h
- # stabilize the SGS
- sgensx = [h for h in sgensx if h[b] == b]
- if b in b_S:
- b_S.remove(b)
- _dumx_remove(dumx, dumx_flat, p_i)
- dsgsx = []
- for i in range(num_types):
- dsgsx.extend(dummy_sgs(dumx[i], sym[i], num_dummies))
- return TAB[0][-1]
- def canonical_free(base, gens, g, num_free):
- """
- Canonicalization of a tensor with respect to free indices
- choosing the minimum with respect to lexicographical ordering
- in the free indices.
- Explanation
- ===========
- ``base``, ``gens`` BSGS for slot permutation group
- ``g`` permutation representing the tensor
- ``num_free`` number of free indices
- The indices must be ordered with first the free indices
- See explanation in double_coset_can_rep
- The algorithm is a variation of the one given in [2].
- Examples
- ========
- >>> from sympy.combinatorics import Permutation
- >>> from sympy.combinatorics.tensor_can import canonical_free
- >>> gens = [[1, 0, 2, 3, 5, 4], [2, 3, 0, 1, 4, 5],[0, 1, 3, 2, 5, 4]]
- >>> gens = [Permutation(h) for h in gens]
- >>> base = [0, 2]
- >>> g = Permutation([2, 1, 0, 3, 4, 5])
- >>> canonical_free(base, gens, g, 4)
- [0, 3, 1, 2, 5, 4]
- Consider the product of Riemann tensors
- ``T = R^{a}_{d0}^{d1,d2}*R_{d2,d1}^{d0,b}``
- The order of the indices is ``[a, b, d0, -d0, d1, -d1, d2, -d2]``
- The permutation corresponding to the tensor is
- ``g = [0, 3, 4, 6, 7, 5, 2, 1, 8, 9]``
- In particular ``a`` is position ``0``, ``b`` is in position ``9``.
- Use the slot symmetries to get `T` is a form which is the minimal
- in lexicographic order in the free indices ``a`` and ``b``, e.g.
- ``-R^{a}_{d0}^{d1,d2}*R^{b,d0}_{d2,d1}`` corresponding to
- ``[0, 3, 4, 6, 1, 2, 7, 5, 9, 8]``
- >>> from sympy.combinatorics.tensor_can import riemann_bsgs, tensor_gens
- >>> base, gens = riemann_bsgs
- >>> size, sbase, sgens = tensor_gens(base, gens, [[], []], 0)
- >>> g = Permutation([0, 3, 4, 6, 7, 5, 2, 1, 8, 9])
- >>> canonical_free(sbase, [Permutation(h) for h in sgens], g, 2)
- [0, 3, 4, 6, 1, 2, 7, 5, 9, 8]
- """
- g = g.array_form
- size = len(g)
- if not base:
- return g[:]
- transversals = get_transversals(base, gens)
- for x in sorted(g[:-2]):
- if x not in base:
- base.append(x)
- h = g
- for i, transv in enumerate(transversals):
- h_i = [size]*num_free
- # find the element s in transversals[i] such that
- # _af_rmul(h, s) has its free elements with the lowest position in h
- s = None
- for sk in transv.values():
- h1 = _af_rmul(h, sk)
- hi = [h1.index(ix) for ix in range(num_free)]
- if hi < h_i:
- h_i = hi
- s = sk
- if s:
- h = _af_rmul(h, s)
- return h
- def _get_map_slots(size, fixed_slots):
- res = list(range(size))
- pos = 0
- for i in range(size):
- if i in fixed_slots:
- continue
- res[i] = pos
- pos += 1
- return res
- def _lift_sgens(size, fixed_slots, free, s):
- a = []
- j = k = 0
- fd = list(zip(fixed_slots, free))
- fd = [y for x, y in sorted(fd)]
- num_free = len(free)
- for i in range(size):
- if i in fixed_slots:
- a.append(fd[k])
- k += 1
- else:
- a.append(s[j] + num_free)
- j += 1
- return a
- def canonicalize(g, dummies, msym, *v):
- """
- canonicalize tensor formed by tensors
- Parameters
- ==========
- g : permutation representing the tensor
- dummies : list representing the dummy indices
- it can be a list of dummy indices of the same type
- or a list of lists of dummy indices, one list for each
- type of index;
- the dummy indices must come after the free indices,
- and put in order contravariant, covariant
- [d0, -d0, d1,-d1,...]
- msym : symmetry of the metric(s)
- it can be an integer or a list;
- in the first case it is the symmetry of the dummy index metric;
- in the second case it is the list of the symmetries of the
- index metric for each type
- v : list, (base_i, gens_i, n_i, sym_i) for tensors of type `i`
- base_i, gens_i : BSGS for tensors of this type.
- The BSGS should have minimal base under lexicographic ordering;
- if not, an attempt is made do get the minimal BSGS;
- in case of failure,
- canonicalize_naive is used, which is much slower.
- n_i : number of tensors of type `i`.
- sym_i : symmetry under exchange of component tensors of type `i`.
- Both for msym and sym_i the cases are
- * None no symmetry
- * 0 commuting
- * 1 anticommuting
- Returns
- =======
- 0 if the tensor is zero, else return the array form of
- the permutation representing the canonical form of the tensor.
- Algorithm
- =========
- First one uses canonical_free to get the minimum tensor under
- lexicographic order, using only the slot symmetries.
- If the component tensors have not minimal BSGS, it is attempted
- to find it; if the attempt fails canonicalize_naive
- is used instead.
- Compute the residual slot symmetry keeping fixed the free indices
- using tensor_gens(base, gens, list_free_indices, sym).
- Reduce the problem eliminating the free indices.
- Then use double_coset_can_rep and lift back the result reintroducing
- the free indices.
- Examples
- ========
- one type of index with commuting metric;
- `A_{a b}` and `B_{a b}` antisymmetric and commuting
- `T = A_{d0 d1} * B^{d0}{}_{d2} * B^{d2 d1}`
- `ord = [d0,-d0,d1,-d1,d2,-d2]` order of the indices
- g = [1, 3, 0, 5, 4, 2, 6, 7]
- `T_c = 0`
- >>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, canonicalize, bsgs_direct_product
- >>> from sympy.combinatorics import Permutation
- >>> base2a, gens2a = get_symmetric_group_sgs(2, 1)
- >>> t0 = (base2a, gens2a, 1, 0)
- >>> t1 = (base2a, gens2a, 2, 0)
- >>> g = Permutation([1, 3, 0, 5, 4, 2, 6, 7])
- >>> canonicalize(g, range(6), 0, t0, t1)
- 0
- same as above, but with `B_{a b}` anticommuting
- `T_c = -A^{d0 d1} * B_{d0}{}^{d2} * B_{d1 d2}`
- can = [0,2,1,4,3,5,7,6]
- >>> t1 = (base2a, gens2a, 2, 1)
- >>> canonicalize(g, range(6), 0, t0, t1)
- [0, 2, 1, 4, 3, 5, 7, 6]
- two types of indices `[a,b,c,d,e,f]` and `[m,n]`, in this order,
- both with commuting metric
- `f^{a b c}` antisymmetric, commuting
- `A_{m a}` no symmetry, commuting
- `T = f^c{}_{d a} * f^f{}_{e b} * A_m{}^d * A^{m b} * A_n{}^a * A^{n e}`
- ord = [c,f,a,-a,b,-b,d,-d,e,-e,m,-m,n,-n]
- g = [0,7,3, 1,9,5, 11,6, 10,4, 13,2, 12,8, 14,15]
- The canonical tensor is
- `T_c = -f^{c a b} * f^{f d e} * A^m{}_a * A_{m d} * A^n{}_b * A_{n e}`
- can = [0,2,4, 1,6,8, 10,3, 11,7, 12,5, 13,9, 15,14]
- >>> base_f, gens_f = get_symmetric_group_sgs(3, 1)
- >>> base1, gens1 = get_symmetric_group_sgs(1)
- >>> base_A, gens_A = bsgs_direct_product(base1, gens1, base1, gens1)
- >>> t0 = (base_f, gens_f, 2, 0)
- >>> t1 = (base_A, gens_A, 4, 0)
- >>> dummies = [range(2, 10), range(10, 14)]
- >>> g = Permutation([0, 7, 3, 1, 9, 5, 11, 6, 10, 4, 13, 2, 12, 8, 14, 15])
- >>> canonicalize(g, dummies, [0, 0], t0, t1)
- [0, 2, 4, 1, 6, 8, 10, 3, 11, 7, 12, 5, 13, 9, 15, 14]
- """
- from sympy.combinatorics.testutil import canonicalize_naive
- if not isinstance(msym, list):
- if msym not in (0, 1, None):
- raise ValueError('msym must be 0, 1 or None')
- num_types = 1
- else:
- num_types = len(msym)
- if not all(msymx in (0, 1, None) for msymx in msym):
- raise ValueError('msym entries must be 0, 1 or None')
- if len(dummies) != num_types:
- raise ValueError(
- 'dummies and msym must have the same number of elements')
- size = g.size
- num_tensors = 0
- v1 = []
- for base_i, gens_i, n_i, sym_i in v:
- # check that the BSGS is minimal;
- # this property is used in double_coset_can_rep;
- # if it is not minimal use canonicalize_naive
- if not _is_minimal_bsgs(base_i, gens_i):
- mbsgs = get_minimal_bsgs(base_i, gens_i)
- if not mbsgs:
- can = canonicalize_naive(g, dummies, msym, *v)
- return can
- base_i, gens_i = mbsgs
- v1.append((base_i, gens_i, [[]] * n_i, sym_i))
- num_tensors += n_i
- if num_types == 1 and not isinstance(msym, list):
- dummies = [dummies]
- msym = [msym]
- flat_dummies = []
- for dumx in dummies:
- flat_dummies.extend(dumx)
- if flat_dummies and flat_dummies != list(range(flat_dummies[0], flat_dummies[-1] + 1)):
- raise ValueError('dummies is not valid')
- # slot symmetry of the tensor
- size1, sbase, sgens = gens_products(*v1)
- if size != size1:
- raise ValueError(
- 'g has size %d, generators have size %d' % (size, size1))
- free = [i for i in range(size - 2) if i not in flat_dummies]
- num_free = len(free)
- # g1 minimal tensor under slot symmetry
- g1 = canonical_free(sbase, sgens, g, num_free)
- if not flat_dummies:
- return g1
- # save the sign of g1
- sign = 0 if g1[-1] == size - 1 else 1
- # the free indices are kept fixed.
- # Determine free_i, the list of slots of tensors which are fixed
- # since they are occupied by free indices, which are fixed.
- start = 0
- for i, (base_i, gens_i, n_i, sym_i) in enumerate(v):
- free_i = []
- len_tens = gens_i[0].size - 2
- # for each component tensor get a list od fixed islots
- for j in range(n_i):
- # get the elements corresponding to the component tensor
- h = g1[start:(start + len_tens)]
- fr = []
- # get the positions of the fixed elements in h
- for k in free:
- if k in h:
- fr.append(h.index(k))
- free_i.append(fr)
- start += len_tens
- v1[i] = (base_i, gens_i, free_i, sym_i)
- # BSGS of the tensor with fixed free indices
- # if tensor_gens fails in gens_product, use canonicalize_naive
- size, sbase, sgens = gens_products(*v1)
- # reduce the permutations getting rid of the free indices
- pos_free = [g1.index(x) for x in range(num_free)]
- size_red = size - num_free
- g1_red = [x - num_free for x in g1 if x in flat_dummies]
- if sign:
- g1_red.extend([size_red - 1, size_red - 2])
- else:
- g1_red.extend([size_red - 2, size_red - 1])
- map_slots = _get_map_slots(size, pos_free)
- sbase_red = [map_slots[i] for i in sbase if i not in pos_free]
- sgens_red = [_af_new([map_slots[i] for i in y._array_form if i not in pos_free]) for y in sgens]
- dummies_red = [[x - num_free for x in y] for y in dummies]
- transv_red = get_transversals(sbase_red, sgens_red)
- g1_red = _af_new(g1_red)
- g2 = double_coset_can_rep(
- dummies_red, msym, sbase_red, sgens_red, transv_red, g1_red)
- if g2 == 0:
- return 0
- # lift to the case with the free indices
- g3 = _lift_sgens(size, pos_free, free, g2)
- return g3
- def perm_af_direct_product(gens1, gens2, signed=True):
- """
- Direct products of the generators gens1 and gens2.
- Examples
- ========
- >>> from sympy.combinatorics.tensor_can import perm_af_direct_product
- >>> gens1 = [[1, 0, 2, 3], [0, 1, 3, 2]]
- >>> gens2 = [[1, 0]]
- >>> perm_af_direct_product(gens1, gens2, False)
- [[1, 0, 2, 3, 4, 5], [0, 1, 3, 2, 4, 5], [0, 1, 2, 3, 5, 4]]
- >>> gens1 = [[1, 0, 2, 3, 5, 4], [0, 1, 3, 2, 4, 5]]
- >>> gens2 = [[1, 0, 2, 3]]
- >>> perm_af_direct_product(gens1, gens2, True)
- [[1, 0, 2, 3, 4, 5, 7, 6], [0, 1, 3, 2, 4, 5, 6, 7], [0, 1, 2, 3, 5, 4, 6, 7]]
- """
- gens1 = [list(x) for x in gens1]
- gens2 = [list(x) for x in gens2]
- s = 2 if signed else 0
- n1 = len(gens1[0]) - s
- n2 = len(gens2[0]) - s
- start = list(range(n1))
- end = list(range(n1, n1 + n2))
- if signed:
- gens1 = [gen[:-2] + end + [gen[-2] + n2, gen[-1] + n2]
- for gen in gens1]
- gens2 = [start + [x + n1 for x in gen] for gen in gens2]
- else:
- gens1 = [gen + end for gen in gens1]
- gens2 = [start + [x + n1 for x in gen] for gen in gens2]
- res = gens1 + gens2
- return res
- def bsgs_direct_product(base1, gens1, base2, gens2, signed=True):
- """
- Direct product of two BSGS.
- Parameters
- ==========
- base1 : base of the first BSGS.
- gens1 : strong generating sequence of the first BSGS.
- base2, gens2 : similarly for the second BSGS.
- signed : flag for signed permutations.
- Examples
- ========
- >>> from sympy.combinatorics.tensor_can import (get_symmetric_group_sgs, bsgs_direct_product)
- >>> base1, gens1 = get_symmetric_group_sgs(1)
- >>> base2, gens2 = get_symmetric_group_sgs(2)
- >>> bsgs_direct_product(base1, gens1, base2, gens2)
- ([1], [(4)(1 2)])
- """
- s = 2 if signed else 0
- n1 = gens1[0].size - s
- base = list(base1)
- base += [x + n1 for x in base2]
- gens1 = [h._array_form for h in gens1]
- gens2 = [h._array_form for h in gens2]
- gens = perm_af_direct_product(gens1, gens2, signed)
- size = len(gens[0])
- id_af = list(range(size))
- gens = [h for h in gens if h != id_af]
- if not gens:
- gens = [id_af]
- return base, [_af_new(h) for h in gens]
- def get_symmetric_group_sgs(n, antisym=False):
- """
- Return base, gens of the minimal BSGS for (anti)symmetric tensor
- Parameters
- ==========
- n : rank of the tensor
- antisym : bool
- ``antisym = False`` symmetric tensor
- ``antisym = True`` antisymmetric tensor
- Examples
- ========
- >>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs
- >>> get_symmetric_group_sgs(3)
- ([0, 1], [(4)(0 1), (4)(1 2)])
- """
- if n == 1:
- return [], [_af_new(list(range(3)))]
- gens = [Permutation(n - 1)(i, i + 1)._array_form for i in range(n - 1)]
- if antisym == 0:
- gens = [x + [n, n + 1] for x in gens]
- else:
- gens = [x + [n + 1, n] for x in gens]
- base = list(range(n - 1))
- return base, [_af_new(h) for h in gens]
- riemann_bsgs = [0, 2], [Permutation(0, 1)(4, 5), Permutation(2, 3)(4, 5),
- Permutation(5)(0, 2)(1, 3)]
- def get_transversals(base, gens):
- """
- Return transversals for the group with BSGS base, gens
- """
- if not base:
- return []
- stabs = _distribute_gens_by_base(base, gens)
- orbits, transversals = _orbits_transversals_from_bsgs(base, stabs)
- transversals = [{x: h._array_form for x, h in y.items()} for y in
- transversals]
- return transversals
- def _is_minimal_bsgs(base, gens):
- """
- Check if the BSGS has minimal base under lexigographic order.
- base, gens BSGS
- Examples
- ========
- >>> from sympy.combinatorics import Permutation
- >>> from sympy.combinatorics.tensor_can import riemann_bsgs, _is_minimal_bsgs
- >>> _is_minimal_bsgs(*riemann_bsgs)
- True
- >>> riemann_bsgs1 = ([2, 0], ([Permutation(5)(0, 1)(4, 5), Permutation(5)(0, 2)(1, 3)]))
- >>> _is_minimal_bsgs(*riemann_bsgs1)
- False
- """
- base1 = []
- sgs1 = gens[:]
- size = gens[0].size
- for i in range(size):
- if not all(h._array_form[i] == i for h in sgs1):
- base1.append(i)
- sgs1 = [h for h in sgs1 if h._array_form[i] == i]
- return base1 == base
- def get_minimal_bsgs(base, gens):
- """
- Compute a minimal GSGS
- base, gens BSGS
- If base, gens is a minimal BSGS return it; else return a minimal BSGS
- if it fails in finding one, it returns None
- TODO: use baseswap in the case in which if it fails in finding a
- minimal BSGS
- Examples
- ========
- >>> from sympy.combinatorics import Permutation
- >>> from sympy.combinatorics.tensor_can import get_minimal_bsgs
- >>> riemann_bsgs1 = ([2, 0], ([Permutation(5)(0, 1)(4, 5), Permutation(5)(0, 2)(1, 3)]))
- >>> get_minimal_bsgs(*riemann_bsgs1)
- ([0, 2], [(0 1)(4 5), (5)(0 2)(1 3), (2 3)(4 5)])
- """
- G = PermutationGroup(gens)
- base, gens = G.schreier_sims_incremental()
- if not _is_minimal_bsgs(base, gens):
- return None
- return base, gens
- def tensor_gens(base, gens, list_free_indices, sym=0):
- """
- Returns size, res_base, res_gens BSGS for n tensors of the
- same type.
- Explanation
- ===========
- base, gens BSGS for tensors of this type
- list_free_indices list of the slots occupied by fixed indices
- for each of the tensors
- sym symmetry under commutation of two tensors
- sym None no symmetry
- sym 0 commuting
- sym 1 anticommuting
- Examples
- ========
- >>> from sympy.combinatorics.tensor_can import tensor_gens, get_symmetric_group_sgs
- two symmetric tensors with 3 indices without free indices
- >>> base, gens = get_symmetric_group_sgs(3)
- >>> tensor_gens(base, gens, [[], []])
- (8, [0, 1, 3, 4], [(7)(0 1), (7)(1 2), (7)(3 4), (7)(4 5), (7)(0 3)(1 4)(2 5)])
- two symmetric tensors with 3 indices with free indices in slot 1 and 0
- >>> tensor_gens(base, gens, [[1], [0]])
- (8, [0, 4], [(7)(0 2), (7)(4 5)])
- four symmetric tensors with 3 indices, two of which with free indices
- """
- def _get_bsgs(G, base, gens, free_indices):
- """
- return the BSGS for G.pointwise_stabilizer(free_indices)
- """
- if not free_indices:
- return base[:], gens[:]
- else:
- H = G.pointwise_stabilizer(free_indices)
- base, sgs = H.schreier_sims_incremental()
- return base, sgs
- # if not base there is no slot symmetry for the component tensors
- # if list_free_indices.count([]) < 2 there is no commutation symmetry
- # so there is no resulting slot symmetry
- if not base and list_free_indices.count([]) < 2:
- n = len(list_free_indices)
- size = gens[0].size
- size = n * (size - 2) + 2
- return size, [], [_af_new(list(range(size)))]
- # if any(list_free_indices) one needs to compute the pointwise
- # stabilizer, so G is needed
- if any(list_free_indices):
- G = PermutationGroup(gens)
- else:
- G = None
- # no_free list of lists of indices for component tensors without fixed
- # indices
- no_free = []
- size = gens[0].size
- id_af = list(range(size))
- num_indices = size - 2
- if not list_free_indices[0]:
- no_free.append(list(range(num_indices)))
- res_base, res_gens = _get_bsgs(G, base, gens, list_free_indices[0])
- for i in range(1, len(list_free_indices)):
- base1, gens1 = _get_bsgs(G, base, gens, list_free_indices[i])
- res_base, res_gens = bsgs_direct_product(res_base, res_gens,
- base1, gens1, 1)
- if not list_free_indices[i]:
- no_free.append(list(range(size - 2, size - 2 + num_indices)))
- size += num_indices
- nr = size - 2
- res_gens = [h for h in res_gens if h._array_form != id_af]
- # if sym there are no commuting tensors stop here
- if sym is None or not no_free:
- if not res_gens:
- res_gens = [_af_new(id_af)]
- return size, res_base, res_gens
- # if the component tensors have moinimal BSGS, so is their direct
- # product P; the slot symmetry group is S = P*C, where C is the group
- # to (anti)commute the component tensors with no free indices
- # a stabilizer has the property S_i = P_i*C_i;
- # the BSGS of P*C has SGS_P + SGS_C and the base is
- # the ordered union of the bases of P and C.
- # If P has minimal BSGS, so has S with this base.
- base_comm = []
- for i in range(len(no_free) - 1):
- ind1 = no_free[i]
- ind2 = no_free[i + 1]
- a = list(range(ind1[0]))
- a.extend(ind2)
- a.extend(ind1)
- base_comm.append(ind1[0])
- a.extend(list(range(ind2[-1] + 1, nr)))
- if sym == 0:
- a.extend([nr, nr + 1])
- else:
- a.extend([nr + 1, nr])
- res_gens.append(_af_new(a))
- res_base = list(res_base)
- # each base is ordered; order the union of the two bases
- for i in base_comm:
- if i not in res_base:
- res_base.append(i)
- res_base.sort()
- if not res_gens:
- res_gens = [_af_new(id_af)]
- return size, res_base, res_gens
- def gens_products(*v):
- """
- Returns size, res_base, res_gens BSGS for n tensors of different types.
- Explanation
- ===========
- v is a sequence of (base_i, gens_i, free_i, sym_i)
- where
- base_i, gens_i BSGS of tensor of type `i`
- free_i list of the fixed slots for each of the tensors
- of type `i`; if there are `n_i` tensors of type `i`
- and none of them have fixed slots, `free = [[]]*n_i`
- sym 0 (1) if the tensors of type `i` (anti)commute among themselves
- Examples
- ========
- >>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, gens_products
- >>> base, gens = get_symmetric_group_sgs(2)
- >>> gens_products((base, gens, [[], []], 0))
- (6, [0, 2], [(5)(0 1), (5)(2 3), (5)(0 2)(1 3)])
- >>> gens_products((base, gens, [[1], []], 0))
- (6, [2], [(5)(2 3)])
- """
- res_size, res_base, res_gens = tensor_gens(*v[0])
- for i in range(1, len(v)):
- size, base, gens = tensor_gens(*v[i])
- res_base, res_gens = bsgs_direct_product(res_base, res_gens, base,
- gens, 1)
- res_size = res_gens[0].size
- id_af = list(range(res_size))
- res_gens = [h for h in res_gens if h != id_af]
- if not res_gens:
- res_gens = [id_af]
- return res_size, res_base, res_gens
|