pyextal.acom.library ==================== .. py:module:: pyextal.acom.library .. autoapi-nested-parse:: Reflection libraries for a candidate orientation. A library is the set of reflections a given zone axis puts on the detector, together with their in-plane coordinates and a kinematic strength. Building one costs only metric-tensor arithmetic, which is what makes searching over orientations affordable. Classes ------- .. autoapisummary:: pyextal.acom.library.ReflectionLibrary Functions --------- .. autoapisummary:: pyextal.acom.library.candidate_zone_axes pyextal.acom.library._enumerate_hkl pyextal.acom.library.frame_gmax pyextal.acom.library.auto_max_index pyextal.acom.library.build_library pyextal.acom.library.visible_holz pyextal.acom.library.min_laue_max pyextal.acom.library.nearest_zone_axis Module Contents --------------- .. py:class:: ReflectionLibrary Reflections of one zone axis, projected onto the detector plane. :ivar zone: The zone axis, direct-space indices. :vartype zone: tuple[int, int, int] :ivar xaxis: Reflection defining the in-plane x axis. :vartype xaxis: tuple[int, int, int] :ivar hkl: (n, 3) int Miller indices. :vartype hkl: np.ndarray :ivar xy: (n, 2) in-plane coordinates, inverse Angstroms. :vartype xy: np.ndarray :ivar gmag: (n,) |g| in inverse Angstroms. :vartype gmag: np.ndarray :ivar laue: (n,) Laue zone index, ``g . zone``; 0 is ZOLZ. :vartype laue: np.ndarray :ivar weight: (n,) kinematic strength |U_g|, 0 for the direct beam. :vartype weight: np.ndarray :ivar e1: In-plane x unit vector, Cartesian. :vartype e1: np.ndarray :ivar e2: In-plane y unit vector, Cartesian. :vartype e2: np.ndarray :ivar n: Zone-axis unit vector, Cartesian. :vartype n: np.ndarray .. py:attribute:: zone :type: tuple .. py:attribute:: xaxis :type: tuple .. py:attribute:: hkl :type: numpy.ndarray .. py:attribute:: xy :type: numpy.ndarray .. py:attribute:: gmag :type: numpy.ndarray .. py:attribute:: laue :type: numpy.ndarray .. py:attribute:: weight :type: numpy.ndarray .. py:attribute:: e1 :type: numpy.ndarray .. py:attribute:: e2 :type: numpy.ndarray .. py:attribute:: n :type: numpy.ndarray .. py:method:: __len__() .. py:method:: select(mask) Returns a new library keeping only the reflections `mask` selects. .. py:method:: strong(rel=1e-06) The subset whose kinematic weight is worth matching against. Extinct and near-extinct reflections are dropped: they may or may not be visible depending on the multiple scattering, so requiring a peak at one, or allowing one to explain a peak, both mislead the orientation search. The direct beam is always kept. :param rel: Keep |U_g| above this fraction of the strongest. Defaults to 1e-6. :type rel: float, optional :returns: The filtered view. :rtype: ReflectionLibrary .. py:property:: zolz Only the zero-order Laue zone. :type: ReflectionLibrary .. py:function:: candidate_zone_axes(max_index=3) Enumerates primitive low-index zone axes. Sign-equivalent axes ([uvw] and [-u-v-w]) give the same pattern, so only one of each pair is returned. Symmetry-equivalent axes are *not* removed: they score identically, and that degeneracy is a real property of the measurement worth reporting rather than hiding. :param max_index: Largest absolute index considered. Defaults to 3. :type max_index: int, optional :returns: Candidate zone axes, shortest first. :rtype: list[tuple[int, int, int]] .. py:function:: _enumerate_hkl(max_index) All integer hkl in a cube, including (0,0,0). The direct beam belongs in the library: it anchors the detector centre, and the engine's own beam list always carries it, so a `.dat` written from a library without it would be rejected. .. py:function:: frame_gmax(mapping, shape) Largest |g| the detector can reach, in inverse Angstroms. :param mapping: The fitted detector mapping. :type mapping: DetectorMap :param shape: Detector shape. :type shape: tuple[int, int] :returns: Distance from the direct beam to the furthest frame corner. :rtype: float .. py:function:: auto_max_index(gmx, gmax) Miller search box big enough to reach `gmax`. A reflection (h00) has |g| = h / a, so the box has to grow with the longest cell edge. Too small a box silently truncates the outer reflections. :param gmx: (3, 3) direct-space metric tensor. :type gmx: np.ndarray :param gmax: Largest |g| wanted, inverse Angstroms. :type gmax: float :returns: Half-width of the index box. :rtype: int .. py:function:: build_library(param, zone, gmax=2.0, max_index=8, laue_max=0, xaxis=None, weights=True, min_weight_rel=0.0) Builds the reflection library for one zone axis. :param param: Parsed simulation parameters; supplies the metric tensors and |K|. :type param: SimParams :param zone: Zone axis, direct-space indices. :type zone: tuple[int, int, int] :param gmax: Largest |g| to include, inverse Angstroms. Defaults to 2.0. :type gmax: float, optional :param max_index: Search box for Miller indices. Defaults to 8. :type max_index: int, optional :param laue_max: Highest Laue zone to include. 0 keeps the ZOLZ only; 1 adds FOLZ, and so on. Defaults to 0. :type laue_max: int, optional :param xaxis: Reflection defining the in-plane x axis. The shortest in-plane reflection is chosen when None; the choice only sets where the fitted rotation angle is measured from. :type xaxis: tuple[int, int, int], optional :param weights: Look up kinematic |U_g| for each reflection. Defaults to True. :type weights: bool, optional :param min_weight_rel: Drop reflections whose |U_g| falls below this fraction of the strongest. Defaults to 0.0, i.e. keep everything. In electron diffraction a kinematically forbidden reflection still carries intensity, because multiple scattering routes electrons into it -- SrTiO3 (100) is nearly extinct kinematically yet plainly visible. Such reflections therefore have to be predicted and integrated. They are useless for *finding* the orientation, though, since whether they appear is not something the kinematic weight can say; use :meth:`ReflectionLibrary.strong` for the matching subset. :type min_weight_rel: float, optional :returns: Reflections with |g| <= `gmax` in the kept Laue zones. :rtype: ReflectionLibrary :raises ValueError: If the zone axis admits no in-plane reflection within `gmax`. .. py:function:: visible_holz(param, lib, mapping, shape, smax=0.05, laue_max=3, gmax=None, max_index=8) Finds higher-order-Laue-zone reflections that land on the detector. Run this once the ZOLZ orientation is settled. A HOLZ reflection is kept when its predicted pixel falls inside the frame and, if `smax` is given, when its excitation error is small enough for it to be excited at all. :param param: Parsed simulation parameters. :type param: SimParams :param lib: The solved ZOLZ library; supplies zone and axes. :type lib: ReflectionLibrary :param mapping: The fitted detector mapping. :type mapping: DetectorMap :param shape: Detector shape (n_x, n_y). :type shape: tuple[int, int] :param smax: Largest |s_g| accepted, inverse Angstroms. Defaults to 0.05. Passing None drops the filter, which is rarely what you want: a reflection can project onto the frame while sitting far off the Ewald sphere, so the unfiltered count is dominated by beams that are never excited. For a zone axis the first Laue ring sits at roughly sqrt(2 |K| H), H being the reciprocal layer spacing -- if that exceeds the detector's reach, no HOLZ is excited at all. :type smax: float, optional :param laue_max: Highest Laue zone to consider. Defaults to 3. :type laue_max: int, optional :param gmax: Largest |g|; defaults to what the frame can hold. :type gmax: float, optional :param max_index: Search box for Miller indices. Defaults to 8. :type max_index: int, optional :returns: The HOLZ reflections predicted to be on the frame. :rtype: ReflectionLibrary .. py:function:: min_laue_max(param, zone, gmax, max_index=6, cover=0.8, limit=40) Laue zones a zone axis needs before its library forms a 2D net. A reflection's Laue index is ``n = g . [uvw]``. For a low-index axis the reflections inside a frame nearly all have ``n = 0``, so the ZOLZ alone is a 2D net and ``laue_max=0`` is right. For a high-index axis they are spread over many zones and the ZOLZ collapses: the [1,1,15] ZOLZ of SrTiO3 inside 1.2 A^-1 holds seven reflections, every one on the single row ``(n,-n,0)``. Fitting a 2D net to a 1D row is what produced a scale 1.4x too small and indexed (200) as (1,-1,0). This returns the smallest ``laue_max`` whose library spans two dimensions out to `cover` of the requested `gmax`. Counting instead to ``gmax * |[uvw]|`` -- the largest Laue index any reflection inside `gmax` can carry -- gives 71 here rather than 6, because it counts reflections lying nearly along the zone axis, which project onto the pattern centre and form no part of the net. :param param: Parsed simulation parameters. :type param: SimParams :param zone: (3,) zone axis, integer Miller indices. :type zone: array-like :param gmax: Reflection library extent, A^-1. :type gmax: float :param max_index: Miller index bound. Defaults to 6. :type max_index: int, optional :param cover: Fraction of `gmax` both in-plane directions must reach. Defaults to 0.8. :type cover: float, optional :param limit: Give up above this. Defaults to 40. :type limit: int, optional :returns: Smallest workable ``laue_max``, or `limit` if none was found. :rtype: int .. note:: Raising `laue_max` weakens the zone-axis search in exchange. The ZOLZ restriction is what makes different axes predict different nets; a library carrying every Laue zone projects to something dense enough to match almost anything. Raise it to test a specific high-index axis against a solution you already have, not to widen a blind search. .. py:function:: nearest_zone_axis(param, direction, max_index=20, tol_deg=None) Rational zone axes closest to a beam direction. A pattern taken off zone has a beam direction that is not, in general, along any lattice row. Naming a high-index axis for it means finding the rational directions that come closest -- and there are usually several at nearly the same angle, so this returns a ranked list rather than an answer. :param param: Parsed simulation parameters. :type param: SimParams :param direction: (3,) beam direction in crystal coordinates, as ``zone`` tilted by the refined ``tiltX``/``tiltY``. Need not be normalised or rational. :type direction: array-like :param max_index: Largest index to enumerate. Defaults to 20. :type max_index: int, optional :param tol_deg: Keep only axes within this angle. All are ranked when None. :type tol_deg: float, optional :returns: ``(uvw, angle_deg)``, closest first. :rtype: list[tuple] .. note:: This is bookkeeping, not a measurement. The projected net of a tilted pattern is the net of the *low-index* axis it is near, and every rational direction close to that axis reproduces the same reflections with the same Miller indices -- verified on the SrTiO3 scan taken 4 degrees off [001], where [1,1,15] with enough Laue zones labelled all 23 shared peaks exactly as [001] did. What the geometry cannot do is say which of them the beam was along. That comes from excitation -- a visible HOLZ ring, or the tilt that :class:`pyextal.optimize.CoarseOptimize` refines against the dynamical simulation.