Skip to content

Commit 507dda2

Browse files
authored
Merge pull request #205 from CSSFrancis/use_phase_class2
Use phase class2
2 parents e450902 + 0e97f22 commit 507dda2

19 files changed

+2539
-11
lines changed

.github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
- os: ubuntu-latest
4747
python-version: 3.8
4848
OLDEST_SUPPORTED_VERSION: true
49-
DEPENDENCIES: diffpy.structure==3.0.2 matplotlib==3.5 numpy==1.17.3 orix==0.9.0 scipy==1.8 tqdm==4.9
49+
DEPENDENCIES: diffpy.structure==3.0.2 matplotlib==3.5 numpy==1.17.3 orix==0.12.1 scipy==1.8 tqdm==4.9
5050
LABEL: -oldest
5151
steps:
5252
- uses: actions/checkout@v4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2017-2024 The diffsims developers
3+
#
4+
# This file is part of diffsims.
5+
#
6+
# diffsims is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# diffsims is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with diffsims. If not, see <http://www.gnu.org/licenses/>.
18+
19+
from diffsims.crystallography import ReciprocalLatticeVector
20+
import numpy as np
21+
from orix.vector.miller import _transform_space
22+
from orix.quaternion import Rotation
23+
24+
25+
class DiffractingVector(ReciprocalLatticeVector):
26+
r"""Reciprocal lattice vectors :math:`(hkl)` for use in electron
27+
diffraction analysis and simulation.
28+
29+
All lengths are assumed to be given in Å or inverse Å.
30+
31+
This extends the :class:`ReciprocalLatticeVector` class. `DiffractingVector`
32+
focus on the subset of reciprocal lattice vectors that are relevant for
33+
electron diffraction based on the intersection of the Ewald sphere with the
34+
reciprocal lattice.
35+
36+
This class is only used internally to store the DiffractionVectors generated from the
37+
:class:`~diffsims.simulations.DiffractionSimulation` class. It is not (currently)
38+
intended to be used directly by the user.
39+
40+
Parameters
41+
----------
42+
phase : orix.crystal_map.Phase
43+
A phase with a crystal lattice and symmetry.
44+
xyz : numpy.ndarray, list, or tuple, optional
45+
Cartesian coordinates of indices of reciprocal lattice vector(s)
46+
``hkl``. Default is ``None``. This, ``hkl``, or ``hkil`` is
47+
required.
48+
hkl : numpy.ndarray, list, or tuple, optional
49+
Indices of reciprocal lattice vector(s). Default is ``None``.
50+
This, ``xyz``, or ``hkil`` is required.
51+
hkil : numpy.ndarray, list, or tuple, optional
52+
Indices of reciprocal lattice vector(s), often preferred over
53+
``hkl`` in trigonal and hexagonal lattices. Default is ``None``.
54+
This, ``xyz``, or ``hkl`` is required.
55+
intensity : numpy.ndarray, list, or tuple, optional
56+
Intensity of the diffraction vector(s). Default is ``None``.
57+
rotation : orix.quaternion.Rotation, optional
58+
Rotation matrix previously applied to the reciprocal lattice vector(s) and the
59+
lattice of the phase. Default is ``None`` which corresponds to the
60+
identity matrix.
61+
62+
63+
Examples
64+
--------
65+
>>> from diffpy.structure import Atom, Lattice, Structure
66+
>>> from orix.crystal_map import Phase
67+
>>> from diffsims.crystallography import DiffractingVector
68+
>>> phase = Phase(
69+
... "al",
70+
... space_group=225,
71+
... structure=Structure(
72+
... lattice=Lattice(4.04, 4.04, 4.04, 90, 90, 90),
73+
... atoms=[Atom("Al", [0, 0, 1])],
74+
... ),
75+
... )
76+
>>> rlv = DiffractingVector(phase, hkl=[[1, 1, 1], [2, 0, 0]])
77+
>>> rlv
78+
ReciprocalLatticeVector (2,), al (m-3m)
79+
[[1. 1. 1.]
80+
[2. 0. 0.]]
81+
82+
"""
83+
84+
def __init__(self, phase, xyz=None, hkl=None, hkil=None, intensity=None):
85+
super().__init__(phase, xyz=xyz, hkl=hkl, hkil=hkil)
86+
if intensity is None:
87+
self._intensity = np.full(self.shape, np.nan)
88+
elif len(intensity) != self.size:
89+
raise ValueError("Length of intensity array must match number of vectors")
90+
else:
91+
self._intensity = np.array(intensity)
92+
93+
def __getitem__(self, key):
94+
new_data = self.data[key]
95+
dv_new = self.__class__(self.phase, xyz=new_data)
96+
97+
if np.isnan(self.structure_factor).all():
98+
dv_new._structure_factor = np.full(dv_new.shape, np.nan, dtype="complex128")
99+
100+
else:
101+
dv_new._structure_factor = self.structure_factor[key]
102+
if np.isnan(self.theta).all():
103+
dv_new._theta = np.full(dv_new.shape, np.nan)
104+
else:
105+
dv_new._theta = self.theta[key]
106+
if np.isnan(self.intensity).all():
107+
dv_new._intensity = np.full(dv_new.shape, np.nan)
108+
else:
109+
slic = self.intensity[key]
110+
if not hasattr(slic, "__len__"):
111+
slic = np.array(
112+
[
113+
slic,
114+
]
115+
)
116+
dv_new._intensity = slic
117+
118+
return dv_new
119+
120+
@property
121+
def basis_rotation(self):
122+
"""
123+
Returns the lattice basis rotation.
124+
"""
125+
return Rotation.from_matrix(self.phase.structure.lattice.baserot)
126+
127+
def rotate_with_basis(self, rotation):
128+
"""Rotate both vectors and the basis with a given `Rotation`.
129+
This differs from simply multiplying with a `Rotation`,
130+
as that would NOT update the basis.
131+
132+
Parameters
133+
----------
134+
rot : orix.quaternion.Rotation
135+
A rotation to apply to vectors and the basis.
136+
137+
Returns
138+
-------
139+
DiffractingVector
140+
A new DiffractingVector with the rotated vectors and basis. This maintains
141+
the hkl indices of the vectors, but the underlying vector xyz coordinates
142+
are rotated by the given rotation.
143+
144+
Notes
145+
-----
146+
Rotating the lattice basis may lead to undefined behavior in orix as it violates
147+
the assumption that the basis is aligned with the crystal axes. Particularly,
148+
applying symmetry operations to the phase may lead to unexpected results.
149+
"""
150+
151+
if rotation.size != 1:
152+
raise ValueError("Rotation must be a single rotation")
153+
# rotate basis
154+
new_phase = self.phase.deepcopy()
155+
br = new_phase.structure.lattice.baserot
156+
# In case the base rotation is set already
157+
new_br = br @ rotation.to_matrix().squeeze()
158+
new_phase.structure.lattice.setLatPar(baserot=new_br)
159+
# rotate vectors
160+
vecs = ~rotation * self.to_miller()
161+
return ReciprocalLatticeVector(new_phase, xyz=vecs.data)
162+
163+
@property
164+
def intensity(self):
165+
return self._intensity
166+
167+
@intensity.setter
168+
def intensity(self, value):
169+
if not hasattr(value, "__len__"):
170+
value = np.array(
171+
[
172+
value,
173+
]
174+
* self.size
175+
)
176+
if len(value) != self.size:
177+
raise ValueError("Length of intensity array must match number of vectors")
178+
self._intensity = np.array(value)
179+
180+
def calculate_structure_factor(self):
181+
raise NotImplementedError(
182+
"Structure factor calculation not implemented for DiffractionVector. "
183+
"Use ReciprocalLatticeVector instead."
184+
)
185+
186+
def to_flat_polar(self):
187+
"""Return the vectors in polar coordinates as projected onto the x,y plane"""
188+
flat_self = self.flatten()
189+
r = np.linalg.norm(flat_self.data[:, :2], axis=1)
190+
theta = np.arctan2(
191+
flat_self.data[:, 1],
192+
flat_self.data[:, 0],
193+
)
194+
return r, theta

