|
| 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 |
0 commit comments