Tensors are required to have the following information.
\begin{itemize}
\item A commutative ring $K$ of coefficients.
\item A valence $\vav$ indicating the number of variables to include in its
associated multilinear map.
\item A list $[U_{\vav},\dots, U_0]$ of $K$-modules called the \emph{frame}.
\item A function $U_{\vav}\times \cdots \times U_1\rightarrowtail U_0$ that is $K$-linear in each $U_i$.
\end{itemize}
Tensors have type \texttt{TenSpcElt} and are formally elements of a tensor space
(type \texttt{TenSpc}).
By default, a tensor's parent space is a universal tensor space:
\begin{align*}
	\hom_K(U_v,\dots,\hom_K(U_1,U_0)\cdots) \cong \hom_K(U_v\otimes_K\cdots \otimes_K U_1,U_0).
\end{align*}
The left hand module is used primarily as it avoids the need to work with the
equivalence classes of a tensor product. Operations such as linear combinations
of tensors take place within a tensor space. Attributes such as coefficients,
valence, and frame  apply to the tensor space as well.

When necessary, the user may further direct the operations on tensors to
appropriate tensor categories (type \texttt{TenCat}).  For instance, covariant
and contravariant variables may be specified together with symmetry
conditions.  If no tensor category is prescribed, then a default tensor category
is used based on the method of creation.
\medskip

\minitoc

\section{Creating tensors}

\subsection{Black-box tensors}
A user can specify a tensor by a black-box function that evaluates the required
multilinear map.

\index{Tensor!black-box}
\begin{intrinsics}
Tensor(S, F) : SeqEnum, UserProgram -> TenSpcElt, List
Tensor(S, F) : List, UserProgram -> TenSpcElt, List
Tensor(S, F, Cat) : SeqEnum, UserProgram, TenCat -> TenSpcElt, List
Tensor(S, F, Cat) : List, UserProgram, TenCat -> TenSpcElt, List
\end{intrinsics}

Returns a tensor $t$ and a list of maps from the given frame into vector spaces
of the returned frame. Note that $t$ is a tensor over vector
spaces---essentially forgetting all other structure. The last entry of
\texttt{S} is assumed to be the codomain of the multilinear map. The
user-defined function $F$ should take as input a tuple of elements of the domain
and return an element of the codomain. If no tensor category is provided, the
homotopism category is used.

\begin{example}[BBTensorsFrame] We demonstrate the black-box constructions by
first constructing the dot product $\cdot : \mathbb{Q}^4\times
\mathbb{Q}^4\rightarrowtail \mathbb{Q}$. The function used to evaluate our
black-box tensor, \texttt{Dot}, must take exactly one argument. The argument
will be a \texttt{Tup}, an element of the Cartesian product $U_{\vav}\times
\cdots\times U_1$. Note that \texttt{x[i]} is the $i$th entry in the tuple and
not the $i$-axis.
\begin{code}
> Q := Rationals();
> U := VectorSpace(Q, 4);
> V := VectorSpace(Q, 4);
> W := VectorSpace(Q, 1);  // Vector space, not the field Q
> Dot := func< x | x[1]*Matrix(4, 1, Eltseq(x[2])) >;
\end{code}

Now we will construct the tensor from the data above. The first object returned
is the tensor, and the second is a list of maps, mapping the given frame into
the vector space frame. In this example, since the given frame consists of
vector spaces, these maps are trivial. Note that the list of maps are not needed
to work with the given tensor, we will demonstrate this later.
\begin{code}
> Tensor([U, V, W], Dot);
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 4 over Rational Field
U1 : Full Vector space of degree 4 over Rational Field
U0 : Full Vector space of degree 1 over Rational Field
[*
    Mapping from: ModTupFld: U to ModTupFld: U given by a rule,
    Mapping from: ModTupFld: U to ModTupFld: U given by a rule,
    Mapping from: ModTupFld: W to ModTupFld: W given by a rule
*]
\end{code}

We will provide a tensor category for the dot product tensor, so that the
returned tensor is not in the default homotopism category. We will use instead
the $\{2,1\}$-adjoint category. While the returned tensor prints out the same as
above, it does indeed live in a different universe. The details of tensor
categories are discussed in Chapter~\ref{ch:tensor-categories}.
\begin{code}
> Cat := AdjointCategory(3, 2, 1);
> Cat;
Tensor category of valence 3 (<-,->,==) ({ 1 },{ 2 },{ 0 })
>
> t := Tensor([U, V, W], Dot, Cat);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 4 over Rational Field
U1 : Full Vector space of degree 4 over Rational Field
U0 : Full Vector space of degree 1 over Rational Field
>
> TensorCategory(t);
Tensor category of valence 3 (<-,->,==) ({ 1 },{ 2 },{ 0 })
\end{code}
\end{example}

\begin{example}[BBCrossProduct]

We will construct the cross product $\times : \mathbb{R}^3\times
\mathbb{R}^3\rightarrowtail \mathbb{R}^3$ and verify that ${\bf i}\times {\bf j}
= {\bf k}$. However, to do this test, we will input integer sequences
(specifically \texttt{[RngIntElt]}), and we will still be able to evaluate.
\begin{code}
> K := RealField(5);
> V := VectorSpace(K, 3);
> CP := function(x)
function>   return V![x[1][2]*x[2][3] - x[1][3]*x[2][2], \
function|return>     x[1][3]*x[2][1] - x[1][1]*x[2][3], \
function|return>     x[1][1]*x[2][2] - x[1][2]*x[2][1] ];
function> end function;
> t := Tensor([V, V, V], CP);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 3 over Real field of precision 5
U1 : Full Vector space of degree 3 over Real field of precision 5
U0 : Full Vector space of degree 3 over Real field of precision 5
>
> // test that i x j = k
> <[1,0,0], [0,1,0]> @ t eq V.3;
true
\end{code}
\end{example}

\index{Tensor!black-box}
\begin{intrinsics}
Tensor(D, C, F) : SeqEnum, Any, UserProgram -> TenSpcElt, List
Tensor(D, C, F) : List, Any, UserProgram -> TenSpcElt, List
Tensor(D, C, F, Cat) : SeqEnum, Any, UserProgram, TenCat -> TenSpcElt, List
Tensor(D, C, F, Cat) : List, Any, UserProgram, TenCat -> TenSpcElt, List
\end{intrinsics}

Returns a tensor $t$ and a list of maps from the given frame into vector spaces
of the returned frame. Note that $t$ is a tensor over vector
spaces---essentially forgetting all other structure. The user-defined function
$F$ should take as input a tuple of elements of $D$ and return an element of
$C$. If no tensor category is provided, then the homotopism category is used.

\begin{example}[BBTripleProduct] Tensors make it easy to create algebras that do
not fit into traditional categories, such as algebras with triple products.
Here, we create a triple product $\langle \,\rangle : \mathbb{M}_{2\times
3}(K)\times \mathbb{M}_{2\times 3}(K)\times\mathbb{M}_{2\times
3}(K)\rightarrowtail \mathbb{M}_{2\times 3}(K)$, given by $\langle A, B,
C\rangle = AB^tC$.

\begin{code}
> K := GF(541);
> U := KMatrixSpace(K,2,3);
> my_prod := func< x | x[1]*Transpose(x[2])*x[3] >;
> t := Tensor([U,U,U,U], my_prod );
> t;
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 6 over GF(541)
U2 : Full Vector space of degree 6 over GF(541)
U1 : Full Vector space of degree 6 over GF(541)
U0 : Full Vector space of degree 6 over GF(541)
\end{code}

Notice that the returned tensor is over vector spaces instead of the universe of \texttt{KMatrixSpace}.
Tensors can still evaluate elements from the given frame, even though it prints out over vector spaces.
However, the returned value form the tensor will be in the codomain of the tensor, so in this case, $K^6$.
\begin{code}
> A := U![1,0,0,0,0,0];
> A;
[  1   0   0]
[  0   0   0]
>
> <A,A,A> @ t;  // A is a generalized idempotent
(  1   0   0   0   0   0)
\end{code}

We can experiment to see if this triple product is left associative. To do this,
we will construct five random matrices $\{ X_1,\dots,X_5\}\subset
\mathbb{M}_{2\times 3}(K)$, and then we test if
\[ \langle \langle X_1, X_2, X_3 \rangle, X_4, X_5\rangle = \langle X_1, \langle X_4, X_3, X_2\rangle, X_5\rangle. \]
Observe that the tuples have mixed entries, one from $K^6$ and two others from $\mathbb{M}_{2\times 3}(K)$.

\begin{code}
> X := [Random(U) : i  in [1..5]];
> X;
[
    [485 378 385]
    [241 505 134],

    [141 531 245]
    [472 484 339],

    [377  85 170]
    [451 522 334],

    [211 340 409]
    [ 95 349 128],

    [264 372 144]
    [205  47 428]
]
>
> A := <X[1],X[2],X[3]> @ t;
> B := <X[4],X[3],X[2]> @ t;
> A, B;
(460 436 181 341 134 404)
(465 420 458 421 291 225)
>
> <A, X[4], X[5]> @ t eq <X[1], B, X[5]> @ t;
true
\end{code}

To confirm this product is left associative, we can create a new tensor for the
left triple-associator and see that its image is $0$. We will create a
$6$-tensor $\lla \cdot \rra : \prod_{k=1}^5 \mathbb{M}_{2\times
3}(K)\rightarrowtail \mathbb{M}_{2\times 3}(K)$ where
\[
    \lla X_1,\dots, X_5\rra = \langle \langle X_1, X_2, X_3 \rangle,
    X_4, X_5\rangle - \langle X_1, \langle X_4, X_3, X_2\rangle, X_5\rangle.
\]
Therefore, if im$(\lla \cdot \rra)=0$, then $\langle \cdot\rangle$ is left
associative.

\begin{code}
> l_asct := func< X | Eltseq(<<X[1], X[2], X[3]> @ t, X[4], X[5]> @ t \
>     - <X[1], <X[4], X[3], X[2]> @ t, X[5]> @ t) >;
> Lt := Tensor([* U : i in [0..5] *], l_asct);
> Lt;
Tensor of valence 6, U5 x U4 x U3 x U2 x U1 >-> U0
U5 : Full Vector space of degree 6 over GF(541)
U4 : Full Vector space of degree 6 over GF(541)
U3 : Full Vector space of degree 6 over GF(541)
U2 : Full Vector space of degree 6 over GF(541)
U1 : Full Vector space of degree 6 over GF(541)
U0 : Full Vector space of degree 6 over GF(541)
>
> I := Image(Lt);
> I;
Vector space of degree 6, dimension 0 over GF(541)
Generators:

>
> Dimension(I);
0
\end{code}

Observe that in \texttt{l\_asct} the function \texttt{Eltseq} is called. This is
because $t$ returns vectors in $K^6$ which is not naturally coercible by
\textsf{Magma} into $\mathbb{M}_{2\times 3}(K)$. On the other hand, sequences
can be coerced into $\mathbb{M}_{2\times 3}(K)$.
\end{example}

\subsection{Tensors with structure constant sequences}
Most computations with tensors $t$ will be carried out using structure
constants, e.g.~in the homotopism category, $t_{j_{\vav}\cdots j_1}^{j_0}\in K$.
Here $t$ is framed by free $K$-modules $[U_{\vav},\dots,U_0]$ with each $U_i$
having an ordered bases $\mathcal{B}_i=[e_{i1},\dots,e_{id_i}]$. The
interpretation of structure constants is that the associated multilinear
function $[x_{\vav},\dots,x_1]$ from $U_{\vav}\times \cdots \times U_1$ into
$U_0$ is determined on bases as follows:
\begin{align*}
	[e_{\vav j_{\vav}},\dots,e_{1j_1} ]& = \sum_{k=1}^{d_0} t_{j_{\vav} \cdots j_1}^{j_0} e_{0k}.
\end{align*}
Structure constants are input and stored as sequences $S$ in $K$ according to the
following assignment. Set $f:\mathbb{Z}^{\vav+1}\to \mathbb{Z}$ to be:
\begin{align*}
		 f(j_{\vav},\dots,j_0) & = 1+\sum_{k=0}^{\vav} (j_k-1)\prod_{\ell=0}^{k-1} d_\ell.
\end{align*}
So $S[f(j_{\vav},\dots,j_0)]=t_{j_{\vav}\cdots j_1}^{j_0}$ specifies the structure constants as a sequence.
\smallskip

\noindent{\bf Notes.}
\begin{itemize}
\item \textsf{Magma} does not presently support the notion of a sparse sequence of structure constants.
A user can provide this functionality by specifying a tensor with a user program rather
than structure constants.

\item Some routines in \textsf{Magma} require structure constant sequences.  If they
are not provided, \textsf{Magma} may compute and store a structure constant representation
inside the tensor.

\item We do not separate structure constant indices that are contravariant.
Instead contravariant variables are signaled by tensor categories.  So Ricci
styled tensors $T_{a_p\cdots a_1}^{b_q\cdots b_1}$ should be input as
$T_{a_{p+q}\cdots a_{1+q} b_q\cdots b_1}$ and the tensor category changed to
mark $\{q,\dots,1\}$ as contravariant. Intrinsics are provided to facilitate
this approach. See Chapter~\ref{ch:tensor-categories} for more details on tensor
categories.
\end{itemize}
\medskip

\index{Tensor!structure constants}
\begin{intrinsics}
Tensor(D, S) : [RngIntElt], [RngElt] -> TenSpcElt
Tensor(R, D, S) : Rng, [RngIntElt], [RngElt] -> TenSpcElt
Tensor(D, S, Cat) : [RngIntElt], [RngElt], TenCat -> TenSpcElt
Tensor(R, D, S, Cat) : Rng, [RngIntElt], [RngElt], TenCat -> TenSpcElt
\end{intrinsics}

Given dimensions $D=[d_{\vav},\dots,d_0]$, returns the tensor in
$R^{d_{\vav}\cdots d_0}$ identified by structure constant sequence $S$. If $R$
is not provided, then the parent ring of the first element of $S$ is used. The
ring $R$ must be commutative and unital. The default tensor category
\texttt{Cat} is the homotopism category.

\begin{example}[SCTensors]

We will create structure constants sequence with all 0s and one 1 that occurs in the first entry.
First, we will input this along with the dimensions of the tensor we are after, $2\times 2\times 2$.
However, since we did not specify a ring, it is assumed to be the parent ring of the first entry of the structure constants sequence.
In this example, the ring is $\mathbb{Z}$.

\begin{code}
> sc := [ 0 : i in [1..8] ];
> sc[1] := 1;
> Tensor([2, 2, 2], sc);
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full RSpace of degree 2 over Integer Ring
U1 : Full RSpace of degree 2 over Integer Ring
U0 : Full RSpace of degree 2 over Integer Ring
\end{code}

We do not want the underlying ring to be $\mathbb{Z}$, so we will input the ring we want: GF$(64)$.
\begin{code}
> K := GF(64);
> t := Tensor(K, [2, 2, 2], sc);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 2 over GF(2^6)
U1 : Full Vector space of degree 2 over GF(2^6)
U0 : Full Vector space of degree 2 over GF(2^6)
>
> Image(t);
Vector space of degree 2, dimension 1 over GF(2^6)
Generators:
(     1      0)
Echelonized basis:
(     1      0)
\end{code}

