Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
0c1018c
First commit of zap_map_generator
pc494 Jan 30, 2020
6c412b5
Copy+Paste of developmental rotation_finder
pc494 Jan 30, 2020
104e869
Tidying the scope up a bit
pc494 Jan 30, 2020
5020c22
Merge branch 'feature-zap-map' of https://github.com/pc494/diffsims i…
pc494 Jan 30, 2020
178a694
Scopes out tests for get_rotation_from_z
pc494 Jan 30, 2020
2fce4a0
[skip ci] fix typo
pc494 Jan 30, 2020
fc052f6
Expanding the zap_map_generator a little
pc494 Jan 31, 2020
4fba6f0
[skip ci] Sizeable scope for zap_map_generator
pc494 Jan 31, 2020
742d9b3
Merge pull request #4 from pc494/zap-map-2
pc494 Feb 3, 2020
d1322e3
Implement three_on_widest
pc494 Feb 3, 2020
aeede3a
Rename and tidy get_sensible
pc494 Feb 3, 2020
df660fa
Adding a few more docstrings
pc494 Feb 3, 2020
8272d59
Tidying up syntax on tests
pc494 Feb 3, 2020
d333e58
Test induced correction
pc494 Feb 3, 2020
ad706fc
Testing works for all but class case
pc494 Feb 3, 2020
48d1b77
Gets the tests running
pc494 Feb 4, 2020
41c82a8
Additional docstrings
pc494 Feb 4, 2020
606a4dd
Tidying up orthonormal tests
pc494 Feb 4, 2020
ff4d531
setting the actual zap generation
pc494 Feb 4, 2020
f59d1d8
hacking get_grid_around_beam_direction apart
pc494 Feb 4, 2020
4590d38
In 2D a resolution is actually a easy concept
pc494 Feb 4, 2020
0c53694
[ci skip] Various docstring adds before final stages
pc494 Feb 4, 2020
94239ad
Adds some marks for deletion
pc494 Feb 4, 2020
eb65eaa
Reducing the number of inferences made
pc494 Feb 4, 2020
22b1a51
Adds a default simulator
pc494 Feb 4, 2020
c3dca13
adding edge_center_and_corners functionality
pc494 Feb 4, 2020
51f4045
Testing ZAP component of the functionality
pc494 Feb 4, 2020
d544aba
Convert 4 index to 3 index
pc494 Feb 4, 2020
be0e05d
Moving to more conventional theta phi ranges
pc494 Feb 4, 2020
6c783b0
Drop triclinic case as no corners on IPZ
pc494 Feb 4, 2020
0858abe
Add assertion into the kwargs test
pc494 Feb 4, 2020
fa8cca0
Making comments PEP compliant
pc494 Feb 4, 2020
e7afd9f
Fix typo
pc494 Feb 4, 2020
00cf43f
Syntax change
pc494 Feb 4, 2020
1a34dcd
Adjusting for the edge case in which monoclinic has an absent edge ce…
pc494 Feb 4, 2020
305765f
Closes out the testing syntax
pc494 Feb 4, 2020
f83b8a0
Moving to new, correct layout for all systems
pc494 Feb 4, 2020
fe3c909
Delete superceded functionality
pc494 Feb 4, 2020
d4c46ff
Add syntax
pc494 Feb 4, 2020
6b95963
Delete bad imports
pc494 Feb 4, 2020
2ae58b5
Fixes test syntax
pc494 Feb 4, 2020
dfcdbd1
Getting the transforms3d syntax correct
pc494 Feb 4, 2020
a5dc3d6
Put beam_direction_to_euler_angles back in
pc494 Feb 4, 2020
2b7763c
Removing an already caught edge case
pc494 Feb 5, 2020
e657b53
Docstrings and comments
pc494 Feb 5, 2020
95285da
Rename get_rotation_from_z
pc494 Feb 5, 2020
e0c51ab
Docstrings
pc494 Feb 5, 2020
3ab22d2
Example for get_zap_map()
pc494 Feb 5, 2020
e7988da
Renames in testing suite
pc494 Feb 6, 2020
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
32 changes: 19 additions & 13 deletions diffsims/generators/rotation_list_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
import warnings
from itertools import product

