and that the space of rank five isotropic tensors is spanned by:
Because the generalized constitutive relation () must be symmetric with respect to changes in sign of the thermodynamic force and flux, we are only interested in even rank tensors. I have written a program that generates the isotropic tensors of arbitrary even rank, and then proceeds to apply symmetrization operations to the tensors to produce the final set of independent tensors. Modifying it to produce odd rank tensors should not be too difficult a task. An example of its use, may be found in finding the number of transport coefficients used to describe the second order Burnett coefficient between stress and strain. This coefficient connects a second rank stress tensor with the square of a second rank strain tensor. The coefficient is thus of sixth rank. There are fifteen sixth rank isotropic tensors, but not all of them are valid candidates because the only non-vanishing components must be symmetric with respect to interchanges of the third and fifth argument, as well as the fourth and the sixth. This is the case because in the expression , interchanging the two s produces no effect, neither the two s. In the program, this is performed by the lines
In the case of a monatomic fluid, we may further expect that the tensor should be symmetric with respect to the first two indices.Symmetrize(LinComb[i], 3, 5); Symmetrize(LinComb[i], 4, 6);
The program listing following can be used for arbitrary rank by changing the constants rank, NoComp and NoCompp1. Rank is fairly obviously the tensorial rank of the tensor under study. NoComp is the number of independent isotropic tensors of that rank, given by .
In the program, the basis tensors are represented as an array with rank components of type integer, which contain the sequence of vectors involved in the inner products. For example, in the rank four case would be represented by the integer sequence 1423. The complete basis is generated in the calcTensors procedure, and stored in Tensors. It does this by generating all possible permutations of the rank vectors, then expressing them all in a canonical form in which each pair is ordered so that the lowest index comes first within the pairs, and the pairs are then ordered according to the first index in each pair. When the canonical form is found, it is checked against a list of all previous canonical tensors found, and only added to the list if it is found to be distinct.
To generate the symmetrized tensors, we store the coefficients of a linear combination of the basis tensors in variables of type LinType. The algorithm proceeds by taking every basis tensor corresponding to non-zero coefficients, swapping the indices to be symmetrized, placing the tensor in canonical form, comparing this result against the basis list to find which tensor it corresponds to, then adding the original non-zero coefficient to the slot corresponding to the swapped basis tensor.
program tensors; {even rank version} const rank = 6; NoComp = 15; {NoComp = (rank-1)(rank-3)...3} NoCompp1 = 16; {NoComp+1} type jvalType = set of 2..rank; TensorType = array[1..rank] of integer; LinType = array[1..NoComp] of integer; var tensor: array[1..NoCompp1] of TensorType; i, j: integer; TensorIndex: integer; zero: LinType; LinComb: array[1..NoComp] of LinType; out: text; procedure swap (var x, y: integer); var t: integer; begin t := x; x := y; y := t; end; procedure Sort (left, right: integer; var Tensor: TensorType); {Yep, the good old Qsort routine copied from ATPUG issue 1} var II, JJ, temp: integer; Pivot: integer; IIisGTright, JJisLTleft, IIgreatThan: boolean; begin II := left; JJ := right; pivot := Tensor[pred(2 * ((II + JJ) div 2))]; repeat while Tensor[pred(2 * II)] < pivot do II := succ(II); while Tensor[pred(2 * JJ)] > pivot do JJ := pred(JJ); if II <= JJ then begin swap(Tensor[2 * II - 1], Tensor[2 * JJ - 1]); swap(Tensor[2 * II], Tensor[2 * JJ]); II := succ(II); JJ := pred(JJ); end; until II > JJ; if JJ > left then Sort(left, JJ, Tensor); if II < right then Sort(II, right, Tensor) end; function eq (x, y: tensorType): boolean; var i: integer; result: boolean; begin result := true; for i := 1 to rank do result := result and (x[i] = y[i]); eq := result; end; function eqL (x, y: LinType): boolean; var i: integer; result: boolean; begin result := true; for i := 1 to NoComp do result := result and (x[i] = y[i]); eqL := result; end; procedure canonical (var tensor: tensortype); var j: integer; begin {order each pair} for j := 1 to rank div 2 do if tensor[2 * j - 1] > tensor[2 * j] then swap(Tensor[2 * j - 1], Tensor[2 * j]); sort(1, rank div 2, Tensor); end; procedure calcTensors; var TTensor: TensorType; {TensorIndex: integer;} procedure GenerLoop (nest: integer; jvals: jvaltype; TTensor: tensorType); var j, k: integer; begin for j := (nest + 2) div 2 to rank do if not (j in jvals) then begin TTensor[nest] := j; if nest < rank then GenerLoop(succ(nest), jvals + [j], TTensor) else begin canonical(TTensor); k := 1; while not eq(TTensor, Tensor[k]) and (k < TensorIndex) do k := succ(k); if k = TensorIndex then begin if TensorIndex > NoComp then writeln('Either I stuffed up,', ' or you guessed the wrong value for NoComp'); tensor[k] := TTensor; TensorIndex := succ(TensorIndex); end; end; {of else} end; {of if not (j in jval)} end; {of GenerLoop} begin TensorIndex := 1; TTensor[1] := 1; GenerLoop(2, [], TTensor); end; procedure symmetrizePairs (var LinComb: LinType; pr1, pr2: integer); {Symmetrize tensor with respect to two pairs of indices} var NewLin: LinType; i, j: integer; TTensor: TensorType; begin NewLin := zero; for i := 1 to NoComp do if LinComb[i] > 0 then begin {get representation of component} TTensor := Tensor[i]; {swap pairs 1 and 2} for j := 1 to rank do if TTensor[j] = 2 * pr1 then TTensor[j] := 2 * pr2 else if TTensor[j] = pred(2 * pr1) then TTensor[j] := pred(2 * pr2) else if TTensor[j] = 2 * pr2 then TTensor[j] := 2 * pr1 else if TTensor[j] = pred(2 * pr2) then TTensor[j] := pred(2 * pr1); canonical(TTensor); {find TTensor in the list} j := 0; repeat j := j + 1 until eq(TTensor, Tensor[j]); {add the swapped part to make it symmetric} NewLin[j] := NewLin[j] + LinComb[i]; end; {of for i:=1 to NoComp} {add NewLin to LinComb} for i := 1 to NoComp do LinComb[i] := LinComb[i] + NewLin[i]; end; {of symmetrizePairs} procedure symmetrize (var LinComb: LinType; Index1, Index2: integer); {Symmetrize tensor with respect to two indices} var NewLin: LinType; i, j: integer; TTensor: TensorType; begin NewLin := zero; for i := 1 to NoComp do if LinComb[i] > 0 then begin {get representation of component} TTensor := Tensor[i]; {swap pairs 1 and 2} for j := 1 to rank do if TTensor[j] = Index1 then TTensor[j] := Index2 else if TTensor[j] = Index2 then TTensor[j] := Index1; canonical(TTensor); {find TTensor in the list} j := 0; repeat j := j + 1 until eq(TTensor, Tensor[j]); {add the swapped part to make it symmetric} NewLin[j] := NewLin[j] + LinComb[i]; end; {of for i:=1 to NoComp} {add NewLin to LinComb} for i := 1 to NoComp do LinComb[i] := LinComb[i] + NewLin[i]; end; {of symmetrize} begin CalcTensors; for i := 1 to NoComp do zero[i] := 0; for i := 1 to NoComp do begin LinComb[i] := zero; LinComb[i][i] := 1; Symmetrize(LinComb[i], 1, 2); Symmetrize(LinComb[i], 3, 5); Symmetrize(LinComb[i], 4, 6); j := 0; repeat j := succ(j) until eqL(LinComb[j], LinComb[i]); if i = j then {the linear combination is unique, so output it} begin for j := 1 to NoComp do if LinComb[i][j] <> 0 then write(LinComb[i][j] : 1, '*I(', j : 3, ')+'); writeln; end; end; end.