###################################################################################################\ # Diffcalc utilities ################################################################################################### ###################################################################################################\ # Installing ################################################################################################### #1- Download from: https://github.com/DiamondLightSource/diffcalc/archive/master.zip #2- Extract the contents to {script}/Lib/diffcalc #3- Download http://central.maven.org/maven2/gov/nist/math/jama/1.0.3/jama-1.0.3.jar # to the extensions folder. #4- On {script}/Lib/diffcalc/diffcalc/gdasupport/you.py, the line " wl.asynchronousMoveTo(1)" # must be commented for the energy not to move when the library is loaded. ###################################################################################################\ # Library loading and Hardware setup ################################################################################################### #1- Create a MotorGroup with the diffractometer motors # e.g. 'sixc', containing mu, delta, gam, eta, chi, phi motors (gam = nu) # or 'fivec', containing delta, gam, eta, chi, phi motors # or 'fourc', containing delta, eta, chi, phi motors #2- Create positioner to read/set the energy in kEv, e.g. named 'en' #3- Execute: run("diffutils") #4- Execute: setup_diff(sixc, en) ###################################################################################################\ # API ################################################################################################### # Orientation commands defined in https://github.com/DiamondLightSource/diffcalc#id19 are # defined heren with identical signatures, and so the constraint commands. # Motion command names were changed because thge original can collide with other globals: # hklci, hklca, hklwh, hklget, hklmv and hklsim(hkl). from __future__ import absolute_import import traceback import Jama.Matrix diffcalc_path = os.path.abspath(get_context().setup.expandPath("{script}/Lib/diffcalc")) if not diffcalc_path in sys.path: sys.path.append(diffcalc_path) import diffcalc import math import os from diffcalc import settings from diffcalc.hkl.you.geometry import YouGeometry,SixCircle, FiveCircle, FourCircle, YouPosition from diffcalc.hardware import HardwareAdapter from diffcalc.ub.persistence import UBCalculationJSONPersister, UbCalculationNonPersister from diffcalc.ub.calcstate import UBCalcStateEncoder from diffcalc.gdasupport.minigda.scannable import ScannableBase, ScannableGroup #from diffcalc.gdasupport.minigda import command from diffcalc.hardware import HardwareAdapter import ch.psi.pshell.device.PositionerConfig as PositionerConfig import ch.psi.pshell.device.RegisterConfig as RegisterConfig import ch.psi.pshell.device.Register as Register _difcalc_names = {} # # Disable error handling designed for interactive use #diffcalc.util.DEBUG = True ################################################################################################### # Device mapping to difcalc ################################################################################################### class PositionerScannable(ScannableBase): def __init__(self, positioner, name = None): self.positioner = positioner self.name = positioner.name if name is None else name self.inputNames = [self.name] self.outputFormat = ['% 6.4f'] self.level = 3 def isBusy(self): return self.positioner.state == State.Busy def waitWhileBusy(self): self.positioner.waitReady(-1) def asynchronousMoveTo(self, new_position): #print "Moving " , self.name, " to: ", new_position self.positioner.moveAsync(float(new_position), -1) def getPosition(self): return self.positioner.getPosition() def _get_diffcalc_axis_names(): nu_name=diffcalc.hkl.you.constraints.NUNAME return ("mu", "delta", nu_name, "eta", "chi", "phi") class PositionerScannableGroup(ScannableGroup): def __init__(self, name, motors, diffcalc_axis_names=None): self.name = name global _difcalc_names _difcalc_names = {} positioners = [] if diffcalc_axis_names is None: if len(motors) == 6: diffcalc_axis_names = _get_diffcalc_axis_names() elif len(motors) == 5: diffcalc_axis_names = ("delta", "gam", "eta", "chi", " phi") elif len(motors) == 4: diffcalc_axis_names = ("delta", "eta", "chi", " phi") self.diffcalc_axis_names = diffcalc_axis_names for i in range(len(motors)): _difcalc_names[motors[i]] = diffcalc_axis_names[i] exec('self.' + diffcalc_axis_names[i] + ' = PositionerScannable(' + motors[i].name + ', "' +diffcalc_axis_names[i] + '")') exec('positioners.append(self.' + diffcalc_axis_names[i] + ')' ) #for m in motors: # exec('self.' + m.name + ' = PositionerScannable(' + m.name + ', "' + m.name + '")') # exec('positioners.append(self.' + m.name + ')' ) ScannableGroup.__init__(self, self.name, positioners) class MotorGroupScannable(PositionerScannableGroup): def __init__(self, motor_group, diffcalc_axis_names=None): self.motor_group = motor_group PositionerScannableGroup.__init__(self, motor_group.name, motor_group.motors, diffcalc_axis_names) class ScannableAdapter(HardwareAdapter): def __init__(self, diffractometer, energy, energy_multiplier_to_kev=1): self.diffractometer = diffractometer self.energy = energy self.energy_multiplier_to_kev = energy_multiplier_to_kev input_names = diffractometer.getInputNames() HardwareAdapter.__init__(self, input_names) #Returns the current physical POSITIONS def get_position(self): """ pos = getDiffractometerPosition() -- returns the current physical diffractometer position as a list in degrees """ return self.diffractometer.getPosition() #returns energy in kEv def get_energy(self): """energy = get_energy() -- returns energy in kEv (NOT eV!) """ multiplier = self.energy_multiplier_to_kev energy = self.energy.getPosition() * multiplier if energy is None: raise DiffcalcException("Energy has not been set") return energy def get_motor(self,name): global _motor_group global _difcalc_names for m in _difcalc_names.keys(): if _difcalc_names[m] == name: return m for m in _motor_group.motors: if m.name == name: return m raise Exception("Invalid axis name: " + str(name)) def get_lower_limit(self, name): '''returns lower limits by axis name. Limit may be None if not set ''' m = self.get_motor(name) ret = m.getMinValue() if ret == float("NaN"): ret = None return ret def get_upper_limit(self, name): '''returns upper limit by axis name. Limit may be None if not set ''' m = self.get_motor(name) ret = m.getMaxValue() if ret == float("NaN"): ret = None return ret def set_lower_limit(self, name, value): """value may be None to remove limit""" if value is None: value = float("NaN") m = self.get_motor(name) m.config.minValue =value def set_upper_limit(self, name, value): """value may be None to remove limit""" if value is None: value = float("NaN") m = self.get_motor(name) m.config.maxValue =value def is_axis_value_within_limits(self, axis_name, value): m = self.get_motor(axis_name) upper = self.get_upper_limit(axis_name) lower = self.get_lower_limit(axis_name) if (upper is None) or (math.isnan(upper)): upper = sys.float_info.max if (lower is None) or (math.isnan(lower)): lower = -sys.float_info.max return lower <= value <= upper @property def name(self): return self.diffractometer.getName() class MotorGroupAdapter(ScannableAdapter): def __init__(self, diffractometer, energy, energy_multiplier_to_kev=1, diffcalc_axis_names=None): self.diffractometer = MotorGroupScannable(diffractometer, diffcalc_axis_names) self.energy = PositionerScannable(energy) self.energy.level = 3 ScannableAdapter.__init__(self, self.diffractometer, self.energy, energy_multiplier_to_kev) class Wavelength(RegisterBase): def doRead(self): try: return get_wavelength().getPosition() except: return None def doWrite(self, val): get_wavelength().asynchronousMoveTo(val) ################################################################################################### # HKL Pseudo-devices ################################################################################################### class HklPositoner (PositionerBase): def __init__(self, name, index, hkl_group): PositionerBase.__init__(self, name, PositionerConfig()) self.setParent(hkl_group) self.index = index def isReady(self): return PositionerBase.isReady(self) and self.getParent().isReady() def doRead(self): return self.getParent()._setpoint[self.index] def doWrite(self, value): #print "Setting " , self.getName(), "to: ", value pos = [None, None, None] pos[self.index] = value self.getParent().write(pos) def doReadReadback(self): if java.lang.Thread.currentThread() != self.getParent()._updating_thread: self.getParent().update() return self.getParent()._readback[self.index] class HklGroup(RegisterBase, Register.RegisterArray): def __init__(self, name): RegisterBase.__init__(self, name, RegisterConfig()) self.hkl=get_hkl() self.h, self.k, self.l = HklPositoner("h", 0, self), HklPositoner("k", 1, self), HklPositoner("l", 2, self) add_device(self.h, True) add_device(self.k, True) add_device(self.l, True) self._setpoint = self.doRead() self._updating = False def getSize(self): return 3 def doRead(self): try: self._readback = self.hkl.getPosition() self._updating_thread = java.lang.Thread.currentThread() self.h.update() self.k.update() self.l.update() except: #traceback.print_exc() self._readback = (None, None, None) finally: self._updating_thread = None return self._readback def doWrite(self, pos): self._setpoint = pos #print "Moving to: " + str(pos) self.hkl.asynchronousMoveTo(pos) def sim(self, pos): return self.hkl.simulateMoveTo(pos) ################################################################################################### # System setup ################################################################################################### you = None dc, ub, hardware, hkl = None, None, None, None _motor_group = None def setup_diff(diffractometer= None, energy= None, diffcalc_axis_names = None, geometry=None, persist_ub=True): """ configure diffractometer. Display configuration if no parameter is given diffractometer: Diffraction motor group energy: Positioner having energy in kev geometry: YouGeometry extension. If none, uses default diffcalc_axis_names: if None use defaults: - mu, delta, gam, eta, chi, phi (six circle) - delta, gam, eta, chi, phi (ficve circle) - delta, eta, chi, phi (four circle) """ global you, dc, ub, hardware, hkl, _motor_group if diffractometer is not None: _motor_group = diffractometer you = None if geometry is not None: settings.geometry = geometry elif diffcalc_axis_names is not None: class CustomGeometry(YouGeometry): def __init__(self): self.all_axis_names = _get_diffcalc_axis_names() self.my_axis_names = diffcalc_axis_names fixed_constraints = {} for axis in self.all_axis_names: if not axis in self.my_axis_names: fixed_constraints[axis] = 0 YouGeometry.__init__(self, diffractometer.name, fixed_constraints) def physical_angles_to_internal_position(self, physical_angle_tuple): pos=[] index = 0 for axis in self.all_axis_names: pos.append(physical_angle_tuple[index] if (axis in self.my_axis_names) else 0) index = index+1 pos.append("DEG")#units return YouPosition(*pos) def internal_position_to_physical_angles(self, internal_position): pos = internal_position.clone() pos.changeToDegrees() pos = pos.totuple() ret = [] for i in range (len(self.all_axis_names)): if self.all_axis_names[i] in self.my_axis_names: ret.append(pos[i]) return tuple(ret) settings.geometry = CustomGeometry() elif len(diffractometer.motors) == 6: settings.geometry = SixCircle() elif len(diffractometer.motors) == 5: settings.geometry = FiveCircle() elif len(diffractometer.motors) == 4: settings.geometry = FourCircle() else: raise Exception("Invalid motor group") settings.hardware = MotorGroupAdapter(diffractometer, energy, diffcalc_axis_names = diffcalc_axis_names) if persist_ub: settings.persistence_path = os.path.abspath(get_context().setup.expandPath("{config}/diffcalc")) if not os.path.exists(settings.persistence_path): os.makedirs(settings.persistence_path) print "UB calculations persistence path: " + settings.persistence_path settings.ubcalc_persister = UBCalculationJSONPersister(settings.persistence_path, UBCalcStateEncoder) else: print "UB calculations are not persisteds" settings.ubcalc_persister = UbCalculationNonPersister() settings.axes_scannable_group = settings.hardware.diffractometer settings.energy_scannable = settings.hardware.energy settings.ubcalc_strategy = diffcalc.hkl.you.calc.YouUbCalcStrategy() settings.angles_to_hkl_function = diffcalc.hkl.you.calc.youAnglesToHkl from diffcalc.gdasupport import you reload(you) # These must be imported AFTER the settings have been configured from diffcalc.dc import dcyou as dc from diffcalc.ub import ub from diffcalc import hardware from diffcalc.hkl.you import hkl add_device(HklGroup("hkl_group"), True) add_device(Wavelength("wavelength", 6), True) hkl_group.polling = 250 wavelength.polling = 250 if settings.hardware is not None: print "Diffractometer defined with:" print " \t" + "Motor group: " + str(settings.hardware.diffractometer.name) print " \t" + "Energy: " + str(settings.hardware.energy.name) print "\nDiffcalc axis names:" for m in _difcalc_names.keys(): print " \t Motor " + m.name + " = Axis " + _difcalc_names[m] else: print "Diffractometer is not defined\n" print def setup_axis(motor = None, min=None, max=None, cut=None): """ configure axis range and cut. displays ranges if motor is None """ if motor is not None: name = get_axis_name(motor) if min is not None: hardware.setmin(name, min) if max is not None: hardware.setmax(name, max) if cut is not None: hardware.setcut(name, cut) else: print "Axis range configuration:" hardware.hardware() print ################################################################################################### # Acceess functions ################################################################################################### def get_diff(): return settings.hardware.diffractometer def get_energy(): return settings.hardware.energy def get_adapter(): return settings.hardware def get_motor_group(): return _motor_group def get_wavelength(): return you.wl def get_hkl(): return you.hkl def get_axis_name(motor): if is_string(motor): motor = get_adapter().get_motor(motor) return _difcalc_names[motor] ################################################################################################### # Orientation Commands ################################################################################################### # State def newub(name): """ start a new ub calculation name """ try: rmub(name) except: pass return ub.newub(name) def loadub(name_or_num): """ load an existing ub calculation """ return ub.loadub(name_or_num) def lastub(): """ load the last used ub calculation """ return ub.lastub() def listub(): """ list the ub calculations available to load """ return ub.listub() def rmub(name_or_num): """ remove existing ub calculation """ return ub.rmub(name_or_num) def saveubas(name): """ save the ub calculation with a new name """ return ub.saveubas(name) # Lattice def setlat(name=None, *args): """ set lattice parameters (Angstroms and Deg) setlat -- interactively enter lattice parameters (Angstroms and Deg) setlat name a -- assumes cubic setlat name a b -- assumes tetragonal setlat name a b c -- assumes ortho setlat name a b c gamma -- assumes mon/hex with gam not equal to 90 setlat name a b c alpha beta gamma -- arbitrary """ return ub.setlat(name, *args) def c2th(hkl, en=None): """ calculate two-theta angle for reflection """ return ub.c2th(hkl, en) def hklangle(hkl1, hkl2): """ calculate angle between [h1 k1 l1] and [h2 k2 l2] crystal planes """ return ub.hklangle(hkl1, hkl2) # Reference (surface) def setnphi(xyz = None): """ sets or displays (xyz=None) n_phi reference """ return ub.setnphi(xyz) def setnhkl(hkl = None): """ sets or displays (hkl=None) n_hkl reference """ return ub.setnhkl(hkl) # Reflections def showref(): """ shows full reflection list """ return ub.showref() def addref(*args): """ Add reflection addref -- add reflection interactively addref [h k l] {'tag'} -- add reflection with current position and energy addref [h k l] (p1, .., pN) energy {'tag'} -- add arbitrary reflection """ return ub.addref(*args) def editref(idx): """ interactively edit a reflection (idx is tag or index numbered from 1) """ return ub.editref(idx) def delref(idx): """ deletes a reflection (idx is tag or index numbered from 1) """ return ub.delref(idx) def clearref(): """ deletes all the reflections """ return ub.clearref() def swapref(idx1=None, idx2=None): """ swaps two reflections swapref -- swaps first two reflections used for calculating U matrix swapref {num1 | 'tag1'} {num2 | 'tag2'} -- swaps two reflections """ return ub.swapref(idx1, idx2) # Crystal Orientations def showorient(): """ shows full list of crystal orientations """ #TODO: Workaround of bug on Diffcalc (str_lines needs parameter) if ub.ubcalc._state.orientlist: print '\n'.join(ub.ubcalc._state.orientlist.str_lines(None)) return return ub.showorient() def addorient(*args): """ addorient -- add crystal orientation interactively addorient [h k l] [x y z] {'tag'} -- add crystal orientation in laboratory frame """ return ub.addorient(*args) def editorient(idx): """ interactively edit a crystal orientation (idx is tag or index numbered from 1) """ return ub.editorient(tag_or_num) def delorient(idx): """ deletes a crystal orientation (idx is tag or index numbered from 1) """ return ub.delorient(tag_or_num) def clearorient(): """ deletes all the crystal orientations """ return ub.clearorient() def swaporient(idx1=None, idx2=None): """ swaps two swaporient swaporient -- swaps first two crystal orientations used for calculating U matrix swaporient {num1 | 'tag1'} {num2 | 'tag2'} -- swaps two crystal orientations """ return ub.swaporient(idx1, idx2) # UB Matrix def showub(): """ show the complete state of the ub calculation NOT A DIFFCALC COMMAND """ return ub.ub() def checkub(): """ show calculated and entered hkl values for reflections """ return ub.checkub() def setu(U=None): """ manually set U matrix setu -- set U matrix interactively setu [[..][..][..]] -- manually set U matrix """ return ub.setu(U) def setub(UB=None): """ manually set UB matrix setub -- set UB matrix interactively setub [[..][..][..]] -- manually set UB matrix """ return ub.setub(UB) def getub(): """ returns current UB matrix NOT A DIFFCALC COMMAND """ return None if ub.ubcalc._UB is None else ub.ubcalc._UB.tolist() def calcub(idx1=None, idx2=None): """ (re)calculate u matrix calcub -- (re)calculate U matrix from the first two reflections and/or orientations. calcub idx1 idx2 -- (re)calculate U matrix from reflections and/or orientations referred by indices and/or tags idx1 and idx2. """ return ub.calcub(idx1, idx2) def trialub(idx=1): """ (re)calculate u matrix using one reflection only Use indice or tags idx1. Default: use first reflection. """ return ub.trialub(idx) def refineub(*args): """ refine unit cell dimensions and U matrix to match diffractometer angles for a given hkl value refineub -- interactively refineub [h k l] {pos} """ return ub.refineub(*args) def fitub(*args): """ fitub ref1, ref2, ref3... -- fit UB matrix to match list of provided reference reflections. """ return ub.fitub(*args) def addmiscut(angle, xyz=None): """ apply miscut to U matrix using a specified miscut angle in degrees and a rotation axis (default: [0 1 0]) """ return ub.addmiscut(angle, xyz) def setmiscut(angle, xyz=None): """ manually set U matrix using a specified miscut angle in degrees and a rotation axis (default: [0 1 0]) """ return ub.setmiscut(angle, xyz) ################################################################################################### # Motion Commands ################################################################################################### #Constraints def con(*args): """ list or set available constraints and values con -- list available constraints and values con {val} -- constrains and optionally sets one constraint con {val} {val} {val} -- clears and then fully constrains """ return hkl.con(*args) def uncon(name): """ remove constraint """ return hkl.uncon(name) # HKL def allhkl(_hkl, wavelength=None): """ print all hkl solutions ignoring limits """ return hkl.allhkl(_hkl, wavelength) #Hardware def setmin(axis, val=None): """ set lower limits used by auto sector code (nan to clear) """ name = get_axis_name(axis) return hardware.setmin(name, val) def setmax(axis, val=None): """ set upper limits used by auto sector code (nan to clear) """ name = get_axis_name(axis) return hardware.setmax(name, val) def setcut(axis, val): """ sets cut angle """ name = get_axis_name(axis) return hardware.setcut(name, val) ################################################################################################### # Motion commands: not standard Diffcalc names ################################################################################################### def hklci(positions, energy=None): """ converts positions of motors to reciprocal space coordinates (H K L) """ return dc.angles_to_hkl(positions, energy) def hklca(hkl, energy=None): """ converts reciprocal space coordinates (H K L) to positions of motors. """ return dc.hkl_to_angles(hkl[0], hkl[1], hkl[2], energy) def hklwh(): """ prints the current reciprocal space coordinates (H K L) and positions of motors. """ hkl = hklget() print "HKL: " + str(hkl) for m in _difcalc_names.keys(): print _difcalc_names[m] + " [" + m.name + "] :" + str(m.take()) def hklget(): """ get current hkl position """ return hkl_group.read() def hklmv(hkl): """ move to hkl position """ hkl_group.write(hkl) def hklsim(hkl): """ simulates moving diffractometer """ return hkl_group.sim(hkl) ################################################################################################### # HKL Combined Scan ################################################################################################### def hklscan(vector, readables,latency = 0.0, passes = 1, **pars): """ HKL Scan: Args: vector(list of lists): HKL values to be scanned readables(list of Readable): Sensors to be sampled on each step. latency(float, optional): settling time for each step before readout, defaults to 0.0. passes(int, optional): number of passes pars(keyworded variable length arguments, optional): scan optional named arguments: - title(str, optional): plotting window name. - hidden(bool, optional): if true generates no effects on user interface. - before_read (function, optional): callback on each step, before sampling. Arguments: positions, scan - after_read (function, optional): callback on each step, after sampling. Arguments: record, scan. - before_pass (function, optional): callback before each scan pass execution. Arguments: pass_num, scan. - after_pass (function, optional): callback after each scan pass execution. Arguments: pass_num, scan. - Aditional arguments defined by set_exec_pars. Returns: ScanResult object. """ readables=to_list(string_to_obj(readables)) pars["initial_move"] = False scan = ManualScan([h,k,l], readables ,vector[0], vector[-1], [len(vector)-1] * 3, dimensions = 1) if not "domain_axis" in pars.keys(): pars["domain_axis"] = "Index" processScanPars(scan, pars) scan.start() try: for pos in vector: #print "Writing ", pos hkl_group.write(pos) time.sleep(0.1) #Make sure is busy get_motor_group().update() get_motor_group().waitReady(-1) time.sleep(latency) hkl_group.update() scan.append ([h.take(), k.take(), l.take()], [h.getPosition(), k.getPosition(), l.getPosition()], [readable.read() for readable in readables ]) finally: scan.end() return scan.result def test_diffcalc(): print "Start test" energy.move(20.0) delta.config.maxSpeed = 50.0 delta.speed = 50.0 delta.move(1.0) #Setup setup_diff(sixc, energy) setup_axis('gam', 0, 179) setup_axis('delta', 0, 179) setup_axis('delta', min=0) setup_axis('phi', cut=-180.0) setup_axis() #Orientation listub() # Create a new ub calculation and set lattice parameters newub('test') setlat('cubic', 1, 1, 1, 90, 90, 90) # Add 1st reflection (demonstrating the hardware adapter) settings.hardware.wavelength = 1 c2th([1, 0, 0]) # energy from hardware settings.hardware.position = 0, 60, 0, 30, 0, 0 addref([1, 0, 0])# energy and position from hardware # Add 2nd reflection (this time without the harware adapter) c2th([0, 1, 0], 12.39842) addref([0, 1, 0], [0, 60, 0, 30, 0, 90], 12.39842) # check the state showub() checkub() #Constraints con('qaz', 90) con('a_eq_b') con('mu', 0) con() #Motion print hklci((0., 60., 0., 30., 0., 0.)) print hklca((1, 0, 0)) sixc.write([0, 60, 0, 30, 90, 0]) print "sixc=" , sixc.position wavelength.write(1.0) print "wavelength = ", wavelength.read() lastub() setu ([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) showref() swapref(1,2) hklwh() hklsim([0.0,1.0,1.0]) hklmv([0.0,1.0,1.0]) #Scans lscan(l, [sin], 1.0, 1.5, 0.1) ascan([k,l], [sin], [1.0, 1.0], [1.2, 1.3], [0.1, 0.1], zigzag=True, parallel_positioning = False) vector = [[1.0,1.0,1.0], [1.0,1.0,1.1], [1.0,1.0,1.2], [1.0,1.0,1.4]] hklscan(vector, [sin, arr], 0.9)