1191 lines
40 KiB
Python
1191 lines
40 KiB
Python
|
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
|