from transforms3d.euler import euler2axangle,axangle2euler

from diffsims.utils.rotation_conversion_utils import Euler
from diffsims.utils.fundemental_zone_utils import get_proper_point_group_string, reduce_to_fundemental_zone
from diffsims.utils.gridding_utils import create_linearly_spaced_array_in_rzxz,rotate_axangle, \
_create_advanced_linearly_spaced_array_in_rzxz, \
_get_rotation_to_beam_direction, get_beam_directions, beam_directions_to_euler_angles
get_beam_directions, beam_directions_to_euler_angles


def _returnable_eulers_from_axangle(grid,axis_convention,round_to):
Expand Down Expand Up @@ -82,7 +84,7 @@ def get_grid_streographic(crystal_system,resolution,equal='angle'):
Returns
-------
rotation_list : list of tuples
List of rotations
List of rotations
"""
beam_directions_rzxz = beam_directions_to_euler_angles(get_beam_directions(crystal_system,resolution,equal=equal))
beam_directions_szxz = beam_directions_rzxz.to_AxAngle().to_Euler(axis_convention='szxz') # convert to high speed convention
Expand Down Expand Up @@ -131,33 +133,37 @@ def get_local_grid(center, max_rotation, resolution):
return _returnable_eulers_from_axangle(raw_grid_axangle,'rzxz',round_to=2)


def get_grid_around_beam_direction(beam_direction,resolution, angular_range=(0, 360),cubic=False):
def get_grid_around_beam_direction(beam_rotation,resolution, angular_range=(0, 360)):
"""
Creates a rotation list of rotations for which the rotation is about given beam direction

Parameters
----------
beam_direction : [x,y,z]
A desired beam direction
beam_rotation : tuple
A desired beam direction as a rotation (rzxz eulers), usually found via get_rotation_from_z_to_direction

resolution : float
The 'resolution' of the grid (degrees)
The resolution of the grid (degrees)

angular_range : tuple
The minimum (included) and maximum (excluded) rotation around the beam direction to be included

cubic : bool
This only works for cubic systems at the present, when False this raises a warning, set to
True to supress said warning. The default is False

Returns
-------
rotation_list : list of tuples

Example
-------
>>> from diffsims.generators.zap_map_generator import get_rotation_from_z_to_direction
>>> beam_rotation = get_rotation_from_z_to_direction(structure,[1,1,1])
>>> grid = get_grid_around_beam_direction(beam_rotation,1)
"""

if not cubic:
warnings.warn("This code only works for cubic systems at present")
rotation_alpha, rotation_beta = _get_rotation_to_beam_direction(beam_direction)
beam_rotation = np.deg2rad(beam_rotation)
axangle = euler2axangle(beam_rotation[0],beam_rotation[1],beam_rotation[2],'rzxz')
euler_szxz = axangle2euler(axangle[0],axangle[1],'szxz') # convert to szxz
rotation_alpha, rotation_beta = np.rad2deg(euler_szxz[0]),np.rad2deg(euler_szxz[1])

# see _create_advanced_linearly_spaced_array_in_rzxz for details
steps_gamma = int(np.ceil((angular_range[1] - angular_range[0])/resolution))
alpha = np.asarray([rotation_alpha])
Expand Down
178 changes: 178 additions & 0 deletions diffsims/generators/zap_map_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# -*- 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 <http://www.gnu.org/licenses/>.

import numpy as np
from transforms3d.euler import axangle2euler

def get_rotation_from_z_to_direction(structure,direction):
"""
Finds the rotation that takes [001] to a given zone axis.

Parameters
----------
structure : diffpy.structure
The structure for which a rotation needs to be found

direction : array like
[UVW] direction that the 'z' axis should end up point down.

Returns
-------
euler_angles : tuple
'rzxz' in degrees

See Also
--------
generate_zap_map
get_grid_around_beam_direction

