Skip to content
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

Merged
merged 36 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1fc6a87
New Feature: Add Simulation classes using Phase Class
CSSFrancis Apr 12, 2024
8282721
New Feature: Add intensity to reciprocal lattice vector
CSSFrancis Apr 12, 2024
bfe0505
Testing: Add tests for adding intensity of reciprocal_lattive_vector.py
CSSFrancis Apr 12, 2024
ac97ee6
New Feature: Added zerobeam to reciprocal lattice vector
CSSFrancis Apr 12, 2024
b0b67fa
New Feature: Moved Shape factor calculation to shape_factor_models.py
CSSFrancis Apr 12, 2024
6ec15b8
Testing: Added testing for simulation_generator.
CSSFrancis Apr 12, 2024
f3bd483
Testing: Testing the zero Beam is most intense.
CSSFrancis Apr 12, 2024
d7c86ec
Min Version: Bump orix min version
CSSFrancis Apr 17, 2024
ef03b39
Packaging: Include function and classes in init file
CSSFrancis Apr 17, 2024
d1839a5
Testing: Clean up tests due to changed function name
CSSFrancis Apr 17, 2024
3567308
Testing: Add coverage for simulation classes
CSSFrancis Apr 17, 2024
66f0133
Documentation: Fixed Broken documentation
CSSFrancis Apr 17, 2024
3d9c662
Refactor: Fixes per @viljarjf
CSSFrancis Apr 21, 2024
99327ad
Refactor: Clean up intensity in rlv class
CSSFrancis Apr 25, 2024
2ad4567
Refactor: Remove calculate structure factor
CSSFrancis Apr 25, 2024
273ffcc
Refactor: Changes from @hakonanes
CSSFrancis May 3, 2024
f32bfda
Refactor: Improve Documentation
CSSFrancis May 3, 2024
a8c68a3
Testing: Test simulation continuity
CSSFrancis May 3, 2024
3a27a7b
Documentation: Add intensity parameter
CSSFrancis May 3, 2024
9bf9404
Manifest: Add numpy old file
CSSFrancis May 3, 2024
9c216f8
Refactor: Refactor DiffractingVector code
CSSFrancis May 5, 2024
2ada567
Refactor: Refactor Rotation implementation
CSSFrancis May 5, 2024
b598a5a
BugFix: Fix HKL computation
CSSFrancis May 5, 2024
e0815d5
Refactor: Fix MANIFEST.in
CSSFrancis May 5, 2024
dff72bb
Plotting: Add interactive plotting
CSSFrancis May 6, 2024
8b92dce
Testing: no cover interactive plotting
CSSFrancis May 6, 2024
f3d9b95
Documentation: Add docstrings for interactive plotting
CSSFrancis May 6, 2024
82b3333
Documentation: Remove lattice rotation
CSSFrancis May 6, 2024
c39e357
Bugfix: Fix polar conversion
CSSFrancis May 7, 2024
05b3736
Bugfix: Fix Rotation with RLV
CSSFrancis May 8, 2024
bfbd299
Revert "Bugfix: Fix Rotation with RLV"
CSSFrancis May 8, 2024
96a48ec
Testing: Improve testing
CSSFrancis May 8, 2024
1f2150d
Refactor: Improve based on @hakonanes and @viljarjf's suggestions
CSSFrancis May 8, 2024
efde31d
Refactor: Tests including zero-vector
CSSFrancis May 8, 2024
da45a32
Refactor: Remove changes from RLV
CSSFrancis May 8, 2024
0e97f22
Refactor: suggestions for @hakonanes
CSSFrancis May 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
- os: ubuntu-latest
python-version: 3.8
OLDEST_SUPPORTED_VERSION: true
DEPENDENCIES: diffpy.structure==3.0.2 matplotlib==3.5 numpy==1.17.3 orix==0.9.0 scipy==1.8 tqdm==4.9
DEPENDENCIES: diffpy.structure==3.0.2 matplotlib==3.5 numpy==1.17.3 orix==0.12.1 scipy==1.8 tqdm==4.9
LABEL: -oldest
steps:
- uses: actions/checkout@v4
Expand Down
137 changes: 137 additions & 0 deletions diffsims/crystallography/_diffracting_vector.py
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.


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)
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
72 changes: 66 additions & 6 deletions diffsims/crystallography/reciprocal_lattice_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@

from collections import defaultdict
from copy import deepcopy
from typing import Tuple
Copy link
Member

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 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,
Expand Down Expand Up @@ -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
--------
Expand Down Expand Up @@ -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(
Expand All @@ -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)
Copy link

@viljarjf viljarjf May 8, 2024

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.

Suggested change
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?

Copy link
Member Author

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.


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.
Expand Down Expand Up @@ -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.

Expand All @@ -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
--------
Expand Down Expand Up @@ -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.
Expand All @@ -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
--------
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions diffsims/generators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
rotation_list_generators,
sphere_mesh_generators,
zap_map_generator,
simulation_generator,
)

__all__ = [
"diffraction_generator",
"library_generator",
"rotation_list_generators",
"sphere_mesh_generators",
"simulation_generator",
"zap_map_generator",
]
Loading
Loading