"""Module providing objects for working with reference reflections and orientations."""
import dataclasses
from typing import Any, Dict, List, Tuple, Union
from diffcalc.hkl.geometry import Position
[docs]@dataclasses.dataclass
class Reflection:
"""Class containing reference reflection information.
Attributes
----------
h: float
h miller index.
k: float
k miller index.
l: float
l miller index.
pos: Position
Diffractometer position object.
energy: float
Beam energy in keV.
tag: str
Identifying tag for the reflection.
"""
h: float
k: float
l: float
pos: Position
energy: float
tag: str
def __post_init__(self):
"""Check input argument types.
Raises
------
TypeError
If pos argument has invalid type.
"""
if not isinstance(self.pos, Position):
raise TypeError(f"Invalid position object type {type(self.pos)}.")
@property
def astuple(
self,
) -> Tuple[
Tuple[float, float, float],
Tuple[float, float, float, float, float, float],
float,
str,
]:
"""Return reference reflection data as tuple.
Returns
-------
Tuple[Tuple[float, float, float],
Tuple[float, float, float, float, float, float],
float,
str]
Tuple containing miller indices, position object, energy and
reflection tag.
"""
h, k, l, pos, en, tag = dataclasses.astuple(self)
return (h, k, l), pos.astuple, en, tag
@property
def asdict(self) -> Dict[str, Any]:
"""Serialise the object into a JSON compatible dictionary.
Returns
-------
Dict[str, Any]
Dictionary containing properties of this class. Can
be unpacked to recreate the object using fromdict
class method.
"""
class_info = self.__dict__.copy()
class_info["pos"] = self.pos.asdict
return class_info
[docs] @classmethod
def fromdict(cls, data: Dict[str, Any]) -> "Reflection":
"""Construct Reflection instance from a JSON compatible dictionary.
Parameters
----------
data: Dict[str, Any]
Dictionary containing properties of reflection class, must have the
equivalent structure of asdict property above.
Returns
-------
Reflection
Instance of this class created from the dictionary.
"""
return cls(
data["h"],
data["k"],
data["l"],
Position(**data["pos"]),
data["energy"],
data["tag"],
)
[docs]class ReflectionList:
"""Class containing collection of reference reflections.
Attributes
----------
reflections: List[Reflection]
List containing reference reflections.
"""
def __init__(self, reflections=None):
self.reflections: List[Reflection] = reflections if reflections else []
[docs] def get_tag_index(self, tag: str) -> int:
"""Get a reference reflection index.
Get a reference reflection index for the provided reflection tag.
Parameters
----------
tag : str
Identifying tag for the reflection.
Returns
-------
int:
The reference reflection index.
Raises
------
ValueError
If tag not found in reflection list.
"""
_tag_list = [ref.tag for ref in self.reflections]
num = _tag_list.index(tag)
return num
[docs] def add_reflection(
self, hkl: Tuple[float, float, float], pos: Position, energy: float, tag: str
) -> None:
"""Add a reference reflection.
Adds a reference reflection object to the reflection list.
Parameters
----------
hkl : Tuple[float, float, float]
Miller indices of the reflection.
pos: Position
Object representing diffractometer angles.
energy : float
Energy of the x-ray beam.
tag : str
Identifying tag for the reflection.
"""
self.reflections += [Reflection(*hkl, pos, energy, tag)]
[docs] def edit_reflection(
self,
idx: Union[str, int],
hkl: Tuple[float, float, float],
pos: Position,
energy: float,
tag: str,
) -> None:
"""Change a reference reflection.
Changes the reference reflection object in the reflection list.
Parameters
----------
idx : Union[str, int]
Index or tag of the reflection to be changed.
hkl : Tuple[float,float,float]
Miller indices of the reflection.
position: Position
Object representing diffractometer angles.
energy : float
Energy of the x-ray beam.
tag : str
Identifying tag for the reflection.
Raises
------
ValueError
Reflection with specified tag not found.
IndexError
Reflection with specified index not found.
"""
if isinstance(idx, str):
num = self.get_tag_index(idx)
else:
num = idx - 1
if isinstance(pos, Position):
self.reflections[num] = Reflection(*hkl, pos, energy, tag)
else:
raise TypeError("Invalid position parameter type")
[docs] def get_reflection(self, idx: Union[str, int]) -> Reflection:
"""Get a reference reflection.
Get an object representing reference reflection.
Parameters
----------
idx : Union[str, int]
Index or tag of the reflection.
Returns
-------
Reflection
Object representing reference reflection.
Raises
------
ValueError
Reflection with the requested index/tan not present.
IndexError
Reflection with specified index not found.
"""
if isinstance(idx, str):
num = self.get_tag_index(idx)
else:
num = idx - 1
return self.reflections[num]
[docs] def remove_reflection(self, idx: Union[str, int]) -> None:
"""Delete a reference reflection.
Parameters
----------
idx : Union[str, int]
Index or tag of the deleted reflection.
Raises
------
ValueError
Reflection with the requested index/tag not present.
IndexError
Reflection with specified index not found.
"""
if isinstance(idx, str):
num = self.get_tag_index(idx)
else:
num = idx - 1
del self.reflections[num]
[docs] def swap_reflections(self, idx1: Union[str, int], idx2: Union[str, int]) -> None:
"""Swap indices of two reference reflections.
Parameters
----------
idx1 : Union[str, int]
Index or tag of the first reflection to be swapped.
idx2 : Union[str, int]
Index or tag of the second reflection to be swapped.
Raises
------
ValueError
Reflection with the requested index/tag not present.
IndexError
Reflection with specified index not found.
"""
if isinstance(idx1, str):
num1 = self.get_tag_index(idx1)
else:
num1 = idx1 - 1
if isinstance(idx2, str):
num2 = self.get_tag_index(idx2)
else:
num2 = idx2 - 1
orig1 = self.reflections[num1]
self.reflections[num1] = self.reflections[num2]
self.reflections[num2] = orig1
def __len__(self) -> int:
"""Return number of reference reflections in the list.
Returns
-------
int
Number of reference reflections.
"""
return len(self.reflections)
def __str__(self) -> str:
"""Represent the reference reflection list as a string.
Returns
-------
str
Table containing list of all reflections.
"""
return "\n".join(self._str_lines())
def _str_lines(self) -> List[str]:
"""Table with reference reflection data.
Returns
-------
List[str]
List containing reference reflection table rows.
"""
axes = tuple(fd.upper() for fd in Position.fields)
if not self.reflections:
return [" <<< none specified >>>"]
lines = []
fmt = " %6s %5s %5s %5s " + "%8s " * len(axes) + " TAG"
header_values = ("ENERGY", "H", "K", "L") + axes
lines.append(fmt % header_values)
for n in range(1, len(self.reflections) + 1):
ref_tuple = self.get_reflection(n)
(h, k, l), pos, energy, tag = ref_tuple.astuple
if tag is None:
tag = ""
fmt = " %2d %6.3f % 4.2f % 4.2f % 4.2f " + "% 8.4f " * len(axes) + " %s"
values = (n, energy, h, k, l) + pos + (tag,)
lines.append(fmt % values)
return lines
@property
def asdict(self) -> List[Dict[str, Any]]:
"""Serialise the object into a JSON compatible dictionary.
Returns
-------
Dict[str, Any]
Dictionary containing properties of this class. Can
be unpacked to recreate this object using fromdict
class method below.
"""
return [ref.asdict for ref in self.reflections]
[docs] @classmethod
def fromdict(cls, data: List[Dict[str, Any]]) -> "ReflectionList":
"""Construct ReflectionList instance from a JSON compatible dictionary.
Parameters
----------
data: Dict[str, Any]
Dictionary containing properties of this class, must have the
equivalent structure of asdict property above.
Returns
-------
ReflectionList
Instance of this class created from the dictionary.
"""
reflections = [Reflection.fromdict(each_ref) for each_ref in data]
return cls(reflections)
[docs]@dataclasses.dataclass
class Orientation:
"""Class containing reference orientation information.
Attributes
----------
h: float
h miller index.
k: float
k miller index.
l: float
l miller index.
x: float
x coordinate in laboratory system.
y: float
y coordinate in laboratory system.
z: float
z coordinate in laboratory system.
pos: Position
Diffractometer position object.
tag: str
Identifying tag for the orientation.
"""
h: float
k: float
l: float
x: float
y: float
z: float
pos: Position
tag: str
def __post_init__(self):
"""Check input argument types.
Raises
------
TypeError
If pos argument has invalid type.
"""
if not isinstance(self.pos, Position):
raise TypeError(f"Invalid position object type {type(self.pos)}.")
@property
def astuple(
self,
) -> Tuple[
Tuple[float, float, float],
Tuple[float, float, float],
Tuple[float, float, float, float, float, float],
str,
]:
"""Return reference orientation data as tuple.
Returns
-------
Tuple[Tuple[float, float, float],
Tuple[float, float, float],
Tuple[float, float, float, float, float, float],
str]
Tuple containing miller indices, laboratory frame coordinates,
position object and orientation tag.
"""
h, k, l, x, y, z, pos, tag = dataclasses.astuple(self)
return (h, k, l), (x, y, z), pos.astuple, tag
@property
def asdict(self) -> Dict[str, Any]:
"""Serialise the object into a JSON compatible dictionary.
Returns
-------
Dict[str, Any]
Dictionary containing properties of this class. Can
be unpacked to recreate this object using fromdict
class method below.
"""
class_info = self.__dict__.copy()
class_info["pos"] = self.pos.asdict
return class_info
[docs] @classmethod
def fromdict(cls, data: Dict[str, Any]) -> "Orientation":
"""Construct Orientation instance from a JSON compatible dictionary.
Parameters
----------
data: Dict[str, Any]
Dictionary containing properties of this class, must have the
equivalent structure to the asdict property.
Returns
-------
Orientation
Instance of this class created from the dictionary.
"""
return cls(
data["h"],
data["k"],
data["l"],
data["x"],
data["y"],
data["z"],
Position(**data["pos"]),
data["tag"],
)
[docs]class OrientationList:
"""Class containing collection of reference orientations.
Attributes
----------
reflections: List[Orientation]
List containing reference orientations.
"""
def __init__(self, orientations=None):
self.orientations: List[Orientation] = orientations if orientations else []
[docs] def get_tag_index(self, tag: str) -> int:
"""Get a reference orientation index.
Get a reference orientation index for the provided orientation tag.
Parameters
----------
tag : str
Identifying tag for the orientation
Returns
-------
int:
The reference orientation index.
Raises
------
ValueError
If tag not found in orientations list.
"""
_tag_list = [orient.tag for orient in self.orientations]
num = _tag_list.index(tag)
return num
[docs] def add_orientation(
self,
hkl: Tuple[float, float, float],
xyz: Tuple[float, float, float],
pos: Position,
tag: str,
) -> None:
"""Add a reference orientation.
Adds a reference orientation in the external diffractometer
coordinate system.
Parameters
----------
hkl : Tuple[float, float, float]
Miller index of the reference orientation.
xyz : Tuple[float, float, float]
Laboratory frame coordinates of the reference orientation.
position: Position
Object representing diffractometer position.
tag : str
Identifying tag for the orientation.
"""
if isinstance(pos, Position):
self.orientations += [Orientation(*hkl, *xyz, pos, tag)]
else:
raise TypeError("Invalid position parameter type")
[docs] def edit_orientation(
self,
idx: Union[str, int],
hkl: Tuple[float, float, float],
xyz: Tuple[float, float, float],
pos: Position,
tag: str,
) -> None:
"""Change a reference orientation.
Changes a reference orientation in the external diffractometer
coordinate system.
Parameters
----------
idx : str or int
Index or tag of the orientation to be changed.
hkl : Tuple[float, float, float]
Miller indices of the reference orientation.
xyz : :Tuple[float, float, float]
Laboratory frame coordinates of the reference orientation.
pos: Position
Object representing diffractometer position.
tag : str
Identifying tag for the orientation.
Raises
------
ValueError
Orientation with specified tag not found.
IndexError
Orientation with specified index not found.
"""
if isinstance(idx, str):
num = self.get_tag_index(idx)
else:
num = idx - 1
if isinstance(pos, Position):
self.orientations[num] = Orientation(*hkl, *xyz, pos, tag)
else:
raise TypeError(f"Invalid position parameter type {type(pos)}")
[docs] def get_orientation(self, idx: Union[str, int]) -> Orientation:
"""Get a reference orientation.
Get an object representing reference orientation.
Parameters
----------
idx : Union[str, int]
Index or tag of the orientation.
Returns
-------
Orientation
Object representing reference orientation.
Raises
------
ValueError
Orientation with the requested index/tag not present.
IndexError
Orientation with specified index not found.
"""
if isinstance(idx, str):
num = self.get_tag_index(idx)
else:
num = idx - 1
return self.orientations[num]
[docs] def remove_orientation(self, idx: Union[str, int]) -> None:
"""Delete a reference orientation.
Parameters
----------
idx : Union[str, int]
Index or tag of the deleted orientation.
Raises
------
ValueError
Orientation with the requested index/tag not present.
IndexError
Orientation with specified index not found.
"""
if isinstance(idx, str):
num = self.get_tag_index(idx)
else:
num = idx - 1
del self.orientations[num]
[docs] def swap_orientations(self, idx1: Union[str, int], idx2: Union[str, int]) -> None:
"""Swap indices of two reference orientations.
Parameters
----------
idx1 : Union[str, int]
Index or tag of the first orientation to be swapped.
idx2 : Union[str, int]
Index or tag of the second orientation to be swapped.
Raises
------
ValueError
Orientation with the requested index/tag not present.
IndexError
Orientation with specified index not found.
"""
if isinstance(idx1, str):
num1 = self.get_tag_index(idx1)
else:
num1 = idx1 - 1
if isinstance(idx2, str):
num2 = self.get_tag_index(idx2)
else:
num2 = idx2 - 1
orig1 = self.orientations[num1]
self.orientations[num1] = self.orientations[num2]
self.orientations[num2] = orig1
def __len__(self) -> int:
"""Return number of reference orientations in the list.
Returns
-------
int
Number of reference orientations.
"""
return len(self.orientations)
def __str__(self) -> str:
"""Represent the reference orientations list as a string.
Returns
-------
str
Table containing list of all orientations.
"""
return "\n".join(self._str_lines())
def _str_lines(self) -> List[str]:
"""Table with reference orientations data.
Returns
-------
List[str]
List containing reference orientations table rows.
"""
axes = tuple(fd.upper() for fd in Position.fields)
if not self.orientations:
return [" <<< none specified >>>"]
lines = []
str_format = " %5s %5s %5s %5s %5s %5s " + "%8s " * len(axes) + " TAG"
header_values = ("H", "K", "L", "X", "Y", "Z") + axes
lines.append(str_format % header_values)
for n in range(1, len(self.orientations) + 1):
orient = self.get_orientation(n)
(h, k, l), (x, y, z), angles, tag = orient.astuple
if tag is None:
tag = ""
str_format = (
" %2d % 4.2f % 4.2f % 4.2f "
+ "% 4.2f % 4.2f % 4.2f "
+ "% 8.4f " * len(axes)
+ " %s"
)
values = (n, h, k, l, x, y, z) + angles + (tag,)
lines.append(str_format % values)
return lines
@property
def asdict(self) -> List[Dict[str, Any]]:
"""Serialise the object into a JSON compatible dictionary.
Returns
-------
Dict[str, Any]
Dictionary containing properties of this class. Can
be unpacked to recreate this object using fromdict
class method below.
"""
return [orient.asdict for orient in self.orientations]
[docs] @classmethod
def fromdict(cls, data: List[Dict[str, Any]]) -> "OrientationList":
"""Construct OrientationList instance from a JSON compatible dictionary.
Parameters
----------
data: Dict[str, Any]
Dictionary containing properties of this class, must have the
equivalent structure to the asdict property.
Returns
-------
OrientationList
Instance of this class created from the dictionary.
"""
orientations = [Orientation.fromdict(each_orient) for each_orient in data]
return cls(orientations)