diffsims/crystallography/reciprocal_lattice_vector.py

+16-5
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ def __init__(self, phase, xyz=None, hkl=None, hkil=None):
119119
self._coordinate_format = "hkl"
120120
xyz = _transform_space(hkl, "r", "c", phase.structure.lattice)
121121
super().__init__(xyz)
122-
123122
self._theta = np.full(self.shape, np.nan)
124123
self._structure_factor = np.full(self.shape, np.nan, dtype="complex128")
125124

@@ -1023,7 +1022,7 @@ def symmetrise(self, return_multiplicity=False, return_index=False):
10231022
return new_out
10241023

10251024
@classmethod
1026-
def from_highest_hkl(cls, phase, hkl):
1025+
def from_highest_hkl(cls, phase, hkl, include_zero_vector=False):
10271026
"""Create a set of unique reciprocal lattice vectors from three
10281027
highest indices.
10291028
@@ -1033,6 +1032,8 @@ def from_highest_hkl(cls, phase, hkl):
10331032
A phase with a crystal lattice and symmetry.
10341033
hkl : numpy.ndarray, list, or tuple
10351034
Three highest reciprocal lattice vector indices.
1035+
include_zero_vector : bool
1036+
If ``True``, include the zero vector (000) in the set of vectors.
10361037
10371038
Examples
10381039
--------
@@ -1067,10 +1068,14 @@ def from_highest_hkl(cls, phase, hkl):
10671068
"""
10681069

