Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

manage diffractometer constraints #308

Merged
merged 4 commits into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions apstools/diffractometer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@

"""
add to capabilities of any diffractometer

.. autosummary::

~Constraints
~DiffractometerMixin

"""

#-----------------------------------------------------------------------------
# :author: Pete R. Jemian
# :email: jemian@anl.gov
# :copyright: (c) 2017-2020, UChicago Argonne, LLC
#
# Distributed under the terms of the Creative Commons Attribution 4.0 International Public License.
#
# The full license is in the file LICENSE.txt, distributed with this software.
#-----------------------------------------------------------------------------

__all__ = [
'Constraints',
'DiffractometerMixin',
]

import collections
import logging
from ophyd import Component, Device, PseudoSingle
import pyRestTable

logger = logging.getLogger(__file__)

Constraints = collections.namedtuple(
"Constraints",
("low_limit", "high_limit", "value", "fit"))


class DiffractometerMixin(Device):
"""
add to capabilities of any diffractometer

.. autosummary::

~applyConstraints
~forwardSolutionsTable
~resetConstraints
~showConstraints
~undoLastConstraints
"""

h = Component(PseudoSingle, '', labels=("hkl",), kind="hinted")
k = Component(PseudoSingle, '', labels=("hkl",), kind="hinted")
l = Component(PseudoSingle, '', labels=("hkl",), kind="hinted")

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._constraints_stack = []

def applyConstraints(self, constraints):
"""
constrain the diffractometer's motions

This action will first the current constraints onto
a stack, enabling both *undo* and *reset* features.
"""
self._push_current_constraints()
self._set_constraints(constraints)

def resetConstraints(self):
"""set constraints back to initial settings"""
if len(self._constraints_stack) > 0:
self._set_constraints(self._constraints_stack[0])
self._constraints_stack = []

def showConstraints(self, fmt="simple"):
"""print the current constraints in a table"""
tbl = pyRestTable.Table()
tbl.labels = "axis low_limit high_limit value fit".split()
for m in self.real_positioners._fields:
tbl.addRow((
m,
*self.calc[m].limits,
self.calc[m].value,
self.calc[m].fit))
print(tbl.reST(fmt=fmt))

def undoLastConstraints(self):
"""remove the current additional constraints, restoring previous constraints"""
if len(self._constraints_stack) > 0:
self._set_constraints(self._constraints_stack.pop())

def _push_current_constraints(self):
"""push current constraints onto the stack"""
constraints = {
m: Constraints(
*self.calc[m].limits,
self.calc[m].value,
self.calc[m].fit)
for m in self.real_positioners._fields
# TODO: any other positioner constraints
}
self._constraints_stack.append(constraints)

def _set_constraints(self, constraints):
"""set diffractometer's constraints"""
for axis, constraint in constraints.items():
self.calc[axis].limits = (constraint.low_limit, constraint.high_limit)
self.calc[axis].value = constraint.value
self.calc[axis].fit = constraint.fit

