-
Notifications
You must be signed in to change notification settings - Fork 22
Add CTMRG algorithm for layers of PEPO
tensors
#184
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
base: master
Are you sure you want to change the base?
Add CTMRG algorithm for layers of PEPO
tensors
#184
Conversation
Codecov ReportAttention: Patch coverage is
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
this includes a small bug fix
Co-authored-by: Lander Burgelman <[email protected]>
Co-authored-by: Lander Burgelman <[email protected]>
Co-authored-by: Lander Burgelman <[email protected]>
Co-authored-by: Lander Burgelman <[email protected]>
Co-authored-by: Lander Burgelman <[email protected]>
This includes a `_dagger` function on a `PEPOTensor`, which is probably far from ideal
This changes the name from PEPOLayers to PEPOTrace. Other names of variables and files are changed accordingly. This also includes a small bug fix on the scalartype in the testfile
I think everything should be fine now. And if @lkdvos is okay with the naming convention, I think we can merge this. |
Co-authored-by: Lander Burgelman <[email protected]>
nrm = PEPSKit._contract_site((1, 1), network, env) | ||
nrm_fused = PEPSKit._contract_site((1, 1), network_fused, env_fused) |
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.
If this is the actual norm, why can't you call norm
explicitly? Why do we have to manually divide by the norm, is this not all equivalent to expectation_value
?
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 don't think that there is currently a norm defined for a generic InfiniteSquareNetwork
apart from the trivial norm of the tensor itself and the specific case of a PEPS. This would also not be well defined in the generic case, which is why I would be okay with keeping the current version. The expectation_value
of an operator (either a PEPO or LocalOperator
) of a PEPOTraceSandwich
could be defined, but this is another PR (see #117).
Writing this down as an expectation_value
of an InfinitePartitionFunction
would require us to fuse the layers, which would defeat the purpose of the test.
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 think my main question is why does the private _contract_site
function show up here, if the user-facing function is contract_local_tensor
? Also, is that not just value
of the network? Basically, if using that private function is the only way to get a sensible result for these contractions, there should be an interface for accessing that
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.
To answer the other question: yes I would prefer to write this in terms of contract_local_tensor
, so that if we ever decide to change the internal private function, we don't also have to update the tests.
Co-authored-by: Lukas Devos <[email protected]>
check network_value instead of mpo update convention of tests
I've taken care of the new comments. As a summary, I've changed the docstring to the |
nrm = PEPSKit._contract_site((1, 1), network, env) | ||
nrm_fused = PEPSKit._contract_site((1, 1), network_fused, env_fused) |
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 think my main question is why does the private _contract_site
function show up here, if the user-facing function is contract_local_tensor
? Also, is that not just value
of the network? Basically, if using that private function is the only way to get a sensible result for these contractions, there should be an interface for accessing that
|
||
# Construct random PEPO tensors | ||
O = randn(T, pspace ⊗ pspace', vspace ⊗ vspace ⊗ vspace' ⊗ vspace') | ||
O = O + twist(flip(PEPSKit._dag(O), 3:6), [4 6]) |
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.
Am I correct that this is making the operator hermitian? If so, I'm not sure the tests for the adjoint are really doing all that much, since the pepo and its adjoint should be equal.
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 would indeed have liked to omit this line. The problem is that a random PEPO is quite hard to converge. For the (physical) operators that I am computing in the finite-temperature case, they all converge nicely. Since these are not in PEPSKit, I can unfortunately not use them for these tests. For the bosonic case, the convergence is also easier.
I think the tests are still checking the definition of the adjoint, even though it's a hermitian operator (especially in the fermionic case, there are some nontrivial twists). If the _dag
function were incorrect, this would just correspond to a different initialisation of the tensor O
, and the tests would fail)
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.
Is there a major reason you cannot add them to PEPSKit?
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.
There reason is that the code is present in my code for cluster expansions, which would be a relatively big addition to PEPSKit (and to be fully correct, requires some functionality for BigFloats that either requires my personal fork of TensorKit (since this is not yet merged, and maybe never will) or, in the long run, maybe MatrixAlgebraKit).
I am still in doubt whether the long-term plan is to add this to PEPSKit, or to keep it as a different package to put on the QuantumKitHub. It will anyway still require a large effort to make everything consistent with PEPSKi.
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.
Why not generate the PEPO there and hardcode the entries in the tests here then?
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.
That is also a possibility, but this is at least a 2x2x5x5x5x5 fermionic tensor (and I checked, with 184 nonzero elements) and would be quite a hassle to implement. Not to mention that it would be extremely ugly, since the elements are derived from exponentials and are not 'pretty' numbers.
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 don't really agree with these statements. The hassle you mention is solved with ctrl+c, ctrl+v, and while I agree that there are nicer things, simply copy pasting that into a separate file you include seems very reasonable.
It is just that the longer I look at this PR and the implementations, the more I seem to have the feeling that there are a lot of very brittle things and things that seem to be having wrong twists, and I am not convinced the tests are capturing any of that.
Some alternative suggestions that might be reasonable as well:
- you can map between an infinite PEPS and a PEPOTrace by "bending" the legs downwards and fusing them. This way, we would have more things to cross-check
- at least one physical result should be reproducible in the tests. An energy of a finite temperature state, or something similar, even for a free fermion model would already be a much more convincing statement than I have a random peps, and after hermitianizing and taking its dagger the network value doesn't change.
projector_alg = projector_algs[1] # only use :halfinfinite for this test due to convergence issues | ||
@testset "Test adjoint of an InfinitePEPO using $alg with $projector_alg" for alg in | ||
ctm_styles | ||
# Test the definition of the adjoint of an operator, i.e. dot(psi, O', phi) == dot(psi, O' * phi) == dot(O * psi, phi) for any two states psi and phi. |
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 think here again you have the mathematicians convention, would it not make more sense if you measure O
instead of O'
? Also, since you explicitly made O
hermitian I'm not sure if this is a very good test
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 don't really get what the problem is with the mathematical convention instead of the physical one. Isn't this just a convention change from
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.
As the name says, it is definitely a convention, but I would say that it makes a lot more sense to use the physics convention given that the application is physics, or is that crazy? I'm happy to hear if there is a good reason to prefer it the other way around, but it does seem nice to remain consistent.
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.
Well, I definitely prefer the mathematical convention, since that is how I learned about adjoints, and so the 'physics convention' seems a bit weird to me. I was also not aware that there were even different conventions, because it seems to me the convention only applies to how you would write the defining equation for the adjoint, and has no influence on the definition itself.
At first glance, I also didn't find anything in PEPSKit or TensorKit where we already chose a convention for this. Is this something of Julia itself?
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.
As discussed before, yes, this is the convention of Julia, see dot
.
network_O = InfiniteSquareNetwork(InfinitePEPS(ψ), OOdag, InfinitePEPS(ψ)) | ||
network_fused_MO = InfiniteSquareNetwork(InfinitePEPS(Mψ), InfinitePEPS(Oψ)) | ||
network_fused_OO = InfinitePEPS(Oψ) |
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.
If I'm understanding this correct, the networks here are: dot(psi, O * O', psi)
, dot(M * psi, O * psi)
, dot(O * psi, O * psi)
, but I'm somewhat surprised that the contractions below line up at all. Why is M * psi
the same as the contract_local_tensor
? Shouldn't M * psi
be replacing a full layer of PEPO tensors instead, or am I missing things?
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.
The M
here really is just a local tensor, not a PEPO. In this test, I contract the sandwich of the (infinite layers of) PEPO
layers and to make sure the code can deal with insertion of operators at specific places (x,y,z coordinates). It is also again a check on whether the adjoint of the PEPO
is correct.
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.
But why is InfinitePEPS(Mpsi)
the same as replacing O with M at one site? That would amount to placing an M at every site?
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.
It is not, InfinitePEPS(Mpsi)
would indeed correspond to replacing M at every site. That is why this is not used in the calculation of an environment. We only need this because the function network_value
requires a InfinitePEPS
or InfiniteSquareNetwork
and not just a single tensor.
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.
Oh I see now. I'm not sure if this is a great way to implement this though, since this very much depends on the exact way that the internal machinery of network_value
works. If there is no other way, perhaps it's also okay to at least at a very big warning/disclaimer in the comments, because this is very non-obvious that you are making use of that hack.
The main problem is that in large codebases with multiple people, you really have to be more consistent with what you are expecting from the functions, and network_value
is only guaranteed to return you the network value, so if someone else, including yourself in the future forgets that this implemetation detail is being depended on, we would create issues.
About your comment on the accessibility of For now, I can either keep it like this, or change these lines to the equivalent calculation nrm_fused = PEPSKit.contract_local_tensor((1, 1, 1), O2, network_fused, env_fused) |
isdual(virtualspace(network[h], NORTH)) || twist!(F_north, h) | ||
isdual(virtualspace(network[h], EAST)) || twist!(F_east, h) |
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'm very confused why this would be twisting the h
th leg of the fuser. Isn't there at least a +1
missing here, since the first leg would be the fused leg?
Given this, I'm also a bit scared about the tests and their coverage, because that seems to imply this isn't showing up at all?
This is definitely possible, since before I wrote the fusers for a single arrow configuration, but I also had stronger assertions on them, which have been loosened here.
|
||
# Construct random PEPO tensors | ||
O = randn(T, pspace ⊗ pspace', vspace ⊗ vspace ⊗ vspace' ⊗ vspace') | ||
O = O + twist(flip(PEPSKit._dag(O), 3:6), [4 6]) |
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 don't really agree with these statements. The hassle you mention is solved with ctrl+c, ctrl+v, and while I agree that there are nicer things, simply copy pasting that into a separate file you include seems very reasonable.
It is just that the longer I look at this PR and the implementations, the more I seem to have the feeling that there are a lot of very brittle things and things that seem to be having wrong twists, and I am not convinced the tests are capturing any of that.
Some alternative suggestions that might be reasonable as well:
- you can map between an infinite PEPS and a PEPOTrace by "bending" the legs downwards and fusing them. This way, we would have more things to cross-check
- at least one physical result should be reproducible in the tests. An energy of a finite temperature state, or something similar, even for a free fermion model would already be a much more convincing statement than I have a random peps, and after hermitianizing and taking its dagger the network value doesn't change.
nrm = PEPSKit._contract_site((1, 1), network, env) | ||
nrm_fused = PEPSKit._contract_site((1, 1), network_fused, env_fused) |
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.
To answer the other question: yes I would prefer to write this in terms of contract_local_tensor
, so that if we ever decide to change the internal private function, we don't also have to update the tests.
projector_alg = projector_algs[1] # only use :halfinfinite for this test due to convergence issues | ||
@testset "Test adjoint of an InfinitePEPO using $alg with $projector_alg" for alg in | ||
ctm_styles | ||
# Test the definition of the adjoint of an operator, i.e. dot(psi, O', phi) == dot(psi, O' * phi) == dot(O * psi, phi) for any two states psi and phi. |
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.
As discussed before, yes, this is the convention of Julia, see dot
.
network_O = InfiniteSquareNetwork(InfinitePEPS(ψ), OOdag, InfinitePEPS(ψ)) | ||
network_fused_MO = InfiniteSquareNetwork(InfinitePEPS(Mψ), InfinitePEPS(Oψ)) | ||
network_fused_OO = InfinitePEPS(Oψ) |
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.
Oh I see now. I'm not sure if this is a great way to implement this though, since this very much depends on the exact way that the internal machinery of network_value
works. If there is no other way, perhaps it's also okay to at least at a very big warning/disclaimer in the comments, because this is very non-obvious that you are making use of that hack.
The main problem is that in large codebases with multiple people, you really have to be more consistent with what you are expecting from the functions, and network_value
is only guaranteed to return you the network value, so if someone else, including yourself in the future forgets that this implemetation detail is being depended on, we would create issues.
ad4945f
to
37ace4c
Compare
@ogauthe do you maybe have some reference for some high temperature expansion results we could use to verify this implementation on? Alternatively, do you have any inspiration for any testcases that we could use? |
Coefficients for high temperatures series expansion for the J1-J2 model can be found in from https://doi.org/10.1103/PhysRevB.67.014416 I detailed a bit more in #215 |
To get a fermionic test which is reproducable with the current version of PEPSKit, we could look at the spinless fermion model. As mentioned above, I was first planning to add such a test using code on cluster expansions, but since this is a different package and would then have to be included in separate txt files, this is also not ideal. Therefore, I would opt for using Simple Update to produce some finite-temperature state, and then compare with figure 8a or 10a of this paper. Another thing that would make this chain of PRs in my opinion a bit more logical, is to first think about how we would implement PR #117. While this can certainly be done in parallel, that PR implements the expectation value of (excited) partition function tensors, which is in principle what an expectation value of a LocalOperator of a density operator (represented as a PEPO) would reduce to. In that way, we could test the physical (fermionic) examples already there without any reference to adjoints or PEPOTraces and the CTMRG algorithm on them. We would only have to define a function On my fork of PEPSKit, I have had a first go at implementing the things above. In this file, it shows how the expectation values reduce to the evaluation of a network of (excited) partition function tensors like in PR #117, while in this and this file the implementation can be found (via the functions |
Sounds like a solid plan. I read through that paper, it seems like they are just using the fact that it is free fermions to obtain exact expressions and compare to that. This sounds like an ideal case to put in our tests, since it isn't trivial but also we are certain of the desired output. For the expectation values, while I get your point, it is a bit strange to tackle that first since I think that actually the reduced density matrix approach might be a better fit after all. It should be somewhat straightforward to generate the contractions for building these reduced density matrices, after which applying operators really is just I do like the idea as well of having additional consistency checks between the different algorithms: For example turning the density matrix into a PEPS by fusing the legs, we can evaluate expectation values that correspond to the partition function at I'm not entirely convinced that the |
The point I wanted to make was that the CTMRG algorithm for partition functions is already implemented. You could indeed write is such that you need a CTMRGEnv of the partition function. Maybe a more natural option is to require a envspace, so we can call something like What do you mean exactly with 'the density-matrix approach'? Do you mean the implementation of this PR? Like you said, this reduces to The current interface of the |
See #230 : instead of inserting the operators immediately, we just literally compute the reduced density matrix and then apply the operator. This is generalizable to PEPOtrace, and retains the same interface.
I think my main point was that you start with
If the approach only works for a single layer, then really we should just use partition functions to begin with, since it is more efficient to trace the pepo once instead of keeping the legs separate. This is a viable strategy btw, if that makes things easier. Similarly, for CTMRG and a double layer PEPO, it is actually easier to map this to an InfinitePEPS double layer, since the contractions would be exactly the same but with slightly less overhead because of a reduced number of legs. (This is not true for the reduced density matrix of a double layer, since there we really want to trace the legs as soon as possible). Also note that in the context of purification with a hermitian and (possibly real, or time-inversion invariant, I forgot) operator, it is actually beneficial to write the reduced density matrix in terms of a double layer object since that would become positive definite by construction, which might give you more physical results:
This is not entirely true (anymore?) since it's actually more convenient to work with reduced density matrices (this was pointed out by @ogauthe in the linked PR), which works well for PEPOs but not for partition functions. |
This builds upon #134, where the CTMRG algorithm now includes
InfiniteSquareNetwork
s without a top and bottom layer, where the top physical leg of the topPEPOTensor
is contracted with the bottom physical leg of the bottomPEPOTensor
. This is based on the typePEPOTraceSandwich
(The equivalent ofPEPOSandwich
in the previous PR).This PR can be summarised as
PEPOTraceSandwich
InfiniteSquareNetwork
ofPEPOTraceSandwich
es based on anInfinitePEPO
dagger
for anInfinitePEPO
PEPOTraceSandwich
mpotensor
function forPEPSSandwich
to bothPEPOSandwich
es andPEPOTraceSandwich
es.PEPO
is compared to the case where this double layer is now squashed into a single layerPEPO
on aPEPS
is compared to the case where this application is squashed into a single layerPEPS
Any suggestions on the implementation and naming of things in this PR is very much appreciated. What anyway needs to be done, is
dagger
mpotensor
functions that now exist