diff --git a/diffsims/generators/structure_library_generator.py b/diffsims/generators/structure_library_generator.py deleted file mode 100644 index 5bb5a428..00000000 --- a/diffsims/generators/structure_library_generator.py +++ /dev/null @@ -1,109 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2017-2019 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 . - -import numpy as np - -from diffsims.libraries.structure_library import StructureLibrary -from diffsims.utils.sim_utils import rotation_list_stereographic - -"""Generating structure libraries.""" - -# Inverse pole figure corners for crystal systems -stereographic_corners = { - 'cubic': [(0, 0, 1), (1, 0, 1), (1, 1, 1)], - 'hexagonal': [(0, 0, 0, 1), (1, 0, -1, 0), (1, 1, -2, 0)], - 'orthorhombic': [(0, 0, 1), (1, 0, 0), (0, 1, 0)], - 'tetragonal': [(0, 0, 1), (1, 0, 0), (1, 1, 0)], - 'trigonal': [(0, 0, 0, 1), (0, -1, 1, 0), (1, -1, 0, 0)], - 'monoclinic': [(0, 0, 1), (0, 1, 0), (0, -1, 0)] -} - - -class StructureLibraryGenerator: - """Generates a structure library for the given phases - - Parameters - ---------- - phases : list - Array of three-component phase descriptions, where the phase - description is [ : string, : - diffpy.structure.Structure, : string], and crystal - system is one of 'cubic', 'hexagonal', 'orthorhombic', 'tetragonal', - 'trigonal', 'monoclinic'. - - Attributes - ---------- - phase_names : list of string - List of phase names. - structures : list of diffpy.structure.Structure - List of structures. - systems : list of string - List of crystal systems. - - Examples - -------- - >>> gen = StructureLibraryGenerator([ - ... ('ZB', structure_zb, 'cubic'), - ... ('WZ', structure_wz, 'hexagonal')]) - """ - - def __init__(self, phases): - self.phase_names = [phase[0] for phase in phases] - self.structures = [phase[1] for phase in phases] - self.systems = [phase[2] for phase in phases] - - def get_orientations_from_list(self, orientations): - """Create a structure library from a list of rotations. - - Parameters - ---------- - orientations : list - A list over identifiers of lists of euler angles (as tuples) in the rzxz - convention and in degrees. - - Returns - ------- - structure_library : StructureLibrary - Structure library for the given phase names, structures and orientations. - """ - return StructureLibrary(self.phase_names, self.structures, orientations) - - def get_orientations_from_stereographic_triangle(self, inplane_rotations, resolution): - """ - Create a structure library from the stereographic triangles of the - given crystal systems. - - Parameters - ---------- - inplane_rotations : list - List over identifiers of lists of inplane rotations of the - diffraction patterns, in degrees. - resolution : float - Rotation list resolution in degrees. - - Returns - ------- - structure_library : StructureLibrary - Structure library for the given phase names, structures and crystal system. - """ - rotation_lists = [ - rotation_list_stereographic(structure, *stereographic_corners[system], - np.deg2rad(inplane_rotation), np.deg2rad(resolution)) - for phase_name, structure, system, inplane_rotation in - zip(self.phase_names, self.structures, self.systems, inplane_rotations)] - return StructureLibrary(self.phase_names, self.structures, rotation_lists) diff --git a/diffsims/libraries/structure_library.py b/diffsims/libraries/structure_library.py index b81f0f07..b1969868 100644 --- a/diffsims/libraries/structure_library.py +++ b/diffsims/libraries/structure_library.py @@ -17,6 +17,7 @@ # along with diffsims. If not, see . import diffsims as ds +from diffsims.generators.rotation_list_generators import get_grid_streographic class StructureLibrary(): @@ -54,3 +55,51 @@ def __init__(self, self.struct_lib = dict() for ident, struct, ori in zip(identifiers, structures, orientations): self.struct_lib[ident] = (struct, ori) + + @classmethod + def from_orientation_lists(cls,identifiers,structures,orientations): + """ + Creates a structure library from "manual" orientation lists + + Parameters + ---------- + identifiers : list of strings/ints + A list of phase identifiers referring to different atomic structures. + structures : list of diffpy.structure.Structure objects. + A list of diffpy.structure.Structure objects describing the atomic + structure associated with each phase in the library. + orientations : list of lists of tuples + A list over identifiers of lists of euler angles (as tuples) in the rzxz + convention and in degrees. + Returns + ------- + StructureLibrary + """ + return cls(identifiers,structures,orientations) + + @classmethod + def from_crystal_systems(cls,identifiers,structures,systems,resolution,equal='angle'): + """ + Creates a structure library from crystal system derived orientation lists + + Parameters + ---------- + identifiers : list of strings/ints + A list of phase identifiers referring to different atomic structures. + structures : list of diffpy.structure.Structure objects. + A list of diffpy.structure.Structure objects describing the atomic + structure associated with each phase in the library. + systems : list + A list over indentifiers of crystal systems + resolution : float + resolution in degrees + equal : str + Default is 'angle' + Returns + ------- + StructureLibrary + """ + orientations = [] + for system in systems: + orientations.append(get_grid_streographic(system,resolution,equal)) + return cls(identifiers,structures,orientations) diff --git a/diffsims/tests/test_generators/test_structure_library_generator.py b/diffsims/tests/test_generators/test_structure_library_generator.py deleted file mode 100644 index 49b34b5c..00000000 --- a/diffsims/tests/test_generators/test_structure_library_generator.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2017-2019 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 . - -import numpy as np - -from diffsims.generators.structure_library_generator import StructureLibraryGenerator -from diffsims.tests.test_utils.test_sim_utils import create_structure_cubic - - -def test_orientations_from_list(): - expected_orientations = [(0, 0, 0), (0, 90, 0)] - structure_library_generator = StructureLibraryGenerator([ - ('a', None, 'cubic'), - ('b', None, 'hexagonal') - ]) - structure_library = structure_library_generator.get_orientations_from_list(expected_orientations) - assert structure_library.identifiers == ['a', 'b'] - assert structure_library.structures == [None, None] - np.testing.assert_almost_equal(structure_library.orientations, expected_orientations) - - -def test_orientations_from_stereographic_triangle(): - structure_cubic = create_structure_cubic() - structure_library_generator = StructureLibraryGenerator([('a', structure_cubic, 'cubic')]) - structure_library = structure_library_generator.get_orientations_from_stereographic_triangle([[0]], np.pi / 8) - assert structure_library.identifiers == ['a'] - assert structure_library.structures == [structure_cubic] - # Tests for rotation_list_stereographic checks correctness of list content - assert len(structure_library.orientations) == 1 diff --git a/diffsims/tests/test_library/test_structure_library.py b/diffsims/tests/test_library/test_structure_library.py index f9ce5bb7..bff32269 100644 --- a/diffsims/tests/test_library/test_structure_library.py +++ b/diffsims/tests/test_library/test_structure_library.py @@ -21,20 +21,24 @@ from diffsims.libraries.structure_library import StructureLibrary - -def test_constructor(): +def test_from_orientations_method(): identifiers = ['a', 'b'] - # Arbitrary values for tracking structures = [1, 2] orientations = [3, 4] - library = StructureLibrary(identifiers, structures, orientations) - + library = StructureLibrary.from_orientation_lists(identifiers,structures,orientations) np.testing.assert_equal(library.identifiers, identifiers) np.testing.assert_equal(library.structures, structures) np.testing.assert_equal(library.orientations, orientations) np.testing.assert_equal(library.struct_lib['a'], (1, 3)) np.testing.assert_equal(library.struct_lib['b'], (2, 4)) +def test_from_systems_methods(): + identifiers = ['a', 'b'] + structures = [1, 2] + systems = ['cubic', 'hexagonal'] + library = StructureLibrary.from_crystal_systems(identifiers,structures,systems,resolution=2,equal='angle') + assert len(library.struct_lib['a'][1]) < len(library.struct_lib['b'][1]) #cubic is less area the hexagonal + @pytest.mark.parametrize('identifiers, structures, orientations', [ (['a'], [1, 2], [3, 4]), diff --git a/diffsims/tests/test_utils/test_sim_utils.py b/diffsims/tests/test_utils/test_sim_utils.py index ca8c613a..422e95bd 100644 --- a/diffsims/tests/test_utils/test_sim_utils.py +++ b/diffsims/tests/test_utils/test_sim_utils.py @@ -23,39 +23,7 @@ from diffsims.utils.sim_utils import get_electron_wavelength, \ get_interaction_constant, get_unique_families, get_kinematical_intensities, \ get_vectorized_list_for_atomic_scattering_factors, get_points_in_sphere, \ - simulate_kinematic_scattering, is_lattice_hexagonal, uvtw_to_uvw, \ - rotation_list_stereographic - - -def create_lattice_structure(a, b, c, alpha, beta, gamma): - lattice = diffpy.structure.lattice.Lattice(a, b, c, alpha, beta, gamma) - atom = diffpy.structure.atom.Atom(atype='Si', xyz=[0, 0, 0], lattice=lattice) - return diffpy.structure.Structure(atoms=[atom], lattice=lattice) - - -def create_structure_cubic(): - return create_lattice_structure(1, 1, 1, 90, 90, 90) - - -def create_structure_hexagonal(): - return create_lattice_structure(1, 1, 1, 90, 90, 120) - - -def create_structure_orthorombic(): - return create_lattice_structure(1, 2, 3, 90, 90, 90) - - -def create_structure_tetragonal(): - return create_lattice_structure(1, 1, 2, 90, 90, 90) - - -def create_structure_trigonal(): - return create_lattice_structure(1, 1, 1, 100, 100, 100) - - -def create_structure_monoclinic(): - return create_lattice_structure(1, 2, 3, 90, 100, 90) - + simulate_kinematic_scattering, is_lattice_hexagonal, uvtw_to_uvw @pytest.mark.parametrize('accelerating_voltage, wavelength', [ (100, 0.0370143659), @@ -143,65 +111,3 @@ def test_kinematic_simulator_invalid_illumination(): def test_uvtw_to_uvw(uvtw, uvw): val = uvtw_to_uvw(uvtw) np.testing.assert_almost_equal(val, uvw) - - -# Three corners of the rotation lists, for comparison -structure_cubic_rotations = [ - [0, 0, 0], - [90, 45, 0], - [135, 54.73561032, 0] -] - -structure_hexagonal_rotations = [ - [0, 0, 0], - [90, 90, 0], - [120, 90, 0] -] - -structure_orthogonal_rotations = [ - [0, 0, 0], - [90, 90, 0], - [180, 90, 0] -] - -structure_tetragonal_rotations = [ - [0, 0, 0], - [90, 90, 0], - [135, 90, 0] -] - -structure_trigonal_rotations = [ - [0, 0, 0], - [-28.64458044, 75.45951959, 0], - [38.93477108, 90, 0] -] - -structure_monoclinic_rotations = [ - [0, 0, 0], - [0, 90, 0], - [180, 90, 0] -] - - -@pytest.mark.parametrize('structure, corner_a, corner_b, corner_c, rotation_list', [ - (create_structure_cubic(), (0, 0, 1), (1, 0, 1), (1, 1, 1), structure_cubic_rotations), - (create_structure_hexagonal(), (0, 0, 0, 1), (1, 0, -1, 0), (1, 1, -2, 0), structure_hexagonal_rotations), - (create_structure_orthorombic(), (0, 0, 1), (1, 0, 0), (0, 1, 0), structure_orthogonal_rotations), - (create_structure_tetragonal(), (0, 0, 1), (1, 0, 0), (1, 1, 0), structure_tetragonal_rotations), - (create_structure_trigonal(), (0, 0, 0, 1), (0, -1, 1, 0), (1, -1, 0, 0), structure_trigonal_rotations), - (create_structure_monoclinic(), (0, 0, 1), (0, 1, 0), (0, -1, 0), structure_monoclinic_rotations), -]) -def test_rotation_list_stereographic(structure, corner_a, corner_b, corner_c, rotation_list): - val = rotation_list_stereographic(structure, corner_a, corner_b, corner_c, [0], np.deg2rad(10)) - for expected in rotation_list: - assert any((np.allclose(expected, actual) for actual in val)) - - -@pytest.mark.xfail(raises=ValueError) -@pytest.mark.parametrize('structure, corner_a, corner_b, corner_c, inplane_rotations, resolution, rotation_list', [ - (create_structure_cubic(), (0, 0, 1), (0, 0, 1), (1, 1, 1), [0], np.deg2rad(10), structure_cubic_rotations), - (create_structure_cubic(), (0, 0, 1), (1, 0, 1), (0, 0, 1), [0], np.deg2rad(10), structure_cubic_rotations) -]) -def test_rotation_list_stereographic_raises_invalid_corners( - structure, corner_a, corner_b, corner_c, inplane_rotations, resolution, rotation_list): - rotation_list_stereographic(structure, corner_a, corner_b, corner_c, inplane_rotations, resolution) diff --git a/diffsims/utils/sim_utils.py b/diffsims/utils/sim_utils.py index 5adbd837..b0bc7fd9 100644 --- a/diffsims/utils/sim_utils.py +++ b/diffsims/utils/sim_utils.py @@ -372,7 +372,7 @@ def simulate_rotated_structure(diffraction_generator, structure, rotation_matrix stdbase = structure.lattice.stdbase stdbase_inverse = np.linalg.inv(stdbase) rotation_matrix_diffpy = stdbase_inverse @ rotation_matrix @ stdbase - + lattice_rotated = diffpy.structure.lattice.Lattice( *structure.lattice.abcABG(), baserot=rotation_matrix_diffpy) @@ -458,111 +458,3 @@ def uvtw_to_uvw(uvtw): u, v, w = 2 * u + v, 2 * v + u, w common_factor = math.gcd(math.gcd(u, v), w) return tuple((int(x / common_factor)) for x in (u, v, w)) - - -def rotation_list_stereographic(structure, corner_a, corner_b, corner_c, - inplane_rotations, resolution): - """Generate a rotation list covering the inverse pole figure specified by - three corners in cartesian coordinates. - - Parameters - ---------- - structure : diffpy.structure.Structure - Structure for which to calculate the rotation list. - corner_a, corner_b, corner_c : tuple - The three corners of the inverse pole figure, each given by a - three-dimensional coordinate. The coordinate system is given by the - structure lattice. - inplane_rotations : list - List of angles in radians for in-plane rotation of the diffraction - pattern. This corresponds to the third Euler angle rotation. The - rotation list will be generated for each of these angles, and combined. - This should be done automatically, but by including all possible - rotations in the rotation list, it becomes too large. - - To cover all inplane rotations, use e.g. np.linspace(0, 2*np.pi, 360). - resolution : float - Angular resolution in radians of the generated rotation list. - - Returns - ------- - rotation_list : numpy.array - Rotations covering the inverse pole figure given as an array of Euler - angles in degrees. - """ - # Convert the crystal directions to cartesian vectors and normalize - if len(corner_a) == 4: - corner_a = uvtw_to_uvw(corner_a) - if len(corner_b) == 4: - corner_b = uvtw_to_uvw(corner_b) - if len(corner_c) == 4: - corner_c = uvtw_to_uvw(corner_c) - - lattice = structure.lattice - - corner_a = np.dot(corner_a, lattice.stdbase) - corner_b = np.dot(corner_b, lattice.stdbase) - corner_c = np.dot(corner_c, lattice.stdbase) - - corner_a /= np.linalg.norm(corner_a) - corner_b /= np.linalg.norm(corner_b) - corner_c /= np.linalg.norm(corner_c) - - angle_a_to_b = get_angle_cartesian(corner_a, corner_b) - angle_a_to_c = get_angle_cartesian(corner_a, corner_c) - angle_b_to_c = get_angle_cartesian(corner_b, corner_c) - axis_a_to_b = np.cross(corner_a, corner_b) - axis_a_to_c = np.cross(corner_a, corner_c) - - # Input validation. The corners have to define a non-degenerate triangle - if np.count_nonzero(axis_a_to_b) == 0: - raise ValueError('Directions a and b are parallel') - if np.count_nonzero(axis_a_to_c) == 0: - raise ValueError('Directions a and c are parallel') - - rotations = [] - - # Generate a list of theta_count evenly spaced angles theta_b in the range - # [0, angle_a_to_b] and an equally long list of evenly spaced angles - # theta_c in the range[0, angle_a_to_c]. - # Ensure that we keep the resolution also along the direction to the corner - # b or c farthest away from a. - theta_count = math.ceil(max(angle_a_to_b, angle_a_to_c) / resolution) - for i, (theta_b, theta_c) in enumerate( - zip(np.linspace(0, angle_a_to_b, theta_count), - np.linspace(0, angle_a_to_c, theta_count))): - # Define the corner local_b at a rotation theta_b from corner_a toward - # corner_b on the circle surface. Similarly, define the corner local_c - # at a rotation theta_c from corner_a toward corner_c. - - rotation_a_to_b = axangle2mat(axis_a_to_b, theta_b) - rotation_a_to_c = axangle2mat(axis_a_to_c, theta_c) - local_b = np.dot(rotation_a_to_b, corner_a) - local_c = np.dot(rotation_a_to_c, corner_a) - - # Then define an axis and a maximum rotation to create a great cicle - # arc between local_b and local_c. Ensure that this is not a degenerate - # case where local_b and local_c are coincident. - angle_local_b_to_c = get_angle_cartesian(local_b, local_c) - axis_local_b_to_c = np.cross(local_b, local_c) - if np.count_nonzero(axis_local_b_to_c) == 0: - # Theta rotation ended at the same position. First position, might - # be other cases? - axis_local_b_to_c = corner_a - axis_local_b_to_c /= np.linalg.norm(axis_local_b_to_c) - - # Generate points along the great circle arc with a distance defined by - # resolution. - phi_count_local = max(math.ceil(angle_local_b_to_c / resolution), 1) - for j, phi in enumerate( - np.linspace(0, angle_local_b_to_c, phi_count_local)): - rotation_phi = axangle2mat(axis_local_b_to_c, phi) - - for k, psi in enumerate(inplane_rotations): - # Combine the rotations. Order is important. The matrix is - # applied from the left, and we rotate by theta first toward - # local_b, then across the triangle toward local_c - rotation = list(mat2euler(rotation_phi @ rotation_a_to_b, 'rzxz')) - rotations.append(np.rad2deg([rotation[0], rotation[1], psi])) - - return np.unique(rotations, axis=0)