def forwardSolutionsTable(self, reflections, full=False):
"""
return table of computed solutions for each (hkl) in the supplied reflections list

The solutions are calculated using the current UB matrix & constraints
"""
_table = pyRestTable.Table()
motors = self.real_positioners._fields
_table.labels = "(hkl) solution".split() + list(motors)
for reflection in reflections:
try:
solutions = self.calc.forward(reflection)
except ValueError as exc:
solutions = exc
if isinstance(solutions, ValueError):
row = [reflection, "none"]
row += ["" for m in motors]
_table.addRow(row)
else:
for i, s in enumerate(solutions):
row = [reflection, i]
row += [f"{getattr(s, m):.5f}" for m in motors]
_table.addRow(row)
if not full:
break # only show the first (default) solution
return _table
6 changes: 3 additions & 3 deletions apstools/plans.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,12 @@ def lineup(
count time per step (if counter is ScalerChannel object)

peak_factor : float (default: 4)
maximum must be greater than 'peak_factor'*minimum
maximum must be greater than ``peak_factor*minimum``

width_factor : float (default: 0.8)
fwhm must be less than 'width_factor'*plot_range
fwhm must be less than ``width_factor*plot_range``

EXAMPLE:
EXAMPLE::

RE(lineup(diode, foemirror.theta, -30, 30, 30, 1.0))
"""
Expand Down
163 changes: 163 additions & 0 deletions docs/source/source/_diffractometer.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@

Diffractometer
--------------

.. automodule:: apstools.diffractometer
:members:


Example : Constraints
+++++++++++++++++++++

Create a custom diffractometer class, adding the
mixin class as first argument::

from apstools.diffractometer import Constraints, DiffractometerMixin

class CustomE4CV(DiffractometerMixin, E4CV):
h = Component(PseudoSingle, '', labels=("hkl", "fourc"))
k = Component(PseudoSingle, '', labels=("hkl", "fourc"))
l = Component(PseudoSingle, '', labels=("hkl", "fourc"))
...

Define an instance of your diffractometer::

e4cv = CustomE4CV('', name='e4cv', labels=("diffractometer", "e4cv"))

Management of diffractometer constraints is enabled by use of
a stack of previous settings. The diffractometer starts with
a default set of constraints. Show the default constraints
with ``e4cv.showConstraints()``. Here is the initial result::

===== ========= ========== ===== ====
axis low_limit high_limit value fit
===== ========= ========== ===== ====
omega -180.0 180.0 0.0 True
chi -180.0 180.0 0.0 True
phi -180.0 180.0 0.0 True
tth -180.0 180.0 0.0 True
===== ========= ========== ===== ====

A set of constraints is defined as a dictionary with the
motor names as keys::

diffractometer_constraints = {
# axis: Constraints(lo_limit, hi_limit, value, fit)
"omega": Constraints(-120, 150, 0, True),
"chi": Constraints(-150, 120, 0, True),
# "phi": Constraints(0, 0, 0, False),
"tth": Constraints(-10, 142, 0, True),
}

These constraints can be applied with command
``e4cv.applyConstraints(diffractometer_constraints)``
which first pushes the current constraints onto a
stack, such as::

e4cv.applyConstraints(diffractometer_constraints)
e4cv.showConstraints()

===== =================== ================== ===== ====
axis low_limit high_limit value fit
===== =================== ================== ===== ====
omega -119.99999999999999 150.0 0.0 True
chi -150.0 119.99999999999999 0.0 True
phi -180.0 180.0 0.0 True
tth -10.0 142.0 0.0 True
===== =================== ================== ===== ====

Revert back to the previous constraints with command
``e4cv.undoLastConstraints()``. Reset back to the
original constraints with command
``e4cv.resetConstraints()``


Example : Solutions
+++++++++++++++++++

With a given UB (orientation) matrix and constraints,
the motor locations of an *(hkl)* reflection may be computed.
The computation may result in zero or more possible
combinations of positions.

Show the possibilities with the
``e4cv.forwardSolutionsTable()`` command, such as::

print(e4cv.forwardSolutionsTable(
(
(0, 0, 0.5),
(0, 0, 1),
(0, 0, 1.5)
),
full=True
))

=========== ======== ======== ========= ========= ========
(hkl) solution omega chi phi tth
=========== ======== ======== ========= ========= ========
(0, 0, 0.5) 0 2.32262 -25.72670 -78.18577 4.64525
(0, 0, 0.5) 1 -2.32262 25.72670 101.81423 -4.64525
(0, 0, 1) 0 4.64907 -25.72670 -78.18577 9.29815
(0, 0, 1) 1 -4.64907 25.72670 101.81423 -9.29815
(0, 0, 1.5) 0 6.98324 -25.72670 -78.18577 13.96647
=========== ======== ======== ========= ========= ========

As shown, for (0, 0, 1/2) and (001) reflections, there are
two possible solutions.
For the (0 0 3/2) reflection, there is only one solution.
If we further restrict *omega* to non-negative values, we'll
only get one solution for all three reflections in the list::

e4cv.applyConstraints({"omega": Constraints(-0, 150, 0, True)})
print(e4cv.forwardSolutionsTable(
(
(0, 0, 0.5),
(0, 0, 1),
(0, 0, 1.5)
),
full=True
))

=========== ======== ======= ========= ========= ========
(hkl) solution omega chi phi tth
=========== ======== ======= ========= ========= ========
(0, 0, 0.5) 0 2.32262 -25.72670 -78.18577 4.64525
(0, 0, 1) 0 4.64907 -25.72670 -78.18577 9.29815
(0, 0, 1.5) 0 6.98324 -25.72670 -78.18577 13.96647
=========== ======== ======= ========= ========= ========

If we reset the constraint back to the default settings, there
are six possible motor positions for each reflection::

e4cv.resetConstraints()
print(e4cv.forwardSolutionsTable(
(
(0, 0, 0.5),
(0, 0, 1),
(0, 0, 1.5)
),
full=True
))

=========== ======== ========== ========== ========= =========
(hkl) solution omega chi phi tth
=========== ======== ========== ========== ========= =========
(0, 0, 0.5) 0 2.32262 -25.72670 -78.18577 4.64525
(0, 0, 0.5) 1 -2.32262 25.72670 101.81423 -4.64525
(0, 0, 0.5) 2 -2.32262 154.27330 -78.18577 -4.64525
(0, 0, 0.5) 3 2.32262 -154.27330 101.81423 4.64525
(0, 0, 0.5) 4 -177.67738 25.72670 101.81423 4.64525
(0, 0, 0.5) 5 -177.67738 154.27330 -78.18577 4.64525
(0, 0, 1) 0 4.64907 -25.72670 -78.18577 9.29815
(0, 0, 1) 1 -4.64907 25.72670 101.81423 -9.29815
(0, 0, 1) 2 -4.64907 154.27330 -78.18577 -9.29815
(0, 0, 1) 3 4.64907 -154.27330 101.81423 9.29815
(0, 0, 1) 4 -175.35093 25.72670 101.81423 9.29815
(0, 0, 1) 5 -175.35093 154.27330 -78.18577 9.29815
(0, 0, 1.5) 0 6.98324 -25.72670 -78.18577 13.96647
(0, 0, 1.5) 1 -6.98324 25.72670 101.81423 -13.96647
(0, 0, 1.5) 2 -6.98324 154.27330 -78.18577 -13.96647
(0, 0, 1.5) 3 6.98324 -154.27330 101.81423 13.96647
(0, 0, 1.5) 4 -173.01676 25.72670 101.81423 13.96647
(0, 0, 1.5) 5 -173.01676 154.27330 -78.18577 13.96647
=========== ======== ========== ========== ========= =========