Notes
-----
This implementation works with an axis arrangement that has +x as left to right,
+y as bottom to top and +z as out of the plane of a page. Rotations are counter clockwise
as you look from the tip of the axis towards the origin
"""

# Case where we don't need a rotation, As axis is [0,0,z] or [0,0,0]
if np.dot(direction,[0,0,1]) == np.linalg.norm(direction):
return (0,0,0)

# Normalize our directions
cartesian_direction = structure.lattice.cartesian(direction)
cartesian_direction = cartesian_direction / np.linalg.norm(cartesian_direction)

#Find the rotation using cartesian vector geometry
rotation_axis = np.cross([0,0,1],cartesian_direction)
rotation_angle = np.arccos(np.dot([0,0,1],cartesian_direction))
euler = axangle2euler(rotation_axis,rotation_angle,axes='rzxz')
return np.rad2deg(euler)

def generate_directional_simulations(structure,simulator,direction_list,reciprocal_radius=1,**kwargs):
"""
Produces simualtion of a structure aligned with certain axes

Parameters
----------
structure : diffpy.structure
The structure from which simulations need to be produced

simulator : DiffractionGenerator
The diffraction generator object used to produce the simulations

direction_list : list of lists
A list of [UVW] indicies, eg) [[1,0,0],[1,1,0]]

reciprocal_radius : float
Default to 1

Returns
-------
direction_dictionary : dict
Keys are zone axes, values are simulations
"""

direction_dictionary = {}
for direction in direction_list:
if np.allclose(direction,0):
break
rotation_rzxz = get_rotation_from_z_to_direction(structure,direction)
simulation = simulator.calculate_ed_data(structure,reciprocal_radius,rotation=rotation_rzxz,**kwargs)
direction_dictionary[direction] = simulation

return direction_dictionary

def corners_to_centroid_and_edge_centers(corners):
"""
Produces the midpoints and center of a trio of corners

Parameters
----------
corners : list of lists
Three corners of a streographic triangle

Returns
-------
list_of_corners : list
Length 7, elements ca,cb,cc,mean,cab,cbc,cac where naming is such that
ca is the first corner of the input, and cab is the midpoint between
corner a and corner b.
"""
ca,cb,cc = corners[0],corners[1],corners[2]
mean = tuple(np.add(np.add(ca,cb),cc))
cab = tuple(np.add(ca,cb))
cbc = tuple(np.add(cb,cc))
cac = tuple(np.add(ca,cc))
return [ca,cb,cc,mean,cab,cbc,cac]

def generate_zap_map(structure,simulator,system='cubic',reciprocal_radius=1,density='7',**kwargs):
"""
Produces a number of zone axis patterns for a structure

Parameters
----------
structure : diffpy.structure
The structure to be simulated

simulator : DiffractionGenerator
The simulator used to generate the simulations

system : str
'cubic','hexagonal', 'trigonal', 'tetragonal','orthorhombic','monoclinic' - Defaults to 'cubic'

reciprocal_radius : float
The range of reciprocal lattice spots to be included. Default to 1

density : str
'3' for the corners or '7' (corners + midpoints + centroids). Defaults to 7

**kwargs :
keyword arguments to be passed to simulator.calculate_ed_data()

Returns
-------
zap_dictionary : dict
Keys are zone axes, values are simulations

Example
-------
Plot all of the patterns that you have generated

>>> zap_map = generate_zap_map(structure,simulator,'hexagonal',density='3')
>>> for k in zap_map.keys():
>>> pattern = zap_map[k]
>>> pattern.calibration = 4e-3
>>> plt.imshow(pattern.get_diffraction_pattern(),vmax=0.02)
>>> plt.figure()

"""

corners_dict = {'cubic': [(0, 0, 1), (1, 0, 1), (1, 1, 1)],
'hexagonal': [(0, 0, 1), (2, 1, 0), (1, 1, 0)],
'orthorhombic': [(0, 0, 1), (1, 0, 0), (0, 1, 0)],
'tetragonal': [(0, 0, 1), (1, 0, 0), (1, 1, 0)],
'trigonal': [(0, 0, 1), (-1, -2, 0), (1, -1, 0)],
'monoclinic': [(0, 0, 1), (0, 1, 0), (0, -1, 0)]}

if density == '3':
direction_list = corners_dict[system]
elif density == '7':
direction_list = corners_to_centroid_and_edge_centers(corners_dict[system])

zap_dictionary = generate_directional_simulations(structure,simulator,direction_list,**kwargs)

return zap_dictionary
10 changes: 9 additions & 1 deletion diffsims/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,24 @@
from transforms3d.euler import euler2mat

from diffsims.libraries.vector_library import DiffractionVectorLibrary
from diffsims.generators.diffraction_generator import DiffractionGenerator

@pytest.fixture
def default_structure():
"""An atomic structure represetned using diffpy
"""An atomic structure represented using diffpy
"""
latt = diffpy.structure.lattice.Lattice(3,3,5,90,90,120)
atom = diffpy.structure.atom.Atom(atype='Ni',xyz=[0,0,0],lattice=latt)
hexagonal_structure = diffpy.structure.Structure(atoms=[atom],lattice=latt)
return hexagonal_structure

