-
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
Changes from 32 commits
1fc6a87
8282721
bfe0505
ac97ee6
b0b67fa
6ec15b8
f3bd483
d7c86ec
ef03b39
d1839a5
3567308
66f0133
3d9c662
99327ad
2ad4567
273ffcc
f32bfda
a8c68a3
3a27a7b
9bf9404
9c216f8
2ada567
b598a5a
e0815d5
dff72bb
8b92dce
f3d9b95
82b3333
c39e357
05b3736
bfbd299
96a48ec
1f2150d
efde31d
da45a32
0e97f22
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,137 @@ | ||||||||||||
# -*- coding: utf-8 -*- | ||||||||||||
# Copyright 2017-2024 The diffsims developers | ||||||||||||
# | ||||||||||||
# This file is part of diffsims. | ||||||||||||
# | ||||||||||||
# diffsims is free software: you can redistribute it and/or modify | ||||||||||||
# it under the terms of the GNU General Public License as published by | ||||||||||||
# the Free Software Foundation, either version 3 of the License, or | ||||||||||||
# (at your option) any later version. | ||||||||||||
# | ||||||||||||
# diffsims is distributed in the hope that it will be useful, | ||||||||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||||||||||
# GNU General Public License for more details. | ||||||||||||
# | ||||||||||||
# You should have received a copy of the GNU General Public License | ||||||||||||
# along with diffsims. If not, see <http://www.gnu.org/licenses/>. | ||||||||||||
|
||||||||||||
from diffsims.crystallography import ReciprocalLatticeVector | ||||||||||||
import numpy as np | ||||||||||||
from orix.vector.miller import _transform_space | ||||||||||||
|
||||||||||||
|
||||||||||||
class DiffractingVector(ReciprocalLatticeVector): | ||||||||||||
r"""Reciprocal lattice vectors :math:`(hkl)` for use in electron | ||||||||||||
diffraction analysis and simulation. | ||||||||||||
|
||||||||||||
All lengths are assumed to be given in Å or inverse Å. | ||||||||||||
|
||||||||||||
This extends the :class:`ReciprocalLatticeVector` class. Diffracting Vectors | ||||||||||||
focus on the subset of reciporical lattice vectors that are relevant for | ||||||||||||
electron diffraction based on the intersection of the Ewald sphere with the | ||||||||||||
reciprocal lattice. | ||||||||||||
|
||||||||||||
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 commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
Parameters | ||||||||||||
---------- | ||||||||||||
phase : orix.crystal_map.Phase | ||||||||||||
A phase with a crystal lattice and symmetry. | ||||||||||||
xyz : numpy.ndarray, list, or tuple, optional | ||||||||||||
Cartesian coordinates of indices of reciprocal lattice vector(s) | ||||||||||||
``hkl``. Default is ``None``. This, ``hkl``, or ``hkil`` is | ||||||||||||
required. | ||||||||||||
hkl : numpy.ndarray, list, or tuple, optional | ||||||||||||
Indices of reciprocal lattice vector(s). Default is ``None``. | ||||||||||||
This, ``xyz``, or ``hkil`` is required. | ||||||||||||
hkil : numpy.ndarray, list, or tuple, optional | ||||||||||||
Indices of reciprocal lattice vector(s), often preferred over | ||||||||||||
``hkl`` in trigonal and hexagonal lattices. Default is ``None``. | ||||||||||||
This, ``xyz``, or ``hkl`` is required. | ||||||||||||
intensity : numpy.ndarray, list, or tuple, optional | ||||||||||||
Intensity of the diffraction vector(s). Default is ``None``. | ||||||||||||
rotation : orix.quaternion.Rotation, optional | ||||||||||||
hakonanes marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
Rotation matrix previously applied to the reciprocal lattice vector(s) and the | ||||||||||||
lattice of the phase. Default is ``None`` which corresponds to the | ||||||||||||
identity matrix. | ||||||||||||
|
||||||||||||
|
||||||||||||
Examples | ||||||||||||
-------- | ||||||||||||
>>> from diffpy.structure import Atom, Lattice, Structure | ||||||||||||
>>> from orix.crystal_map import Phase | ||||||||||||
>>> from diffsims.crystallography import DiffractingVector | ||||||||||||
>>> phase = Phase( | ||||||||||||
... "al", | ||||||||||||
... space_group=225, | ||||||||||||
... structure=Structure( | ||||||||||||
... lattice=Lattice(4.04, 4.04, 4.04, 90, 90, 90), | ||||||||||||
... atoms=[Atom("Al", [0, 0, 1])], | ||||||||||||
... ), | ||||||||||||
... ) | ||||||||||||
>>> rlv = DiffractingVector(phase, hkl=[[1, 1, 1], [2, 0, 0]]) | ||||||||||||
>>> rlv | ||||||||||||
ReciprocalLatticeVector (2,), al (m-3m) | ||||||||||||
[[1. 1. 1.] | ||||||||||||
[2. 0. 0.]] | ||||||||||||
|
||||||||||||
""" | ||||||||||||
|
||||||||||||
def __init__(self, phase, xyz=None, hkl=None, hkil=None, intensity=None): | ||||||||||||
super().__init__(phase, xyz=xyz, hkl=hkl, hkil=hkil) | ||||||||||||
if intensity is None: | ||||||||||||
self._intensity = np.full(self.shape, np.nan) | ||||||||||||
elif len(intensity) != self.size: | ||||||||||||
raise ValueError("Length of intensity array must match number of vectors") | ||||||||||||
else: | ||||||||||||
self._intensity = np.array(intensity) | ||||||||||||
|
||||||||||||
def __getitem__(self, key): | ||||||||||||
dv_new = super().__getitem__(key) | ||||||||||||
if np.isnan(self.intensity).all(): | ||||||||||||
dv_new._intensity = np.full(dv_new.shape, np.nan) | ||||||||||||
else: | ||||||||||||
slic = self.intensity[key] | ||||||||||||
if not hasattr(slic, "__len__"): | ||||||||||||
slic = np.array( | ||||||||||||
[ | ||||||||||||
slic, | ||||||||||||
] | ||||||||||||
) | ||||||||||||
dv_new._intensity = slic | ||||||||||||
|
||||||||||||
return dv_new | ||||||||||||
|
||||||||||||
@property | ||||||||||||
def intensity(self): | ||||||||||||
return self._intensity | ||||||||||||
|
||||||||||||
@intensity.setter | ||||||||||||
def intensity(self, value): | ||||||||||||
if not hasattr(value, "__len__"): | ||||||||||||
value = np.array( | ||||||||||||
[ | ||||||||||||
value, | ||||||||||||
] | ||||||||||||
* self.size | ||||||||||||
) | ||||||||||||
if len(value) != self.size: | ||||||||||||
raise ValueError("Length of intensity array must match number of vectors") | ||||||||||||
self._intensity = np.array(value) | ||||||||||||
|
||||||||||||
def calculate_structure_factor(self): | ||||||||||||
raise NotImplementedError( | ||||||||||||
"Structure factor calculation not implemented for DiffractionVector. " | ||||||||||||
"Use ReciprocalLatticeVector instead." | ||||||||||||
) | ||||||||||||
|
||||||||||||
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 commentThe reason will be displayed to describe this comment to others. Learn more. What if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. |
||||||||||||
theta = np.arctan2( | ||||||||||||
self.data[:, 1], | ||||||||||||
self.data[:, 0], | ||||||||||||
) | ||||||||||||
return r, theta |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -18,12 +18,14 @@ | |||||||
|
||||||||
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 commentThe reason will be displayed to describe this comment to others. Learn more. This line can be dropped. |
||||||||
|
||||||||
from diffpy.structure.symmetryutilities import expandPosition | ||||||||
from diffpy.structure import Structure | ||||||||
import numba as nb | ||||||||
import numpy as np | ||||||||
from orix.vector import Miller, Vector3d | ||||||||
from orix.quaternion import Rotation | ||||||||
from orix.vector.miller import ( | ||||||||
_check_hkil, | ||||||||
_get_highest_hkl, | ||||||||
|
@@ -77,6 +79,8 @@ class ReciprocalLatticeVector(Vector3d): | |||||||
Indices of reciprocal lattice vector(s), often preferred over | ||||||||
``hkl`` in trigonal and hexagonal lattices. Default is ``None``. | ||||||||
This, ``xyz``, or ``hkl`` is required. | ||||||||
rotation : orix.quaternion.Rotation, optional | ||||||||
Rotation to apply to the vectors. Default is ``None``. | ||||||||
|
||||||||
Examples | ||||||||
-------- | ||||||||
|
@@ -119,13 +123,12 @@ def __init__(self, phase, xyz=None, hkl=None, hkil=None): | |||||||
self._coordinate_format = "hkl" | ||||||||
xyz = _transform_space(hkl, "r", "c", phase.structure.lattice) | ||||||||
super().__init__(xyz) | ||||||||
|
||||||||
self._theta = np.full(self.shape, np.nan) | ||||||||
self._structure_factor = np.full(self.shape, np.nan, dtype="complex128") | ||||||||
|
||||||||
def __getitem__(self, key): | ||||||||
miller_new = self.to_miller().__getitem__(key) | ||||||||
rlv_new = self.from_miller(miller_new) | ||||||||
new_data = self.data[key] | ||||||||
rlv_new = self.__class__(self.phase, xyz=new_data) | ||||||||
|
||||||||
if np.isnan(self.structure_factor).all(): | ||||||||
rlv_new._structure_factor = np.full( | ||||||||
|
@@ -150,6 +153,53 @@ def __repr__(self): | |||||||
phase_name = self.phase.name | ||||||||
return f"{name} {shape}, {phase_name} ({symmetry})\n" f"{data}" | ||||||||
|
||||||||
@property | ||||||||
def basis_rotation(self): | ||||||||
""" | ||||||||
Returns the lattice basis rotation. | ||||||||
""" | ||||||||
return Rotation.from_matrix(self.phase.structure.lattice.baserot) | ||||||||
|
||||||||
@basis_rotation.setter | ||||||||
def basis_rotation(self, value): | ||||||||
""" | ||||||||
Sets the lattice basis rotation. | ||||||||
""" | ||||||||
if isinstance(value, Rotation): | ||||||||
if value.size != 1: | ||||||||
raise ValueError("Rotation must be a single rotation") | ||||||||
matrix = value.to_matrix().squeeze() | ||||||||
elif isinstance(value, np.ndarray): | ||||||||
if value.shape != (3, 3): | ||||||||
raise ValueError("Rotation matrix must be 3x3") | ||||||||
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 commentThe 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.
Suggested change
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). There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||||||||
|
||||||||
def rotate_with_basis(self, 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. | ||||||||
""" | ||||||||
|
||||||||
if rotation.size != 1: | ||||||||
raise ValueError("Rotation must be a single rotation") | ||||||||
# rotate basis | ||||||||
new_phase = self.phase.deepcopy() | ||||||||
br = new_phase.structure.lattice.baserot | ||||||||
# In case the base rotation is set already | ||||||||
new_br = br @ rotation.to_matrix().squeeze() | ||||||||
new_phase.structure.lattice.setLatPar(baserot=new_br) | ||||||||
# rotate vectors | ||||||||
vecs = ~rotation * self.to_miller() | ||||||||
return ReciprocalLatticeVector(new_phase, xyz=vecs.data) | ||||||||
|
||||||||
@property | ||||||||
def hkl(self): | ||||||||
"""Miller indices. | ||||||||
|
@@ -1023,7 +1073,7 @@ def symmetrise(self, return_multiplicity=False, return_index=False): | |||||||
return new_out | ||||||||
|
||||||||
@classmethod | ||||||||
def from_highest_hkl(cls, phase, hkl): | ||||||||
def from_highest_hkl(cls, phase, hkl, include_zero_vector=False): | ||||||||
"""Create a set of unique reciprocal lattice vectors from three | ||||||||
highest indices. | ||||||||
|
||||||||
|
@@ -1033,6 +1083,8 @@ def from_highest_hkl(cls, phase, hkl): | |||||||
A phase with a crystal lattice and symmetry. | ||||||||
hkl : numpy.ndarray, list, or tuple | ||||||||
Three highest reciprocal lattice vector indices. | ||||||||
include_zero_vector : bool | ||||||||
If ``True``, include the zero vector (000) in the set of vectors. | ||||||||
|
||||||||
Examples | ||||||||
-------- | ||||||||
|
@@ -1067,10 +1119,14 @@ def from_highest_hkl(cls, phase, hkl): | |||||||
""" | ||||||||
|
||||||||
idx = _get_indices_from_highest(highest_indices=hkl) | ||||||||
return cls(phase, hkl=idx).unique() | ||||||||
new = cls(phase, hkl=idx).unique() | ||||||||
if include_zero_vector: | ||||||||
CSSFrancis marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
new_data = np.vstack((new.hkl, np.zeros(3, dtype=int))) | ||||||||
new = ReciprocalLatticeVector(phase, hkl=new_data) | ||||||||
return new | ||||||||
|
||||||||
@classmethod | ||||||||
def from_min_dspacing(cls, phase, min_dspacing=0.7): | ||||||||
def from_min_dspacing(cls, phase, min_dspacing=0.7, include_zero_vector=False): | ||||||||
"""Create a set of unique reciprocal lattice vectors with a | ||||||||
a direct space interplanar spacing greater than a lower | ||||||||
threshold. | ||||||||
|
@@ -1083,6 +1139,8 @@ def from_min_dspacing(cls, phase, min_dspacing=0.7): | |||||||
Smallest interplanar spacing to consider. Default is 0.7, | ||||||||
in the unit used to define the lattice parameters in | ||||||||
``phase``, which is assumed to be Ångström. | ||||||||
include_zero_vector: bool | ||||||||
If ``True``, include the zero vector (000) in the set of vectors. | ||||||||
|
||||||||
Examples | ||||||||
-------- | ||||||||
|
@@ -1128,6 +1186,8 @@ def from_min_dspacing(cls, phase, min_dspacing=0.7): | |||||||
dspacing = 1 / phase.structure.lattice.rnorm(hkl) | ||||||||
idx = dspacing >= min_dspacing | ||||||||
hkl = hkl[idx] | ||||||||
if include_zero_vector: | ||||||||
hakonanes marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
hkl = np.vstack((hkl, np.zeros(3, dtype=int))) | ||||||||
return cls(phase, hkl=hkl).unique() | ||||||||
|
||||||||
@classmethod | ||||||||
|
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.