Thanks for the good & detailed questions. These touch on things we want everyone to understand when working in a more hands-on way with the QN conserving ITensor system. We're trying to find the best way to document and introduce them, and one way is just by building a knowledge base on this forum. As you probably know, the draft of the ITensor Book on the website also introduces some QN ITensor concepts: http://itensor.org/docs.cgi?vers=julia&page=book . And we will soon publish an ITensor paper that provides many details.
To answer your questions:
(1) yes we require the quantum numbers to be integers for reasons of efficiency and correctness. E.g. we wouldn't want to store them as floating point because that would introduce all sorts of complications. But your question (which others have asked before) does remind me that Julia has facilities for exact computations with rational numbers, which we might consider expanding QN's to use in the future. For now please just make an integer correspondence between the quantum numbers as used on paper and in ITensor (e.g. we have the convention of working in units of 1/2 for spin quantum numbers, so 1/2 becomes 1, 1 becomes 2, etc.).
(2) The notation
A => B in Julia creates a
Pair(A,B) object, which is just a struct holding the values A and B. It can be an unfamiliar notation coming from other languages, and it was for me too. So it is nothing specific to ITensor but just a convenient way to group two values together that we've adopted for this Index constructor. Each pair denotes a subspace of the Index, meaning that the entire index labels the basis of a vector space (of dimension 3 here) and each subspace occurs sequentially giving the space a direct-sum structure. The second number of each pair is the dimension of that subspace. So the 1's are saying that each subspace is of dimension 1.
(3) I'm not sure exactly in what sense you're asking about having an arbitrary number of QNs at once, but I can comment on a few relevant things here. One is that an Index can have as many QN subspaces as you want. So in code like:
i = Index(QN(0)=>1, QN(1)=>2, Q(2)=4, ... )
the list of QN=>Int pairs can go on as long as you want, up to the memory limits of the machine. On the other hand, within a QN object itself, you can only have up to 4 different types of quantum numbers stored. So like
QN(("A",1),("B",0),("C",-1),("D",3)) would be reaching the limit of 4 different QN values. This is for reasons of efficiency as it greatly reduces allocations while on the other hand we've so far heard of few cases where more than 4 QN names were needed at once. But if you definitely do need more, we can work with you to lift this restriction.
I don't know much about the Schwinger model or its symmetries. Could you provide more information about how you're thinking of implementing it and the Hamiltonian used? Our ITensor QN system is currently only designed to handle the case of global, Abelian symmetries of a Hamiltonian. So it does not handle non-Abelian symmetries such as global SU(2) for example. But local symmetries such as in lattice gauge theories are an interesting case I think we've just not thought about. So it's not obvious to me whether our existing system can handle that case, or whether it could with some small changes, or even what is the right approach to such symmetries in tensor networks more broadly. This review could provide some information about the status of this topic: https://arxiv.org/abs/1810.12838
(4) Regarding the construction of QN-conserving MPOs, some of the precise details can get rather involved and at the same time many are just conventions and could vary from one implementation to another. The conventions we use in ITensor code are that for a QN-conserving MPO with total flux zero (such as the Hamiltonian, which by definition has to be total flux zero since it preserves the quantum numbers), all of the MPO tensors are themselves individually flux zero. This isn't formally necessary, but is a very natural and convenient convention. So when constructing each tensor, flux non-zero operators such as S+ or S- are "balanced" by bond or link indices of the MPO having subspaces which cancel out the flux of the operator to keep things net flux zero.
I gave a pretty detailed answer about some of these conventions in this forum question:
Please take a look at that, even though the question is about a C++ code file that makes the Heisenberg MPO. Both my answer there as well as the C++ example itself could be helpful. If you'd like to discuss how some of that C++ code would be adapted to the Julia version we could discuss that in the future.
Ok hope all of that helps you to make some more progress - I understand it's a lot to digest.