"""
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()