-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Svd truncate with minblockdim #491
base: dev-master
Are you sure you want to change the base?
Conversation
-mindim = 1 as a standard, and mindim = 0 is treated like mindim = 1 to ensure one singular value at least -added input checks in (Ge)Svd_truncate -cleaned up code, added comments
…h block can be given -changed default arguments everywhere to mindim=1 -changed argument types from unsigned int to cytnx_uint
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev-master #491 +/- ##
==============================================
+ Coverage 16.60% 16.69% +0.08%
==============================================
Files 221 221
Lines 48490 53456 +4966
Branches 20272 20014 -258
==============================================
+ Hits 8053 8923 +870
- Misses 36143 40220 +4077
- Partials 4294 4313 +19 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the difference between keepdim
and mindim
? Can we leave only one of them? There should be only two use cases:
- truncated with the low error bound
- truncated with the minimal dimensions
Please correct me if I am wrong~
-improvements in the API documentation in linalg.hpp
I added a detailed explanation of the truncation order in the API documentation:
Concerning the input parameters: currently, input arguments are taken as const and, if they need to be changed in a program, will be copied. This ensures two things:
This creates some overhead, but I think it is negligible in the cases here. |
I think the meaning of mindim (specifically for with symmetrty) is different than the the keepdim. one is upper bound and one is lower bound (with the truncation rate). @yingjerkao could explain it more on this topic. For pybind issue, I am happy to chat on this, just shoot me a msg. |
The API document is clearer after the change!! When a container is passed as a const-value or a non-const-value, the container is copied. Modifying the value argument inside the function will not cause effects outside the function. This means just passing the argument as a non-const-value if we decide to pass the argument as a value instead of a reference. See this StackOverflow discussion and this demo. And the reason I suggest expanding |
Two brief comments:
Currently,
Currently, in those last two cases Cytnx will make a copy of the tensor, and then immediately destroy it. Secondly, in practice you might want to do something moderately complicated with the selection of singular values to keep, especially in the UniTensor case where there are many quantum number sectors. In my Toolkit, depending on the algorithm, there is an overall minimum number of states, minimum number of states per quantum number sector, a flag to indicate whether this minimum number of states per quantum number sector should include states with zero singular value or not (depending on the type of bond dimension expansion, both choices are possible), maximum number of states, a cutoff, a truncation error, a flag to indicate whether the truncation error is absolute or relative. All of these options are actually used in various different situations. I implement this in two stages; there is a class object that does the actual SVD and constructs a list of the singular values, and it appears as a (sorted) container of singular values. The caller can then select which singular values to keep via a helper function that iterates through the list. In simple cases this is just a subset of the original list (eg if you want to keep the N largest singular values), but in more complicated cases (eg you want to keep a minimum number per quantum sector) the states you want to keep are not simply the largest N so you need to assemble a list or vector of the states that you want to keep. |
@IvanaGyro You are right, the vector is copied already anyways when passed to the function, so we can make it non-const and change it internally as in your code example @ianmccul I agree, there are more ways to truncate the SVD and since this is the crucial step in many algorithms many options might be important (and I missed a relative truncation error as well). In addition to your suggestions, another topic is how to deal with degeneracies.
If one wants to have In general, I think we can go in two ways:
|
@manuschneider, depending on the context, I have an overall minimum number of states (eg in DMRG), or a minimum number of states per sector (which the bond expansion code uses, to make sure that all viable quantum number sectors are included. Usually this number is either 1 or 0, but you could set it to > 1 if you want). Other than that, I've never been concerned about the actual number of states in each sector -- I can't imagine the circumstances where I'd want to construct an array for the number per sector (except as some implementation detail). But maybe this has some uses in special cases? I'd forgotten about degeneracies - I've never worried about this in my own code since it usually means there is some non-abelian symmetry that you should be using (so just use it!), and for large bond dimension it doesn't have much effect. But yes it is better to keep degenerate blocks, and I might add that to my code sometime. Just need a good algorithm for finding degeneracies. It looks like itensor just checks to see if the next singular value is within 1E-3 (or probably eigenvalue [squared singular value] rather than singular value itself). That seems like a bit of a random choice. This is https://github.com/ITensor/ITensor/blob/ea88f512d258ef23b5554950639c5acedf4df1f0/itensor/decomp.cc#L437 I cannot see anything corresponding in the Julia version of itensor, it looks like they got rid of it? But something that does arise with non-abelian symmetries is how to count the weight of a state. Do you order an N-degenerate multiplet, each of weight w, by w itself or N*w? This makes a difference for which states you keep at the bottom of the spectrum, eg do you keep some degeneracy 1 state with weight epsilon, or do you instead keep a degeneracy 3 state with weight epsilon/2 ? Minimizing the truncation error corresponds to including the degeneracy in the weight, but that gives a different selection of states than if you use w itself (and therefore a different selection versus if you didn't use the symmetry). in practice, for finite DMRG, I include the degeneracy but in iDMRG I do not (since it makes it slightly unstable, to favor higher spin representations you can end up with the calculation going haywire). So this needs to be another option when doing the truncation! Adding some functions to separate the SVD itself from the selection of states to keep doesn't mean throwing away the existing |
The use case I had in mind was the following: I agree that it would be best to have an implementation of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't go deep into the algorithm. Except for the algorithm, LGTM.
@manuschneider The if-clause is in |
What will happen if I dismiss this review?
@manuschneider selecting states with largest singular values will automatically select the important sectors, it shouldn't normally be necessary to force it. And if something has gone wrong (eg some sector that you think should be important has zero singular value) then forcing it into the basis probably won't work anyway. My code for truncations is at https://github.com/mptoolkit/mptoolkit/blob/main/mps/truncation.h and - https://github.com/mptoolkit/mptoolkit/blob/main/mps/density.h I wouldn't necessarily copy this style in new code, it grew somewhat organically and isn't exactly what I'd do if I was writing it from scratch. But the basic approach, of an SVD that gives a container of eigenvalues (the I also have (at least) 3 variants of the SVD, depending on what you want to do with structurally zero singular values. i.e. given an @manuschneider , @IvanaGyro , I can't imagine any circumstances where the cost of copying the vector of block dimensions is significant; the amount of work is insignificant compared with doing the SVD and performing the truncation. Much more significant is avoiding making a extra copy in the case where you don't need the tensor anymore (and LAPACK dgesvd can safely overwrite it, or even better, re-use that memory directly into the U/V matrix). My QR decomposition code in the Toolkit does exactly this - the tensor is passed by value and the returned Q matrix uses the same memory, so if you do a QR decomposition of a temporary or an |
@manuschneider if you want to avoid the copy of the - _svd_truncate_Block_UT(outCyT, Tin, keepdim, min_blockdim, err, is_UvT, return_err, mindim);
+ _svd_truncate_Block_UT(outCyT, Tin, keepdim, std::move(min_blockdim), err, is_UvT, return_err, mindim); Footnotes |
Here is my suggestion to pragmatically deal with |
The problem about this solution is that the blocknumber of the singular value tensor is not know outside |
In
Svd_truncate
andGevd_truncate
, I added an optional argumentminblockdim
. With this, one can set a minimal bond dimension for each block.Additionally, some cleanup and problems were fixed in the original implementation with
mindim
. The standard was set tomindim=1
.