Tensor Node Notation
Nested boxtimes
Determining the order of contractions
Once we have any mode name appearing more than twice in a -product, we may not simply resolve brackets. For example for as follows,
.
The result may be rather unintended.
n = assign_mode_size({'alpha','beta','gamma'},[4,3,2]);
N1 = init_node({'alpha','beta'},n);
N2 = init_node({'beta','gamma'},n);
N3 = N1;
N4 = N2;
N1 = randomize_node(N1)
N2 = randomize_node(N2)
N3 = randomize_node(N3)
N4 = randomize_node(N4)
w = boxtimes(node_transpose(N2),node_transpose(N1),N3,N4);
w.data
net_view(node_transpose(N2),node_transpose(N1),N3,N4)
But instead of performing three single -products, we can transfer the bracket structure as before to one single boxtimes in form of nested cells. boxtimes will never call itself!
r1 = boxtimes(node_transpose(boxtimes(N1,N2)),boxtimes(N3,N4));
r1.data
r2 = boxtimes(net_transpose({N1,N2}),{N3,N4});
r2.data
net_view(net_transpose({N1,N2}),{N3,N4})
In the following call we can see that, interestingly, the optimal order of contractions (in terms of complexity) is not the one prescribed by the original brackets . So boxtimes indeed does not call itself, but uses the nested input solely to determine contraction rules.
a = n.alpha; b = n.beta; c = n.gamma;
cost_as_by_brackets = (a*b*c) + (a*b*c) + (a*c)
boxtimes(net_transpose({N1,N2}),{N3,N4},'mode','optimal_show')
The difference is tiny for now, but points at a central idea behind tensor networks.
Scalar products between different tensor formats
This mechanic allows us to be more flexible when for example taking the scalar product between two different tensor formats in which we used β as mode names in both cases.
load('TT-format')
boxtimes(G)
load('Tucker-format')
boxtimes(G,{C,U})
G{:}
net_view(G,{C,U}) % node numbers for G: 1-4, C: 5, U: 6-9
Here, the order of contractions is crucial. If we would first evaluate , then let alone the resulting tensor has size . However, the optimal order of multiplication just requires an order of complexity of (calculated classically) and might not be the order of contractions one expects at a first glance.
node_size(G)
node_size(C)
boxtimes(G,{C,U},'mode','optimal_show')
boxtimes(boxtimes(G),boxtimes(C,U),'mode','optimal_show')
Boxtimes memory
Once this order of computations is found, we may of course reuse this insight for the next analogous multiplication.
activate_boxtimes_mem()
clear_boxtimes_mem()
tic
boxtimes(G,{C,U},'mode','optimal');
toc
Once the boxtimes memory is activated, the required cpu time may reduce considerably:
tic
boxtimes(G,{C,U},'mode','optimal');
toc
The memory of boxtimes is smart, but does not overthink, since its main purpose is to avoid repeated calculations in loops:
So while it realizes that changing the order of mode names in the single node Cdoes not change it (since it has no dublicate mode names)...
CT = node_transpose(C)
tic
boxtimes(G,{CT,U},'mode','optimal');
toc
...it does not realize that transposing makes no difference either.
{C,U}
CUT = net_transpose({C,U})
tic
boxtimes(CUT,G,'mode','optimal');
toc
deactivate_boxtimes_mem()