Hi Boris,
Nice that you are looking into the ITensor internals. I think it should be very possible to make a wrapper around some of the ITensor functionality like you are thinking, but indeed these internal parts are not yet documented and not so obvious!
You correctly identified a part of the code which is making the new blocks for a new IQTensor. To explain some of what it's doing, Ustore is a QDense storage object, constructed from an IQIndexSet and a QN object. The QN object is a "zero" QN object = QN() so this says that Ustore will be set up to contain only those blocks which can be made from the IQIndexSet with a net QN flux of zero. So this just means blocks for which all of the quantum numbers of the various IQIndex sectors add up to zero (when weighted with the IQIndex arrows giving appropriate plus and minus signs).
Then, once the QDense storage object is allocated, the getBlock helper function (itensor/itdata/qutil.h) returns a "DataRange" object (itensor/tensor/types.h) which is conceptually just a pointer that knows the size of the memory it points to (in fact just a struct which is a pointer and a size_t, designed to aid with memory safety).
To explain getBlock, the arguments to it are the block indices of a particular block to be fetched, in this case B.i1 and n, which are passed as a std::array called uind. By block indices I mean like if uind = (m,n) then getBlock returns the DataRange pointer to the block corresponding to the m'th sector of the first IQIndex in Uis (=uI), and the n'th sector of the second IQIndex in Uis (=L). So these blocks are just the tensor blocks making up a block-sparse tensor.
If the returned DataRange object holds a nullptr (pU.data() == nullptr) then it means one has requested an invalid block (a block which is zero by the QN symmetry) so there is an assert checking for that erroneous case.
Finally, the DataRange object pU is used to construct a MatrixRef object. This is just a matrix "reference" or "view" (non-owning array with reference semantics) of the data pointed to by pU with appropriate row and column dimensions, namely the sizes of the IQIndex's uI and L making up Uis. Some manipulations are done on the resulting MatrixRef object, namely setting it to be the first L[n].m() columns of the matrix UU.
So the last two lines of that block of code (reduceCols and Uref &= UU) are the least applicable to your case, since they are some specific matrix related manipulations, and just a way of setting the values of the block appropriate for this case. More generally, one could loop over the elements of the data range pU and modify them like this:
for(size_t i = 0; i < pU.size(); ++i)
{
pU[i] = ... ;
}
But if you are curious to learn more about the operation Uref &= UU I could discuss that some more. The idea there is that the left-hand side is a MatrixRef, and the normal = assignment operation for MatrixRef's just defines a new MatrixRef, similar to assigning to a pointer which just makes it a different pointer. So to modify the elements actually referenced or pointed to by a MatrixRef, one uses the operator &= which can be read as "dereference and assign". The right-hand side is a regular matrix with value semantics, so in this case the elements pointed to by Uref get overwritten with the entries of UU.
Finally, you had asked for an example. I think the code you cited could already be seen as an example if you understand what each part is doing and if you replace the last two lines (reduceCols and Uref &= UU) by your own code that assigns the elements of the block (pointed to by pU) in whatever way is appropriate for your case.
To understand what operations are available with a DataRange (the type of the object pU) have a look at the definition of DataRange in itensor/tensor/types.h
Best regards,
Miles