Source code for hklpy2.backends.base

"""
Backend: abstract base class

.. autosummary::

    ~SolverBase
"""

import logging
from abc import ABC
from abc import abstractmethod

from .. import __version__
from ..operations.lattice import Lattice
from ..operations.reflection import Reflection
from ..operations.sample import Sample

logger = logging.getLogger(__name__)

# TODO: move to misc
IDENTITY_MATRIX_3X3 = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]


[docs] class SolverBase(ABC): """ Base class for all |hklpy2| |solver| classes. .. rubric:: Parameters * ``geometry``: (str) Name of geometry. * ``mode``: (str) Name of operating mode. (default: current mode) Example:: import hklpy2 class MySolver(hklpy2.SolverBase): ... .. note:: :class:`~SolverBase`, an `abstract base class <https://docs.python.org/3/library/abc.html#abc.ABC>`_, cannot not be used directly by |hklpy2| users. As the parent class for all custom :index:`Solver` classes, :class:`~SolverBase` defines the methods and attributes to be written that will connect |hklpy2| with the support library that defines specific diffractometer geometries and the computations for using them. Subclasses should implement each of these methods as best fits the underlying support library. .. seealso:: :ref:`api.solvers.hkl_soleil` & :ref:`api.solvers.no_op` .. rubric:: Python Methods .. autosummary:: ~addReflection ~calculate_UB ~extra_axis_names ~forward ~geometries ~inverse ~pseudo_axis_names ~real_axis_names ~refineLattice ~removeAllReflections .. rubric:: Python Properties .. autosummary:: ~geometry ~lattice ~mode ~modes ~sample ~UB """ name = "base" """Name of this Solver.""" version = __version__ """Version of this Solver.""" def __init__( self, geometry: str, *, mode: str = "", # "": accept solver's default mode **kwargs, ) -> None: self.geometry = geometry self.mode = mode self._sample = None logger.debug("geometry=%s, kwargs=%s", repr(geometry), repr(kwargs)) def __repr__(self) -> str: # fmt: off args = [ f"{s}={getattr(self, s)!r}" for s in "name version geometry".split() ] # fmt: on return f"{self.__class__.__name__}({', '.join(args)})"
[docs] @abstractmethod def addReflection(self, reflection: Reflection) -> None: """Add coordinates of a diffraction condition (a reflection)."""
[docs] @abstractmethod def calculate_UB( self, r1: Reflection, r2: Reflection, ) -> list[list[float]]: """ Calculate the UB (orientation) matrix with two reflections. The method of Busing & Levy, Acta Cryst 22 (1967) 457. """ return self.UB
@property @abstractmethod def extra_axis_names(self) -> list[str]: """Ordered list of any extra axis names (such as x, y, z).""" # Do NOT sort. return []
[docs] @abstractmethod def forward(self, pseudos: dict) -> list[dict[str, float]]: """Compute list of solutions(reals) from pseudos (hkl -> [angles]).""" # based on geometry and mode return [{}]
[docs] @classmethod @abstractmethod def geometries(cls) -> list[str]: """ Ordered list of the geometry names. EXAMPLES:: >>> from hklpy2 import get_solver >>> Solver = get_solver("no_op") >>> Solver.geometries() [] >>> solver = Solver("TH TTH Q") >>> solver.geometries() [] """ return []
@property @abstractmethod def geometry(self) -> str: """ Name of selected diffractometer geometry. Cannot be changed once solver is created. Instead, make a new solver for each geometry. """ return self._geometry @geometry.setter @abstractmethod def geometry(self, value: str): self._geometry = value
[docs] @abstractmethod def inverse(self, reals: dict) -> dict[str, float]: """Compute tuple of pseudos from reals (angles -> hkl)."""
@property def lattice(self) -> object: """ Crystal lattice parameters. (Not used by this |solver|.) """ return self._lattice @lattice.setter def lattice(self, value: Lattice): if not isinstance(value, Lattice): raise TypeError(f"Must supply Lattice object, received {value!r}") self._lattice = value @property def mode(self) -> str: """ Diffractometer geometry operation mode for :meth:`forward()`. A mode defines which axes will be modified by the :meth:`forward` computation. """ try: self._mode except AttributeError: self._mode = "" return self._mode @mode.setter def mode(self, value: str): from .. import check_value_in_list # avoid circular import here check_value_in_list("Mode", value, self.modes, blank_ok=True) self._mode = value @property @abstractmethod def modes(self) -> list[str]: """List of the geometry operating modes.""" return [] @property @abstractmethod def pseudo_axis_names(self) -> list[str]: """Ordered list of the pseudo axis names (such as h, k, l).""" # Do NOT sort. return [] @property @abstractmethod def real_axis_names(self) -> list[str]: """Ordered list of the real axis names (such as th, tth).""" # Do NOT sort. return []
[docs] @abstractmethod def refineLattice(self, reflections: list[Reflection]) -> Lattice: """Refine the lattice parameters from a list of reflections."""
[docs] @abstractmethod def removeAllReflections(self) -> None: """Remove all reflections."""
@property def sample(self) -> object: """ Crystalline sample. """ return self._sample @sample.setter def sample(self, value: Sample): if not isinstance(value, Sample): raise TypeError(f"Must supply Sample object, received {value!r}") self._sample = value @property def UB(self): """Orientation matrix (3x3).""" return IDENTITY_MATRIX_3X3