-
Notifications
You must be signed in to change notification settings - Fork 26
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
Use phase class2 #205
Use phase class2 #205
Conversation
6d884bb
to
f3bd483
Compare
@viljarjf, can you review this? It seems like we should get this in, and then we can work on getting the rest 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.
Looks good! See comments for some minor things. I did not go through all the tests very thoroughly, but they all pass at least.
diffsims/simulations/simulation2d.py
Outdated
phases : Sequence[Phase] | ||
The phases in the library. |
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.
phases : Sequence[Phase] | |
The phases in the library. | |
simulation: Simulation2D | |
The simulation to get from. |
diffsims/simulations/simulation2d.py
Outdated
phases : Sequence[Phase] | ||
The phases in the library. |
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.
phases : Sequence[Phase] | |
The phases in the library. | |
simulation: Simulation2D | |
The simulation to get from. |
diffsims/simulations/simulation2d.py
Outdated
return copy.deepcopy(self.coordinates) | ||
|
||
def get_current_rotation(self): | ||
"""Returns the matrix for the current matrix""" |
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.
"""Returns the matrix for the current matrix""" | |
"""Returns the matrix for the current rotation""" |
for i, (excitation_error_i, r_spot_i) in enumerate(zip(excitation_error, r_spot)): | ||
|
||
def integrand(theta): | ||
# Equation 8 in L.Palatinus et al. Acta Cryst. (2019) B75, 512-522 | ||
S_zero = excitation_error_i | ||
variable_term = r_spot_i * (phi) * np.cos(theta) | ||
return shape_function(S_zero + variable_term, max_excitation, **kwargs) | ||
|
||
# average factor integrated over the full revolution of the beam | ||
shf[i] = (1 / (2 * np.pi)) * quad(integrand, 0, 2 * np.pi)[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.
This can be vectorized with quad_vec
, but that can wait
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.
Let's make an issue for this and we can handle this later.
@viljarjf This should be better now based on your comments. Thanks, let me know what you think and maybe we can merge this? |
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 this can be merged :)
I'd like to review it given it touches on |
I can review within the next few days. |
So, sorry about the hold-up, @CSSFrancis. Couple of questions right away:
|
That's a good question; it's mostly that we really want to calculate things once and then not do it again, or it's going to really slow down the orientation mapping. We can add a function
Shorting names is fairly un-pythonic. A 1-1 mapping of Signal1D -> Simulation1D and Signal2D --> Simulation2D makes more sense in the long term. I want to make this change long-term in diffsims and don't want to break the pyxem API when that happens. I'm open to suggestions. |
I totally understand that. That's why e.g. the
Hm, where is this class? We have the
But, the end result is ultimately structure factors? Or the intensities directly? And what is the current route for calculating these structure factors (steps and input parameters)? It may be best to include that route into the
This is fine, and something I agree with in general. Can I suggest to present this idea in an issue for a discussion, albeit a brief one? |
I misspoke sorry :) Trying to deal with too many things at one time... I meant Maybe it's best if we think about the entire workflow and determine which parts should be methods for We want to: 1 - Copy the For step 4, we can derive the intensities from the structure factor. We just need to make sure we are only calculating this once; otherwise, it might slow down the template matching. We must also add Debye Waller factors and the shape function. Having these factors in the
See above. The result that we care about if the diffraction intensity for comparison. I don't think we want those steps in the I guess the question is: If you wanted to plot the diffraction spots and the Kikuchi lines, how would that look/ how is that handled? It might be nice to do something like:
Sounds good :) |
I agree! It sounds like we want the generator to use The only method that takes a microscope parameter, an accelerating voltage, is
These are already part of the diffpy.structure atoms API in the diffsims/diffsims/structure_factor/atomic_scattering_factor.py Lines 62 to 64 in bb359b9
What is We have plotting of Kikuchi lines in kikuchipy (see this tutorial). It may be that the simulation generator should be upstreamed to diffsims, but that would also require upstreaming |
@hakonanes Okay, so what do we do about the diffraction intensity, which depends on the microscope parameters? I can separate them if you like and make that an attribute of Simulation2D. Still, then there is always the possibility that you have more intensity values than
I'm not really trying to change everything all at once... Can we leave that to another PR? This PR is just supposed to use the orix phase class and
That would be your Simulation2D object. It might make more sense to calculate the two separately and add them to the same plot. It might also be nice to have one SimulationGenerator object that can do both kikuchi and vector diffraction.
For now maybe we can just have a tutorial that makes a combined plot is someone is interested. |
@hakonanes I guess the question is what needs to be changed for this to be merged? Most of this is just copied from the old function. It's not really anything new, just makes it significantly faster and (somewhat) cleaner. |
diffsims/simulations/simulation2d.py
Outdated
theta_templates = np.zeros((len(flattened_vectors), max_num_spots)) | ||
intensities_templates = np.zeros((len(flattened_vectors), max_num_spots)) | ||
for i, v in enumerate(flattened_vectors): | ||
r, t, _ = v.to_polar() |
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 am getting some strange results when I try to template match, and I believe this line is the cause. Should it not be t, _, r = v.to_polar()
? Seeing as Vector3d.to_polar
returns (azimuthal, polar, radius) spherical coordinates. By inspection, the current way yields t
-values close to pi/2, which would make sense for the polar angle with a mostly flat Ewald's sphere.
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.
@viljarjf Sorry, I've gotten kind of confused with all of these comments and changes. We don't want to use that function at all... We want the radius of the projection of the vector onto the x,y plane.
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.
@viljarjf This (should) be better
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've had a look at the new changes.
This extends the :class:`ReciprocalLatticeVector` class. Diffracting Vectors | ||
focus on the subset of reciporical lattice vectors that are relevant for |
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.
This extends the :class:`ReciprocalLatticeVector` class. Diffracting Vectors | |
focus on the subset of reciporical lattice vectors that are relevant for | |
This extends the :class:`ReciprocalLatticeVector` class. `DiffractingVector` | |
focus on the subset of reciprocal lattice vectors that are relevant for |
This class is only used internally to store the DiffractionVectors generated from the | ||
DiffractionSimulation class. It is not (currently) intended to be used directly by the user. |
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.
This class is only used internally to store the DiffractionVectors generated from the | |
DiffractionSimulation class. It is not (currently) intended to be used directly by the user. | |
This class is only used internally to store the diffracting vectors generated from a | |
:class:`~diffsims.simulations.DiffractionSimulation`. It is not intended to be used | |
directly by the user. |
self._intensity = np.array(value) | ||
|
||
@property | ||
def lattice_aligned_data(self): |
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.
So these vectors are the same as they were before rotated by a rotation. Can't we separately keep track of a set of starting vectors, a rotation, and a set of rotated vectors? Why do these separate things have to be stored in a single class instance?
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.
Hmm.... I think this is related to above. What use is the RLV if it doesn't have information about the Rotation otherwise it is just the same as a Vector3D
with confusing hkl values that don't really make physical sense and look correct because they are rounded.
|
||
def to_flat_polar(self): | ||
"""Return the vectors in polar coordinates as projected onto the x,y plane""" | ||
r = np.linalg.norm(self.data[:, :2], axis=1) |
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 if self.data.ndim > 2
? The reciprocal lattice vectors, as normal 3D vectors, are allowed to have a "navigation shape" > 1. I suggest to flatten before extracting the x, y coordinates.
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.
Good point.
@@ -18,6 +18,7 @@ | |||
|
|||
from collections import defaultdict | |||
from copy import deepcopy | |||
from typing import Tuple |
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.
This line can be dropped.
from orix.quaternion import Rotation | ||
from orix.crystal_map import Phase | ||
|
||
from diffsims.crystallography import ReciprocalLatticeVector, DiffractingVector |
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.
Can the diffracting vector class be private?
You state in its docstring that it is not meant to be used by end-users. This tells me that the logic should be a simpler class or sets of functions to handle this particular use-case, instead of a public class with many caveats.
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 the user wants the original reciprocal lattice vectors, rotations, or rotated vectors, can they instead get ReciprocalLatticeVector
or Rotation
(preferably the more powerful Orientation
)?
@hakonanes I think lots of the confusion is related to not properly handling the rotation with the RLV. We need to track the rotations applied to the RLV to recover the hkl. This is possible with pyxem/orix#499 and adding the from orix.crystal_map import Phase
from orix.quaternion import Rotation
from diffsims.crystallography import ReciprocalLatticeVector
import numpy as np
phase = Phase(point_group="m-3m")
g = ReciprocalLatticeVector(phase, [[1, 1, 1], [1,0,0]])
R1 = Rotation.random(2)
random1 = R1 * g
R2 = Rotation.random(2)
random2 =R2*random1
assert np.allclose(g.hkl, random1.hkl) # Previously this would fail
assert np.allclose(g.hkl, random2.hkl) # Previously this would fail I can push this change if you want but it requires pyxem/orix#499. I think this helps the logic quite a bit but does change the ReciprocalLatticeVector class a little bit. |
Perfect. Then I've got no objections to how that class is used internally in diffsims. |
This reverts commit 05b3736.
@hakonanes I made some changes based on @viljarjf's suggestion in #211 |
matrix = value | ||
else: | ||
raise ValueError("Rotation must be a Rotation or a 3x3 numpy array") | ||
self.phase.structure.lattice.setLatPar(baserot=matrix) |
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.
Phases are passed by refrence, so this would affect all other vectors with the same phase.
This is why I copied the phase in rotate_with_basis
.
self.phase.structure.lattice.setLatPar(baserot=matrix) | |
self.phase = self.phase.deepcopy() | |
self.phase.structure.lattice.setLatPar(baserot=matrix) |
The base rotation is not always the identity as well, especially for hexagonal crystals from cif or initialized from a structure. This comes from the enforced x || a, z || c* convention used in orix (https://orix.readthedocs.io/en/stable/tutorials/crystal_reference_frame.html).
What do you intend the basis_rotation
member to be used for?
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.
Fair point. I'll probably just remove this then. I added it for completeness sake but didn't realize that the base rot was used for hexagonal phases.
Based on:
I just made everything part of the private DiffractingVectors class. That way, we can merge things and start working on the next part, the Simulation Part of this code is much more straight forward and I'm much more confident on that implementation. @hakonanes can you take another pass through and then we can think about merging? |
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.
Just a few suggestions left. After those are addressed, I'm OK with the parts of this PR that I've reviewed. Will have a final look after that.
diffsims/tests/crystallography/test_reciprocal_lattice_vector.py
Outdated
Show resolved
Hide resolved
diffsims/tests/crystallography/test_reciprocal_lattice_vector.py
Outdated
Show resolved
Hide resolved
Done! |
@hakonanes Did you want to do another pass through or do you want to merge? |
Fingers crossed the simulations work as you intended in the wild next week! |
At the very least it's an improvement :) Thanks for your help here! |
Description of the change
This simplified the commit history for #201 and focuses the scope of the PR. Hopefully that makes it (slightly) more manageable when checking/merging.
Progress of the PR
Minimal example of the bug fix or new feature
For reviewers
__init__.py
.unreleased section in
CHANGELOG.rst
.credits
indiffsims/release_info.py
andin
.zenodo.json
.