How to Use Constraints#
Constraints filter the candidate solutions returned by
forward(). Every real axis on a
diffractometer has a default
LimitsConstraint that accepts any
finite angle. This guide shows how to tighten those constraints and how to
write a custom one.
See also
Constraints — explains what constraints are and how they work internally.
Presets — hold a real axis at a fixed value before
forward() runs (complementary to constraints).
Setup#
All examples use a simulated 4-circle diffractometer:
>>> import hklpy2
>>> e4cv = hklpy2.creator(name="e4cv")
Inspect the default constraints:
>>> e4cv.core.constraints
{'omega': LimitsConstraint(label='omega', low=-180.0, high=180.0, cut=-180.0),
'chi': LimitsConstraint(label='chi', low=-180.0, high=180.0, cut=-180.0),
'phi': LimitsConstraint(label='phi', low=-180.0, high=180.0, cut=-180.0),
'tth': LimitsConstraint(label='tth', low=-180.0, high=180.0, cut=-180.0)}
How do I set a limit on one axis?#
Set limits to a
(low, high) tuple:
>>> e4cv.core.constraints["chi"].limits = (0, 100)
>>> e4cv.core.constraints["chi"].limits
(0.0, 100.0)
Now any forward() solution where chi falls outside [0, 100]
degrees is discarded.
Tip
limits always sorts the pair automatically, so (100, 0) and
(0, 100) are equivalent.
How do I change the cut point?#
A cut point controls which 360-degree window is used to express an
angle — it does not accept or reject solutions, only changes the
representation. The default cut point is -180, giving angles in
[-180, +180).
To express angles in [0, 360) instead:
>>> e4cv.core.constraints["chi"].cut_point = 0
To express angles in [-90, +270):
>>> e4cv.core.constraints["omega"].cut_point = -90
How do I use cut points and limits together?#
A common scenario: a motor can physically travel from 0° to 290°, and you
want angles expressed in [0, 360) so the values are easy to compare
against hardware limits.
Set the cut point first (representation), then the limits (filtering):
>>> e4cv.core.constraints["chi"].cut_point = 0
>>> e4cv.core.constraints["chi"].limits = (0, 290)
After wrapping, every chi solution is in [0, 360). Then the
limits filter further to [0, 290], rejecting solutions in
(290, 360).
Note
The cut point is applied first, then limits are checked on the wrapped value. See Cut Points for the pipeline diagram.
How do I reset constraints to defaults?#
reset_constraints() restores all axes to ±180
limits and a cut point of -180:
>>> e4cv.core.reset_constraints()
>>> e4cv.core.constraints["chi"].limits
(-180.0, 180.0)
>>> e4cv.core.constraints["chi"].cut_point
-180.0
How do I write a custom constraint?#
Subclass ConstraintBase and implement
the valid() method. The
method receives the full set of real-axis positions as keyword arguments and
must return True to keep a solution or False to reject it.
Example: keep only solutions where tth is positive (upper hemisphere
only):
from hklpy2.blocks.constraints import ConstraintBase
class PositiveTthConstraint(ConstraintBase):
"""Accept only solutions where tth > 0."""
def valid(self, **values):
return values.get("tth", 0.0) > 0.0
Install it on the diffractometer by replacing the default constraint:
>>> e4cv.core.constraints["tth"] = PositiveTthConstraint(label="tth")
Important
The label argument must match the real axis name exactly.
valid() looks up the
axis value from its **values keyword arguments using self.label
as the key. A mismatch raises
ConstraintsError.
A more involved example: reject solutions where two axes simultaneously reach their limits (useful for avoiding mechanical conflicts):
class NoSimultaneousLimitConstraint(ConstraintBase):
"""Reject solutions where both omega and chi are at their maximum."""
def __init__(self, omega_max, chi_max, **kwargs):
super().__init__(**kwargs)
self.omega_max = omega_max
self.chi_max = chi_max
def valid(self, **values):
at_omega_limit = abs(values.get("omega", 0.0) - self.omega_max) < 1e-3
at_chi_limit = abs(values.get("chi", 0.0) - self.chi_max) < 1e-3
return not (at_omega_limit and at_chi_limit)
>>> e4cv.core.constraints["omega"] = NoSimultaneousLimitConstraint(
... omega_max=180.0, chi_max=180.0, label="omega"
... )