We can recover the structure constants by calling \texttt{StructureConstants} or \texttt{Eltseq}.
We will also test that the tensor evaluates inputs correctly.
\begin{code}
> StructureConstants(t);
[ 1, 0, 0, 0, 0, 0, 0, 0 ]
>
> <[1, 0], [K.1^3, 0]> @ t;
( K.1^3      0)
>
> <[K.1^29, 1], [0, K.1^2]> @ t;
(     0      0)
\end{code}
\end{example}

\index{StructureConstants}\index{Eltseq}
\begin{intrinsics}
StructureConstants(t) : TenSpcElt -> SeqEnum
Eltseq(t) : TenSpcElt -> SeqEnum
\end{intrinsics}

Returns the sequence of structure constants of the given tensor $t$.

\index{Assign}
\begin{intrinsics}
Assign(t, ind, k) : TenSpcElt, [RngIntElt], Any -> TenSpcElt
Assign(~t, ind, k) : TenSpcElt, [RngIntElt], Any ->
\end{intrinsics}

Returns the tensor $t$ where the \texttt{ind} element (viewed as a
multi-dimensional array) is replaced with $k$. For example, replacing the
$(a,b,c)$ entry of a $3$-tensor, set \texttt{ind}$\,=[a,b,c]$.

\begin{example}[SCFromBBTensors]

We will construct the natural Lie module action for $\mathfrak{sl}_2$, but we
will construct it as a \emph{left} module. To do this, we construct a function
that takes elements from $\mathfrak{sl}_2\times V$ and returns an element that
\textsf{Magma} can coerce into $V$. We run a quick test to make sure our function runs on
the trivial example; this is the only check the intrinsic runs on black-box
tensors. Since $\mathfrak{sl}_2$ and $V$ are part of different universes in
\textsf{Magma}, we must use the \texttt{List} environment when constructing this
black-box tensor.

\begin{code}
> sl2 := MatrixLieAlgebra("A1", GF(7));
> V := VectorSpace(GF(7), 2);
> left_action := func< x | x[2]*Transpose(Matrix(x[1])) >;
> left_action(<sl2!0, V!0>);
(0 0)
>
> sl2 := MatrixLieAlgebra("A1", GF(7));
> V := VectorSpace(GF(7), 2);
> left_action := func< x | x[2]*Transpose(Matrix(x[1])) >;
> left_action(<sl2!0, V!0>);
(0 0)
>
> t := Tensor([* sl2, V, V *], left_action);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 3 over GF(7)
U1 : Full Vector space of degree 2 over GF(7)
U0 : Full Vector space of degree 2 over GF(7)
\end{code}

Now we will extract the structure constants from this Lie module action. We will
then construct a tensor with these structure constants and compare it with our
first tensor above.

\begin{code}
> StructureConstants(T);
[ 1, 0, 0, 6, 0, 0, 1, 0, 0, 1, 0, 0 ]
>
> s := Tensor([3, 2, 2], Eltseq(t));
> s;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 3 over GF(7)
U1 : Full Vector space of degree 2 over GF(7)
U0 : Full Vector space of degree 2 over GF(7)
>
> t eq s;
true
\end{code}
\end{example}

\begin{example}[SCStored]

The structure constants are convenient data structure for nearly all the
algorithms in TensorSpace. In fact, most computations require structure
constants, so we store the structure constants sequence with the tensor. This
means that after the initial structure constant sequence computation, every time
\texttt{StructureConstants} or \texttt{Eltseq} is called, \textsf{Magma} retrieves what
was previously computed.

We will demonstrate this on a large black-box example, so some time is spent
computing the structure constants. Of course, the exact timing will vary by
machine. We will construct a product of two subalgebras of
$\mathbb{M}_{20}(\mathbb{F}_3)$, namely $* :
\mathfrak{sl}_{20}(\mathbb{F}_3)\times \mathbb{M}_4(\mathbb{F}_3)\rightarrowtail
\mathbb{M}_{20}(\mathbb{F}_3)$.

\begin{code}
> sl20 := MatrixLieAlgebra("A19", GF(3));
> M4 := MatrixAlgebra(GF(3), 4);
> Prod := func< x | Matrix(x[1])*DiagonalJoin(<x[2] : i in [1..5]>) >;
> t := Tensor([* sl20, M4 *], MatrixAlgebra(GF(3), 20), Prod);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 399 over GF(3)
U1 : Full Vector space of degree 16 over GF(3)
U0 : Full Vector space of degree 400 over GF(3)
\end{code}

We record the time it takes to initially compute the structure constants
sequence, and then we record the time when we call the function again.
\begin{code}
> time sc := StructureConstants(t);
Time: 58.670
>
> time sc := StructureConstants(t);
Time: 0.000
\end{code}
\end{example}

\subsection{Bilinear tensors}
A special case of structure constants for bilinear maps $U_2\times
U_1\rightarrowtail U_0$ is to format the data as lists of matrices $[M_1,\dots,
M_d]$. This can be considered as a left (resp.~right) representation $U_2\to
\hom_K(U_1,U_0)$, (resp.~$U_1\to \hom_K(U_2,U_0)$). Or it can be treated as {\em
systems of bilinear forms} $[M_1,\dots,M_d]$ where the matrices are the Gram
matrices of bilinear forms $\phi_i:U_2\times U_1\rightarrowtail K$. Here the
associated bilinear map $U_2\times U_1\rightarrowtail U_0$ is specified by
\begin{align*}
	(u_2,u_1) & \mapsto ( \phi_1(u_2,u_1),\dots, \phi_a(u_2,u_1)).
\end{align*}

\index{Tensor!bilinear}\index{Tensor!forms}
\begin{intrinsics}
Tensor(M, a, b) : Mtrx, RngIntElt, RngIntElt -> TenSpcElt
Tensor(M, a, b, Cat) : Mtrx, RngIntElt, RngIntElt, TenCat -> TenSpcElt
Tensor(M, a, b) : [Mtrx], RngIntElt, RngIntElt -> TenSpcElt
Tensor(M, a, b, Cat) : [Mtrx], RngIntElt, RngIntElt, TenCat -> TenSpcElt
\end{intrinsics}

Returns the bilinear tensor given by the list of matrices.  The interpretation
of the matrices as structure constants is specified by the coordinates $a$ and
$b$ which must be positions in $\{2,1,0\}$. Optionally a tensor category
\texttt{Cat} can be assigned.

\begin{example}[SymplecticForm]

We will construct a symplectic bilinear form on $V=K^8$. It would be cumbersome
to construct this tensor as a black-box tensor or by providing the structure
constants sequence. Instead, we will provide a (Gram) matrix.
\begin{code}
> K := GF(17);
> MS := KMatrixSpace(K, 2, 2);
> J := KroneckerProduct(IdentityMatrix(K, 4), MS![0, 1, -1, 0]);
> J;
[ 0  1  0  0  0  0  0  0]
[16  0  0  0  0  0  0  0]
[ 0  0  0  1  0  0  0  0]
[ 0  0 16  0  0  0  0  0]
[ 0  0  0  0  0  1  0  0]
[ 0  0  0  0 16  0  0  0]
[ 0  0  0  0  0  0  0  1]
[ 0  0  0  0  0  0 16  0]
>
> t := Tensor(J, 2, 1);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 8 over GF(17)
U1 : Full Vector space of degree 8 over GF(17)
U0 : Full Vector space of degree 1 over GF(17)
>
> IsAlternating(t);
true
\end{code}

Now we will construct the symplectic form using the black-box construction and
verify that the two tensors are the same.
\begin{code}
> V := VectorSpace(K, 8);
> symp := func< x | x[1]*J*Matrix(8, 1, Eltseq(x[2])) >;
> s := Tensor([V, V], VectorSpace(K, 1), symp);
> s;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 8 over GF(17)
U1 : Full Vector space of degree 8 over GF(17)
U0 : Full Vector space of degree 1 over GF(17)
>
> SystemOfForms(s);
[
    [ 0  1  0  0  0  0  0  0]
    [16  0  0  0  0  0  0  0]
    [ 0  0  0  1  0  0  0  0]
    [ 0  0 16  0  0  0  0  0]
    [ 0  0  0  0  0  1  0  0]
    [ 0  0  0  0 16  0  0  0]
    [ 0  0  0  0  0  0  0  1]
    [ 0  0  0  0  0  0 16  0]
]
\end{code}
\end{example}

\index{AsMatrices}\index{SystemOfForms}
\begin{intrinsics}
AsMatrices(t, a, b) : TenSpcElt, RngIntElt, RngIntElt -> SeqEnum
SystemOfForms(t) : TenSpcElt -> SeqEnum
\end{intrinsics}

For a tensor $t$ with frame $[K^{d_{\vav}},\dots,K^{d_0}]$,
returns a list $[M_1,\dots,M_d]$, $d=(d_{\vav}\cdots d_0)/(d_a d_b)$,
of $(d_a\times d_b)$-matrices in $K$ representing the tensor
as an element of $\hom_K(K^{d_a}\otimes_K K^{d_b},K^d)$.
For \texttt{SystemOfForms}, $t$ must have valence $3$ and the implied values are $a=2$ and $b=1$.

\begin{example}[TrilinearAsMats]

We construct the associator of $\mathfrak{sl}_2(\mathbb{Q})$ where
$\langle \,\rangle : \prod_{k=1}^3\mathfrak{sl}_2\rightarrowtail \mathfrak{sl}_2$
given by
\[
    \langle x,y,z \rangle = [[x,y],z] - [x,[y,z]].
\]
It can be hard to understand some of the features of this trilinear map by only
looking at the structure constants sequence. The function \texttt{AsMatrices}
slices the sequence and presents the data as a sequence of matrices.
\begin{code}
> K := Rationals();
> L := LieAlgebra("A1", K);
> t := AssociatorTensor(L);
> t;
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 3 over Rational Field
U2 : Full Vector space of degree 3 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 3 over Rational Field
>
> Eltseq(t);
[ 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, 0, 0,
0,-2, 0, 0, 0, 0, 0, 0, 0, 2, 0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 2, 0, 0, 0,
0, 0, 0, 0, -2, 0, 0, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2,
0, 0, 0, 0, 0, 0 ]
\end{code}

Calling \texttt{AsMatrices(t, 3, 1)} returns a sequence of nine matrices, but we
only show the first four. As explained in the documentation above, this sequence
of matrices can be interpreted as the system of bilinear forms for the 3-tensor
$\circ_{31} : V_3 \times V_1 \rightarrowtail \Hom_K(V_2,V_0)$ given by
\[ x\circ_{31} z = \langle x, -,z \rangle = [[x,-],z] - [x,[-,z]].\]

\begin{code}
> AsMatrices(t, 3, 1)[1..4];
[
    [ 0  0  2]
    [ 0  0  0]
    [-2  0  0],

    [ 0  0  0]
    [ 0  0  2]
    [ 0 -2  0],

    [0 0 0]
    [0 0 0]
    [0 0 0],

    [ 0  1  0]
    [-1  0  0]
    [ 0  0  0]
]
\end{code}
\end{example}

\subsection{Tensors from algebraic objects}
A natural and important source of tensors come from algebraic object with a
distributive property. One main source is from algebras, where $*:A\times
A\rightarrowtail A$ is given by multiplication in $A$. Like with the previous
sections on tensor constructions, all tensors will be constructed over vector
spaces. The user can still input elements from the original algebra, but map(s)
will also be returned. Furthermore, each tensor is assigned a category relevant
to its origin, see Chapter~\ref{ch:tensor-categories} for more details on tensor
categories.

\index{Tensor!algebra}
\begin{intrinsics}
Tensor(A) : Alg -> TenSpcElt, Map
\end{intrinsics}

Returns the bilinear tensor given by the product in algebra $A$.

\begin{example}[D4LieAlgebra]

We want to get the Lie bracket from $\mathfrak{so}_8(\mathbb{F}_{11})$. Tensors created from algebras
will have a homotopism category, but with $U_2=U_1=U_0$. This forces the
operators acting to be the same on all the coordinates; in other words,
$\Omega=\End(U_2)$ instead of $\Omega=\End(U_2)\times\End(U_1)\times\End(U_0)$.
\begin{code}
> D := DerivationAlgebra(T);
> L := LieAlgebra("D4", GF(11));
> t := Tensor(L);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 28 over GF(11)
U1 : Full Vector space of degree 28 over GF(11)
U0 : Full Vector space of degree 28 over GF(11)
> IsAlternating(t);
true
> TensorCategory(t);
Tensor category of valence 3 (->,->,->) ({ 0, 1, 2 })
\end{code}

If we compute the derivation algebra of $L$, our operators will act in the same
way on each coordinate. This is the standard definition of the derivation
algebra of a ring.
\begin{code}
> D := DerivationAlgebra(t);
> Dimension(D);
28
> SemisimpleType(D);
D4
\end{code}

Now we will change the category to the standard homotopism category, where we do
\emph{not} fuse $U_2$, $U_1$, and $U_0$.
\begin{code}
> ChangeTensorCategory(~t, HomotopismCategory(3));
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 28 over GF(11)
U1 : Full Vector space of degree 28 over GF(11)
U0 : Full Vector space of degree 28 over GF(11)
> TensorCategory(t);
Tensor category of valence 3 (->,->,->) ({ 1 },{ 2 },{ 0 })
\end{code}

We compare the same computation of derivation algebra. This time, the theory
tells us that there will be a solvable radical. In this example, Rad$(D)=K^2$.
\begin{code}
> D := DerivationAlgebra(t);
> Dimension(D);
30
> R := SolvableRadical(D);
> SemisimpleType(D/R);
D4
\end{code}
\end{example}

\index{Tensor!polynomial ring}
\begin{intrinsics}
Tensor(Q) : RngUPolRes -> TenSpcElt, Map
\end{intrinsics}

Returns the bilinear tensor given by the product in quotient polynomial ring $Q$.

\begin{example}[WittAlgebra]

The Witt algebra over a finite field of characteristic $p$ is isomorphic to
the derivation algebra of $K[x]/(x^p)$. The Witt algebra is a simple Lie algebra
with dimension $p$ and a trivial Killing form. First, we will construct the
tensor from the ring $\mathbb{F}_5[x]/(x^5)$. Note that, like with algebras, the
tensor category will fuse $U_2$, $U_1$, and $U_0$, so that the operators act the
same way on every coordinate.
\begin{code}
> p := 5;
> R<x> := PolynomialRing(GF(p));
> I := ideal< R | x^p >;
> Q := quo< R | I >;
> Q;
Univariate Quotient Polynomial Algebra in $.1 over Finite field of size
5 with modulus $.1^5
> t := Tensor(Q);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 5 over GF(5)
U1 : Full Vector space of degree 5 over GF(5)
U0 : Full Vector space of degree 5 over GF(5)
> TensorCategory(t);
Tensor category of valence 3 (->,->,->) ({ 0, 1, 2 })
\end{code}

Now we will construct a Lie representation of the Witt algebra from the tensor $t$.
\begin{code}
> D := DerivationAlgebra(t);
> IsSimple(D);
true
> Dimension(D);
5
> KillingForm(D);
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
\end{code}
\end{example}

\index{CommutatorTensor}\index{AnticommutatorTensor}
\begin{intrinsics}
CommutatorTensor(A) : Alg -> TenSpcElt, Map
AnticommutatorTensor(A) : Alg -> TenSpcElt, Map
\end{intrinsics}

Returns the bilinear commutator map $[a,b]=ab-ba$ or the anticommutator map
$\langle a,b\rangle = ab+ba$ of the algebra $A$. This should not be used to get
the tensor given by the Lie or Jordan product in a Lie or Jordan algebra;
instead use \texttt{Tensor}.

