Source code for hkl.user

"""
Provide a simplified interface for |hklpy| diffractometer users.

The user must define a diffractometer object, then
register that object here.  For example::

    from hkl import SimulatedE4CV
    from hkl.user import *

    e4cv = SimulatedE4CV("", name="e4cv")
    select_diffractometer(e4cv)
    wh()

FUNCTIONS

.. autosummary::

    ~cahkl
    ~cahkl_table
    ~calc_UB
    ~current_diffractometer
    ~list_samples
    ~new_sample
    ~or_swap
    ~select_diffractometer
    ~set_energy
    ~setor
    ~show_sample
    ~show_selected_diffractometer
    ~update_sample
    ~wh
    ~pa
"""

__all__ = """
    cahkl
    cahkl_table
    calc_UB
    change_sample
    current_diffractometer
    list_samples
    new_sample
    or_swap
    pa
    select_diffractometer
    set_energy
    setor
    show_sample
    show_selected_diffractometer
    update_sample
    wh
""".split()

import logging

logger = logging.getLogger(__name__)

import numpy
import pyRestTable

from .diffract import Diffractometer
from .util import Lattice

_geom_ = None  # selected diffractometer geometry


def _check_geom_selected(*args, **kwargs):
    """Raise ValueError if no diffractometer geometry is selected."""
    if _geom_ is None:
        raise ValueError(
            "No diffractometer selected."
            " Call 'select_diffractometer(diffr)' where"
            " 'diffr' is a diffractometer instance."
        )


[docs] def cahkl(h, k, l): """ Calculate motor positions for one reflection. Returns a namedtuple. Does not move motors. """ _check_geom_selected() # TODO: make certain this will not move the motors! return _geom_.forward(h, k, l)
[docs] def cahkl_table(reflections, digits=5): """ Print a table with motor positions for each reflection given. Parameters ---------- reflections : list(tuple(number,number,number)) This is a list of reflections where each reflection is a tuple of 3 numbers specifying (h, k, l) of the reflection to compute the ``forward()`` computation. Example: ``[(1,0,0), (1,1,1)]`` digits : int Number of digits to roundoff each position value. Default is 5. """ _check_geom_selected() print(_geom_.forward_solutions_table(reflections, digits=digits))
# def calc_energy(): # # TODO: should this be added? # raise NotImplementedError
[docs] def calc_UB(r1, r2, wavelength=None): """Compute the UB matrix with two reflections.""" _check_geom_selected() _geom_.calc.sample.compute_UB(r1, r2) print(_geom_.calc.sample.UB)
[docs] def change_sample(sample): """Pick a known sample to be the current selection.""" _check_geom_selected() if sample not in _geom_.calc._samples: # fmt: off raise KeyError( f"Sample '{sample}' is unknown." f" Known samples: {list(_geom_.calc._samples.keys())}" ) # fmt: on _geom_.calc.sample = sample show_sample(sample)
[docs] def current_diffractometer(): """Return the currently-selected diffractometer (or ``None``).""" return _geom_
[docs] def list_samples(verbose=True): """List all defined crystal samples.""" _check_geom_selected() # always show the default sample first current_name = _geom_.calc.sample_name show_sample(current_name, verbose=verbose) # now, show any other samples for sample in _geom_.calc._samples.keys(): if sample != current_name: if verbose: print("") show_sample(sample, verbose=verbose)
[docs] def new_sample(nm, a, b, c, alpha, beta, gamma): """Define a new crystal sample.""" _check_geom_selected() if nm in _geom_.calc._samples: logger.warning( ( "Sample '%s' is already defined." " Use 'update_sample()' to change lattice parameters" " on the *current* sample." ), nm, ) else: lattice = Lattice(a=a, b=b, c=c, alpha=alpha, beta=beta, gamma=gamma) _geom_.calc.new_sample(nm, lattice=lattice) show_sample()
[docs] def or_swap(): """ Swap the 2 [UB] reflections, re-compute & return new [UB]. Example:: # define 2 reflections r400 = hkl.user.setor(4, 0, 0, tth=69.0966, omega=-145.451, chi=0, phi=0, wavelength=1.54) r040 = hkl.user.setor(0, 4, 0, tth=69.0966, omega=-145.451, chi=0, phi=90, wavelength=1.54) # calculate UB hkl.user.calc_UB(r400, r040) # swap the two reflections (and recalculate UB) hkl.user.or_swap() """ # note: or_swap is how the community of SPEC users knows this function _check_geom_selected() return _geom_.calc.sample.swap_orientation_reflections()
[docs] def select_diffractometer(instrument=None): """Name the diffractometer to be used.""" global _geom_ if instrument is None or isinstance(instrument, Diffractometer): _geom_ = instrument else: raise TypeError(f"{instrument} must be a 'Diffractometer' subclass")
[docs] def set_energy(value, units=None, offset=None): """ Set the energy (thus wavelength) to be used. """ _check_geom_selected() if units is not None: _geom_.energy_units.put(units) if offset is not None: _geom_.energy_offset.put(offset) _geom_.energy.put(value)
[docs] def setor(h, k, l, *args, wavelength=None, **kwargs): """Define a crystal reflection and its motor positions.""" _check_geom_selected() if len(args) == 0: if len(kwargs) == 0: pos = _geom_.real_position else: # fmt: off pos = [ kwargs[m] for m in _geom_.calc.physical_axis_names if m in kwargs ] # fmt: on else: pos = args # NOTE: libhkl gets the wavelength on a reflection from hkl.calc # when the wavelength is set, it calls libhkl directly # as self._geometry.wavelength_set(wavelength, self._units) # The code here uses that procedure. if wavelength not in (None, 0): _geom_.calc.wavelength = wavelength refl = _geom_.calc.sample.add_reflection(h, k, l, position=pos) return refl
[docs] def show_sample(sample_name=None, verbose=True): """Print the default sample name and crystal lattice.""" _check_geom_selected() sample_name = sample_name or _geom_.calc.sample_name sample = _geom_.calc._samples[sample_name] title = sample_name if sample_name == _geom_.calc.sample.name: title += " (*)" # Print Lattice more simply (than as a namedtuple). lattice = [getattr(sample.lattice, parm) for parm in sample.lattice._fields] if verbose: tbl = pyRestTable.Table() tbl.addLabel("key") tbl.addLabel("value") tbl.addRow(("name", sample_name)) tbl.addRow(("lattice", lattice)) for i, r in enumerate(sample.reflections): tbl.addRow((f"reflection {i+1}", r)) tbl.addRow(("U", numpy.round(sample.U, 5))) tbl.addRow(("UB", numpy.round(sample.UB, 5))) print(f"Sample: {title}\n") print(tbl) else: print(f"{title}: {lattice}")
[docs] def show_selected_diffractometer(instrument=None): """Print the name of the selected diffractometer.""" geom = current_diffractometer() if geom is None: print("No diffractometer selected.") print(geom.name)
[docs] def update_sample(a, b, c, alpha, beta, gamma): """Update current sample lattice.""" _check_geom_selected() _geom_.calc.sample.lattice = ( a, b, c, alpha, beta, gamma, ) # define the current sample show_sample(_geom_.calc.sample.name, verbose=False)
[docs] def pa(): """Report (all) the diffractometer settings.""" _check_geom_selected() current_diffractometer().pa()
[docs] def wh(): """Report (brief) where is the diffractometer.""" _check_geom_selected() current_diffractometer().wh()