Skip to content

Commit

Permalink
Merge pull request #93 from bluesky/86-energy-units
Browse files Browse the repository at this point in the history
Changes to energy units and offset do not affect .calc.energy
  • Loading branch information
prjemian authored Jan 7, 2021
2 parents 2b748b1 + 3392ffc commit b4b6646
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 2 deletions.
59 changes: 59 additions & 0 deletions docs/source/diffract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,65 @@ on these geometries.
.. [#hklcpp] **hkl-c++** documentation:
https://people.debian.org/~picca/hkl/hkl.html
Energy
++++++

The monochromatic X-ray energy used by the diffractometer is defined as
an ophyd ``Signal``. The ``.energy`` signal may be used as-provided, as
a constant or the Component may be replaced by an ``ophyd.EpicsSignal``
in a custom subclass of :class:`~hkl.diffract.Diffractometer`, to
connect with an EPICS PV from the instrument's control system. There is
no corresponding ``.wavelength`` signal at the diffractometer level.
The ``.energy_offset`` signal is used to allow for a difference between
the reported energy of ``.energy`` and the energy used for
diffractometer operations ``.calc.energy``.

These are the terms:

==================== =======================
term meaning
==================== =======================
``.energy`` nominal energy (such as value reported by control system)
``.energy_offset`` adjustment from nominal to calibrated energy for diffraction
``.energy_units`` engineering units to be used for ``.energy`` and ``.energy_offset``
``.calc.energy`` energy for diffraction (used to compute wavelength)
``.calc.wavelength`` used for diffraction calculations
==================== =======================

These are the relationships::

.calc.energy = in_keV(.energy + .energy_offset)
.calc.wavelength = A*keV / .calc.energy

For use with other types of radiation (such as neutrons or electrons),
the conversion between energy and wavelength must be changed by editing
the energy and wavelength methods in the :class:`hkl.calc.CalcRecip`
class.

When the Diffractometer ``.energy`` signal is written (via
``.energy.put(value)`` operation), the ``.energy_offset`` is added.
This result is converted to ``keV`` as required by the lower-level
:mod:`hkl.calc` module, and then written to ``.calc.energy`` which in
turn writes the ``.calc.wavelength`` value. Likewise, when
``.energy.get()`` reads the energy, it takes its value from
``.calc.energy``, converts into ``.energy_units``, and then applies
``.energy_offset``.

.. tip:: How to set energy, offset, and units

To change energy, offset, & units, do it in this order:

* First, set units and offset
* Finally, set energy

Example for a ``e4cv`` diffractometer::

e4cv.energy_units.put("eV")
e4cv.energy_offset.put(1.5)
e4cv.energy.put(8000)
# now, e4cv.calc.energy = 8.0015 keV


Engineering Units
+++++++++++++++++

Expand Down
56 changes: 54 additions & 2 deletions hkl/diffract.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,12 @@ def __init__(self, prefix, calc_kw=None, decision_fcn=None,
self.energy.subscribe(
self._energy_changed, event_type=Signal.SUB_VALUE)

self.energy_offset.subscribe(
self._energy_offset_changed, event_type=Signal.SUB_VALUE)

self.energy_units.subscribe(
self._energy_units_changed, event_type=Signal.SUB_VALUE)

@property
def _calc_energy_update_permitted(self):
"""return boolean `True` if permitted"""
Expand All @@ -238,9 +244,55 @@ def _energy_changed(self, value=None, **kwargs):
if self._calc_energy_update_permitted:
self._update_calc_energy(value)

def _energy_offset_changed(self, value=None, **kwargs):
"""
Callback indicating that the energy offset signal was updated.
.. note::
The ``energy_offset`` signal is subscribed to this method
in the :meth:`Diffractometer.__init__()` method.
"""
if not self.connected:
logger.warning(
(
"%s not fully connected,"
" '%s.calc.energy_offset' not updated"
),
self.name, self.name)
return

# TODO: is there a loop back through _update_calc_energy?
units = self.energy_units.get()
try:
energy = pint.Quantity(self.calc.energy, "keV").to(units)
except Exception as exc:
raise NotImplementedError(f"units = {units}: {exc}")
self.energy.put(energy.magnitude - value)

def _energy_units_changed(self, value=None, **kwargs):
"""
Callback indicating that the energy units signal was updated.
.. note::
The ``energy_units`` signal is subscribed to this method
in the :meth:`Diffractometer.__init__()` method.
"""
if not self.connected:
logger.warning(
(
"%s not fully connected,"
" '%s.calc.energy_units' not updated"
),
self.name, self.name)
return

# TODO: is there a loop back through _update_calc_energy?
energy = pint.Quantity(self.calc.energy, "keV").to(value)
self.energy.put(energy.magnitude - self.energy_offset.get())

def _update_calc_energy(self, value=None, **kwargs):
"""
writes self.calc.energy from value or self.energy
writes ``self.calc.energy`` from ``value`` or ``self.energy``.
"""
if not self.connected:
logger.warning(
Expand All @@ -249,7 +301,7 @@ def _update_calc_energy(self, value=None, **kwargs):
return

# use either supplied value or get from signal
value = value or self.energy.get()
value = float(value or self.energy.get())

# energy_offset has same units as energy
value += self.energy_offset.get()
Expand Down
24 changes: 24 additions & 0 deletions hkl/tests/test_diffract.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,30 @@ def test_energy_units(fourc):
fourc.energy.get(),
)

fourc.energy_units.put("keV")
fourc.energy.put(8)
fourc.energy_offset.put(0.015)
assert fourc.calc.energy == 8.0
assert round(fourc.energy.get(), 6) == 7.985
fourc.energy.put(8)
assert fourc.calc.energy == 8.015
assert round(fourc.energy.get(), 6) == 8

# issue #86
# changing units or offset changes .energy, not .calc.energy
fourc.energy_units.put("eV")
assert fourc.calc.energy == 8.015
assert round(fourc.energy.get(), 1) == 8015
fourc.energy.put(8000)
assert round(fourc.calc.energy, 8) == 8.000015
assert round(fourc.energy.get(), 1) == 8000
fourc.energy_offset.put(15)
assert round(fourc.calc.energy, 8) == 8.000015
assert round(fourc.energy.get(), 1) == 7985
fourc.energy.put(8000)
assert round(fourc.calc.energy, 8) == 8.015
assert round(fourc.energy.get(), 1) == 8000


def test_names(fourc):
assert fourc.geometry_name.get() == "E4CV"
Expand Down

0 comments on commit b4b6646

Please sign in to comment.