10691070
idx = _get_indices_from_highest(highest_indices=hkl)
1070-
return cls(phase, hkl=idx).unique()
1071+
new = cls(phase, hkl=idx).unique()
1072+
if include_zero_vector:
1073+
new_data = np.vstack((new.hkl, np.zeros(3, dtype=int)))
1074+
new = ReciprocalLatticeVector(phase, hkl=new_data)
1075+
return new
10711076

10721077
@classmethod
1073-
def from_min_dspacing(cls, phase, min_dspacing=0.7):
1078+
def from_min_dspacing(cls, phase, min_dspacing=0.7, include_zero_vector=False):
10741079
"""Create a set of unique reciprocal lattice vectors with a
10751080
a direct space interplanar spacing greater than a lower
10761081
threshold.
@@ -1083,6 +1088,8 @@ def from_min_dspacing(cls, phase, min_dspacing=0.7):
10831088
Smallest interplanar spacing to consider. Default is 0.7,
10841089
in the unit used to define the lattice parameters in
10851090
``phase``, which is assumed to be Ångström.
1091+
include_zero_vector: bool
1092+
If ``True``, include the zero vector (000) in the set of vectors.
10861093
10871094
Examples
10881095
--------
@@ -1128,7 +1135,11 @@ def from_min_dspacing(cls, phase, min_dspacing=0.7):
11281135
dspacing = 1 / phase.structure.lattice.rnorm(hkl)
11291136
idx = dspacing >= min_dspacing
11301137
hkl = hkl[idx]
1131-
return cls(phase, hkl=hkl).unique()
1138+
new = cls(phase, hkl=hkl).unique()
1139+
if include_zero_vector:
1140+
new_data = np.vstack((new.hkl, np.zeros(3, dtype=int)))
1141+
new = cls(phase, hkl=new_data)
1142+
return new
11321143

11331144
@classmethod
11341145
def from_miller(cls, miller):

diffsims/generators/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@
2626
rotation_list_generators,
2727
sphere_mesh_generators,
2828
zap_map_generator,
29+
simulation_generator,
2930
)
3031

3132
__all__ = [
3233
"diffraction_generator",
3334
"library_generator",
3435
"rotation_list_generators",
3536
"sphere_mesh_generators",
37+
"simulation_generator",
3638
"zap_map_generator",
3739
]

0 commit comments

Comments
 (0)