-
Notifications
You must be signed in to change notification settings - Fork 27
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
Rotation of ReciprocalLatticeVector throws an error #211
Comments
Phase information kan be kept by changing https://github.com/pyxem/orix/blob/develop/orix/quaternion/quaternion.py#L238 to |
Should this even be possible?
|
I might be misunderstanding what rotating a hkl vector entails. What do you mean by preserving the hkl value? When rotating, should they not change? |
@viljarjf The HKL is dependent on the RLV and the lattice. If we have a set of RLV and we rotate them (and don't rotate the lattice in some way) calculating the hkl no longer works as expected and gives non physical values. |
This is exactly the same operation as your |
import diffpy
import numpy as np
from diffsims.crystallography import ReciprocalLatticeVector
from orix.crystal_map import Phase
from orix.quaternion import Rotation
# Shared parameters
latt = diffpy.structure.lattice.Lattice(2.464, 2.464, 6.711, 90, 90, 120)
atoms = [diffpy.structure.atom.Atom(atype='C', xyz=[0.0, 0.0, 0.25], lattice=latt),
diffpy.structure.atom.Atom(atype='C', xyz=[0.0, 0.0, 0.75], lattice=latt),
diffpy.structure.atom.Atom(atype='C', xyz=[1/3, 2/3, 0.25], lattice=latt),
diffpy.structure.atom.Atom(atype='C', xyz=[2/3, 1/3, 0.75], lattice=latt),]
structure_matrix = diffpy.structure.Structure(atoms=atoms, lattice=latt)
calibration = 0.0262
p = Phase("Graphite", point_group="6/mmm", structure=structure_matrix)
euler_angles_new = np.array([[0, 90, 90]])
rot = Rotation.from_euler(euler_angles_new, degrees=True)
rlv = ReciprocalLatticeVector.from_min_dspacing(phase=p, min_dspacing= 1)
rotated_rlv = rlv.rotate_from_matrix(rot.to_matrix()[0])
rlv.hkl ==rotated_rlv.hkl # False Maybe this will help show the problem related to rotating the RLV. |
@hakonanes have you ever used the https://www.diffpy.org/diffpy.structure/mod_lattice.html |
@hakonanes This is partially fixed in pyxem/orix#499. Proper HKL values requires some more thought and better handling. |
@viljarjf, I assume you mean that we can override
@CSSFrancis, good point! Now that I think about it, I'd say no. I see you've suggested a change. May I suggest that we instead overwrite |
Nope.
Hm... But, then what is the point of rotating the vectors in the first place? When I rotate vectors, I always want new vectors. |
@hakonanes So what I've come to is that it should be possible to rotate the RLV as long as we can track the rotation. This allows us to have consistent |
Can this be a feature of the private |
@hakonanes sure it can be... What is the argument for not having it part of the RLV. How does kikchuipy use the |
I'm fine with leaving my reported bug a bit longer.
I've got to think a bit on it. Quoting
We use composition instead of inheritance. Steps for creating geometrical EBSD simulations:
Relevant part of tutorial: https://kikuchipy.org/en/stable/tutorials/geometrical_ebsd_simulations.html#Create-simulations-on-detector. |
That is fine; that is kind of how we coded ourselves into this mess in pyxem :) At some point, we do have to figure out a way to fix it. From a purely pragmatic standpoint, I have to teach people how to do this next week :) and I don't think any of these changes break the API (only fix broken things) so it would be nice to push a fix before then. I can just move everything to the DiffractingVector class and make it private but pyxem/orix#499 would be a nice addition.
Hmmm. Okay, that works as well... |
But do you need to rotate reciprocal lattice vectors for the tutorial? This "bug" was reported based on our discussions in #205, not by a user in a real use-case. |
It would be nice to be able to rotate them. The only reason I didn't rotate them in the simulation at first was to reduce the changes to the RLV. Essentially what the rotation does is rotates the underlying lattice object (as well as the vectors). I guess the question is: is this only useful for electron diffraction or is this universally useful. In the case of Kikichi diffraction wouldn't this simplify your simulation calculations? I'm struggling with the concept of kinematic diffraction differences obviously :) |
@hakonanes I've looked at the kikuchipy simulation code and do you end up rotating the hkl vectors rather the xyz vectors? |
I did mean Actually, it seems from orix.vector import Miller
from orix.crystal_map import Phase
p = Phase(point_group="m-3m")
v = Miller([1, 0, 0], phase=p)
print(v)
print(-v)
>>> Miller (1,), point group m-3m, xyz
[[1 0 0]]
>>> Miller (1,), point group None, xyz
[[-1 0 0]] |
@hakonanes @viljarjf both of these are solved by the PR to orix. |
This is actually really promising, since what we really want is to rotate the vectors AND the basis. This is different from the normal handling of rotations, which only concerns the former. class ReciprocalLatticeVector(Vector3d):
...
def rotate_with_basis(self, rot: Rotation):
"""Rotate both vectors and the basis with a given `Rotation`.
This differs from simply multiplying with a `Rotation`,
as that would NOT update the basis.
Parameters
----------
rot : orix.quaternion.Rotation
A rotation to apply to vectors and the basis.
"""
# rotate basis
new_phase = self.phase.deepcopy()
br = new_phase.structure.lattice.baserot
# In case the base rotation is set already
new_br = br @ rot.to_matrix().squeeze()
new_phase.structure.lattice.setLatPar(baserot=new_br)
# rotate vectors
vecs = ~rot * self.to_miller()
self.data = vecs.data
self.phase = new_phase This avoids having to store the rotation and do a bunch of conversions, while keeping hkl values intact AND rotating the lattice as desired. from diffsims.crystallography import DiffractingVector, ReciprocalLatticeVector
from orix.crystal_map import Phase
from orix.quaternion import Rotation
import numpy as np
p = Phase(point_group="6/mmm")
r = Rotation.random()
rlv = ReciprocalLatticeVector(p, hkl=[1, 2, 3])
# Somewhat convoluted initialization process, but it is private after all...
miller = ~r * rlv.to_miller()
dv = DiffractingVector(p, miller.data, rotation=r)
old_data = rlv.data.copy()
rlv.rotate_with_basis(r)
assert np.allclose(dv.hkl, rlv.hkl)
assert np.allclose(dv.data, rlv.data)
assert not np.allclose(rlv.data, old_data)
print(rlv)
>>> ReciprocalLatticeVector (1,), (6/mmm)
[[1. 2. 3.]] |
@viljarjf this would only work for Rotations with size==(1,) but that isn't really a huge issue as we already are creating seperate RLV for storing the simulation results. |
True, that was intentional but not communicated |
@hakonanes Do you feel more comfortable with this? This doesn't change anything about the RLV; it only uses already existing attributes in the Lattice object. |
Yes, we rotate the (hkl) from the reciprocal lattice directly to the EBSD detector using a transformation matrix combining the three transformations detector -> sample, sample -> cartesian crystal, cartesian crystal -> reciprocal crystal.
Of course, thanks for the thorough explanation. And for spotting the bug with the discarded phase in the negated crystal vector. I've opened pyxem/orix#501 to track that issue.
The symmetry operations in the |
You're right... |
We could always go back to the idea of saving the Rotation seperately and then applying the inverse rotation when calculating the hkl. It maybe isn't as elegant of a solution but works. |
This could be enforced in the constructor of RLV, no? i.e. class ReciprocalLatticeVector(Miller):
def __init__(self, xyz=None, hkl=None, hkil=None, phase=None):
if phase is None:
raise ValueError("Must provide phase")
# Temporarily allow xyz
self._allowed_coordinate_formats = ["hkl", "hkil", "xyz"]
super().__init__(xyz=xyz, hkl=hkl, hkil=hkil, phase=phase)
self._allowed_coordinate_formats.pop() # xyz is now illegal
if self.coordinate_format == "xyz":
self.coordinate_format = "hkl"
self._raise_if_no_point_group()
self._theta = np.full(self.shape, np.nan)
self._structure_factor = np.full(self.shape, np.nan, dtype="complex128")
@Miller.coordinate_format.setter
def coordinate_format(self, value):
"""Set the vector coordinate format, either ``"hkl"``, or
``"hkil"``.
"""
if value not in self._allowed_coordinate_formats:
raise ValueError(f"Available coordinate formats are {self._allowed_coordinate_formats}")
self._coordinate_format = value With this (+ explicitly setting phase=phase everywhere, as it is no longer the first parameter but instead a keyword parameter), all tests pass, and ReciprocalLatticeVectors can be rotated. Additionally, a lot of the code duplication between the two classes can be avoided. The above example is a little hacky Are there any more reasons for wanting to keep the two classes seperate, @hakonanes? I tried looking through previous discussions and did not find much, but I am not familiar with kikuchipy and its use of RLVs. |
Describe the bug
Rotation of a
ReciprocalLatticeVector
,g
, with aRotation
from orix,R
, throws an error. The multiplication usesQuaternion.__mul__()
. There, theother
being multiplied, hereg
, fails the testisinstance(other, Miller)
and thus phase information is not passed on when creating the rotatedReciprocalLatticeVector
in its__init__()
.Code in orix: https://github.com/pyxem/orix/blob/a1be2697dc0cf02974b00d06d94272de77efeafa/orix/quaternion/quaternion.py#L238-L243
Minimal example
throws
The rotated
ReciprocalLatticeVector
with the phase information intact should be returned.I'm not 100% sure how to fix this.
ReciprocalLatticeVector
should not subclassMiller
, as the coordinates of the former can only be (hkl). Any input would be appreciated.The text was updated successfully, but these errors were encountered: