Tensor Node Notation
Orthogonality and Duplicate Mode Names
Duplicate mode names
For many applications, we require one addition to the basic rules of tensor nodes and the contraction of these. We allow an identical mode name to appear twice in a tensor node.
Whenever a dublicate mode name appears
- in an assigment, the first appearance and second appearance within both sides each correspond to each other
- in one single tensor node within a chain of tensor nodes, its first appearance corresponds to the last appearance of its left neighbour, and its second appearance to the first appearance of its right neighbour.
The function boxtimes literally renames dublicate mode names temporarily until all nodes have been contracted (boxtimes can deal with nodes that have triplicate mode names or more, but there is not much use for this).
n = assign_mode_size({'alpha','beta','gamma','delta','epsilon'},[7,6,4,3,2]);
N = init_node({'alpha','beta'},n);
N = randomize_node(N)
A = init_node({'beta','beta'},n);
A = randomize_node(A)
We might even assign different mode sizes to the first and second identical mode name. If we do so, we can no longer speak of a global assignment of a mode size to a mode name.
ntilde = assign_mode_size({'alpha','beta','beta','gamma'},[7,6,5,4])
Atilde = init_node({'beta','beta'},ntilde);
Atilde = randomize_node(Atilde)
Once we use dublicate mode names, the
-product is not commutative anymore, except if the nodes with duplicate mode names are symmetric regarding these.
AN = boxtimes(A,N)
unfold(AN,{'alpha','beta'})
NA = boxtimes(N,A)
unfold(NA,{'alpha','beta'})
The tranpose applied to a tensor node with dublicate mode names is the generalization of a transpose of a matrix. For example, for
H = init_node({'alpha','beta','alpha','beta'},4)
H = randomize_node(H); HT = node_transpose(H);
i = 1; j = 2; k = 3; ell = 4;
we have
node_part(HT,'alpha',i,'beta',j,'alpha',k,'beta',ell)
node_part(H,'alpha',k,'beta',ell,'alpha',i,'beta',j)
The implementation of node_transpose only has to invert the order of modes of the underlying data H.data (and then adapt H.pos and H.mode_names). For tensor nodes without duplicate mode names, applying transpose has no actual consequence.
We obtain the equality
NAT = boxtimes(N,node_transpose(A))
unfold(NAT,{'alpha','beta'})
Partial contraction
For the concept of orthogonality, we require to use partial contractions such as
. This denotes that any (even dupilcate) mode names other than γ are (for the duration of the multiplication) identified as different mode names. The results thereby may have duplicate mode names.
boxtimes(N,N,'_','beta')
net_view(N,N,'_','beta')
The general boxtimes product
The following demonstrates how a combination
of kept mode names γ and partial contractions β works:
N1 = init_node({'alpha','beta','gamma','delta'},n)
N2 = init_node({'alpha','beta','gamma','epsilon'},n)
boxtimes(N1,N2,'_','beta','^','gamma')
net_view(N1,N2,'_','beta','^','gamma')
Orthogonality
By definition,
is β-orthogonal, if
, where I is the identity matrix (i.e. the node with duplicate mode name β). Applying the tranpose to N is not necessary if
, but it makes things more familiar.
[Q,~] = qr(rand(n.alpha,n.beta),0);
N1 = fold(Q,{'alpha','beta'},n)
N1T_alpha_N1 = boxtimes(node_transpose(N1),N1,'_','alpha');
unfold(N1T_alpha_N1,'beta','beta')
We can verify a "chain rule" for orthogonality:
[Q2,~] = qr(rand(n.beta,n.gamma),0);
N2 = fold(Q2,{'beta','gamma'},n); % gamma-orthogonal
N12 = boxtimes(N1,N2)
net_view(N1,N2)
N12T_alpha_N12 = boxtimes(node_transpose(N12),N12,'_','alpha')
unfold(N12T_alpha_N12,'gamma','gamma')
We will from here on not anylonger apply the unnecessary transpose:
the_same = boxtimes(N2,boxtimes(N1,N1,'_','alpha'),N2,'_','beta')
unfold(the_same,'gamma','gamma')
net_view(N2,boxtimes(N1,N1,'_','alpha'),N2,'_','beta')
boxtimes and net_view can even do a bit more, but we will come to this later:
net_view(N2,{N1,N1,'_','alpha'},N2,'_','beta')
We can also verify that orthogonality is kept for nodes with distinct mode names:
[Q3,~] = qr(rand(n.delta,n.epsilon),0);
N3 = fold(Q3,{'delta','epsilon'},n); % epsilon-orthogonal
N13 = boxtimes(N1,N3)
net_view(N1,N3)
N13T_alpha_epsilon_N13 = boxtimes(node_transpose(N13),N13,'_',{'alpha','delta'})
unfold(N13T_alpha_epsilon_N13,{'beta','epsilon'},{'beta','epsilon'})
Orthogonal nodes also conserve the Frobenius norm of other nodes in direction of their orthogonality. We will often use this when working with tensor formats.
N1
norm(N1.data(:)) % beta orthogonal
N2
norm(N2.data(:)) % gamma orthogonal
N12
norm(N12.data(:)) % still gamma orthogonal
net_view(N1,N2)