\begin{example}[CommutatorFromAlgebra]

We will construct the commutator tensor from $\mathbb{M}_4(\mathbb{Q})$.
\begin{code}
> A := MatrixAlgebra(Rationals(), 4);
> t := CommutatorTensor(A);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 16 over Rational Field
U1 : Full Vector space of degree 16 over Rational Field
U0 : Full Vector space of degree 16 over Rational Field
> IsAlternating(t); // [X, X] = 0?
true
\end{code}

With this tensor, we will compute the dimension of the centralizer of the
diagonal matrix $M$ with diagonal entries $(1,1,-1,-1)$ in
$\mathbb{M}_4(\mathbb{Q})$. To do this, we will subtract the dimension of the
image of $[M, A]$ from the dimension of $A$.

\begin{code}
> M := A![1,0,0,0,0,1,0,0,0,0,-1,0,0,0,0,-1];
> M;
[ 1  0  0  0]
[ 0  1  0  0]
[ 0  0 -1  0]
[ 0  0  0 -1]
> Dimension(A) - Dimension(<M, A> @ t);
8
\end{code}
\end{example}

\begin{example}[MatrixJordanAlgebra]
This time, we will obtain a Jordan product from $\mathbb{M}_4(\mathbb{Q})$.
That is, we will construct the bilinear map
$* : \mathbb{M}_4(\mathbb{Q})\times \mathbb{M}_4(\mathbb{Q})\rightarrowtail \mathbb{M}_4(\mathbb{Q})$
where $A*B = \frac{1}{2}(AB+BA)$.
\begin{code}
> A := MatrixAlgebra(Rationals(), 4);
> t := AnticommutatorTensor(A);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 16 over Rational Field
U1 : Full Vector space of degree 16 over Rational Field
U0 : Full Vector space of degree 16 over Rational Field
> SystemOfForms(t)[1];
[2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0]
[0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
> A.1*t*A.1;
(2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
\end{code}

From the documentation on \texttt{AnticommutatorTensor}, we have to scale our
tensor above $t$ by $1/2$ to get what we want. Of course this won't affect the
proceeding tests though.
\begin{code}
> s := (1/2)*t;
> A.1*s*A.1;
(1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
\end{code}

Now we will confirm that $s$ is a Jordan product. First, we check that $s$ is
commutative, and then we check that it satisfies the Jordan identity:
$(xy)(xx)=x(y(xx))$.
\begin{code}
> IsSymmetric(s);
true
> JordanID := func< x, y | (x*s*y)*s*(x*s*x) - x*s*(y*s*(x*s*x)) >;
> forall{ <x,y> : x in Basis(A), y in Basis(A) | \
>     JordanIdentity(x, y) eq Codomain(s)!0 };
true
\end{code}
\end{example}

\index{AssociatorTensor}
\begin{intrinsics}
AssociatorTensor(A) : Alg -> TenSpcElt, Map
\end{intrinsics}

Returns the trilinear associator map $[a,b,c]=(ab)c-a(bc)$ of the algebra $A$.

\begin{example}[AssociatorFromAlgebra]

Do three random octonions associate? Hardly ever.

\begin{code}
> O := OctonionAlgebra(GF(1223),-1,-1,-1);
> t := AssociatorTensor(O);
> t;
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 8 over GF(1223)
U2 : Full Vector space of degree 8 over GF(1223)
U1 : Full Vector space of degree 8 over GF(1223)
U0 : Full Vector space of degree 8 over GF(1223)
> <Random(O),Random(O),Random(O)> @ t eq O!0;
false
\end{code}

However, for all $a,b\in\mathbb{O}$, $(aa)b=a(ab)$ as octonions are alternative algebras.

\begin{code}
> a := Random(O);
> b := Random(O);
> <a,a,b> @ t eq O!0;
true
> IsAlternating(t);
true
\end{code}
\end{example}

\index{pCentralTensor}
\begin{intrinsics}
pCentralTensor(G, p, a, b) : Grp, RngIntElt, RngIntElt, RngIntElt -> TenSpcElt, List
pCentralTensor(G, a, b) : Grp, RngIntElt, RngIntElt -> TenSpcElt, List
pCentralTensor(G, a, b) : GrpPC, RngIntElt, RngIntElt -> TenSpcElt, List
pCentralTensor(G) : Grp -> TenSpcElt, List
\end{intrinsics}

Returns the bilinear map of commutation from the associated graded Lie algebra
of the lower exponent-$p$ central series $\eta$ of $G$.  The bilinear map pairs
$\eta_a/\eta_{a+1}$ with $ \eta_{b}/\eta_{b+1}$ into $\eta_{a+b}/\eta_{a+b+1}$.
If $a=b$ the tensor category is set to force $U_2=U_1$; otherwise it is the
general homotopism category. In addition, maps from the subgroups into the
vector spaces are returned as a list. If $p$, $a$, and $b$ are not given, it is
assumed $G$ is a $p$-group and $a=b=1$.

\begin{example}[TensorPGroup]
Groups have a single binary operation. So even when groups are built from rings
it can be difficult to recover the ring from the group operations. Tensors
supply one approach for that task. We will get the $p$-central tensor of a Sylow
$p$-subgroup $P$ of $\SL(3,125)$; however, we will lose the fact that there is a
field $\mathbb{F}_{125}$. The tensor we will get back is $[,] : K^6\times K^6
\rightarrowtail K^3$, where $K=\mathbb{F}_5$.

\begin{code}
> P := ClassicalSylow(SL(3,125),5);
> Q := PCGroup(P); // Loose track of GF(125).
> Q;
GrpPC : Q of order 1953125 = 5^9
PC-Relations:
    Q.4^Q.1 = Q.4 * Q.7^4,
    Q.4^Q.2 = Q.4 * Q.8^4,
    Q.4^Q.3 = Q.4 * Q.9^4,
    Q.5^Q.1 = Q.5 * Q.8^4,
    Q.5^Q.2 = Q.5 * Q.9^4,
    Q.5^Q.3 = Q.5 * Q.7^3 * Q.8^3,
    Q.6^Q.1 = Q.6 * Q.9^4,
    Q.6^Q.2 = Q.6 * Q.7^3 * Q.8^3,
    Q.6^Q.3 = Q.6 * Q.8^3 * Q.9^3
> t := pCentralTensor(Q);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 6 over GF(5)
U1 : Full Vector space of degree 6 over GF(5)
U0 : Full Vector space of degree 3 over GF(5)
\end{code}

Knowing that $P$ is defined over $\mathbb{F}_{125}$, we know that the commutator
is really just the alternating form $\cdot : \mathbb{F}_{125}^2\times
\mathbb{F}_{125}^2\rightarrowtail \mathbb{F}_{125}$. This information can be
extracted from the centroid of $t$ above, and we can rewrite $t$ over the field
$\mathbb{F}_{125}$.
\begin{code}
> F := Centroid(t); // Recover GF(125)
> Dimension(F);
3
> IsSimple(F);
true
> IsCommutative(F);
true
> s := TensorOverCentroid(t);
> s;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 2 over GF(5^3)
U1 : Full Vector space of degree 2 over GF(5^3)
U0 : Full Vector space of degree 1 over GF(5^3)
\end{code}
\end{example}

\index{MatrixTensor}
\begin{intrinsics}
MatrixTensor(K, S) : Fld, [RngIntElt] -> TenSpcElt, List
\end{intrinsics}

Given a field $K$ and a sequence of positive integers $S=[s_1,\dots, s_{\vav}]$,
return the tensor
\[
    \mathbb{M}_{s_1\times s_2}(K) \times \cdots
        \times \mathbb{M}_{s_{\vav-1}\times s_{\vav}}(K)\rightarrowtail \mathbb{M}_{s_1\times s_{\vav}}(K),
\]
given by matrix multiplication. A list of maps from the matrix spaces to the
vector spaces is given as well even though the given tensor will evaluate
matrices as well.

\index{Polarisation}\index{Polarization}
\begin{intrinsics}
Polarisation(f) : MPolElt -> TenSpcElt, MPolElt
Polarisation(f) : RngUPolElt -> TenSpcElt
Polarization(f) : MPolElt -> TenSpcElt, MPolElt
Polarization(f) : RngUPolElt -> TenSpcElt
\end{intrinsics}

Returns the polarization of the homogeneous multivariate polynomial (or
univariate polynomial) $f$ as a tensor and as a multivariate polynomial.
Polarization does \emph{not} normalize by $1/d!$, where $d$ is the degree of
$f$.

\begin{example}[TensorPolarization] We polarize the polynomial $f(x,y)=x^2y$.
Because $f$ is homogeneous of degree 3 with 2 variables, we expect that the
polarization will have 6 variables and that the corresponding multilinear form
will be $K^2\times K^2\times K^2\rightarrowtail K$. The polarization of $f$ is
given by $P(x_1,x_2,y_1,y_2,z_1,z_2 ) = 2 (x_1y_1z_2 + x_1y_2z_1 + x_2y_1z_1)$.

\begin{code}
> R<x,y> := PolynomialRing(Rationals(),2);
> t, p := Polarization(x^2*y);
> p;
2*$.1*$.3*$.6 + 2*$.1*$.4*$.5 + 2*$.2*$.3*$.5
> t;
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 2 over Rational Field
U2 : Full Vector space of degree 2 over Rational Field
U1 : Full Vector space of degree 2 over Rational Field
U0 : Full Vector space of degree 1 over Rational Field
> <[1,0],[1,0],[1,0]> @ t;
(0)
> <[1,0],[1,0],[0,1]> @ t;
(2)
\end{code}
\end{example}

\subsection{New tensors from old}
We can construct new tensors from old.

\index{AlternatingTensor}
\begin{intrinsics}
AlternatingTensor(t) : TenSpcElt -> TenSpcElt
\end{intrinsics}

Returns the alternating tensor induced by the given tensor. If
the tensor is already alternating, then the given tensor is returned.

\index{AntisymmetricTensor}
\begin{intrinsics}
AntisymmetricTensor(t) : TenSpcElt -> TenSpcElt
\end{intrinsics}

Returns the antisymmetric tensor induced by the given tensor. If
the tensor is already antisymmetric, then the given tensor is returned.

\index{SymmetricTensor}
\begin{intrinsics}
SymmetricTensor(t) : TenSpcElt -> TenSpcElt
\end{intrinsics}

Returns the symmetric tensor induced by the given tensor. If the tensor is
already symmetric, then the given tensor is returned.

\begin{example}[AlternatingTensor]

Tensors coming from Lie algebras are alternating.
If we call \texttt{AlternatingTensor} on a tensor from a Lie algebra, nothing will be changed.
\begin{code}
> L := LieAlgebra("A3", GF(3));
> t := Tensor(L);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 15 over GF(3)
U1 : Full Vector space of degree 15 over GF(3)
U0 : Full Vector space of degree 15 over GF(3)
> AlternatingTensor(t) eq t;
true
\end{code}
\end{example}

\begin{example}[MakeSymmetric]

We will make the tensor coming from the product in $\mathbb{M}_3(\mathbb{Q})$ symmetric.
\begin{code}
> A := MatrixAlgebra(Rationals(), 3);
> t := Tensor(A);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 9 over Rational Field
U1 : Full Vector space of degree 9 over Rational Field
U0 : Full Vector space of degree 9 over Rational Field
> SystemOfForms(t)[1];
[1 0 0 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0]
[0 0 0 0 0 0 1 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]
\end{code}

We see that the first matrix in the sequence of bilinear forms of $t$ is not
symmetric, so $t$ is not symmetric (of course matrix multiplication is not
commutative also). We will construct a symmetric version of $t$ and inspect the
first matrix of the bilinear forms.
\begin{code}
> s := SymmetricTensor(t);
> s;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 9 over Rational Field
U1 : Full Vector space of degree 9 over Rational Field
U0 : Full Vector space of degree 9 over Rational Field
> SystemOfForms(s)[1];
[2 0 0 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0]
[0 0 0 0 0 0 1 0 0]
[0 1 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0]
\end{code}
\end{example}

\index{Shuffle!tensors}
\begin{intrinsics}
Shuffle(t, g) : TenSpcElt, GrpPermElt -> TenSpcElt
Shuffle(t, g) : TenSpcElt, SeqEnum -> TenSpcElt
\end{intrinsics}

For a tensor $t$ in $\hom(U_{\vav},\dots,\hom(U_1,U_0)\cdots)$,
generates a representation of $t$ in
\[ \hom(U_{{\vav}^g},\dots,\hom(U_{1^g},U_{0^g})\dots). \]
In order to be defined, $g\in\text{Sym}(\zrange{\vav})$.
If $0^g\ne 0$, then both the image and pre-image of $0$ under $g$ will be replaced by their $K$-dual space.
For cotensors, $g\in\text{Sym}(\range{\vav})$.
Sequences $[a_1,\dots,a_{\vav+1}]$ are interpreted as
\[
    \begin{array}{cccc}
        0 & 1 & \cdots & \vav \\
    \downarrow & \downarrow & & \downarrow \\
    a_1 & a_2 & \cdots & a_{\vav+1}.
    \end{array}
\]

\begin{example}[ShuffleToTranspose]

We will shuffle the alternating form
$\mathbb{Q}^2\times\mathbb{Q}^2\rightarrowtail \mathbb{Q}$ as a means of
performing a transpose on the Gram matrix. To do this, we need to shuffle by the
transposition $(1, 2)$ in Sym$(\{0,1,2\})$.
\begin{code}
> t := Tensor(Rationals(), [2, 2, 1], [0, 1, -1, 0]);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 2 over Rational Field
U1 : Full Vector space of degree 2 over Rational Field
U0 : Full Vector space of degree 1 over Rational Field
> SystemOfForms(t);
[
    [ 0  1]
    [-1  0]
]
>
> s := Shuffle(t, [0,2,1]);
> s;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 2 over Rational Field
U1 : Full Vector space of degree 2 over Rational Field
U0 : Full Vector space of degree 1 over Rational Field
> SystemOfForms(s);
[
    [ 0 -1]
    [ 1  0]
]
\end{code}
\end{example}

\begin{example}[Shuffling]

We will generate a random 5-tensor and shuffle it with $(0,2,4,1,3)$.
\begin{code}
> t := RandomTensor(GF(2), [5,4,3,2,1]);
> t;
Tensor of valence 5, U4 x U3 x U2 x U1 >-> U0
U4 : Full Vector space of degree 5 over GF(2)
U3 : Full Vector space of degree 4 over GF(2)
U2 : Full Vector space of degree 3 over GF(2)
U1 : Full Vector space of degree 2 over GF(2)
U0 : Full Vector space of degree 1 over GF(2)
>
> G := Sym({0..4});
> g := G![2, 3, 4, 0, 1];
> g;
(0, 2, 4, 1, 3)
>
> s := Shuffle(t, g);
> s;
Tensor of valence 5, U4 x U3 x U2 x U1 >-> U0
U4 : Full Vector space of degree 2 over GF(2)
U3 : Full Vector space of degree 1 over GF(2)
U2 : Full Vector space of degree 5 over GF(2)
U1 : Full Vector space of degree 4 over GF(2)
U0 : Full Vector space of degree 3 over GF(2)
\end{code}
\end{example}

\index{TensorProduct}
\begin{intrinsics}
TensorProduct(t, s) : TenSpcElt, TenSpcElt -> TenSpcElt
\end{intrinsics}

Given $K$-tensors $t:U_{\vav}\times \cdots \times U_1\rightarrowtail U_0$ and
$s:V_{\vav}\times \cdots \times V_1\rightarrowtail V_0$, returns the tensor
$t\otimes s : U_{\vav}\otimes V_{\vav}\times \cdots \times U_1\otimes
V_1\rightarrowtail U_0\otimes V_0$. This is like a generalized Kronecker
product.

\section{Operations with Tensors}

We take two perspectives for operations with tensors.
First, tensors determine multilinear maps and so behave as
functions.  Second, tensors are elements of a tensor space and
so behave as elements in a module.

\subsection{Elementary operations}
Treating the tensor space as a $K$-module, we have the standard operations.

\index{$+$}\index{$*$!as module}
\begin{intrinsics}
s + t : TenSpcElt, TenSpcElt -> TenSpcElt
s - t : TenSpcElt, TenSpcElt -> TenSpcElt
k * t : RngElt, TenSpcElt -> TenSpcElt
-t : TenSpcElt -> TenSpcElt
\end{intrinsics}

Returns the sum, difference, scalar multiple, or additive inverse of the given
tensor(s) as module elements of the tensor space. The corresponding multilinear
maps are the sum, difference, scalar multiple, or additive inverse of the
multilinear maps.

\begin{example}[ModuleOperations]

As tensors are elements of a tensor space, it inherits module operations.
We will demonstrate them all here.
First, here are the tensors we will operate with.
\begin{code}
> K := Rationals();
> t := Tensor(K, [2, 2, 2], [1..8]);
> s := Tensor(K, [2, 2, 2], &cat[[2, -1] : i in [1..4]]);
> SystemOfForms(t);
[
    [1 3]
    [5 7],

    [2 4]
    [6 8]
]
> SystemOfForms(s);
[
    [2 2]
    [2 2],

    [-1 -1]
    [-1 -1]
]
\end{code}

Now we perform the module operations.
\begin{code}
> SystemOfForms(-t);
[
    [-1 -3]
    [-5 -7],

    [-2 -4]
    [-6 -8]
]
> SystemOfForms((1/3)*s);
[
    [2/3 2/3]
    [2/3 2/3],

    [-1/3 -1/3]
    [-1/3 -1/3]
]
> SystemOfForms(t+s);
[
    [3 5]
    [7 9],

    [1 3]
    [5 7]
]
> SystemOfForms(t-2*s);
[
    [-3 -1]
    [ 1  3],

    [ 4  6]
    [ 8 10]
]
\end{code}
\end{example}

\index{AssociatedForm}
\begin{intrinsics}
AssociatedForm(t) : TenSpcElt -> TenSpcElt
\end{intrinsics}

For a tensor $t$ with frame $U_{\vav}\times \cdots \times U_1\rightarrowtail U_0$,
creates the associated multilinear form
$U_{\vav}\times\cdots\times U_1\times U_0^*\rightarrowtail K$.
The valence is increased by $1$.

\index{Compress}
\begin{intrinsics}
Compress(t) : TenSpcElt -> TenSpcElt
Compress(~t) : TenSpcElt ->
\end{intrinsics}

Returns the compression of the tensor. This removes all 1-dimensional spaces in the domain.

\begin{example}[CompressAssocForm]

We will construct the associated form of the tensor from the Lie algebra of type
$B_3$ over $\mathbb{F}_5$. The codomain of the original tensor gets moved (and
dualized) to the domain.
\begin{code}
> L := LieAlgebra("B3", GF(5));
> t := Tensor(L);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 21 over GF(5)
U1 : Full Vector space of degree 21 over GF(5)
U0 : Full Vector space of degree 21 over GF(5)
> s := AssociatedForm(t);
> s;
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 21 over GF(5)
U2 : Full Vector space of degree 21 over GF(5)
U1 : Full Vector space of degree 21 over GF(5)
U0 : Full Vector space of degree 1 over GF(5)
> <L.2, L.11> @ t;
(0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
> <L.2, L.11, L.2> @ s;
(4)
> <L.2, L.11, L> @ s;
Full Vector space of degree 1 over GF(5)
Generators:
(4)
\end{code}

We shuffle the associated form by the permutation $(0,3)$ and compress it.
The result is just the shuffle of the original bilinear map $t$ by $(0,2,1)$.

\begin{code}
> shf := Shuffle(s, [3,1,2,0]);
> shf;
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 1 over GF(5)
U2 : Full Vector space of degree 21 over GF(5)
U1 : Full Vector space of degree 21 over GF(5)
U0 : Full Vector space of degree 21 over GF(5)
> cmp := Compress(shf);
> cmp;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 21 over GF(5)
U1 : Full Vector space of degree 21 over GF(5)
U0 : Full Vector space of degree 21 over GF(5)
> cmp eq Shuffle(t, [2, 0, 1]);
true
\end{code}
\end{example}

\subsection{General properties}

We provide basic intrinsics to get data stored in the \texttt{TenSpcElt} object
in \textsf{Magma}. Most of these functions are already stored as attributes. The ones
that are not initially stored at construction are stored immediately after the
initial computation, such as \texttt{Image}.

\index{Parent!tensor}
\begin{intrinsics}
Parent(t) : TenSpcElt -> TenSpc
\end{intrinsics}

Returns the tensor space that contains $t$. The default space is the universal
tensor space.

\index{Domain!tensor}
\begin{intrinsics}
Domain(t) : TenSpcElt -> List
\end{intrinsics}

Returns the domain of the tensor as a list of modules.

\index{Codomain!tensor}
\begin{intrinsics}
Codomain(t) : TenSpcElt -> Any
\end{intrinsics}

Returns the codomain of the tensor.

\index{Valence!tensor}
\begin{intrinsics}
Valence(t) : TenSpcElt -> RngIntElt
\end{intrinsics}

Returns the valence of the tensor.

\index{Frame!tensor}
\begin{intrinsics}
Frame(t) : TenSpcElt -> List
\end{intrinsics}

Returns the modules in the frame of $t$; this is the concatenation of
the domain modules and the codomain.

\index{BaseRing!tensor}\index{BaseField!tensor}
\begin{intrinsics}
BaseRing(t) : TenSpcElt -> Rng
BaseField(t) : TenSpcElt -> Fld
\end{intrinsics}

Returns the base ring or field of the tensor.

\begin{example}[BasicProps]

We demonstrate how to get basic properties of a tensor and what to expect as an
output. We will construct a tensor $\bra{t}:\mathbb{M}_{2\times 3}(\mathbb{Q})\times
\mathbb{Q}^3\rightarrowtail \mathbb{Q}^2$ given by multiplication. Nearly all of
this information is displayed when printing a tensor.
\begin{code}
> K := Rationals();
> U2 := KMatrixSpace(K, 2, 3);
> U1 := VectorSpace(K, 3);
> U0 := VectorSpace(K, 2);
> mult := func< x | Eltseq(x[1]*Matrix(3,1,Eltseq(x[2]))) >;
> t := Tensor([* U2, U1, U0 *], mult);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 6 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
>
> Parent(t);
Tensor space of dimension 36 over Rational Field with valence 3
U2 : Full Vector space of degree 6 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
>
> Domain(t);
[*
    Full Vector space of degree 6 over Rational Field,

    Full Vector space of degree 3 over Rational Field
*]
>
> Codomain(t);
Full Vector space of degree 2 over Rational Field
>
> Valence(t);
3
>
> Frame(t);
[*
    Full Vector space of degree 6 over Rational Field,

    Full Vector space of degree 3 over Rational Field,

    Full Vector space of degree 2 over Rational Field
*]
> BaseRing(t);
Rational Field
\end{code}
\end{example}

\index{TensorCategory!tensor}
\begin{intrinsics}
TensorCategory(t) : TenSpcElt -> TenCat
\end{intrinsics}

Returns the underlying tensor category of $t$.

\index{ChangeTensorCategory!tensor}
\begin{intrinsics}
ChangeTensorCategory(t, C) : TenSpcElt, TenCat -> TenSpcElt
ChangeTensorCategory(~t, C) : TenSpcElt, TenCat ->
\end{intrinsics}

Returns the tensor with the given category.

\index{IsCovariant!tensor}\index{IsContravariant!tensor}
\begin{intrinsics}
IsCovariant(t) : TenSpcElt -> BoolElt
IsContravariant(t) : TenSpcElt -> BoolElt
\end{intrinsics}

Decides if the underlying category of $t$ is covariant or contravariant.

\begin{example}[TensorCatProps]

We will construct a tensor from a right module, $\bra{t}:\mathbb{Q}^2 \times
\mathbb{M}_{2\times 2}(\mathbb{Q})\rightarrowtail \mathbb{Q}^2$.
\begin{code}
> K := Rationals();
> U := KMatrixSpace(K, 2, 2);
> V := VectorSpace(K, 2);
> mult := func< x | x[1]*x[2] >;
> t := Tensor([* V, U, V *], mult);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 2 over Rational Field
U1 : Full Vector space of degree 4 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
\end{code}

Because this tensor comes from a module, we want the tensor category to reflect this.
Currently, the tensor category is the default homotopism category.
We will keep everything about the category the same, except we will fuse coordinates 2 and 0.
These changes could easily go unnoticed if no operators are constructed.
\begin{code}
> TensorCategory(t);
Tensor category of valence 3 (->,->,->) ({ 1 },{ 2 },{ 0 })
> Cat := TensorCategory([1, 1, 1], {{2,0},{1}});
> Cat;
Tensor category of valence 3 (->,->,->) ({ 1 },{ 0, 2 })
> ChangeTensorCategory(~t, Cat);
> TensorCategory(t);
Tensor category of valence 3 (->,->,->) ({ 1 },{ 0, 2 })
> IsCovariant(t);
true
\end{code}
\end{example}

\index{NondegenerateTensor}
\begin{intrinsics}
NondegenerateTensor(t) : TenSpcElt -> TenSpcElt, Hmtp
\end{intrinsics}

Returns the nondegenerate tensor associated to $t$ along with a homotopism
from the given tensor to the returned nondegenerate tensor.

\index{IsNondegenerate}
\begin{intrinsics}
IsNondegenerate(t) : TenSpcElt -> BoolElt
\end{intrinsics}

Decides whether $t$ is a nondegenerate tensor.

\begin{example}[Nondegeneracy]

An important property for tensors is nondegeneracy: all radicals are trivial.
First we create a tensor with degeneracy.
\begin{code}
> K := GF(541);
> V := VectorSpace(K, 10);
> U := VectorSpace(K, 5);
> mult := function(x)
function>   M := Matrix(3, 3, Eltseq(x[1])[2..10]);
function>   v := VectorSpace(K, 3)!(Eltseq(x[2])[[1,3,5]]);
function>   return Eltseq(v*M) cat [0,0];
function> end function;
> t := Tensor([V, U, U], mult);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 10 over GF(541)
U1 : Full Vector space of degree 5 over GF(541)
U0 : Full Vector space of degree 5 over GF(541)
\end{code}

In this example, both $U_{2}^\perp$ and $U_{1}^\perp$ are nontrivial. We will
construct the associated nondegenerate tensor $\bra{s} : U_2/U_{2}^\perp \times
U_1/U_{1}^\perp \rightarrowtail U_0$.
\begin{code}
> IsNondegenerate(t);
false
> s, H := NondegenerateTensor(t);
> s;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 9 over GF(541)
U1 : Full Vector space of degree 3 over GF(541)
U0 : Full Vector space of degree 5 over GF(541)
> H;
Maps from U2 x U1 >-> U0 to V2 x V1 >-> V0.
U2 -> V2: Mapping from: Full Vector space of degree 10 over GF(541) to
Full Vector space of degree 9 over GF(541)
U1 -> V1: Mapping from: Full Vector space of degree 5 over GF(541) to
Full Vector space of degree 3 over GF(541)
U0 -> V0: Mapping from: Full Vector space of degree 5 over GF(541) to
Full Vector space of degree 5 over GF(541)
\end{code}
\end{example}

\index{Image!tensor}
\begin{intrinsics}
Image(t) : TenSpcElt -> ModTupRng
\end{intrinsics}

Returns the image of the tensor along with a map to the vector space.

\index{FullyNondegenerateTensor}
\begin{intrinsics}
FullyNondegenerateTensor(t) : TenSpcElt -> TenSpcElt, Hmtp
\end{intrinsics}

Returns the fully nondegenerate tensor associated to $t$ along with a
cohomotopism from the given tensor to the returned tensor.

\index{IsFullyNondegenerate}
\begin{intrinsics}
IsFullyNondegenerate(t) : TenSpcElt -> BoolElt
\end{intrinsics}

Decides whether $t$ is a fully nondegenerate tensor.

\begin{example}[FullyNondegenerate]

We use the same tensor as the previous example illustrating the use of \texttt{NondegenerateTensor}.
\begin{code}
> K := GF(541);
> V := VectorSpace(K, 10);
> U := VectorSpace(K, 5);
> mult := function(x)
function>   M := Matrix(3, 3, Eltseq(x[1])[2..10]);
function>   v := VectorSpace(K, 3)!(Eltseq(x[2])[[1,3,5]]);
function>   return Eltseq(v*M) cat [0,0];
function> end function;
> t := Tensor([V, U, U], mult);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 10 over GF(541)
U1 : Full Vector space of degree 5 over GF(541)
U0 : Full Vector space of degree 5 over GF(541)
\end{code}

Here, we want to construct a fully nondegenerate tensor. Of course, the tensor
from the previous example is not fully nondegenerate as it is not degenerate,
but we check that the image is not isomorphic to the codomain.
\begin{code}
> IsFullyNondegenerate(t);
false
> Image(t);
Vector space of degree 5, dimension 3 over GF(541)
Generators:
(  1   0   0   0   0)
(  0   1   0   0   0)
(  0   0   1   0   0)
Echelonized basis:
(  1   0   0   0   0)
(  0   1   0   0   0)
(  0   0   1   0   0)
\end{code}

Now we will construct the associated fully nondegenerate tensor: $\bra{s}:
U_2/U_{2}^\perp\times U_1/U_{1}^\perp\rightarrowtail \bra{t} U_2, U_1\rangle$.
Notice that the morphism between the original tensor and the fully nondegenerate
tensor is a cohomotopism.
\begin{code}
> s, H := FullyNondegenerateTensor(t);
> s;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 9 over GF(541)
U1 : Full Vector space of degree 3 over GF(541)
U0 : Vector space of degree 5, dimension 3 over GF(541)
Generators:
(  1   0   0   0   0)
(  0   1   0   0   0)
(  0   0   1   0   0)
Echelonized basis:
(  1   0   0   0   0)
(  0   1   0   0   0)
(  0   0   1   0   0)
> H;
Maps from U2 x U1 >-> U0 to V2 x V1 >-> V0.
U2 -> V2: Mapping from: Full Vector space of degree 10 over GF(541) to
Full Vector space of degree 9 over GF(541)
U1 -> V1: Mapping from: Full Vector space of degree 5 over GF(541) to
Full Vector space of degree 3 over GF(541)
U0 <- V0: Mapping from: Full Vector space of degree 5 over GF(541) to
Full Vector space of degree 5 over GF(541)
Composition of Mapping from: Full Vector space of degree 5 over GF(541)
to Full Vector space of degree 5 over GF(541) and
Mapping from: Vector space of degree 5, dimension 3 over GF(541) to Full
Vector space of degree 5 over GF(541)
\end{code}
\end{example}

\index{IsAlternating!tensor}\index{IsAntisymmetric!tensor}
\begin{intrinsics}
IsAlternating(t) : TenSpcElt -> BoolElt
\end{intrinsics}

Decides whether $t$ is an alternating tensor.

\index{IsAntisymmetric!tensor}
\begin{intrinsics}
IsAntisymmetric(t) : TenSpcElt -> BoolElt
\end{intrinsics}

Decides whether $t$ is an antisymmetric tensor.

\index{IsSymmetric!tensor}
\begin{intrinsics}
IsSymmetric(t) : TenSpcElt -> BoolElt
\end{intrinsics}

Decides whether $t$ is a symmetric tensor.

\begin{example}[SymmetricPolar]

We will construct the multilinear form given by polarizing the homogeneous
polynomial $f(x,y,z)=x^3+y^3+z^3+xyz$. Since $f$ is a symmetric polynomial, its
multilinear form is also symmetric.
\begin{code}
> K := Rationals();
> R<x,y,z> := PolynomialRing(K, 3);
> f := x^3 + y^3 + z^3 + x*y*z;
> t, p := Polarization(f);
> p;
6*$.1*$.4*$.7 + $.1*$.5*$.9 + $.1*$.6*$.8 + $.2*$.4*$.9 +
    6*$.2*$.5*$.8 + $.2*$.6*$.7 + $.3*$.4*$.8 + $.3*$.5*$.7 +
    6*$.3*$.6*$.9
> t;
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 3 over Rational Field
U2 : Full Vector space of degree 3 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 1 over Rational Field
\end{code} %$

The resulting homogeneous polynomial from polarizing is
\[
    p(x_1,x_2,x_3,y_1,y_2,y_3,z_1,z_2,z_3)
        =\sum_{\sigma\in S_3} (x_{1^\sigma}y_{2^\sigma}z_{3^\sigma}
            + 3x_{1^\sigma}y_{1^\sigma}z_{1^\sigma}).
\]
\begin{code}
> IsSymmetric(t);
true
> AsMatrices(t, 3, 1) eq AsMatrices(t, 2, 1);
true
> AsMatrices(t, 3, 1) eq AsMatrices(t, 3, 2);
true
> AsMatrices(t, 3, 1);
[
    [6 0 0]
    [0 0 1]
    [0 1 0],

    [0 0 1]
    [0 6 0]
    [1 0 0],

    [0 1 0]
    [1 0 0]
    [0 0 6]
]
\end{code}

Because the underlying field is $\mathbb{Q}$ and because $t$
is symmetric, we know that $t$ is not alternating nor antisymmetric.
\begin{code}
> IsAlternating(t);
false
> IsAntisymmetric(t);
false
\end{code}
\end{example}

\subsection{As multilinear maps}
Regarding tensors as multilinear maps, we allow for evaluation and composition.

\index{AT!tensor}
\begin{intrinsics}
x @ t : Tup, TenSpcElt -> Any
\end{intrinsics}

Returns $\bra{t} x\rangle$, where $x\in U_{\vav}\times \cdots \times U_1$. The
entries can be elements from the vector space $U_i$ or sequences that
\textsf{Magma} can naturally coerce into the vector space $U_i$. In some
circumstances, tensors come from algebraic objects (e.g.\! algebras), and in
these cases the entries of $x$ can be contained in the original algebraic object
as well.

\begin{example}[MultiMapEval]

Here we create the 4-tensor $\bra{t}$ of an algebra $A$ given by the Jacobi identity:
\[ (x,y,z)\mapsto (xy)z + (yz)x + (zx)y. \]
Therefore, the algebra satisfies the Jacobi identity if $\bra{t} A, A, A \rangle
= 0$. We will also change the tensor category so that all the coordinates are
fused together.
\begin{code}
> A := MatrixAlgebra(GF(3), 3);
> JacobiID := func< x | x[1]*x[2]*x[3]+x[2]*x[3]*x[1]+x[3]*x[1]*x[2] >;
> Cat := TensorCategory([1 : i in [0..3]], {{0..3}});
> t, maps := Tensor([A : i in [0..3]], JacobiID, Cat);
> t;
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 9 over GF(3)
U2 : Full Vector space of degree 9 over GF(3)
U1 : Full Vector space of degree 9 over GF(3)
U0 : Full Vector space of degree 9 over GF(3)
> TensorCategory(t);
Tensor category of valence 4 (->,->,->,->) ({ 0 .. 3 })
\end{code}

Even though our tensor originated over the algebra
$A=\mathbb{M}_{3}(\mathbb{F}_3)$, the returned tensor is over vector spaces
$\mathbb{F}_3^9$. However, the tensor $\bra{t}$ can still evaluate elements from
$\mathbb{M}_3(\mathbb{F}_3)$ as well as $\mathbb{F}_3^9$. Observe that the out
of $\bra{t}$ will be a vector regardless of the input. The second output at
construction, the \texttt{List} of maps, can be used to map the vectors to
$\mathbb{M}_3(\mathbb{F}_3)$.
\begin{code}
> x := <A.1, A.2, A.2^2>;
> x;
<
    [1 0 0]
    [0 0 0]
    [0 0 0],

    [0 1 0]
    [0 0 1]
    [1 0 0],

    [0 0 1]
    [1 0 0]
    [0 1 0]
>
> x @ t;
(2 0 0 0 1 0 0 0 0)
>
> phi := maps[1];
> x := <A.1 @ phi, A.2 @ phi, (A.2^2) @ phi>;
> x;
<(1 0 0 0 0 0 0 0 0), (0 1 0 0 0 1 1 0 0), (0 0 1 1 0 0 0 1 0)>
> x @ t;
(2 0 0 0 1 0 0 0 0)
\end{code}

Because \texttt{@} takes \texttt{Tup} as input, $\bra{t}$ can evaluate mixed
tuples as well: where some entries are contained in $\mathbb{M}_3(\mathbb{F}_3)$
and other entries are contained in $\mathbb{F}_3^9$.
\begin{code}
> x := <A.1, A.2 @ phi, Eltseq(A.2^2)>;
> x;
<
    [1 0 0]
    [0 0 0]
    [0 0 0],

    (0 1 0 0 0 1 1 0 0),

    [ 0, 0, 1, 1, 0, 0, 0, 1, 0 ]
>
> <Type(i) : i in x>;
<AlgMatElt, ModTupFldElt, SeqEnum>
> x @ t;
(2 0 0 0 1 0 0 0 0)
\end{code}
\end{example}

\index{$*$!as multilinear map}
\begin{intrinsics}
t * f : TenSpcElt, Map -> TenSpcElt
t * s : TenSpcElt, TenSpcElt -> TenSpcElt
\end{intrinsics}

Returns the tensor $f\bra{t}$ which is the composition of $\bra{t}$ with the
given map $f$. If a tensor $\bra{s}$ is used instead of a \texttt{Map},
$\bra{s}$ must have valence $\leq 1$.

\index{eq!tensor}
\begin{intrinsics}
t eq s : TenSpcElt, TenSpcElt -> BoolElt
\end{intrinsics}

Decides if the tensors $\bra{t}$ and $\bra{s}$ are the same. Two tensors are
equivalent if, and only if, they have the same tensor category, base ring,
frame, and structure constants.

\begin{example}[TensorComp]

We start with the same tensor as the previous example: the tensor given by the
Jacobi identity on the algebra $A=\mathbb{M}_3(\mathbb{F}_3)$.
\begin{code}
> A := MatrixAlgebra(GF(3), 3);
> JacobiID := func< x | x[1]*x[2]*x[3]+x[2]*x[3]*x[1]+x[3]*x[1]*x[2] >;
> Cat := TensorCategory([1 : i in [0..3]], {{0..3}});
> t, maps := Tensor([A : i in [0..3]], JacobiID, Cat);
> t;
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 9 over GF(3)
U2 : Full Vector space of degree 9 over GF(3)
U1 : Full Vector space of degree 9 over GF(3)
U0 : Full Vector space of degree 9 over GF(3)
> TensorCategory(t);
Tensor category of valence 4 (->,->,->,->) ({ 0 .. 3 })
\end{code}

The maps in \texttt{Maps} are vector space isomorphisms and map $A$ to $V$.
Suppose $\phi: A\rightarrow V$ is a vector space isomorphism. If we compose
$\bra{t}$ with $\phi^{-1}$, the returned tensor is \emph{still} over vector
spaces. The codomain is \emph{not} $A$; this is because all tensors are over
vector spaces. In fact, the returned tensor is exactly the same as $\bra{t}$.
\begin{code}
> phi := maps[1];
> t * (phi^-1);
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 9 over GF(3)
U2 : Full Vector space of degree 9 over GF(3)
U1 : Full Vector space of degree 9 over GF(3)
U0 : Full Vector space of degree 9 over GF(3)
> t * (phi^-1) eq t;
true
\end{code}

Let $\mathcal{E}\subset A$ be an orthogonal frame---a set of primitive,
orthogonal, idempotents. We will compose $\bra{t}$ with the linear
transformation
\[ a \mapsto \sum_{e\in\mathcal{E}} eae.\]
Because the codomain of $\bra{t}$ is a vector space, we will pre-compose this
map by $\phi^{-1}$.
\begin{code}
> E := [A.1, A.2^-1*A.1*A.2, A.2^-2*A.1*A.2^2];
> E;
[
    [1 0 0]
    [0 0 0]
    [0 0 0],

    [0 0 0]
    [0 1 0]
    [0 0 0],

    [0 0 0]
    [0 0 0]
    [0 0 1]
]
> f := map< A -> A | x :-> &+[ E[i]*x*E[i] : i in [1..3] ] >;
> f;
Mapping from: AlgMat: A to AlgMat: A given by a rule [no inverse]
> s := t*(phi^-1*f);
> s;
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 9 over GF(3)
U2 : Full Vector space of degree 9 over GF(3)
U1 : Full Vector space of degree 9 over GF(3)
U0 : Full Vector space of degree 9 over GF(3)
> s eq t;
false
\end{code}

We can also wrap $f$ as a 2-tensor and compose it with $\bra{t}$.
\begin{code}
> g := Tensor([A, A], func< x | x[1]@f >);
> g;
Tensor of valence 2, U1 >-> U0
U1 : Full Vector space of degree 9 over GF(3)
U0 : Full Vector space of degree 9 over GF(3)
> t * g eq s;
true
\end{code}
\end{example}

\subsection{Operations with Bilinear maps}
Tensors of valence $3$, also known as bilinear tensors, or as bilinear maps,
are commonly described as distributive products. For instance as the product of
an algebra, the product of a ring on a module, or an inner product.  We support
these interpretations in two ways: by permitting an infix $x*t*y$ notation for a
3-tensor $t$, and a product $x*y$ notation for the evaluation of bilinear
tensors.  For the latter, we do this by creating special types
\texttt{BmpU[Elt]}, \texttt{BmpV[Elt]}, and \texttt{BmpW[Elt]} for the frame of
a bilinear tensor. For bilinear maps, we fix an interpretation $\bra{\cdot} : T
\rightarrow W\oslash V\oslash U$.

\index{$*$!bilinear tensor (infix)}
\begin{intrinsics}
x * t : Any, TenSpcElt -> Any
t * y : Any, TenSpcElt -> Any
\end{intrinsics}

Given a bilinear tensor $t$ framed by $[U, V, W]$, $x*t$ returns the action on
the right as a linear map $L : V\rightarrow W$ given by $vL = x* v$ if $x$ is an
element of $U$. If $x$ is a subspace of $U$, then this returns a subspace of the
tensor space $T$ with frame $V\rightarrowtail W$. For the left action use $t*y$
instead. If $t$ is valence 1, then the image of either $x$ or $y$ is returned.
Therefore, the possible outputs are a tensor space \texttt{TenSpc}, a tensor
\texttt{TenSpcElt}, or a vector \texttt{ModTupFld}.

Related to this intrinsic is the following: using tensor spaces with the infix notation.

\index{$*$!bilinear tensor (infix)}
\begin{intrinsics}
x * T : Any, TenSpc -> Any
T * y : Any, TenSpc -> Any
\end{intrinsics}

Given a subspace of bilinear tensors, return the subspace generated by all
$x*t$, for $t\in T$. This is either a tensor space or a vector space.

\begin{example}[BimapInfix]

We demonstrate the infix notation by constructing the tensor in
$A=\mathbb{M}_2(\mathbb{Q})$ given by multiplication.
\begin{code}
> A := MatrixAlgebra(Rationals(), 2);
> t := Tensor(A);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 4 over Rational Field
U1 : Full Vector space of degree 4 over Rational Field
U0 : Full Vector space of degree 4 over Rational Field
\end{code}

Like tensor evaluation, the infix notation will accept elements of a vector
space (or objects that \textsf{Magma} can easily coerce into a vector space) or elements
from the original algebraic object. For $M=\left(\begin{smallmatrix} 0 & 1 \\ 0
& 0 \end{smallmatrix}\right)$, we will use the infix notation to construct the
2-tensor $* : A\rightarrowtail A$, where $X\mapsto MX$.
\begin{code}
> M := A![0, 1, 0, 0];
> M;
[0 1]
[0 0]
> s := M*t;
> s;
Tensor of valence 2, U1 >-> U0
U1 : Full Vector space of degree 4 over Rational Field
U0 : Full Vector space of degree 4 over Rational Field
> AsMatrices(s, 1, 0);
[
    [0 0 0 0]
    [0 0 0 0]
    [1 0 0 0]
    [0 1 0 0]
]
\end{code}

From the structure constants above, evaluating \texttt{M*t} at \texttt{V.3}
should output \texttt{W.1}. Furthermore, the image of \texttt{M*t} is
2-dimensional in $W$.
\begin{code}
> M*t*[0, 0, 1, 0];
(1 0 0 0)
> M*t*VectorSpace(Rationals(), 4);
Vector space of degree 4, dimension 2 over Rational Field
Generators:
(1 0 0 0)
(0 1 0 0)
Echelonized basis:
(1 0 0 0)
(0 1 0 0)
\end{code}

If we switch the order and multiply $t$ by $U$ on the left first, we will get a
tensor space $T$. Because that tensor space came from $t$, which originally came
from algebraic objects, we can use the returned tensor space to evaluate $M$. An
arbitrary tensor space, however, would not evaluate $M$ unless it is an
appropriate vector.
\begin{code}
> T := VectorSpace(Rationals(), 4)*t;
> T;
Tensor space of dimension 4 over Rational Field with valence 2
U1 : Full Vector space of degree 4 over Rational Field
U0 : Full Vector space of degree 4 over Rational Field
> T*M;
Vector space of degree 4, dimension 2 over Rational Field
Generators:
(0 1 0 0)
(0 0 0 1)
Echelonized basis:
(0 1 0 0)
(0 0 0 1)
\end{code}
\end{example}

The next style of notation we support is the product notation, $x*y$. In order
to use this style, the user needs to coerce both $x$ and $y$ into the
\texttt{LeftDomain} and \texttt{RightDomain} respectively.

\index{$*$!bilinear tensor (product)}
\begin{intrinsics}
x * y : BmpUElt, BmpVElt -> Any
x * y : BmpU, BmpV -> Any
x * y : BmpUElt, BmpV -> Any
x * y : BmpU, BmpVElt -> Any
\end{intrinsics}

If $x$ and $y$ are associated to the bilinear map $t$, these operations return
\texttt{<x,y> @ t}.

\index{LeftDomain}
\begin{intrinsics}
LeftDomain(t) : TenSpcElt -> BmpU
\end{intrinsics}

Returns the left domain $U$ of $t$, framed by $W\oslash V\oslash U$, setup for
use with infix notation.

\index{RightDomain}
\begin{intrinsics}
RightDomain(t) : TenSpcElt -> BmpV
\end{intrinsics}

Returns the right domain $V$ of $t$ framed by $W\oslash V\oslash U$, setup for
use with infix notation.

\index{IsCoercible!bilinear}\index{BANG!bilinear}
\begin{intrinsics}
IsCoercible(U, x) : BmpU, Any -> BoolElt, BmpUElt
IsCoercible(V, x) : BmpV, Any -> BoolElt, BmpVElt
U ! x : BmpU, Any -> BmpUElt
V ! x : BmpV, Any -> BmpVElt
\end{intrinsics}

Decides if $x$ can be coerced into $U$ or $V$, and if it can, it returns the
coerced element.

\begin{example}[BimapProduct]

We demonstrate the product notation for tensors of valence 3 using a tensor
derived from a $p$-group. Suppose $G$ is a $p$-group and $[,] : U\times
V\rightarrowtail W$ is the tensor given by commutation where $U=V=G/\eta_2$ and
$W=\eta_2/\eta_3$, where $\eta_i$ denotes the $i$th term of the exponent-$p$
central series of $G$.
\begin{code}
> G := SmallGroup(512, 10^6);
> t := pCentralTensor(G);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 5 over GF(2)
U1 : Full Vector space of degree 5 over GF(2)
U0 : Full Vector space of degree 4 over GF(2)
> U := LeftDomain(t);
> V := RightDomain(t);
> U;
Bimap space U: Full Vector space of degree 5 over GF(2)
> V;
Bimap space V: Full Vector space of degree 5 over GF(2)
\end{code}

Like with the other styles of notation (infix and tuple), users can evaluate
elements from the original algebraic object, vectors from the vector spaces, and
even sequences that \textsf{Magma} can easily coerce into vector spaces. To use the
product notation, coerce elements into the \texttt{LeftDomain} and
\texttt{RightDomain}.

\begin{code}
> x := U!(G.1*G.2*G.4);
> y := V![1,0,0,0,0];
> x;
Bimap element of U: (1 1 0 1 0)
> y;
Bimap element of V: (1 0 0 0 0)
> x*y;
(1 0 0 1)
\end{code}

We can take this further and evaluate subspaces of $U$ or $V$.
\begin{code}
> H := sub< G | G.2,G.4 >;
> U!H * V!G.1;
Vector space of degree 4, dimension 2 over GF(2)
Generators:
(0 0 0 1)
(1 0 0 0)
Echelonized basis:
(1 0 0 0)
(0 0 0 1)
> U!H * V;
Vector space of degree 4, dimension 3 over GF(2)
Generators:
(1 0 0 0)
(0 0 1 0)
(0 0 0 1)
(1 0 0 0)
(1 0 0 0)
Echelonized basis:
(1 0 0 0)
(0 0 1 0)
(0 0 0 1)
\end{code}
\end{example}

\index{Parent!bilinear}
\begin{intrinsics}
Parent(x) : BmpUElt -> BmpU
Parent(x) : BmpVElt -> BmpV
\end{intrinsics}

Returns the parent space of the bilinear map element.

\index{Parent!bilinear}
\begin{intrinsics}
Parent(X) : BmpU -> TenSpcElt
Parent(X) : BmpV -> TenSpcElt
\end{intrinsics}

Returns the original bilinear map where these spaces came from.

\index{eq!bilinear}
\begin{intrinsics}
u1 eq u2 : BmpUElt, BmpUElt -> BoolElt
v1 eq v2 : BmpUElt, BmpUElt -> BoolElt
U1 eq U2 : BmpU, BmpU -> BoolElt
V1 eq V2 : BmpV, BmpV -> BoolElt
\end{intrinsics}

Decides if the elements or spaces are equal.

\begin{example}[BimapProduct2]

We will construct the same tensor as the previous example.
\begin{code}
> G := SmallGroup(512, 10^6);
> t := pCentralTensor(G);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 5 over GF(2)
U1 : Full Vector space of degree 5 over GF(2)
U0 : Full Vector space of degree 4 over GF(2)
> U := LeftDomain(t);
> V := RightDomain(t);
> U;
Bimap space U: Full Vector space of degree 5 over GF(2)
> V;
Bimap space V: Full Vector space of degree 5 over GF(2)
\end{code}

The product notation has some basic functions for comparing objects and retrieving information.
\begin{code}
> V!G.1 eq V![1,0,0,0,0];
true
> Parent(U);
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 5 over GF(2)
U1 : Full Vector space of degree 5 over GF(2)
U0 : Full Vector space of degree 4 over GF(2)
> Parent(U) eq t;
true
\end{code}
\end{example}

\subsection{Manipulating tensor data}
The data from a tensor is accessible in multiple ways. For tensors given by
structure constants this can be described as the multidimensional analog of
choosing a row or column of a matrix. Other operations are generalization of the
transpose of a matrix. We do these operations with some care towards efficiency,
e.g.\! it may not physically move the values in a structure constant sequence
but instead permute the lookup of the values.

\index{Slice}\index{InducedTensor}
\begin{intrinsics}
Slice(t, grid) : TenSpcElt, [SetEnum] -> SeqEnum
InducedTensor(t, grid) : TenSpcElt, [SetEnum] -> TenSpcElt
\end{intrinsics}

Returns the slice of the structure constants running through the given grid. For
a tensor $t$ framed by free modules $[U_{\vav},\dots,U_0]$ with $d_i=\dim U_i$,
a \texttt{grid} is a sequence $[G_{\vav},\dots,G_0]$ of subsets $G_i\subseteq
\{1,\dots, d_i\}\cup \{-d_i,\dots, -1\}$. If an entry $g\in G_i$ is negative, it
will be taken to mean $d_i+g+1$, so $-1$ would be equivalent to $d_i$. The slice
is the list of entries in the structure constants of the tensor indexed by
$G_{\vav}\times \cdots \times G_0$. \texttt{Slice} returns the structure
constants whereas \texttt{InducedTensor} produces a tensor with these structure
constants.

\begin{example}[TensorSlicing]

We will construct a tensor $*: \mathbb{Q}^4\times \mathbb{Q}^3\rightarrowtail
\mathbb{Q}^2$ with a structure constants sequence equal to $[1,\dots,24]$. If
every $G_i=\{1,\dots, d_i\}$, then the result is the same as \texttt{Eltseq}.
\begin{code}
> U := VectorSpace(Rationals(),4);
> V := VectorSpace(Rationals(),3);
> W := VectorSpace(Rationals(),2);
> T := TensorSpace([U, V, W]);
> t := T![1..24];
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 4 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
> Slice(t, [{1..4},{1..3},{1..2}]) eq Eltseq(t);
true
\end{code}

Now we will slice with the following grid $[\{1,\dots,4\}, \{2\}, \{1\}]$.
Compare this with the product $U*v_2$.
\begin{code}
> [ U.i*t*V.2 : i in [1..4]];
[
    (3 4),
    ( 9 10),
    (15 16),
    (21 22)
]
> Slice(t, [{1..4},{2},{1}]);
[ 3, 9, 15, 21 ]
\end{code}

If, instead, we slice $W$ at its last basis vector, we get the following.
\begin{code}
> Slice(t, [{1..4},{2},{-1}]);
[ 4, 10, 16, 22 ]
\end{code}

Notice that if we use $-1$ and $2$ in the last set of the grid we get the same
output we got above.
\begin{code}
> Slice(t, [{1..4},{2},{-1,2}]);
[ 4, 10, 16, 22 ]
\end{code}

Now we will compare \texttt{Slice} and \texttt{InducedTensor}.
\texttt{InducedTensor} is basically a \texttt{Tensor} wrapping the \texttt{Slice} function.
\begin{code}
> s := InducedTensor(t, [{1..4}, {2}, {1,2}]);
> s;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 4 over Rational Field
U1 : Full Vector space of degree 1 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
> s2 := Tensor([4, 1, 2], Slice(t, [{1..4}, {2}, {1,2}]));
> s2;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 4 over Rational Field
U1 : Full Vector space of degree 1 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
>
> s eq s2;
true
> Eltseq(s);
[ 3, 4, 9, 10, 15, 16, 21, 22 ]
\end{code}
\end{example}

\index{SliceAsMatrices}
\begin{intrinsics}
SliceAsMatrices(t, grid, a, b) : TenSpcElt, [SetEnum], RngIntElt, RngIntElt -> SeqEnum
\end{intrinsics}

Returns a sequence of matrices whose output is equivalent to composing
\texttt{InducedTensor} and \texttt{AsMatrices}. This intrinsic will be slightly
faster than actually composing those two functions together as a tensor is not
constructed with \texttt{SliceAsMatrices}.

\begin{example}[SliceAsMatrices]

We will create the same tensor as the previous example.
\begin{code}
> t := Tensor(Rationals(), [4,3,2], [1..24]);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 4 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
> AsMatrices(t, 1, 0);
[
    [1 2]
    [3 4]
    [5 6],

    [ 7  8]
    [ 9 10]
    [11 12],

    [13 14]
    [15 16]
    [17 18],

    [19 20]
    [21 22]
    [23 24]
]
\end{code}

Now we will slice up this sequence of matrices. We will remove the second row
and the second and third matrix from the sequence above.
\begin{code}
> SliceAsMatrices(t, [{1,-1}, {1,-1}, {1,2}], 1, 0);
[
    [1 2]
    [5 6],

    [19 20]
    [23 24]
]
\end{code}
\end{example}

\index{Foliation}
\begin{intrinsics}
Foliation(t, a) : TenSpcElt, RngIntElt -> Mtrx
\end{intrinsics}

For a tensor $t$ with frame $U_{\vav}\times\cdots\times U_1\rightarrowtail U_0$,
return the matrix representing the linear map
$U_a\rightarrow \hom(\bigotimes_{b\ne a}U_b,U_0)$ using the bases of each $U_b$.
If $a=0$, then the returned matrix is given by the representation
$U_0^*\rightarrow \hom(\bigotimes U_b,K)$.

\begin{example}[ExfoliateFoliation]

We will, again, construct the same tensor,
$*:\mathbb{Q}^4\times\mathbb{Q}^3\rightarrowtail \mathbb{Q}^2$,
from the previous two examples whose structure constants sequence is $[1,\dots,24]$.
\begin{code}
> K := Rationals();
> Forms := [Matrix(K, 3, 2, [6*i+1..6*(i+1)]) : i in [0..3]];
> t := Tensor(Forms, 1, 0);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 4 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
\end{code}

We will use \texttt{Foliation} to compute the 2-radical of $t$.
(This is essentially what \texttt{Radical} does in \textsf{Magma}.)
That is, we will compute the subspace $U_{\hat{2}}^\perp\leq \mathbb{Q}^4$
such that $U_{\hat{2}}^\perp*\mathbb{Q}^3=0$.
This computation can be regarded as a nullspace computation.
\begin{code}
> F2 := Foliation(t, 2);
> F2;
[ 1  2  3  4  5  6]
[ 7  8  9 10 11 12]
[13 14 15 16 17 18]
[19 20 21 22 23 24]
> R := Nullspace(F2);
> R;
Vector space of degree 4, dimension 2 over Rational Field
Echelonized basis:
( 1  0 -3  2)
( 0  1 -2  1)
\end{code}

We claim this is the 2-radical of $t$. Our claim is verified if \texttt{R*t} is
a 0-dimensional subspace of the tensor space $T$ with frame
$\mathbb{Q}^3\rightarrowtail\mathbb{Q}^2$. In other words, \texttt{R*t} is the
trivial 2-tensor.
\begin{code}
> R*t;
Tensor space of dimension 0 over Rational Field with valence 2
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
> Dimension(R*t);
0
\end{code}
\end{example}

\index{AsTensorSpace}
\begin{intrinsics}
AsTensorSpace(t, a) : TenSpcElt, RngIntElt -> TenSpc, Mtrx
\end{intrinsics}

Returns the associated tensor space of $t$ at $a>0$ along with a matrix given by
the foliation of $t$ at $a$.
The returned tensor space is framed by
$U_{\vav}\times \cdots \times U_{a+1}\times U_{a-1}\times \cdots \times U_1\rightarrowtail U_0$
and is generated by the tensors $t_u$ for each $u$ in the basis of $U_a$.
For $a=0$, use \texttt{AsCotensorSpace}.

\index{AsCotensorSpace}
\begin{intrinsics}
AsCotensorSpace(t) : TenSpcElt -> TenSpc, Mtrx
\end{intrinsics}

Returns the associated cotensor space of $t$ along with a matrix given by the
foliation of $t$ at $0$. The returned cotensor space is framed by
$U_{\vav}\times \cdots \times U_1\rightarrowtail K$ and is generated by the
tensors $t_f$ for each $f$ in the basis of $U_0^*$. In the case that $t$ is a
bilinear map, this is equivalent to the cotensor space generated by the
\texttt{SystemOfForms}.

\begin{example}[TensorsToSpaces]

We begin by creating a 4-tensor and constructing the associated tensor space at
the third coordinate. \texttt{AsCotensorSpace} works similarly but when $a=0$.
\begin{code}
> t := Tensor(Rationals(), [5,4,3,2], [1..120]);
> t;
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 5 over Rational Field
U2 : Full Vector space of degree 4 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
> T := AsTensorSpace(t, 3);
> T;
Tensor space of dimension 2 over Rational Field with valence 3
U2 : Full Vector space of degree 4 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
\end{code}

The dimension of $T$ cannot be larger than 5 because that is the dimension of
$U_3$. However, $T$ is 2-dimensional. Slicing $t$ as a sequence of matrices will
illuminate why $T$ is 2-dimensional.
\begin{code}
> F := [SliceAsMatrices(t, [{k},{1..4},{1..3},{1,2}], 2, 1) : \
>     k in [1..5]];
\end{code}

Here, $F$ is a sequence of a system of forms for $t$, one for each basis vector
in $U_3$. If $T$ were 5-dimensional, the systems of forms in $F$ would be
linearly independent. However, it is not, and evidently, three of the five
systems of forms are linear combinations two systems of forms. We will determine
the linear combinations. The first two systems of forms are independent.
\begin{code}
> F[1];
[
    [ 1  3  5]
    [ 7  9 11]
    [13 15 17]
    [19 21 23],

    [ 2  4  6]
    [ 8 10 12]
    [14 16 18]
    [20 22 24]
]
> F[2];
[
    [25 27 29]
    [31 33 35]
    [37 39 41]
    [43 45 47],

    [26 28 30]
    [32 34 36]
    [38 40 42]
    [44 46 48]
]
\end{code}

We see that \texttt{F[3]} is \texttt{2*F[2]-F[1]}, and we fill in the rest of the linear combinations.
\begin{code}
> F[3];
[
    [49 51 53]
    [55 57 59]
    [61 63 65]
    [67 69 71],

    [50 52 54]
    [56 58 60]
    [62 64 66]
    [68 70 72]
]
> Tensor(F[3], 2, 1) eq 2*Tensor(F[2], 2, 1) - Tensor(F[1], 2, 1);
true
> Tensor(F[4], 2, 1) eq 3*Tensor(F[2], 2, 1) - 2*Tensor(F[1], 2, 1);
true
> Tensor(F[5], 2, 1) eq 4*Tensor(F[2], 2, 1) - 3*Tensor(F[1], 2, 1);
true
\end{code}

So, indeed, $T$ is the tensor space generated by the tensors in \texttt{F}.
Note that the dimension of the 3-radical of $t$ is 3.
\begin{code}
> SystemOfForms(T.1) eq F[1];
true
> SystemOfForms(T.2) eq F[2];
true
> Radical(t, 3);
Vector space of degree 5, dimension 3 over Rational Field
Echelonized basis:
( 1  0  0 -4  3)
( 0  1  0 -3  2)
( 0  0  1 -2  1)
Mapping from: Full Vector space of degree 5 over Rational Field to Full
Vector space of degree 5 over Rational Field given by a rule
\end{code}
\end{example}

\index{AsTensor}
\begin{intrinsics}
AsTensor(T) : TenSpc -> TenSpcElt
\end{intrinsics}

Returns a tensor corresponding to the given tensor space $T$. If the given
tensor space is contravariant, then the returned tensor has the frame
$U_{\vav}\times \cdots \times U_1\rightarrowtail T$, where $T$ is thought of as
a free $K$-module. If the given tensor space is covariant, then the returned
tensor has the frame $T\times U_{\vav}\times \cdots \times U_1\rightarrowtail
U_0$. Note that \texttt{AsTensor} is ``inverse'' to \texttt{AsCotensorSpace} and
\texttt{AsTensorSpace} when $a=\vav$.

\begin{example}[SpacesToTensors]

We will construct the same tensor as the previous example and turn it into a tensor space at the third coordinate.
\begin{code}
> t := Tensor(Rationals(), [5,4,3,2], [1..120]);
> t;
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 5 over Rational Field
U2 : Full Vector space of degree 4 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
> T := AsTensorSpace(t, 3);
> T;
Tensor space of dimension 2 over Rational Field with valence 3
U2 : Full Vector space of degree 4 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
\end{code}

Now we are going to ``recover'' $t$ by creating a tensor from $T$.
However, it is not equal to $t$ because the 3-radical of the new tensor is trivial.
\begin{code}
> s := AsTensor(T);
> s;
Tensor of valence 4, U3 x U2 x U1 >-> U0
U3 : Full Vector space of degree 2 over Rational Field
U2 : Full Vector space of degree 4 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
> Radical(s, 3);
Vector space of degree 2, dimension 0 over Rational Field
\end{code}

We can even see the same sequences of matrices in $s$.
\begin{code}
> AsMatrices(s, 2, 1);
[
    [ 1  3  5]
    [ 7  9 11]
    [13 15 17]
    [19 21 23],

    [ 2  4  6]
    [ 8 10 12]
    [14 16 18]
    [20 22 24],

    [25 27 29]
    [31 33 35]
    [37 39 41]
    [43 45 47],

    [26 28 30]
    [32 34 36]
    [38 40 42]
    [44 46 48]
]
\end{code}
\end{example}

\section{Invariants of tensors}

In this subsection, we detail functions to construct invariants of tensors. To
access the projections or the objects acting on a specific factor $U_i$, the
following function(s) should be used.

\index{Induce}
\begin{intrinsics}
Induce(X, a) : AlgMat, RngIntElt -> Map, AlgMat
Induce(X, a) : AlgMatLie, RngIntElt -> Map, AlgMatLie
Induce(X, a) : GrpMat, RngIntElt -> Map, GrpMat
\end{intrinsics}

Returns the projection from the given object to the induced sub-object on the
$a$th coordinate and the induced sub-object of the associated tensor.

\begin{example}[Inducing]

To demonstrate how to \texttt{Induce}, we construct the 2-dimensional symplectic
form on $K =$ GF$(3)$.
\begin{code}
> t := Tensor(GF(3), [2, 2, 1], [0, 1, 2, 0]);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 2 over GF(3)
U1 : Full Vector space of degree 2 over GF(3)
U0 : Full Vector space of degree 1 over GF(3)
> IsAlternating(t);
true
\end{code}

This tensor has a nontrivial derivation algebra, isomorphic to $K^2\rtimes
\mathfrak{sl}_2(3)$. However, $\Der(t)$ is represented in
$\End(U_2)\times\End(U_1)\times \End(U_0)$. We will induce the action on the
$1$st coordinate.
\begin{code}
> D := DerivationAlgebra(t);
> D.1;
[2 2 0 0 0]
[1 0 0 0 0]
[0 0 0 2 0]
[0 0 1 1 0]
[0 0 0 0 0]
> D.2;
[2 0 0 0 0]
[0 1 0 0 0]
[0 0 0 0 0]
[0 0 0 2 0]
[0 0 0 0 1]
> pi, D1 := Induce(D, 1);
> D1;
Matrix Lie Algebra of degree 2 over Finite field of size 3
> pi;
Mapping from: AlgMatLie: D to AlgMatLie: D1 given by a rule [no inverse]
\end{code}

Now we can see that have the action of $D$ on $U_1$.
\begin{code}
> D1.1;
[0 2]
[1 1]
> D1.2;
[0 0]
[0 2]
\end{code}
\end{example}

We include a partner intrinsic to \texttt{Include} and that is the following procedure.

\index{DerivedFrom}
\begin{intrinsics}
DerivedFrom(~X, t, C, RC) : Any, TenSpcElt, {RngIntElt}, {RngIntElt}
    Fused : BoolElt : true
\end{intrinsics}

Includes the following tensor data in the matrix object $X$: the tensor $t$, the
relevant coordinates which are in the set $C\subseteq \{0,\dots,\vav\}$, and the
coordinates $RC\subseteq C$ for which the object is represented on. The reason
for the subset $C$ is so that we know the coordinates which can be induced on,
see \texttt{Induce}. Currently, this only works for objects of type
\texttt{AlgMat}, \texttt{AlgMatLie}, \texttt{GrpMat}, and \texttt{ModMatFld}. It
is assumed that $X$ is block diagonal, whose blocks starting from the top left
go in decreasing order in the coordinates---in the same way the coordinates of
the frame decrease from left to right for a tensor.

% Maybe include an example at some point.

\subsection{Standard invariants}

We integrate the invariant theory associated to bilinear and multilinear maps
into the realm of tensors.

\index{Radical}
\begin{intrinsics}
Radical(t, a) : TenSpcElt, RngIntElt -> ModTupRng
\end{intrinsics}

Returns the $a$-radical of $t$ as a subspace of $U_a$.
This is the subspace
\[ U_{\comp{a}}^\perp = \left\{ u_a \in U_a : \forall \left| u_{\comp{a}}\right\rangle,\; \left\langle t \middle| u\right\rangle =0\right\}. \]

\index{Radical}
\begin{intrinsics}
Radical(t) : TenSpcElt -> Tup
\end{intrinsics}

Returns the tuple of all the $a$-radicals for each $a\in \{1,\dots,\vav\}$.

\index{Coradical}
\begin{intrinsics}
Coradical(t) : TenSpcElt -> ModTupRng, Map
\end{intrinsics}

Returns the coradical of $t$ and a surjection from the codomain to the coradical.
This is the quotient $U_0 / \langle t | U_{\vav}, \dots, U_1\rangle$.

\begin{example}[Radicals]

We will construct the tensor for multiplication in
$\mathfrak{gl}_3(\mathbb{Q})$, or equivalently, the commutator tensor of
$\mathbb{M}_3(\mathbb{Q})$. Because $\mathfrak{gl}_3(\mathbb{Q})$ contains the
center of $\mathbb{M}_3(\mathbb{Q})$, there will be a 2- and 1-radical.
\begin{code}
> K := Rationals();
> A := MatrixAlgebra(K, 3);
> t, phi := CommutatorTensor(A);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 9 over Rational Field
U1 : Full Vector space of degree 9 over Rational Field
U0 : Full Vector space of degree 9 over Rational Field
>
> R2 := Radical(t, 2);
> R2.1 @@ phi;
[1 0 0]
[0 1 0]
[0 0 1]
> Radical(t);
<
    Vector space of degree 9, dimension 1 over Rational Field
    Echelonized basis:
    (1 0 0 0 1 0 0 0 1),

    Vector space of degree 9, dimension 1 over Rational Field
    Echelonized basis:
    (1 0 0 0 1 0 0 0 1)
>
\end{code}

Similarly, the image of $t$ will be 8-dimensional in $\mathbb{Q}^9$, so the coradical is $\mathbb{Q}$.
\begin{code}
> Image(t);
Vector space of degree 9, dimension 8 over Rational Field
Generators:
( 1  0  0  0  0  0  0  0 -1)
( 0  1  0  0  0  0  0  0  0)
( 0  0  1  0  0  0  0  0  0)
( 0  0  0  1  0  0  0  0  0)
( 0  0  0  0  1  0  0  0 -1)
( 0  0  0  0  0  1  0  0  0)
( 0  0  0  0  0  0  1  0  0)
( 0  0  0  0  0  0  0  1  0)
Echelonized basis:
( 1  0  0  0  0  0  0  0 -1)
( 0  1  0  0  0  0  0  0  0)
( 0  0  1  0  0  0  0  0  0)
( 0  0  0  1  0  0  0  0  0)
( 0  0  0  0  1  0  0  0 -1)
( 0  0  0  0  0  1  0  0  0)
( 0  0  0  0  0  0  1  0  0)
( 0  0  0  0  0  0  0  1  0)
> Coradical(t);
Full Vector space of degree 1 over Rational Field
Mapping from: Full Vector space of degree 9 over Rational Field to Full
Vector space of degree 1 over Rational Field
\end{code}
\end{example}

We include some well-known polynomial invariants for bilinear maps.
\index{Discriminant}
\begin{intrinsics}
Discriminant(t) : TenSpcElt -> RngMPolElt
\end{intrinsics}

Returns the discriminant of the bilinear map.

\begin{example}[DiscriminatingOctonions]

We will compute the discriminant of the tensor $\cdot :\mathbb{O}\times\mathbb{O}\rightarrowtail\mathbb{O}$.
The discriminant of this tensor is homogeneous of degree 8 with 330 terms.
\begin{code}
> A := OctonionAlgebra(GF(7), -1, -1, -1);
> t := Tensor(A);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 8 over GF(7)
U1 : Full Vector space of degree 8 over GF(7)
U0 : Full Vector space of degree 8 over GF(7)
> R<a,b,c,d,e,f,g,h> := PolynomialRing(GF(7), 8);
> disc := R!Discriminant(t);
> Degree(disc);
8
> IsHomogeneous(disc);
true
> #Terms(disc);
330
\end{code}

However, if we factor the discriminant, then we can see that $\texttt{disc} = \left(x_1^2 + \cdots x_8^2\right)^4$.
\begin{code}
> Factorization(disc);
[
    <a^2 + b^2 + c^2 + d^2 + e^2 + f^2 + g^2 + h^2, 4>
]
\end{code}
\end{example}

\index{Pfaffian}
\begin{intrinsics}
Pfaffian(t) : TenSpcElt -> RngMPolElt
\end{intrinsics}

Returns the Pfaffian of the antisymmetric bilinear map.

\begin{example}[Genus2Pfaff]

In this demonstration, we pull from \cite{BMW:genus2}. A $p$-group $G$ has genus
2, if the image of the exponent-$p$ central tensor of $G$ is 2-dimensional over
the centroid. One of the algorithms in \cite{BMW:genus2} to decide isomorphism
of such groups depends on the Pfaffian of the tensor.

First, we create two $3$-groups with genus 2.
The first group we create as a quotient of the Sylow 3-subgroup of $\GL(3,\GF(3^5))$.
\begin{code}
> P := ClassicalSylow(GL(3, 3^5), 3);
> P := PCPresentation(UnipotentMatrixGroup(P));
> Z := Center(P);
> N := sub< Z | [Random(Z) : i in [1..3]] >;
> G := P/N;
\end{code}

The second group we create will be a quotient of the Sylow 3-subgroup of the  of $\GL(3, \GF(9))\times\GL(3, \GF(27))$.
\begin{code}
> A := ClassicalSylow(GL(3, 9), 3);
> B := ClassicalSylow(GL(3, 27), 3);
> A := PCPresentation(UnipotentMatrixGroup(A));
> B := PCPresentation(UnipotentMatrixGroup(B));
> Q, inc := DirectProduct(A, B);
> ZA := Center(A);
> ZB := Center(B);
> gens := [(ZA.i@inc[1])*(ZB.i@inc[2])^-1 : i in [1..2]] \
>     cat [ZB.3@inc[2]];
> M := sub< Q | gens >;
> H := Q/M;
\end{code}

Now we will construct the exponent-$p$ central tensors of $G$ and $H$.
From the way we have created the groups, $G\cong H$ if, and only if, their tensors are pseudo-isometric.
\begin{code}
> t := pCentralTensor(G);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 10 over GF(3)
U1 : Full Vector space of degree 10 over GF(3)
U0 : Full Vector space of degree 2 over GF(3)
>
> s := pCentralTensor(H);
> s;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 10 over GF(3)
U1 : Full Vector space of degree 10 over GF(3)
U0 : Full Vector space of degree 2 over GF(3)
\end{code}

An implication of the main algorithm in \cite{BMW:genus2} is that if the
splitting behavior of the Pfaffians are different, then the groups are not
isomorphic. Therefore, because the Pfaffian of $G$ has a different splitting
behavior from the Pfaffian of $H$, we conclude that $G\not\cong H$.
\begin{code}
> R<x,y> := PolynomialRing(GF(3), 2);
> f := R!Pfaffian(t);
> g := R!Pfaffian(s);
> f;
x^5 + x^3*y^2 + 2*x^2*y^3 + 2*x*y^4 + y^5
> g;
x^5 + 2*x^4*y + x^3*y^2 + 2*x^2*y^3 + x*y^4 + y^5
> Factorization(f), Factorization(g);
[
    <x^5 + x^3*y^2 + 2*x^2*y^3 + 2*x*y^4 + y^5, 1>
]
[
    <x^2 + x*y + 2*y^2, 1>,
    <x^3 + x^2*y + x*y^2 + 2*y^3, 1>
]
\end{code}
\end{example}

\section{Exporting tensors}

In the previous sections, we used groups, rings, and algebras to define tensors,
but tensors can be used define algebraic structures as well. In this section, we
describe ways to build groups and algebras from tensors. Currently, all
intrinsics in this section only support 3-tensors.

\index{HeisenbergAlgebra}
\begin{intrinsics}
HeisenbergAlgebra(t) : TenSpcElt -> AlgGen
\end{intrinsics}

Returns the Heisenberg algebra $A$ induced by the bilinear tensor $t: U\times
V\rightarrowtail W$. The algebra $A$ depends on the tensor category of $t$. If
the tensor category forces equality between $U$, $V$, and $W$, then $A\cong U$
as vector spaces and is a nonassociative algebra (i.e.\! not necessarily
associative). If the tensor category forces equality between $U$ and $V$, then
$A\cong U\oplus W$ as vector spaces and is a nilpotent algebra where $C(A)\geq
W$ and $A^2\leq W$. Otherwise, $A\cong U\oplus V\oplus W$ as vector spaces, and
using $*$ for $t$,
\[ (u,v,w)\cdot (u',v',w') = (0,0,u*v'). \]

\begin{example}[CraftingAlgebras]

We will demonstrate some of the nuances of \texttt{HeisenbergAlgebra} and how it
interacts with the tensor category of the given tensor. We will use the same
tensor throughout but changing the categories. The frame of the tensor is
$\mathbb{Q}^3\times\mathbb{Q}^3\rightarrowtail\mathbb{Q}^3$.
\begin{code}
> t := Tensor(Rationals(), [3, 3, 3], [i : i in [1..27]]);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 3 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 3 over Rational Field
> SystemOfForms(t);
[
    [ 1  4  7]
    [10 13 16]
    [19 22 25],

    [ 2  5  8]
    [11 14 17]
    [20 23 26],

    [ 3  6  9]
    [12 15 18]
    [21 24 27]
]
> Radical(t);
<
    Vector space of degree 3, dimension 1 over Rational Field
    Echelonized basis:
    ( 1 -2  1),

    Vector space of degree 3, dimension 1 over Rational Field
    Echelonized basis:
    ( 1 -2  1)
>
> Image(t);
Vector space of degree 3, dimension 2 over Rational Field
Generators:
( 1  0 -1)
( 0  1  2)
Echelonized basis:
( 1  0 -1)
( 0  1  2)
\end{code}

The default tensor category of $t$ above is the homotopism category where none
of the modules are fused together. Therefore, if we construct the Heisenberg
algebra of $t$, the resulting algebra will be 9-dimensional. Looking at the
system of forms, the first 6 matrices will be zero, and the last 3 matrices will
contain the above system of forms as a $3\times 3$ block, starting at the
$(1,4)$ entry.
\begin{code}
> A := HeisenbergAlgebra(t);
> A;
Algebra of dimension 9 with base ring Rational Field
> Center(A);
Algebra of dimension 5 with base ring Rational Field
> SystemOfForms(Tensor(A))[7..9];
[
    [ 0  0  0  1  4  7  0  0  0]
    [ 0  0  0 10 13 16  0  0  0]
    [ 0  0  0 19 22 25  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0],

    [ 0  0  0  2  5  8  0  0  0]
    [ 0  0  0 11 14 17  0  0  0]
    [ 0  0  0 20 23 26  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0],

    [ 0  0  0  3  6  9  0  0  0]
    [ 0  0  0 12 15 18  0  0  0]
    [ 0  0  0 21 24 27  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0  0]
]
\end{code}

Now we will fuse $U$ and $V$ together with a new category. The resulting algebra
will then be 6-dimensional. We will also look at the system of forms of $A$ to
see the structure constants of $A$.
\begin{code}
> NilCat := TensorCategory([1, 1, 1], {{2,1},{0}});
> NilCat;
Tensor category of valence 3 (->,->,->) ({ 0 },{ 1, 2 })
> ChangeTensorCategory(~t, NilCat);
> A := HeisenbergAlgebra(t);
> A;
Algebra of dimension 6 with base ring Rational Field
> Center(A);
Algebra of dimension 4 with base ring Rational Field
> A^2;
Algebra of dimension 2 with base ring Rational Field
> SystemOfForms(Tensor(A))[4..6];
[
    [ 1  4  7  0  0  0]
    [10 13 16  0  0  0]
    [19 22 25  0  0  0]
    [ 0  0  0  0  0  0]
    [ 0  0  0  0  0  0]
    [ 0  0  0  0  0  0],

    [ 2  5  8  0  0  0]
    [11 14 17  0  0  0]
    [20 23 26  0  0  0]
    [ 0  0  0  0  0  0]
    [ 0  0  0  0  0  0]
    [ 0  0  0  0  0  0],

    [ 3  6  9  0  0  0]
    [12 15 18  0  0  0]
    [21 24 27  0  0  0]
    [ 0  0  0  0  0  0]
    [ 0  0  0  0  0  0]
    [ 0  0  0  0  0  0]
]
\end{code}

Finally, we will put a tensor category on $t$ where $U$, $V$ and $W$ are all fused together.
THe Heisenberg algebra from this tensor is 3-dimensional, and the product is exactly the tensor $t$.
\begin{code}
> AlgCat := TensorCategory([1, 1, 1], {{2,1,0}});
> AlgCat;
Tensor category of valence 3 (->,->,->) ({ 0, 1, 2 })
> ChangeTensorCategory(~t, AlgCat);
> A := HeisenbergAlgebra(t);
> A;
Algebra of dimension 3 with base ring Rational Field
> SystemOfForms(Tensor(A));
[
    [ 1  4  7]
    [10 13 16]
    [19 22 25],

    [ 2  5  8]
    [11 14 17]
    [20 23 26],

    [ 3  6  9]
    [12 15 18]
    [21 24 27]
]
\end{code}
\end{example}

\index{HeisenbergLieAlgebra}
\begin{intrinsics}
HeisenbergLieAlgebra(t) : TenSpcElt -> AlgLie
\end{intrinsics}

Returns the Heisenberg Lie algebra $L$ with Lie bracket given by the 3-tensor
$t:U\times V\rightarrowtail W$. If the tensor category of $t$ forces equality
only between $U$ and $V$ and $t$ is alternating, then the Heisenberg Lie algebra
will have structure constants equal to $t$. In this case, $L\cong U\oplus W$ as
vector spaces. Otherwise, $L\cong U\oplus V\oplus W$ as vector spaces, and,
using $*$ in place for $t$, the product in $L$ is given by
\[ (u,v,w) * (u',v',w') = (0,0,uv'-u'v). \]

\begin{example}[CraftingLieAlgberas]

We will construct an alternating tensor with frame $\mathbb{Q}^3\times\mathbb{Q}^3\rightarrowtail \mathbb{Q}^2$.
\begin{code}
> t := Tensor(Rationals(), [3, 3, 2], &cat[[1,-1,0] : i in [1..6]]);
> t := AlternatingTensor(t);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 3 over Rational Field
U1 : Full Vector space of degree 3 over Rational Field
U0 : Full Vector space of degree 2 over Rational Field
> SystemOfForms(t);
[
    [ 0 -1 -2]
    [ 1  0 -1]
    [ 2  1  0],

    [ 0  2  1]
    [-2  0 -1]
    [-1  1  0]
]
\end{code}

First, we will just construct the Heisenberg Lie algebra from $t$ as is. Because
the tensor category of $t$ does not fuse together $U$, $V$, and $W$, the Lie
algebra will be 8-dimensional.
\begin{code}
> L := HeisenbergLieAlgebra(t);
> L;
Lie Algebra of dimension 8 with base ring Rational Field
> SystemOfForms(Tensor(L))[7..8];
[
    [ 0  0  0  0 -1 -2  0  0]
    [ 0  0  0  1  0 -1  0  0]
    [ 0  0  0  2  1  0  0  0]
    [ 0 -1 -2  0  0  0  0  0]
    [ 1  0 -1  0  0  0  0  0]
    [ 2  1  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0],

    [ 0  0  0  0  2  1  0  0]
    [ 0  0  0 -2  0 -1  0  0]
    [ 0  0  0 -1  1  0  0  0]
    [ 0  2  1  0  0  0  0  0]
    [-2  0 -1  0  0  0  0  0]
    [-1  1  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0]
    [ 0  0  0  0  0  0  0  0]
]
\end{code}

Now if we fuse $U$ and $V$ together, the resulting Lie algebra will be
5-dimensional, and the Lie bracket will essentially be equal to $t$.
\begin{code}
> NilCat := TensorCategory([1, 1, 1], {{2,1},{0}});
> NilCat;
Tensor category of valence 3 (->,->,->) ({ 0 },{ 1, 2 })
> ChangeTensorCategory(~t, NilCat);
> L := HeisenbergLieAlgebra(t);
> L;
Lie Algebra of dimension 5 with base ring Rational Field
> SystemOfForms(Tensor(L))[4..5];
[
    [ 0 -1 -2  0  0]
    [ 1  0 -1  0  0]
    [ 2  1  0  0  0]
    [ 0  0  0  0  0]
    [ 0  0  0  0  0],

    [ 0  2  1  0  0]
    [-2  0 -1  0  0]
    [-1  1  0  0  0]
    [ 0  0  0  0  0]
    [ 0  0  0  0  0]
]
\end{code}
\end{example}

\index{HeisenbergGroup}\index{HeisenbergGroupPC}
\begin{intrinsics}
HeisenbergGroup(t) : TenSpcElt -> GrpMat
HeisenbergGroupPC(t) : TenSpcElt -> GrpPC
\end{intrinsics}

Returns the class 2, exponent $p$, Heisenberg $p$-group with commutator given by
the bilinear tensor $t: U \times V \rightarrowtail W$ over a finite field. If
$t$ is alternating and the tensor category of $t$ forces equality between $U$
and $V$, then the group returned is an extension of $V$ by $W$, so $|G| =
|V|\cdot |W|$. Otherwise, the group returned is an extension of $U\oplus V$ by
$W$, so $|G| = |U|\cdot |V| \cdot |W|$. If $t$ is not full, then $G$ is an
extension of $U\oplus V\oplus W/\im(t)$ (or $V\oplus W/\im(t)$) by $\im(t)$.

\begin{example}[CraftingPGroups]

Here we demonstrate how to export a group from a tensor. We will start with a
tensor that is alternating and compare the different outputs based on the
category of the tensor.
\begin{code}
> t := KTensorSpace(GF(5), [5, 5, 4])!0;
> for i in [1..4] do
for>   Assign(~t, [i, i+1, i], 1);   // 1s above diag
for>   Assign(~t, [i+1, i, i], -1);  // 4s below diag
for> end for;
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 5 over GF(5)
U1 : Full Vector space of degree 5 over GF(5)
U0 : Full Vector space of degree 4 over GF(5)
> IsAlternating(t);
true
\end{code}

In this example, we will focus on the more straight-forward case, when $t$ is full.
The next example demonstrates a tensor that is not full.
\begin{code}
> SystemOfForms(t);
[
    [0 1 0 0 0]
    [4 0 0 0 0]
    [0 0 0 0 0]
    [0 0 0 0 0]
    [0 0 0 0 0],

    [0 0 0 0 0]
    [0 0 1 0 0]
    [0 4 0 0 0]
    [0 0 0 0 0]
    [0 0 0 0 0],

    [0 0 0 0 0]
    [0 0 0 0 0]
    [0 0 0 1 0]
    [0 0 4 0 0]
    [0 0 0 0 0],

    [0 0 0 0 0]
    [0 0 0 0 0]
    [0 0 0 0 0]
    [0 0 0 0 1]
    [0 0 0 4 0]
]
> IsFullyNondegenerate(t);
true
\end{code}

Even though $t$ is alternating, the category does not fuse together $U$ and $V$.
Therefore, the Heisenberg group $H$ is an extension of $U\oplus V$ by $W$, and
hence, $|H|=5^{5+5+4}$.
\begin{code}
> H := HeisenbergGroup(t);
> LMGOrder(LMGCenter(H)) eq 5^4;
true
> s := pCentralTensor(H, 5, 1, 1);
> s;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 10 over GF(5)
U1 : Full Vector space of degree 10 over GF(5)
U0 : Full Vector space of degree 4 over GF(5)
\end{code}

Now we will fuse the 2 and 1 coordinate, so that $|H|=5^{5+4}$.
\begin{code}
> PgrpCat := TensorCategory([1, 1, 1], {{2,1},{0}});
> PgrpCat;
Tensor category of valence 3 (->,->,->) ({ 0 },{ 1, 2 })
> ChangeTensorCategory(~t, PgrpCat);
> H := HeisenbergGroup(t);
> LMGOrder(LMGCenter(H)) eq 5^4;
true
> s := pCentralTensor(H, 5, 1, 1);
> s;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 5 over GF(5)
U1 : Full Vector space of degree 5 over GF(5)
U0 : Full Vector space of degree 4 over GF(5)
\end{code}

Note that $t$ and $s$ are not the same tensor. However, they are
pseudo-isometric (isotopic in the category with $U_2=U_1$). This effect is
exaggerated with \texttt{HeisenbergGroupPC} because of how PC-generators are
organized.
\begin{code}
> SystemOfForms(s);
[
    [0 0 0 0 0]
    [0 0 0 0 1]
    [0 0 0 0 0]
    [0 0 0 0 0]
    [0 4 0 0 0],

    [0 0 0 0 0]
    [0 0 0 0 0]
    [0 0 0 0 4]
    [0 0 0 0 0]
    [0 0 1 0 0],

    [0 0 0 0 0]
    [0 0 0 0 0]
    [0 0 0 1 0]
    [0 0 4 0 0]
    [0 0 0 0 0],

    [0 0 0 4 0]
    [0 0 0 0 0]
    [0 0 0 0 0]
    [1 0 0 0 0]
    [0 0 0 0 0]
]
\end{code}
\end{example}

\begin{example}[PGroupsHalfFull]

We will start with an alternating tensor that is not full with the frame
$\mathbb{F}_3^4\times\mathbb{F}_3^4\rightarrowtail \mathbb{F}_3^3$.
\begin{code}
> t := Tensor(GF(3), [4, 4, 3], &cat[[1,0,0,1] : i in [1..12]]);
> t := AlternatingTensor(t);
> t;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 4 over GF(3)
U1 : Full Vector space of degree 4 over GF(3)
U0 : Full Vector space of degree 3 over GF(3)
> Radical(t);
<
    Vector space of degree 4, dimension 1 over GF(3)
    Echelonized basis:
    (1 2 1 2),

    Vector space of degree 4, dimension 1 over GF(3)
    Echelonized basis:
    (1 2 1 2)
>
> Image(t);
Vector space of degree 3, dimension 2 over GF(3)
Generators:
(1 0 2)
(0 1 0)
Echelonized basis:
(1 0 2)
(0 1 0)
> SystemOfForms(t);
[
    [0 0 2 2]
    [0 0 2 2]
    [1 1 0 0]
    [1 1 0 0],

    [0 1 1 0]
    [2 0 0 2]
    [2 0 0 2]
    [0 1 1 0],

    [0 0 1 1]
    [0 0 1 1]
    [2 2 0 0]
    [2 2 0 0]
]
\end{code}

Because the tensor category of $t$ does not force equality between $U$ and $V$,
the group $H$ created from $t$ will have order $3^{11}$ and be 8-generated.
Furthermore, the tensor has a 2-dimensional image, so the tensor from the
exponent-$p$ central series of $H$ will have frame $\mathbb{F}_3^{4+4+1}\times
\mathbb{F}_3^{4+4+1}\rightarrowtail\mathbb{F}_3^2$. We display the system of
forms of the exponent-$p$ central tensor of $H$ to show how different it looks
compared to the original.
\begin{code}
> G := HeisenbergGroup(t);
> LMGOrder(G) eq 3^11;
true
> s := pCentralTensor(G);
> s;
Tensor of valence 3, U2 x U1 >-> U0
U2 : Full Vector space of degree 9 over GF(3)
U1 : Full Vector space of degree 9 over GF(3)
U0 : Full Vector space of degree 2 over GF(3)
> SystemOfForms(s);
[
    [0 0 0 0 0 0 1 0 0]
    [0 0 0 0 0 0 0 2 2]
    [0 0 0 0 0 0 1 0 0]
    [0 0 0 0 0 0 2 1 1]
    [0 0 0 0 0 0 0 0 0]
    [0 0 0 0 0 0 0 0 0]
    [2 0 2 1 0 0 0 0 0]
    [0 1 0 2 0 0 0 0 0]
    [0 1 0 2 0 0 0 0 0],

    [0 0 0 0 0 0 0 0 1]
    [0 0 0 0 0 0 0 0 1]
    [0 0 0 0 0 0 2 2 0]
    [0 0 0 0 0 0 2 2 0]
    [0 0 0 0 0 0 0 0 0]
    [0 0 0 0 0 0 0 0 0]
    [0 0 1 1 0 0 0 0 0]
    [0 0 1 1 0 0 0 0 0]
    [2 2 0 0 0 0 0 0 0]
]
\end{code}
\end{example}