### # Copyright 2008-2011 Diamond Light Source Ltd. # This file is part of Diffcalc. # # Diffcalc is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Diffcalc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Diffcalc. If not, see . ### from __future__ import absolute_import from diffcalc.util import DiffcalcException from diffcalc import settings SMALL = 1e-8 from diffcalc.util import command __all__ = ['hardware', 'setcut', 'setmin', 'setmax'] def getNameFromScannableOrString(o): try: # it may be a scannable return o.getName() except AttributeError: return str(o) @command def hardware(): """hardware -- show diffcalc limits and cuts""" print settings.hardware.repr_sector_limits_and_cuts() # @UndefinedVariable @command def setcut(scannable_or_string=None, val=None): """setcut {name {val}} -- sets cut angle """ if scannable_or_string is None and val is None: print settings.hardware.repr_sector_limits_and_cuts() # @UndefinedVariable else: name = getNameFromScannableOrString(scannable_or_string) if val is None: print '%s: %f' % (name, settings.hardware.get_cuts()[name]) # @UndefinedVariable else: oldcut = settings.hardware.get_cuts()[name] # @UndefinedVariable settings.hardware.set_cut(name, float(val)) # @UndefinedVariable newcut = settings.hardware.get_cuts()[name] # @UndefinedVariable @command def setmin(name=None, val=None): """setmin {axis {val}} -- set lower limits used by auto sector code (None to clear)""" #@IgnorePep8 _setMinOrMax(name, val, settings.hardware.set_lower_limit) # @UndefinedVariable @command def setmax(name=None, val=None): """setmax {name {val}} -- sets upper limits used by auto sector code (None to clear)""" #@IgnorePep8 _setMinOrMax(name, val, settings.hardware.set_upper_limit) # @UndefinedVariable @command def setrange(name=None, lower=None, upper=None): """setrange {axis {min} {max}} -- set lower and upper limits used by auto sector code (None to clear)""" #@IgnorePep8 _setMinOrMax(name, lower, settings.hardware.set_lower_limit) # @UndefinedVariable _setMinOrMax(name, upper, settings.hardware.set_upper_limit) # @UndefinedVariable def _setMinOrMax(name, val, setMethod): if name is None: print settings.hardware.repr_sector_limits_and_cuts() # @UndefinedVariable else: name = getNameFromScannableOrString(name) if val is None: print settings.hardware.repr_sector_limits_and_cuts(name) # @UndefinedVariable else: setMethod(name, float(val)) commands_for_help = ['Hardware', hardware, setcut, setmin, setmax] class HardwareAdapter(object): def __init__(self, diffractometerAngleNames, defaultCuts={}, energyScannableMultiplierToGetKeV=1): self._diffractometerAngleNames = diffractometerAngleNames self._upperLimitDict = {} self._lowerLimitDict = {} self._cut_angles = {} self._configure_cuts(defaultCuts) self.energyScannableMultiplierToGetKeV = \ energyScannableMultiplierToGetKeV self._name = 'base' @property def name(self): return self._name def get_axes_names(self): return tuple(self._diffractometerAngleNames) def get_position(self): """pos = get_position() -- returns the current physical diffractometer position as a diffcalc.util object in degrees """ raise NotImplementedError() def get_wavelength(self): """wavelength = get_wavelength() -- returns wavelength in Angstroms """ return 12.39842 / self.get_energy() def get_energy(self): """energy = get_energy() -- returns energy in kEv """ raise NotImplementedError() def __str__(self): s = self.name + ":\n" s += " energy : " + str(self.get_energy()) + " keV\n" s += " wavelength : " + str(self.get_wavelength()) + " Angstrom\n" names = self._diffractometerAngleNames for name, pos in zip(names, self.get_position()): s += " %s : %r deg\n" % (name, pos) return s def __repr__(self): return self.__str__() def get_position_by_name(self, angleName): names = list(self._diffractometerAngleNames) return self.get_position()[names.index(angleName)] ### Limits ### def get_lower_limit(self, name): '''returns lower limits by axis name. Limit may be None if not set ''' if name not in self._diffractometerAngleNames: raise ValueError("No angle called %s. Try one of: %s" % (name, self._diffractometerAngleNames)) return self._lowerLimitDict.get(name) def get_upper_limit(self, name): '''returns upper limit by axis name. Limit may be None if not set ''' if name not in self._diffractometerAngleNames: raise ValueError("No angle called %s. Try one of: %s" % name, self._diffractometerAngleNames) return self._upperLimitDict.get(name) def set_lower_limit(self, name, value): """value may be None to remove limit""" if name not in self._diffractometerAngleNames: raise ValueError( "Cannot set lower Diffcalc limit: No angle called %s. Try one " "of: %s" % (name, self._diffractometerAngleNames)) if value is None: try: del self._lowerLimitDict[name] except KeyError: print ("WARNING: There was no lower Diffcalc limit %s set to " "clear" % name) else: self._lowerLimitDict[name] = value def set_upper_limit(self, name, value): """value may be None to remove limit""" if name not in self._diffractometerAngleNames: raise ValueError( "Cannot set upper Diffcalc limit: No angle called %s. Try one " "of: %s" % (name, self._diffractometerAngleNames)) if value is None: try: del self._upperLimitDict[name] except KeyError: print ("WARNING: There was no upper Diffcalc limit %s set to " "clear" % name) else: self._upperLimitDict[name] = value def is_position_within_limits(self, positionArray): """ where position array is in degrees and cut to be between -180 and 180 """ names = self._diffractometerAngleNames for axis_name, value in zip(names, positionArray): if not self.is_axis_value_within_limits(axis_name, value): return False return True def is_axis_value_within_limits(self, axis_name, value): if axis_name in self._upperLimitDict: if value > self._upperLimitDict[axis_name]: return False if axis_name in self._lowerLimitDict: if value < self._lowerLimitDict[axis_name]: return False return True def repr_sector_limits_and_cuts(self, name=None): if name is None: s = '' for name in self.get_axes_names(): s += self.repr_sector_limits_and_cuts(name) + '\n' s += "Note: When auto sector/transforms are used,\n " s += " cuts are applied before checking limits." return s # limits: low = self.get_lower_limit(name) high = self.get_upper_limit(name) s = ' ' if low is not None: s += "% 6.1f <= " % low else: s += ' ' * 10 s += '%5s' % name if high is not None: s += " <= % 6.1f" % high else: s += ' ' * 10 # cuts: try: if self.get_cuts()[name] is not None: s += " (cut: % 6.1f)" % self.get_cuts()[name] except KeyError: pass return s ### Cutting Stuff ### def _configure_cuts(self, defaultCutsDict): # 1. Set default cut angles self._cut_angles = dict.fromkeys(self._diffractometerAngleNames, -180.) if 'phi' in self._cut_angles: self._cut_angles['phi'] = 0. # 2. Overide with user-specified cuts for name, val in defaultCutsDict.iteritems(): self.set_cut(name, val) def set_cut(self, name, value): if name in self._cut_angles: self._cut_angles[name] = value else: raise KeyError("Diffractometer has no angle %s. Try: %s." % (name, self._diffractometerAngleNames)) def get_cuts(self): return self._cut_angles def cut_angles(self, positionArray): '''Assumes each angle in positionArray is between -360 and 360 ''' cutArray = [] names = self._diffractometerAngleNames for axis_name, value in zip(names, positionArray): cutArray.append(self.cut_angle(axis_name, value)) return tuple(cutArray) def cut_angle(self, axis_name, value): cut_angle = self._cut_angles[axis_name] if cut_angle is None: return value return cut_angle_at(cut_angle, value) def cut_angle_at(cut_angle, value): if (cut_angle == 0 and (abs(value - 360) < SMALL) or (abs(value + 360) < SMALL) or (abs(value) < SMALL)): value = 0. if value < (cut_angle - SMALL): return value + 360. elif value >= cut_angle + 360. + SMALL: return value - 360. else: return value class DummyHardwareAdapter(HardwareAdapter): def __init__(self, diffractometerAngleNames): super(self.__class__, self).__init__(diffractometerAngleNames) # HardwareAdapter.__init__(self, diffractometerAngleNames) self._position = [0.] * len(diffractometerAngleNames) self._wavelength = 1. self.energyScannableMultiplierToGetKeV = 1 self._name = "Dummy" # Required methods def get_position(self): """ pos = getDiffractometerPosition() -- returns the current physical diffractometer position as a list in degrees """ return self._position def _set_position(self, pos): assert len(pos) == len(self.get_axes_names()), \ "Wrong length of input list" self._position = pos position = property(get_position, _set_position) def get_energy(self): """energy = get_energy() -- returns energy in kEv """ if self._wavelength is None: raise DiffcalcException( "Energy or wavelength have not been set") return (12.39842 / (self._wavelength * self.energyScannableMultiplierToGetKeV)) def _set_energy(self, energy): self._wavelength = 12.39842 / energy energy = property(get_energy, _set_energy) def get_wavelength(self): """wavelength = get_wavelength() -- returns wavelength in Angstroms""" if self._wavelength is None: raise DiffcalcException( "Energy or wavelength have not been set") return self._wavelength def _set_wavelength(self, wavelength): self._wavelength = wavelength wavelength = property(get_wavelength, _set_wavelength) class ScannableHardwareAdapter(HardwareAdapter): def __init__(self, diffractometerScannable, energyScannable, energyScannableMultiplierToGetKeV=1): input_names = diffractometerScannable.getInputNames() super(self.__class__, self).__init__(input_names) # HardwareAdapter.__init__(self, input_names) self.diffhw = diffractometerScannable self.energyhw = energyScannable self.energyScannableMultiplierToGetKeV = \ energyScannableMultiplierToGetKeV self._name = "ScannableHarwdareMonitor" # Required methods def get_position(self): """ pos = getDiffractometerPosition() -- returns the current physical diffractometer position as a list in degrees """ return self.diffhw.getPosition() def get_energy(self): """energy = get_energy() -- returns energy in kEv (NOT eV!) """ multiplier = self.energyScannableMultiplierToGetKeV energy = self.energyhw.getPosition() * multiplier if energy is None: raise DiffcalcException("Energy has not been set") return energy def get_wavelength(self): """wavelength = get_wavelength() -- returns wavelength in Angstroms""" energy = self.get_energy() return 12.39842 / energy @property def name(self): return self.diffhw.getName()