-
-
Notifications
You must be signed in to change notification settings - Fork 58
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
mnt: enabled tiling an eigenstate #355
Conversation
How does one use I was thinking something like: es = H.eigenstate(k)
es_ext = np.concatenate([np.exp(2j * np.pi * k.dot(isc)) * es.state for _, isc in H.geom.sc], axis=1) where one could then compute matrix elements (in this case band energies) in "real space" for the operator/matrix like ev1 = np.diag(es.state.conj().dot(H.tocsr().dot(es_ext.T)))
ev2 = es.inner(matrix=H.Hk(k))
assert np.allclose(ev1, ev2) |
es = H.eigenstate(...)
es_nsc = es
for axis, nsc in enumerate(H.geometry.sc.nsc):
es_nsc = es_nsc.tile(nsc, axis)
# possibly one should re-arrange data in some other way to make them *diagonal*
es.inner(es_nsc.state.T, H.Hk(...) and then you have the full eigenstate vectors in a geometry as large as the supercell, and the phase-factors will run from
Also, these big calculations will quickly take up lots of memory, for a system with 1000 orbitals and Wouldn't the above method suffice? |
Using the Bloch expansion we can get the same state for a new lattice by expanding it.
I have now amended the code to have an |
Nice! Could we have a property to an eigenstate object, like Reordering seems also necessary before one can do the dot product |
Hmm. I am a little bit hesitant since you can't really use it as you imagine it, i.e. the problem of re-ordering. And if one has a hard time figuring out what it actually means, then probably not so useful.. ?
Yeah, this makes it rather problematic. I have now added a es_exts = map(lambda x: es.translate(x[1]), H.geometry.sc)
np.concatenate((es.inner(ext) for ext in es_exts), axis=1) would this suffice? It seems this is a bit more natural? |
Actually, the offset should not be subtractive, I'll fix this, the above should be |
I would really still like to see this as something the user can invoke directly from the object. I think Said more generally, I think there are important use cases for Couldn't you make it a property? @property
def state_sc(self):
r""" Eigenstate(s) in the extended supercell """
return np.concatenate([np.exp(2j * np.pi * self.k.dot(isc)) * self.state for _, isc in self.geom.sc], axis=1) |
I guess you mean: @property
def state_sc(self):
return np.concatenate([self.translate(isc).state for _, isc in H.geometry.sc], axis=1) ? However, it still isn't natural to use, I think. It requires bookkeeing to figure out where what belongs, and since you can directly use the above in an Also, perhaps another way to use the state would be: @property
def state_sc(self):
return np.stack([self.translate(isc).state for _, isc in H.geometry.sc]) which would return But... Is this intuitive? What does |
Sorry, yes, I copied the wrong piece of code. The book-keeping is already taken care of by adhering to the supercell ordering, no? From my perspective, being able to evaluate Regarding the wording, my logic was that as |
This allows one to retrieve the state-vectors in the auxiliary supercell. This is important for further post-processing.
Ok, I have added what should work. Before merging, one final question: Currently the |
else: | ||
def translate(isc): | ||
return self.state * exp(2j*_pi * k @ isc) | ||
return np.concatenate([translate(isc) for _, isc in sc], axis=0) |
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.
Shouldn't it be axis=1
here? Such that self.state_sc.shape == (len(state), len(state) * state.parent.n_s)
?
The first index should naturally be the band index as for self.state
. The number of bands dnoes not change by extending to the supercell. The second index the orbital index which grows.
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.
No, that would leave the ordering wrong. You would have to increase the number of bands to nbands * n_s
to get this correct. Alternatively it should return (n_s, nbands, ncoeffs)
?
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.
or (nbands, n_s, ncoeffs)
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.
actually, I am here more in favour of (n_s, nbands, ncoeffs)
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 mean you want to do es.inner(es.state_sc)
, right, then you would have to increase the number of bands, and not number of coefficients, no?
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.
Yes ok, so what you want is to use a non-square matrix as the operator. However, I don't really follow what you then get, since [i, J] . [m, J].T -> [i, m]
and becomes a square matrix again, and you loose the supercell information? No?
Note: generally you shouldn't use tocsr
for this, rather use H.Hk(format='sc:csr')
, this will ensure to be working for NC and SOC as well.
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 seems to me that this is a greater problem than what this PR proposes to do, I think I should delete the state_sc
, and then another PR can take care of that, then the discussion can continue there, and more ftitingly so I think. :)
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 I want is basically the matrix elements between primitive cell band indices n, m
but where the operator A
is given in "real space". I do not want any phases applied to A
.
As far as I see this is also the correct approach to do the inner product between states at different k-points with non-ortho basis, i.e.,
<k1(n) | k2(m)> = k1.state[n, i].S[i, J].((k2.state_sc[m, J]).T)
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.
becomes a square matrix again, and you loose the supercell information? No?
That's the point, I do not want to retain information about the supercell. But I need to consider the supercell to perform the inner product in the most general case.
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.
lets continue discussion in #356
This is now merged (the last commit here isn't and will be followed by #356) |
For completeness sake, the code left out of this PR is: @property
def state_sc(self):
r""" Calculate the state coefficients in the auxiliary supercell
The format of this state vector is usable for `inner` as the ``ket`` argument.
The order of the state vectors is equivalent to the auxiliary supercell order.
See Also
--------
inner : these state vectors may be used in the inner product
tile : equivalent form along a single given axis
translate : a single translation of the state vectors
Examples
--------
The returned state vectors can be formulated like this:
>>> state = State(...)
>>> state_sc = np.stack([state.translate(isc)
... for _, isc in state.parent.sc])
>>> assert state_sc.shape == (len(state) * state.parent.n_s, state.shape[1])
"""
k = _a.asarrayd(self.info.get("k", [0]*3))
sc = self.parent.sc
if np.allclose(k, 0):
dtype = self.state.dtype
def translate(isc):
return self.state
else:
dtype = np.complex128
def translate(isc):
return self.state * exp(2j*_pi * k @ isc)
state = np.empty((sc.n_s, ) + self.shape, dtype=dtype)
for i, (_, isc) in enumerate(sc):
state[i] = translate(isc)
return state.reshape(-1, self.shape[1]) and a test def test_eigenstate_state_sc(self, setup):
# Test of eigenvalues
R, param = [0.1, 1.5], [0., 2.7]
H = setup.H.copy()
H.construct((R, param))
k = [0] * 3
# we must select a k that does not fold on
# itself which then creates degenerate states
for k1 in [0.5, 1/3]:
k[1] = k1
es = H.eigenstate(k)
state_sc = es.state_sc.reshape(-1, *es.shape)
for i, (_, isc) in enumerate(H.geometry.sc):
est = es.translate(isc)
assert np.allclose(state_sc[i], est.state) |
Using the Bloch expansion we can get the same state for
a new lattice by expanding it.
CHANGELOG
@tfrederiksen could you have a look at this