@pytest.fixture
def default_simulator():
accelerating_voltage = 300
max_excitation_error = 1e-2
return DiffractionGenerator(accelerating_voltage,max_excitation_error)


@pytest.fixture()
def random_eulers():
""" Using [0,360] [0,180] and [0,360] as ranges """
Expand Down
80 changes: 80 additions & 0 deletions diffsims/tests/test_generators/test_zap_map_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# -*- 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 <http://www.gnu.org/licenses/>.

import pytest

import diffpy.structure
import numpy as np
from diffsims.generators.zap_map_generator import get_rotation_from_z_to_direction, generate_zap_map

def test_zero_rotation_cases(default_structure):
r_test = get_rotation_from_z_to_direction(default_structure,[0,0,2])
r_test_zero = get_rotation_from_z_to_direction(default_structure,[0,0,0])
assert r_test == (0,0,0)
assert r_test_zero == (0,0,0)


class TestOrthonormals:

@pytest.fixture(params=[(3,3,3),(3,3,4),(3,4,5)])
def sample_system(self,request):
"""Orthonormal structures"""
a,b,c = request.param[0],request.param[1],request.param[2]
latt = diffpy.structure.lattice.Lattice(a,b,c,90,90,90)
atom = diffpy.structure.atom.Atom(atype='Ni',xyz=[0,0,0],lattice=latt)
return diffpy.structure.Structure(atoms=[atom],lattice=latt)

def test_rotation_to__static_x_axis(self,sample_system):
r_to_x = get_rotation_from_z_to_direction(sample_system,[1,0,0])
assert np.allclose(r_to_x,(90,90,-90))

def test_rotation_to_static_y_axis(self,sample_system):
r_to_y = get_rotation_from_z_to_direction(sample_system,[0,1,0])
assert np.allclose(r_to_y,(180,90,-180))

def test_rotations_to_static_yz(self,sample_system):
""" We rotate from z towards y, and compare the results to geometry"""
r_to_yz = get_rotation_from_z_to_direction(sample_system,[0,1,1])
tan_angle = np.tan(np.deg2rad(r_to_yz[1]))
tan_lattice = sample_system.lattice.b / sample_system.lattice.c
assert np.allclose(tan_angle,tan_lattice,atol=1e-5)


@pytest.mark.parametrize('system',['cubic','hexagonal','trigonal','orthorhombic','tetragonal','monoclinic'])
def test_zap_map_all_systems(default_structure,default_simulator,system):
z_dict = generate_zap_map(default_structure,default_simulator,system=system)
assert (0,0,1) in z_dict.keys()
assert (0,0,0) not in z_dict.keys()

@pytest.mark.parametrize('density',['3','7'])
def test_zap_map_density_changes(default_structure,default_simulator,density):
""" Checks density arguments are passed correctly """
z_dict = generate_zap_map(default_structure,default_simulator,density=density)
if density == '3':
assert str(len(z_dict.keys())) == '3'
elif density == '7':
assert len(z_dict.keys()) > 5 #monoclinic case gives 6 rather than 7

def test_zap_map_kwargs(default_structure,default_simulator):
z_dict_no_beam = generate_zap_map(default_structure,default_simulator,with_direct_beam=False)
z_dict_yes_beam = generate_zap_map(default_structure,default_simulator,with_direct_beam=True)
for k in z_dict_no_beam.keys():
#both dictionary's have the same keys
assert k in z_dict_yes_beam.keys()
# no beam has one fewer spots than yes beam
assert z_dict_no_beam[k].intensities.shape[0] == z_dict_yes_beam[k].intensities.shape[0] - 1
Loading