def __init__(self, name, idgap, dcmenergy, lut, gap_offset=None, feedbackPVs=None): """ Constructor - Only succeeds if it finds the lookup table, otherwise raises exception. """ self.lut = readLookupTable( LocalProperties.get("gda.config") + "/lookupTables/" + lut) self.gap = idgap self.mono_energy = dcmenergy self.lambdau = 27 # undulator period self.scannables = ScannableGroup(name, [dcmenergy, idgap]) self.detune = gap_offset self.feedbackPVs = feedbackPVs self._busy = 0 self.setName(name) self.setLevel(3) self.setOutputFormat(["%10.6f"]) self.inputNames = [name] self.order = 3 self.SCANNING = False self.logger = logger.getChild(self.__class__.__name__)
def pathscan(*args): #@UndefinedVariable ''' Scan a group of scannables following the specified path and collect data at each point from scannables args ''' starttime = time.ctime() if PRINTTIME: print "=== Scan started: " + starttime newargs = [] i = 0 while i < len(args): arg = args[i] if type(arg) == TupleType: if allElementsAreScannable(arg): scannableGroup = ScannableGroup("pathgroup") for each in arg: scannableGroup.addGroupMember(each) newargs.append(scannableGroup) elif allElementsAreListOfNumber(arg): newargs.append(arg) else: raise TypeError, "Only tuple of scannables and tuple of list of numbers are supported." else: newargs.append(arg) i = i + 1 scan(newargs) if PRINTTIME: print("=== Scan ended: " + time.ctime() + ". Elapsed time: %.0f seconds" % (time.time() - starttime))
def __init__(self, name, idctrl, pgmenergy, lut="JIDEnergy2GapCalibrations.csv", energyConstant=False, polarisationConstant=False, gap_offset=None, feedbackPV=None): '''Constructor - Only succeed if it find the lookupTable table, otherwise raise exception.''' self.lut,self.header = load_lookup_table(LocalProperties.get("gda.config")+"/lookupTables/"+lut) self.idscannable = idctrl self.mono_energy = pgmenergy self.scannables = ScannableGroup(name, [pgmenergy, idctrl]) self.detune = gap_offset self.feedbackPV = feedbackPV self._busy = 0 self.setName(name) self.setLevel(3) self.setOutputFormat(["%10.6f"]) self.setInputNames([name]) self.setExtraNames([]) self.polarisation = 'LH' self.gap = 50 self.phase = 0 self.energyConstant = energyConstant self.polarisationConstant = polarisationConstant self.SCANNING = False if self.energyConstant and self.polarisationConstant: raise RuntimeError("Cannot create an instance with both energy and polarisation being constant.") self.isConfigured = False self.inputSignSwitched = False self.beamlinename = LocalProperties.get(LocalProperties.GDA_BEAMLINE_NAME) self.logger = logger.getChild(self.__class__.__name__)
def create_group_and_tuples(self): """Function to create a scannable group from scannables passed in""" # If scan_group has already been defined in the namespace, remove scannables global scan_group if "scan_group" not in globals(): scan_group = ScannableGroup() self.insert_into_namespace("scan_group", scan_group) else: group_member_names = scan_group.getGroupMemberNames() for name in group_member_names: scan_group.removeGroupMemberByScannable(scan_group.getGroupMember(name)) # Add the primary scannable to the group self.add_scannable_to_group(self.scannable_func_list[0]) # Add the other list members to the group and append their function values to tuple list for scannable, func, scannable_start_val in self.scannable_func_list[1:]: self.add_scannable_to_group(scannable) self.append_function_values(func, scannable_start_val) # Configure the scan_group scan_group.setName("scan_group") scan_group.configure() tuples = tuple(self.tuple_list) self.insert_into_namespace("scan_points", tuples) return tuples
def __init__(self, name, idctrl, pgmenergy, lut="JIDEnergy2GapCalibrations.txt", energyConstant=False, polarisationConstant=False): '''Constructor - Only succeed if it find the lookupTable table, otherwise raise exception.''' self.lut=loadLookupTable(LocalProperties.get("gda.config")+"/lookupTables/"+lut) self.idscannable=idctrl self.pgmenergy=pgmenergy self.scannables=ScannableGroup(name, [pgmenergy, idctrl]) self._busy=0 self.setName(name) self.setLevel(3) self.setOutputFormat(["%10.6f"]) self.setInputNames([name]) self.setExtraNames([]) self.order=1 self.polarisation=0.0 self.gap=50 self.polarisationMode='UNKNOWN' self.phase=0 self.energyConstant=energyConstant self.polarisationConstant=polarisationConstant self.SCANNING=False if self.energyConstant and self.polarisationConstant: raise Exception("Cannot create an instance with both energy and polarisation being constant.") self.isConfigured=False self.inputSignSwitched=False self.beamlinename=LocalProperties.get(LocalProperties.GDA_BEAMLINE_NAME) self.logger = logger.getChild(self.__class__.__name__)
def analyserpathscan(*args): ''' perform single/multiple regions analyser data collection at each point on the specified path, and produce a single scan file recording all scannables' poistions and metadata, along with analyser scan data under region's name as NXdetector node. implementation details: This function pre-process sequence file to set up analyser 'ew4000' ready for data collection. It creates scannable group to support point-to-point concurrent scan. ''' starttime = time.ctime() if PRINTTIME: print "=== Scan started: " + starttime newargs = [] i = 0 while i < len(args): arg = args[i] if type(arg) == TupleType: if allElementsAreScannable(arg): scannableGroup = ScannableGroup("pathgroup") for each in arg: scannableGroup.addGroupMember(each) newargs.append(scannableGroup) elif allElementsAreListOfNumber(arg): newargs.append(arg) else: raise TypeError, "Only tuple of scannables and tuple of list of numbers are supported." else: newargs.append(arg) i = i + 1 if isinstance(arg, EW4000): controller = Finder.find("SequenceFileObserver") xmldir = InterfaceProvider.getPathConstructor( ).createFromDefaultProperty() + "xml" + os.sep filename = xmldir + args[i] if (OsUtil.isWindows()): FilenameUtil.setPrefix("D:") filename = FilenameUtil.convertSeparator(filename) controller.update(controller, SequenceFileChangeEvent(filename)) sleep(2.0) jythonServerStatus = InterfaceProvider.getJythonServerStatusProvider( ).getJythonServerStatus() while (jythonServerStatus.isScriptOrScanPaused()): sleep(1.0) arg.setSequenceFilename(filename) sequence = arg.loadSequenceData(filename) if isinstance(arg.getCollectionStrategy(), EW4000CollectionStrategy): arg.getCollectionStrategy().setSequence(sequence) i = i + 1 scan(newargs) if PRINTTIME: print("=== Scan ended: " + time.ctime() + ". Elapsed time: %.0f seconds" % (time.time() - starttime))
def __init__(self, name, detectorToUse=edxd):#@UndefinedVariable self.name=name self.sg = ScannableGroup() self.pointid=[] self.path=[] self.startline=1 self.lastline=10 self.scannablelist=[] self.scannableunitlist=[] self.detector=detectorToUse self.detectorunit="s" self.exposuretime=[]
def setup_method(self): self.a = MockMotor() self.b = MockMotor() self.c = MockMotor() self.d = MockMotor() self.e = MockMotor() self.f = MockMotor() self.grp = ScannableGroup( 'grp', [self.a, self.b, self.c, self.d, self.e, self.f]) self.grp.configure() self.sg = DiffractometerScannableGroup('sixc', MockDiffcalc(6), self.grp)
def setup_method(self): class BadMockAngleCalculator: def angles_to_hkl(self, pos): raise Exception("Problem") dummy = createDummyAxes( ['alpha', 'delta', 'gamma', 'omega', 'chi', 'phi']) self.group = ScannableGroup('grp', dummy) self.group.configure() self.sg = DiffractometerScannableGroup('sixc', BadMockAngleCalculator(), self.group)
class TestDiffractometerScannableGroup(unittest.TestCase): def setUp(self): self.a = MockMotor() self.b = MockMotor() self.c = MockMotor() self.d = MockMotor() self.e = MockMotor() self.f = MockMotor() self.grp = ScannableGroup( 'grp', [self.a, self.b, self.c, self.d, self.e, self.f]) self.grp.configure() self.sg = DiffractometerScannableGroup( 'sixc', MockDiffcalc(6), self.grp) def testInit(self): self.assertEqual(list(self.sg.getPosition()), [0., 0., 0., 0., 0., 0.]) def testAsynchronousMoveTo(self): self.sg.asynchronousMoveTo([1, 2.0, 3, 4, 5, 6]) self.assertEqual(list(self.sg.getPosition()), [1., 2., 3., 4., 5., 6.]) def testAsynchronousMoveToWithNones(self): self.sg.asynchronousMoveTo([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) self.sg.asynchronousMoveTo([None, None, 3.2, None, 5.2, None]) self.assertEqual(list(self.sg.getPosition()), [1., 2., 3.2, 4., 5.2, 6.]) def testGetPosition(self): #implicitely tested above pass def testWhereMoveTo(self): # just check for exceptions print self.sg.simulateMoveTo((1.23, 2, 3, 4, 5, 6)) def testIsBusy(self): self.assertEqual(self.sg.isBusy(), False) self.sg.asynchronousMoveTo([1.0, 2.0, 3.0, 4, 5, 6]) self.assertEqual(self.sg.isBusy(), True) self.b.makeNotBusy() self.assertEqual(self.sg.isBusy(), True) self.a.makeNotBusy() self.c.makeNotBusy() self.d.makeNotBusy() self.e.makeNotBusy() self.f.makeNotBusy() self.assertEqual(self.sg.isBusy(), False) def testRepr(self): print self.sg.__repr__()
class TestDiffractometerScannableGroup(object): def setup_method(self): self.a = MockMotor() self.b = MockMotor() self.c = MockMotor() self.d = MockMotor() self.e = MockMotor() self.f = MockMotor() self.grp = ScannableGroup( 'grp', [self.a, self.b, self.c, self.d, self.e, self.f]) self.grp.configure() self.sg = DiffractometerScannableGroup('sixc', MockDiffcalc(6), self.grp) def testInit(self): assert list(self.sg.getPosition()) == [0., 0., 0., 0., 0., 0.] def testAsynchronousMoveTo(self): self.sg.asynchronousMoveTo([1, 2.0, 3, 4, 5, 6]) assert list(self.sg.getPosition()) == [1., 2., 3., 4., 5., 6.] def testAsynchronousMoveToWithNones(self): self.sg.asynchronousMoveTo([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) self.sg.asynchronousMoveTo([None, None, 3.2, None, 5.2, None]) assert list(self.sg.getPosition()) == [1., 2., 3.2, 4., 5.2, 6.] def testGetPosition(self): #implicitely tested above pass def testWhereMoveTo(self): # just check for exceptions print self.sg.simulateMoveTo((1.23, 2, 3, 4, 5, 6)) def testIsBusy(self): assert not self.sg.isBusy() self.sg.asynchronousMoveTo([1.0, 2.0, 3.0, 4, 5, 6]) assert self.sg.isBusy() self.b.makeNotBusy() assert self.sg.isBusy() self.a.makeNotBusy() self.c.makeNotBusy() self.d.makeNotBusy() self.e.makeNotBusy() self.f.makeNotBusy() assert not self.sg.isBusy() def testRepr(self): print self.sg.__repr__()
class TestDiffractometerScannableGroupWithFailingAngleCalculator(object): def setup_method(self): class BadMockAngleCalculator: def angles_to_hkl(self, pos): raise Exception("Problem") dummy = createDummyAxes(['alpha', 'delta', 'gamma', 'omega', 'chi', 'phi']) self.group = ScannableGroup('grp', dummy) self.group.configure() self.sg = DiffractometerScannableGroup( 'sixc', BadMockAngleCalculator(), self.group) def testGetPosition(self): self.sg.getPosition() def testSimulateMoveTo(self): assert (self.sg.simulateMoveTo([1., 2., 3., 4., 5., 6.]) == "Error: Problem")
def setup_method(self): class BadMockAngleCalculator: def angles_to_hkl(self, pos): raise Exception("Problem") dummy = createDummyAxes(['alpha', 'delta', 'gamma', 'omega', 'chi', 'phi']) self.group = ScannableGroup('grp', dummy) self.group.configure() self.sg = DiffractometerScannableGroup( 'sixc', BadMockAngleCalculator(), self.group)
class TestDiffractometerScannableGroupWithFailingAngleCalculator(object): def setup_method(self): class BadMockAngleCalculator: def angles_to_hkl(self, pos): raise Exception("Problem") dummy = createDummyAxes( ['alpha', 'delta', 'gamma', 'omega', 'chi', 'phi']) self.group = ScannableGroup('grp', dummy) self.group.configure() self.sg = DiffractometerScannableGroup('sixc', BadMockAngleCalculator(), self.group) def testGetPosition(self): self.sg.getPosition() def testSimulateMoveTo(self): assert (self.sg.simulateMoveTo([1., 2., 3., 4., 5., 6.]) == "Error: Problem")
def __init__(self, name, lut): """ Constructor - Only succeeds if it finds the lookup table, otherwise raises exception. """ self.lut = readLookupTable( LocalProperties.get("gda.config") + "/lookupTables/" + lut) self.gap = "igap" self.dcm = "dcmenergy" self.lambdau = 27 # undulator period self.scannableNames = ["dcmenergy", "igap"] self.scannables = ScannableGroup( name, [Finder.find(x) for x in self.scannableNames]) self._busy = 0 self.setName(name) self.setLevel(3) self.setOutputFormat(["%10.6f"]) self.inputNames = [name] self.order = 3 self.logger = logger.getChild(self.__class__.__name__)
def setup_method(self): class BadMockAngleCalculator: def angles_to_hkl(self, pos): raise Exception("Problem in angles_to_hkl") dummy = createDummyAxes(['alpha', 'delta', 'gamma', 'omega', 'chi', 'phi']) self.group = ScannableGroup('grp', dummy) self.SixCircleGammaOnArmGeometry = DiffractometerScannableGroup( 'SixCircleGammaOnArmGeometry', MockDiffcalc(6), self.group) self.hkl = Hkl('hkl', self.SixCircleGammaOnArmGeometry, BadMockAngleCalculator())
def setup_method(self): self.a = MockMotor() self.b = MockMotor() self.c = MockMotor() self.d = MockMotor() self.e = MockMotor() self.f = MockMotor() self.grp = ScannableGroup( 'grp', [self.a, self.b, self.c, self.d, self.e, self.f]) self.grp.configure() self.sg = DiffractometerScannableGroup( 'sixc', MockDiffcalc(6), self.grp)
def __init__(self, name, idgap, pgmenergy, lut, gap_offset=None, feedbackPV=None): """ Constructor - Only succeeds if it finds the lookup table, otherwise raises exception. """ self.lut = readLookupTable(LocalProperties.get("gda.config") + "/lookupTables/" + lut) self.gap = idgap self.mono_energy = pgmenergy self.scannables = ScannableGroup(name, [pgmenergy, idgap]) self.detune=gap_offset self.feedbackPV=feedbackPV self._busy = 0 self.setName(name) self.setLevel(3) self.setOutputFormat(["%10.6f"]) self.inputNames = [name] self.SCANNING=False self.order = 1 self.polarisation = 'LH' self.jidphase = Finder.find("jidphase") self.logger = logger.getChild(self.__class__.__name__)
def pathscan(scannables, path, detector, exposure, *args): #@UndefinedVariable ''' Scan a group of scannables following the specified path and collect data at each point from specified detector and time''' sg = ScannableGroup() for each in scannables: sg.addGroupMember(each) sg.setName("pathgroup") scan([sg, path, detector, exposure] + list(args))
def __init__(self, name, gap="jgap", dcm="pgmenergy", undulatorperiod=27, lut="JIDCalibrationTable.txt"): '''Constructor - Only succeed if it find the lookup table, otherwise raise exception.''' self.lut=readLookupTable(LocalProperties.get("gda.config")+"/lookupTables/"+lut) self.gap=gap self.dcm=dcm self.lambdau=undulatorperiod if dcm is None: self.scannableNames=[gap] else: self.scannableNames=[dcm,gap] self.scannables=ScannableGroup(name, [Finder.find(x) for x in self.scannableNames]) self._busy=0 self.setName(name) self.setLevel(3) self.setOutputFormat(["%10.6f"]) self.inputNames=[name] if self.dcm == "dcmenergy": self.order=3 else: self.order=1 self.energy=self.scannables.getGroupMember(self.scannableNames[0]).getPosition() self.polarisation='H'
def pathscan(scannables, path, args=[]): #@UndefinedVariable ''' Scan a group of scannables following the specified path and collect data at each point from scannables args ''' sg = ScannableGroup() for each in scannables: sg.addGroupMember(each) sg.setName("pathgroup") scan([sg, path] + args)
class SoftEnergy(ScannableMotionBase): """ Create beam energy scannable that encapsulates and fan-outs control to ID gap and DCM energy. This pseudo device requires a lookup table object to provide allowed energy ranges. However The lookup table object must be created before the instance creation of this class. Equations to calculate insertion device gaps are hard-coded into this class. The child scannables or pseudo devices must exist in Jython's global namespace prior to any method call of this class instance. The lookup Table object is described by gda.function.LookupTable class. """ def __init__(self, name, idgap, pgmenergy, lut, gap_offset=None, feedbackPV=None): """ Constructor - Only succeeds if it finds the lookup table, otherwise raises exception. """ self.lut = readLookupTable(LocalProperties.get("gda.config") + "/lookupTables/" + lut) self.gap = idgap self.mono_energy = pgmenergy self.scannables = ScannableGroup(name, [pgmenergy, idgap]) self.detune=gap_offset self.feedbackPV=feedbackPV self._busy = 0 self.setName(name) self.setLevel(3) self.setOutputFormat(["%10.6f"]) self.inputNames = [name] self.SCANNING=False self.order = 1 self.polarisation = 'LH' self.jidphase = Finder.find("jidphase") self.logger = logger.getChild(self.__class__.__name__) def setPolarisation(self, value): """Sets the polarisation.""" if value == "LH" or value == "LH3": self.jidphase.hortizontal() self.polarisation=value elif value == "LV": self.jidphase.vertical() self.polarisation = value elif value == "CL": self.jidphase.circular_left() self.polarisation = value elif value == "CR": self.jidphase.circular_right() self.polarisation = value else: raise ValueError("Input " + str(value) + " invalid. Valid values are 'LH', 'LV', 'CL' and 'CR'.") # Move back to the current position i.e. the correct gap for the new polarisation # Note this also causes the ID to actually move, if the gap demand is exactly the same it will never! self.asynchronousMoveTo(self.getPosition()) while (self.isBusy()) : sleep(0.5) def getPolarisation(self): """Returns the current polarisation (cached in object not directly from EPICS)""" return self.polarisation def harmonicEnergyRanges(self): """Prints out a table of harmonics with corresponding min and max energies""" print ("%s\t%s\t%s" % ("Harmonic", "Min Energy", "Max Energy")) keys = [int(key) for key in self.lut.keys()] for key in sorted(keys): print ("%8.0d\t%10.2f\t%10.2f" % (key, self.lut[key][2], self.lut[key][3])) def energyRangeForOrder(self, order): """Returns a tuple with min and max energies for a harmonic order Args: order (int): The order of the harmonic Returns: (min_energy, max_energy) (tuple) """ return (self.lut[order][2], self.lut[order][3]) def idgap(self, Ep, n): """ Function to calculate the insertion device gap Arguments: Ep -- Energy n -- order """ gap = 20.0 self.logger.debug("'idgap' function called with energy {} and order {}" .format(Ep, n)) self.logger.debug("Current cached polarisation is {}" .format(self.polarisation)) # Soft ID J branch # Linear Horizontal if self.getPolarisation() == "LH": if (Ep < 0.104 or Ep > 1.2): raise ValueError("Polarisation = LH but the demanding energy is outside the valid range between 0.104 and 1.2 keV!") # gap=3.06965 +177.99974*Ep -596.79184*Ep**2 +1406.28911*Ep**3 -2046.90669*Ep**4 +1780.26621*Ep**5 -844.81785*Ep**6 +168.99039*Ep**7 # gap=2.75529 + 184.24255*Ep - 639.07279*Ep**2 +1556.23192*Ep**3 -2340.01233*Ep**4 +2100.81252*Ep**5 -1027.88771*Ep**6 +211.47063*Ep**7 gap=0.52071 + 238.56372*Ep - 1169.06966*Ep**2 +4273.03275*Ep**3 -10497.36261*Ep**4 +17156.91928*Ep**5 -18309.05195*Ep**6 +12222.50318*Ep**7 -4623.70738*Ep**8 +755.90853*Ep**9 if (gap < 16 or gap > 60): raise ValueError("Required Soft X-Ray ID gap is out side allowable bound (16, 60)!") # Linear Horizontal 3rd Harmonic for 400 line/mm grating elif (self.getPolarisation()=="LH3"): if (Ep<0.7 or Ep > 1.95): raise ValueError("Polarisation = LH3 but the demanding energy is outside the valid range between 0.7 and 1.9 keV!") gap=10.98969 + 25.8301*Ep - 9.36535*Ep**2 + 1.74461*Ep**3 if (gap < 16 or gap > 60): raise ValueError("Required Soft X-Ray ID gap is out side allowable bound (16, 60)!") # Linear Vertical elif self.getPolarisation() == "LV": if (Ep < 0.22 or Ep > 1.0): raise ValueError("Demanding energy must lie between 0.22 and 1.0 eV!") gap = (5.33595 + 72.53678 * Ep - 133.96826 * Ep ** 2 + 179.99229 * Ep ** 3 - 128.83048 * Ep ** 4 + 39.34346 * Ep ** 5) if (gap < 16.01 or gap > 60): raise ValueError("Required Soft X-Ray ID gap is out side allowable bound (16, 60)!") # Circular left elif self.getPolarisation() == "CL": if (Ep < 0.145 or Ep > 1.2): raise ValueError("Demanding energy must lie between 0.146 and 1.2 eV!") # Circular left gap polymonimal gap = (5.32869 + 101.28316 * Ep - 192.74788 * Ep ** 2 + 249.91788 * Ep ** 3 - 167.93323 * Ep ** 4 + 47.22008 * Ep ** 5 - 0.054 * Ep - .0723) # Check the gap is possible if (gap < 16.01 or gap > 60): raise ValueError("Required Soft X-Ray ID gap is out side allowable bound (16, 60)!") # Circular right elif self.getName() == "jenergy" and self.getPolarisation() == "CR": if (Ep < 0.145 or Ep > 1.2): raise ValueError("Demanding energy must lie between 0.1 and 1.2 eV!") # Circular right gap polymonimal gap = (5.32869 + 101.28316 * Ep - 192.74788 * Ep ** 2 + 249.91788 * Ep ** 3 - 167.93323 * Ep ** 4 + 47.22008 * Ep ** 5) # Check the gap is possible if (gap < 16.01 or gap > 60): raise ValueError("Required Soft X-Ray ID gap is out side allowable bound (16, 60)!") # Unsupported else: raise ValueError("Unsupported scannable or polarisation mode") return gap def rawGetPosition(self): """returns the current position of the beam energy.""" return self.mono_energy.getPosition()/1000.0 def calc(self, energy, order): return self.idgap(energy, order) def moveDevices(self, energy, gap): for s in self.scannables.getGroupMembers(): if s.getName() == self.gap.getName(): try: if self.detune: gap = gap + float(self.detune.getPosition()) self.logger.debug("Calling asynchronousMoveTo() on {} with gap {}".format(s.getName(), gap)) s.asynchronousMoveTo(gap) except: self.logger.error("cannot set " + s.getName() + " to " + str(gap), exc_info=True) raise else: try: self.logger.debug("Calling asynchronousMoveTo() on {} with energy {}".format(s.getName(), energy * 1000)) s.asynchronousMoveTo(energy * 1000) # Allow time for s to become busy sleep(0.1) except: self.logger.error("Can not set " + s.getName() + " to " + str(energy), exc_info=True) raise def rawAsynchronousMoveTo(self, new_position): """ move beam energy to specified value. At the background this moves both ID gap and Mono Bragg to the values corresponding to this energy. If a child scannable can not be reached for whatever reason, it just prints out a message, then continue to next. """ energy = float(new_position) gap = self.idgap(energy, self.order) if self.feedbackPV is not None and not self.SCANNING: caput(self.feedbackPV, 1) self.moveDevices(energy, gap) self.waitWhileBusy() caput(self.feedbackPV, 0) else: self.moveDevices(energy, gap) def isBusy(self): """ checks the busy status of all child scannable. If and only if all child scannable are done this will be set to False. """ self._busy = 0 for s in self.scannables.getGroupMembers(): try: self._busy += s.isBusy() except: print ("%s isBusy() throws exception" % (s.getName()), sys.exc_info()) raise if self._busy == 0: return 0 else: return 1 def toString(self): """formats what to print to the terminal console.""" return self.name + " : " + str(self.rawGetPosition()) def atScanStart(self): self.SCANNING=True def atScanEnd(self): self.SCANNING=False
def analyserscan(*args): ''' a more generalised scan that extends standard GDA scan syntax to support 1. scannable tuple (e.g. (s1,s2,...) argument) as scannable group and 2. its corresponding path tuple (e.g. tuple of position lists), if exist, and 3. EW4000 analyser detector that takes a reion sequence file name as input, if exist, and 4. syntax 'analyserscan ew4000 "user.seq ...' for analyser scan only It parses input parameters described above before delegating to the standard GDA scan to do the actual data collection. Thus it can be used anywhere the standard GDA 'scan' is used. ''' starttime = time.ctime() if PRINTTIME: print "=== Scan started: " + starttime newargs = [] i = 0 while i < len(args): arg = args[i] if i == 0 and isinstance(arg, EW4000): newargs.append(zeroScannable) newargs.append(0) newargs.append(0) newargs.append(1) newargs.append(arg) elif type(arg) == TupleType: if allElementsAreScannable(arg): #parsing (scannable1, scannable2,...) as scannable group scannableGroup = ScannableGroup() for each in arg: scannableGroup.addGroupMember(each) scannableGroup.setName("pathgroup") newargs.append(scannableGroup) elif allElementsAreListOfNumber(arg): #parsing scannable group's position lists newargs.append(arg) elif allElementsAreNumber(arg): #parsing scannable group's position lists newargs.append(arg) elif allElementsAreTuplesOfNumbers(arg): # This case is to fix BLIX-206 when using a scannable group with a tuple of tuples of positions newargs.append(arg) else: raise TypeError, "Only tuple of scannables, tuple of numbers, tuple of tuples of numbers, or list of numbers are supported." else: newargs.append(arg) i = i + 1 if isinstance(arg, EW4000): controller = Finder.find("SequenceFileObserver") xmldir = InterfaceProvider.getPathConstructor( ).getVisitSubdirectory('xml') + os.sep filename = xmldir + args[i] if (OsUtil.isWindows()): FilenameUtil.setPrefix("D:") filename = FilenameUtil.convertSeparator(filename) controller.update(controller, SequenceFileChangeEvent( filename)) #update client sequence view sleep(2.0) jythonServerStatus = InterfaceProvider.getJythonServerStatusProvider( ).getJythonServerStatus() while (jythonServerStatus.isScriptOrScanPaused()): sleep(1.0) # wait for user saving dirty file arg.setSequenceFilename(filename) sequence = arg.loadSequenceData(filename) if isinstance(arg.getCollectionStrategy(), EW4000CollectionStrategy): arg.getCollectionStrategy().setSequence(sequence) i = i + 1 scan(newargs) if ENABLEZEROSUPPLIES: zerosupplies() # @UndefinedVariable if PRINTTIME: print("=== Scan ended: " + time.ctime() + ". Elapsed time: %.0f seconds" % (time.time() - starttime))
''' Created on 1 Aug 2019 @author: fy65 ''' from epics.motor.positionCompareMotorClass import PositionCompareMotorClass from future.singleEpicsPositionerNoStatusClassDeadbandOrStop import SingleEpicsPositionerNoStatusClassDeadbandOrStop from utils.ExceptionLogs import localStation_exception import sys from gda.device.scannable.scannablegroup import ScannableGroup try: m3m5_x = PositionCompareMotorClass("m3m5_x", "BL10I-OP-SWTCH-01:X.VAL", "BL10I-OP-SWTCH-01:X.RBV", "BL10I-OP-SWTCH-01:X.STOP", 0.002, "mm", "%.3f") m3m5_y = PositionCompareMotorClass("m3m5_y", "BL10I-OP-SWTCH-01:Y.VAL", "BL10I-OP-SWTCH-01:Y.RBV", "BL10I-OP-SWTCH-01:Y.STOP", 0.002, "mm", "%.3f") m3m5_z = PositionCompareMotorClass("m3m5_z", "BL10I-OP-SWTCH-01:Z.VAL", "BL10I-OP-SWTCH-01:Z.RBV", "BL10I-OP-SWTCH-01:Z.STOP", 0.002, "mm", "%.3f") m3m5_yaw = PositionCompareMotorClass("m3m5_yaw", "BL10I-OP-SWTCH-01:YAW.VAL", "BL10I-OP-SWTCH-01:YAW.RBV", "BL10I-OP-SWTCH-01:YAW.STOP", 0.002, "urad", "%.3f") m3m5_pitch = PositionCompareMotorClass("m3m5_pitch", "BL10I-OP-SWTCH-01:PITCH.VAL", "BL10I-OP-SWTCH-01:PITCH.RBV", "BL10I-OP-SWTCH-01:PITCH.STOP", 0.002, "urad", "%.3f") m3m5_roll = PositionCompareMotorClass("m3m5_roll", "BL10I-OP-SWTCH-01:ROLL.VAL", "BL10I-OP-SWTCH-01:ROLL.RBV", "BL10I-OP-SWTCH-01:ROLL.STOP", 0.002, "urad", "%.3f") m3m5fpitch = SingleEpicsPositionerNoStatusClassDeadbandOrStop('m3m5fpitch', 'BL10I-OP-SWTCH-01:FPITCH:DMD:AO', 'BL10I-OP-SWTCH-01:FPITCH:RBV:AI', 'V', '%.3f', 0.001) m3m5=ScannableGroup("m3m5", [m3m5_x, m3m5_y, m3m5_z, m3m5_yaw, m3m5_pitch, m3m5_roll, m3m5fpitch]) except: localStation_exception(sys.exc_info(), "initialising m3m5 hexapod and fpitch scannables")
"BL10J-OP-FOCA-01:X.STOP", 0.002, "mm", "%.3f") m6_y = PositionCompareMotorClass("m6_y", "BL10J-OP-FOCA-01:Y.VAL", "BL10J-OP-FOCA-01:Y.RBV", "BL10J-OP-FOCA-01:Y.STOP", 0.002, "mm", "%.3f") m6_z = PositionCompareMotorClass("m6_z", "BL10J-OP-FOCA-01:Z.VAL", "BL10J-OP-FOCA-01:Z.RBV", "BL10J-OP-FOCA-01:Z.STOP", 0.002, "mm", "%.3f") m6_yaw = PositionCompareMotorClass("m6_yaw", "BL10J-OP-FOCA-01:YAW.VAL", "BL10J-OP-FOCA-01:YAW.RBV", "BL10J-OP-FOCA-01:YAW.STOP", 0.002, "urad", "%.3f") m6_pitch = PositionCompareMotorClass("m6_pitch", "BL10J-OP-FOCA-01:PITCH.VAL", "BL10J-OP-FOCA-01:PITCH.RBV", "BL10J-OP-FOCA-01:PITCH.STOP", 0.002, "urad", "%.3f") m6_roll = PositionCompareMotorClass("m6_roll", "BL10J-OP-FOCA-01:ROLL.VAL", "BL10J-OP-FOCA-01:ROLL.RBV", "BL10J-OP-FOCA-01:ROLL.STOP", 0.002, "urad", "%.3f") m6fpitch = SingleEpicsPositionerNoStatusClassDeadbandOrStop( 'm6fpitch', 'BL10J-OP-FOCA-01:FPITCH:DMD:AO', 'BL10J-OP-FOCA-01:FPITCH:RBV:AI', 'V', '%.3f', 0.001) m6 = ScannableGroup( "m6", [m6_x, m6_y, m6_z, m6_yaw, m6_pitch, m6_roll, m6fpitch]) except: localStation_exception(sys.exc_info(), "initialising m6 hexapod and fpitch scannables")
class HardEnergy(ScannableMotionBase): """ Create beam energy scannable that encapsulates and fan-outs control to ID gap and DCM energy. This pseudo device requies a lookup table object to provide ID parameters for calculation of ID gap from beam energy required and harmonic order. The lookup table object must be created before the instance creation of this class. The child scannables or pseudo devices must exist in jython's global namespace prior to any method call of this class instance. The lookup Table object is described by gda.function.LookupTable class. """ def __init__(self, name, idgap, dcmenergy, lut, gap_offset=None, feedbackPVs=None): """ Constructor - Only succeeds if it finds the lookup table, otherwise raises exception. """ self.lut = readLookupTable( LocalProperties.get("gda.config") + "/lookupTables/" + lut) self.gap = idgap self.mono_energy = dcmenergy self.lambdau = 27 # undulator period self.scannables = ScannableGroup(name, [dcmenergy, idgap]) self.detune = gap_offset self.feedbackPVs = feedbackPVs self._busy = 0 self.setName(name) self.setLevel(3) self.setOutputFormat(["%10.6f"]) self.inputNames = [name] self.order = 3 self.SCANNING = False self.logger = logger.getChild(self.__class__.__name__) def harmonicEnergyRanges(self): """ Prints out a table of harmonics with corresponding minimum and maximum energies """ print("%s\t%s\t%s" % ("Harmonic", "Min Energy", "Max Energy")) keys = [int(key) for key in self.lut.keys()] for key in sorted(keys): print("%8.0d\t%10.2f\t%10.2f" % (key, self.lut[key][2], self.lut[key][3])) def energyRangeForOrder(self, order): """Returns a tuple with min and max energies for a harmonic order Args: order (int): The order of the harmonic Returns: (min_energy, max_energy) (tuple) """ return (self.lut[order][2], self.lut[order][3]) def setOrder(self, n): """Method to set the harmonic order""" self.order = n def getOrder(self): """Method to retrieve the harmonic order""" return self.order def idgap(self, Ep, n): """ Function to calculate the insertion device gap Arguments: Ep -- Energy n -- order """ lambda_u = self.lambdau M = 4 h = 16 me = 0.510999 gamma = 1000 * self.lut[n][0] / me k_squared = (4.959368e-6 * (n * gamma * gamma / (lambda_u * Ep)) - 2) if k_squared < 0: raise ValueError("k_squared must be positive!") K = math.sqrt(k_squared) A = ((2 * 0.0934 * lambda_u * self.lut[n][1] * M / math.pi) * math.sin(math.pi / M) * (1 - math.exp(-2 * math.pi * h / lambda_u))) gap = (lambda_u / math.pi) * math.log(A / K) + self.lut[n][6] if self.detune: gap = gap + float(self.detune.getPosition()) self.logger.debug("Required gap calculated to be {}".format(gap)) return gap def rawGetPosition(self): """Returns the current position of the beam energy.""" return self.mono_energy.getPosition() def calc(self, energy, order): return self.idgap(energy, order) def moveDevices(self, energy, gap): for scannable in self.scannables.getGroupMembers(): if scannable.getName() == self.gap.getName(): try: scannable.asynchronousMoveTo(gap) except: print("cannot set %s to %f " % (scannable.getName(), gap)) raise elif scannable.getName() == self.mono_energy.getName(): try: scannable.asynchronousMoveTo(energy) sleep(0.1) # Allow time for s to become busy except: print("cannot set %s to %f" % (scannable.getName(), energy)) raise def rawAsynchronousMoveTo(self, new_position): """ move beam energy to specified value. In the background this moves both ID gap and Mono Bragg to the values corresponding to this energy. If a child scannable can not be reached for whatever reason, it just prints out a message, then continue to next. """ min_energy, max_energy = self.energyRangeForOrder(self.order) energy = float(new_position) self.logger.debug( ("rawAsynchronousMoveTo called for energy {}. " "min_energy for order is: {}, max_energy is: {}").format( energy, min_energy, max_energy)) gap = self.idgap(energy, self.order) if not min_energy < energy < max_energy: raise ValueError(("Requested photon energy {} is out of range for " "harmonic {}: min: {}, max: {}").format( energy, self.order, min_energy, max_energy)) if self.feedbackPVs is not None and not self.SCANNING: caput(self.feedbackPVs[0], 1) caput(self.feedbackPVs[1], 1) self.moveDevices(energy, gap) self.waitWhileBusy() caput(self.feedbackPVs[0], 0) caput(self.feedbackPVs[1], 0) else: self.moveDevices(energy, gap) def isBusy(self): """ Checks the busy status of all child scannable. If and only if all child scannable are done this will be set to False. """ self._busy = 0 for scannable in self.scannables.getGroupMembers(): try: self._busy += scannable.isBusy() except: self.logger.error(scannable.getName() + "isBusy() method threw exception:", exc_info=True) raise if self._busy == 0: return 0 else: return 1 def toString(self): """formats what to print to the terminal console.""" return self.name + " : " + str(self.rawGetPosition()) def atScanStart(self): self.SCANNING = True def atScanEnd(self): self.SCANNING = False
"BL10I-OP-FOCS-01:X.STOP", 0.002, "mm", "%.3f") m4_y = PositionCompareMotorClass("m4_y", "BL10I-OP-FOCS-01:Y.VAL", "BL10I-OP-FOCS-01:Y.RBV", "BL10I-OP-FOCS-01:Y.STOP", 0.002, "mm", "%.3f") m4_z = PositionCompareMotorClass("m4_z", "BL10I-OP-FOCS-01:Z.VAL", "BL10I-OP-FOCS-01:Z.RBV", "BL10I-OP-FOCS-01:Z.STOP", 0.002, "mm", "%.3f") m4_yaw = PositionCompareMotorClass("m4_yaw", "BL10I-OP-FOCS-01:YAW.VAL", "BL10I-OP-FOCS-01:YAW.RBV", "BL10I-OP-FOCS-01:YAW.STOP", 0.002, "urad", "%.3f") m4_pitch = PositionCompareMotorClass("m4_pitch", "BL10I-OP-FOCS-01:PITCH.VAL", "BL10I-OP-FOCS-01:PITCH.RBV", "BL10I-OP-FOCS-01:PITCH.STOP", 0.002, "urad", "%.3f") m4_roll = PositionCompareMotorClass("m4_roll", "BL10I-OP-FOCS-01:ROLL.VAL", "BL10I-OP-FOCS-01:ROLL.RBV", "BL10I-OP-FOCS-01:ROLL.STOP", 0.002, "urad", "%.3f") m4fpitch = SingleEpicsPositionerNoStatusClassDeadbandOrStop( 'm4fpitch', 'BL10I-OP-FOCS-01:FPITCH:DMD:AO', 'BL10I-OP-FOCS-01:FPITCH:RBV:AI', 'V', '%.3f', 0.001) m4 = ScannableGroup( "m4", [m4_x, m4_y, m4_z, m4_yaw, m4_pitch, m4_roll, m4fpitch]) except: localStation_exception(sys.exc_info(), "initialising m4 hexapod and fpitch scannables")
def make_tomoScanDevice(tomography_theta, tomography_shutter, tomography_translation, tomography_optimizer, image_key, tomography_imageIndex): tomoScanDevice = ScannableGroup() tomoScanDevice.addGroupMember(tomography_theta) tomoScanDevice.addGroupMember( EnumPositionerDelegateScannable("tomography_shutter", tomography_shutter)) tomoScanDevice.addGroupMember(tomography_translation) tomoScanDevice.addGroupMember(tomography_optimizer) tomoScanDevice.addGroupMember(image_key) tomoScanDevice.addGroupMember(tomography_imageIndex) tomoScanDevice.setName("tomoScanDevice") tomoScanDevice.configure() return tomoScanDevice
print " (change threshold with checkrc.minumumThreshold=12345)" print " 2. 'checktopup_time', - avoid topup period, pause 5 seconds before topup starts, 5s wait after topup finished." print " 3. 'checkfe', - check Front end shutter, pause when shutter closed, resume 60s after shutter opened." print " 4. 'checkbeam', - composite scannable of above 3 scannables" print " Checking is done every second!" from gdascripts.scannable.beamokay import WaitWhileScannableBelowThreshold, WaitForScannableState from gda.device.scannable.scannablegroup import ScannableGroup checkrc = WaitWhileScannableBelowThreshold('checkrc', ringcurrent, 190, secondsBetweenChecks=1, secondsToWaitAfterBeamBackUp=5) checktopup_time = WaitWhileScannableBelowThreshold( 'checktopup_time', topup_time, 5, secondsBetweenChecks=1, secondsToWaitAfterBeamBackUp=5) checkfe = WaitForScannableState('checkfe', fepb, secondsBetweenChecks=1, secondsToWaitAfterBeamBackUp=60) checkbeam = ScannableGroup('checkbeam', [checkrc, checkfe, checktopup_time]) checkbeam.configure() except: localStation_exception(sys.exc_info(), "creating checkbeam objects")
timekeeper = ShowTimeClass('timekeeper') timekeeper.autoReset = False clock = ShowClockClass('clock') print 'For time measuring, using "lineTime" and "pointTime" for the time spent on one line of a scan and each scan point, respectively' lineTime = LineTimeClass('lineTime') pointTime = PointTimeClass('pointTime') print "To control the speed of a scan, using 'waitTimer' for wait delay or 'scanTimer' or 'timer' as scannable" waitTimer = WaitTimerClass('waitTimer') #To scan against time. timer = ScanTimerClass('timer') scanTimer = ScanTimerClass('scanTimer') Timers = ScannableGroup() Timers.setName('Timers') Timers.addGroupMember(clock) Timers.addGroupMember(stopwatch) Timers.addGroupMember(timekeeper) Timers.addGroupMember(lineTime) Timers.addGroupMember(pointTime) Timers.addGroupMember(waitTimer) Timers.addGroupMember(timer) Timers.addGroupMember(scanTimer) dummyCounter = SoftCounterClass('dummyCounter') dummyCounter1 = SoftCounterClass('dummyCounter1') dummyCounter2 = SoftCounterClass('dummyCounter2') from Diamond.PseudoDevices.DummyShutter import DummyShutterClass
class PathScan(): def __init__(self, name, detectorToUse=edxd):#@UndefinedVariable self.name=name self.sg = ScannableGroup() self.pointid=[] self.path=[] self.startline=1 self.lastline=10 self.scannablelist=[] self.scannableunitlist=[] self.detector=detectorToUse self.detectorunit="s" self.exposuretime=[] def read_scan_path(self,filename): f = open(filename, "r") lines = f.readlines() f.close() lines = map(string.split, map(string.strip, lines)) self.pointid=[] self.path=[] self.scannablelist=[] self.scannableunitlist=[] self.exposuretime=[] # parsing the input data for line in lines: print line if line[0].startswith("#"): #ignore comment continue elif line[0].startswith("First"): self.startline=line[-1] elif line[0].startswith("Last"): self.lastline=line[-1] elif line[0].startswith("ScannableNames"): self.scannablelist=[globals()[x] for x in line[1:]] # get all motors self.sg.setName("pathscangroup") self.sg.setGroupMembers(self.scannablelist) self.sg.configure() elif line[0].startswith("ScannableUnits"): self.scannableunitlist=[x for x in line[1:]] else: #real data go here if int(line[0])>= self.startline or int(line[0]) <= self.lastline: self.pointid.append(int(line[0])) self.path.append([float(x) for x in line[1:]]) def setStartPoint(self, start): self.startline=start def getStartPoint(self): return self.startline def setStopPoint(self, stop): self.lastline=stop def getStopPoint(self): return self.lastline def setDetector(self, det): self.detector=det def getDetector(self): return self.detector def getPath(self): return self.path def setPath(self, path): self.path=path def getPointIDs(self): return self.pointid def setPointIDs(self, points): self.pointid=points def start(self,filename, exposureTime): ''' kept for backward compatibility''' self.read_scan_path(filename) print self.pointid print self.path print self.exposuretime pathPositions=tuple(self.path) print pathPositions scan([self.sg, pathPositions, self.detector, exposureTime]) def startScan(self,filename, exposureTime): print self.pointid print self.exposuretime pathPositions=tuple(self.path) print pathPositions scan([self.sg, pathPositions, self.detector, exposureTime]) def setName(self, name): self.name=name def getName(self): return self.name
def miscan(*args): ''' a more generalised scan that extends standard GDA scan syntax to support 1. scannable tuple (e.g. (s1,s2,...) argument) as scannable group, 2. its corresponding path tuple (e.g. list of position tuples), if exist, and 3. area detector that takes 2 input numbers - 1st input is the number of images to be collected at each point, if omitted it default to 1, and 2nd input is detector exposure time which must be provided, 4. syntax 'miscan mpx 10 0.1 ...' is supported for collecting 10 images at a single point. It parses input parameters described above before delegating to the standard GDA scan to do the actual data collection. Thus it can be used anywhere the standard GDA 'scan' is used. ''' starttime = time.ctime() if PRINTTIME: print "=== Scan started: " + starttime newargs = [] i = 0 while i < len(args): arg = args[i] if i == 0 and isinstance(arg, NXDetector): newargs.append(zeroScannable) newargs.append(0) newargs.append(0) newargs.append(1) newargs.append(arg) elif type(arg) == TupleType: if allElementsAreScannable(arg): #parsing (scannable1, scannable2,...) as scannable group scannableGroup = ScannableGroup() for each in arg: scannableGroup.addGroupMember(each) scannableGroup.setName("pathgroup") newargs.append(scannableGroup) elif allElementsAreListOfNumber(arg): #parsing scannable group's position lists newargs.append(arg) elif allElementsAreNumber(arg): #parsing scannable group's position lists newargs.append(arg) elif allElementsAreTuplesOfNumbers(arg): # This case is to fix BLIX-206 when using a scannable group with a tuple of tuples of positions newargs.append(arg) elif allElementsAreString(arg): newargs.append(arg) else: raise TypeError, "Only tuple of scannables, tuple of numbers, tuple of tuples of numbers, list of numbers, or tuple of Strings are supported." else: newargs.append(arg) i = i + 1 if isinstance(arg, NXDetector): decoratee = arg.getCollectionStrategy().getDecoratee() if isinstance(decoratee, ImageModeDecorator): if i < len( args) - 1: # more than 2 arguments following detector if type(args[i]) == IntType and ( type(args[i + 1]) == IntType or type(args[i + 1]) == FloatType): #support the miscan command - first input after detector is number of images per data point decoratee.setNumberOfImagesPerCollection(args[i]) elif type(args[i]) == FloatType and ( type(args[i + 1]) == IntType or type(args[i + 1]) == FloatType): raise TypeError, "Number of images to collect per scan data point must be Int type." elif type(args[i]) == FloatType and not ( type(args[i + 1]) == IntType or type(args[i + 1]) == FloatType): decoratee.setNumberOfImagesPerCollection(1) elif i == len( args ) - 1: #followed by only one argument - must be exposure time decoratee.setNumberOfImagesPerCollection(1) else: #exposure time is the last one in the scan command newargs.append(args[i]) #single image per data point i = i + 1 scan([e for e in newargs]) if PRINTTIME: print("=== Scan ended: " + time.ctime() + ". Elapsed time: %.0f seconds" % (time.time() - starttime))
def make_tomoScanDevice( tomography_theta, tomography_shutter, tomography_translation, tomography_optimizer, image_key, tomography_imageIndex ): tomoScanDevice = ScannableGroup() tomoScanDevice.addGroupMember(tomography_theta) tomoScanDevice.addGroupMember(EnumPositionerDelegateScannable("tomography_shutter", tomography_shutter)) tomoScanDevice.addGroupMember(tomography_translation) tomoScanDevice.addGroupMember(tomography_optimizer) tomoScanDevice.addGroupMember(image_key) tomoScanDevice.addGroupMember(tomography_imageIndex) tomoScanDevice.setName("tomoScanDevice") tomoScanDevice.configure() return tomoScanDevice
class BeamEnergyPolarisationClass(ScannableMotionBase): '''Coupled beam energy and polarisation scannable that encapsulates and fan-outs control to ID gap, row phase, and PGM energy. This pseudo device requires a lookupTable table object to provide ID parameters for calculation of ID idgap from beam energy required and harmonic order. The lookupTable table object must be created before the instance creation of this class. The child scannables or pseudo devices must exist in jython's global namespace prior to any method call of this class instance. ''' harmonicOrder = 1 def __init__(self, name, idctrl, pgmenergy, lut="JIDEnergy2GapCalibrations.csv", energyConstant=False, polarisationConstant=False, gap_offset=None, feedbackPV=None): '''Constructor - Only succeed if it find the lookupTable table, otherwise raise exception.''' self.lut,self.header = load_lookup_table(LocalProperties.get("gda.config")+"/lookupTables/"+lut) self.idscannable = idctrl self.mono_energy = pgmenergy self.scannables = ScannableGroup(name, [pgmenergy, idctrl]) self.detune = gap_offset self.feedbackPV = feedbackPV self._busy = 0 self.setName(name) self.setLevel(3) self.setOutputFormat(["%10.6f"]) self.setInputNames([name]) self.setExtraNames([]) self.polarisation = 'LH' self.gap = 50 self.phase = 0 self.energyConstant = energyConstant self.polarisationConstant = polarisationConstant self.SCANNING = False if self.energyConstant and self.polarisationConstant: raise RuntimeError("Cannot create an instance with both energy and polarisation being constant.") self.isConfigured = False self.inputSignSwitched = False self.beamlinename = LocalProperties.get(LocalProperties.GDA_BEAMLINE_NAME) self.logger = logger.getChild(self.__class__.__name__) def configure(self): if self.idscannable is not None: self.maxGap=self.idscannable.getController().getMaxGapPos() self.minGap=self.idscannable.getController().getMinGapPos() self.maxPhase=self.idscannable.getController().getMaxPhaseMotorPos() self.isConfigured=True def getIDPositions(self): '''get gap and phase from ID hardware controller, and set polarisation mode in GDA 'idscannable' instance This method sync current object states with ID state in EPICS IOC. ''' result = list(self.idscannable.getPosition()) gap = float(result[0]) polarisation = str(result[1]) if BeamEnergyPolarisationClass.harmonicOrder > 1: # support other harmonic polarisation = str(polarisation)+str(BeamEnergyPolarisationClass.harmonicOrder) phase = float(result[2]) return (gap, polarisation, phase) def showFittingCoefficentsLookupTable(self): formatstring="%4s\t%11s\t%11s\t%11s\t%11s\t%11s\t%11s\t%11s\t%11s\t%11s\t%11s\t%11s\t%11s" print (formatstring % tuple([x for x in self.header])) for key, value in sorted(self.lut.iteritems()): print (formatstring % tuple([x for x in key] + [x for x in value])) def setOrder(self,n): BeamEnergyPolarisationClass.harmonicOrder = n def getOrder(self): return BeamEnergyPolarisationClass.harmonicOrder def idgap(self, energy): '''return gap for the given energy for current polarisation. used in cvscan where polarisation doesn't change during continuous energy moving. ''' gap, polarisation, phase = self.getIDPositions() # @UnusedVariable gap, phase = self.idgapphase(Ep=energy, mode=polarisation) # @UnusedVariable return gap def idgapphase(self, Ep=None, mode='LH'): '''coverts energy and polarisation to gap and phase. It supports polarisation modes: LH, LV, CR, CL, and LH3 ''' phase = 0 #phase value for LH and LV is ignored by self.idscannable if mode in ["LH", "LV", "CR", "CL", "LH3"]: #polarisation is constant in these modes coef = get_fitting_coefficents(mode, Ep, self.lut) gap = coef[0] + coef[1]*Ep + coef[2]*Ep**2 +coef[3]*Ep**3 + coef[4]*Ep**4 + coef[5]*Ep**5 + coef[6]*Ep**6 + coef[7]*Ep**7 + coef[8]*Ep**8 + coef[9]*Ep**9 #adjust gap if self.detune: gap = gap + float(self.detune.getPosition()) if (gap<self.minGap or gap>self.maxGap): #IDGroup Excel table only cover this range raise ValueError("Required Soft X-Ray ID gap is %s out side allowable bound (%s, %s)!" % (gap, self.minGap, self.maxGap)) if mode == "LH3": BeamEnergyPolarisationClass.harmonicOrder = 3 if mode == "LH": BeamEnergyPolarisationClass.harmonicOrder = 1 if mode == "LV": BeamEnergyPolarisationClass.harmonicOrder = 1 phase = self.maxPhase if mode in ["CR", "CL"]: BeamEnergyPolarisationClass.harmonicOrder = 1 phase=15.0 else: raise ValueError("Unsupported polarisation mode, only LH, LV, CR, CL, and LH3 are supported.") if phase < 0 or phase > self.maxPhase: #Physical limits of ID Row Phase raise ValueError("Required Soft X-Ray ID phase is %s out side allowable bound (%s, %s)!" % (phase, 0, self.maxPhase)) return (gap, phase) def calc(self, energy, order=1): message = "'order' input is no longer required. this is now merged into polarisation mode in the calibration lookup table!" print(message) self.logger.warn(message) return self.idgap(energy) def rawGetPosition(self): '''returns the current beam energy, or polarisation, or both.''' gap, polarisation, phase = self.getIDPositions() # @UnusedVariable energy=float(self.mono_energy.getPosition()/1000.0) #energy unit is in keV if polarisation in ["LH","LV","CR","CL","LH3"]: if self.polarisationConstant: return energy elif self.energyConstant: self.setOutputFormat(["%s"]) self.polarisation = polarisation return polarisation else: self.setOutputFormat(["%10.6f","%s"]) self.polarisation = polarisation return energy, polarisation def moveDevices(self, gap, new_polarisation, phase, energy): for s in self.scannables.getGroupMembers(): if str(s.getName()) == str(self.idscannable.getName()): try: if new_polarisation == "LH3" : new_polarisation = "LH" s.asynchronousMoveTo([gap, new_polarisation, phase]) except: print("cannot set %s to [%f, %s, %f]" % (s.getName(), gap, new_polarisation, phase)) raise elif not self.energyConstant: try: s.asynchronousMoveTo(energy * 1000) except: print("cannot set %s to %f." % (s.getName(), energy)) raise def rawAsynchronousMoveTo(self, new_position): '''move beam energy, polarisation, or both to specified values. At the background this moves both ID gap, phase, and PGM energy to the values corresponding to this energy, polarisation or both. If a child scannable can not be reached for whatever reason, it just prints out a message, then continue to next.''' gap = 20 new_polarisation = None phase = 0 try: if not self.SCANNING: #ensure ID hardware in sync in 'pos' command self.rawGetPosition() #parse arguments as it could be 1 or 2 inputs, string or number type, depending on polarisation mode and instance attribute value if not isinstance(new_position, list): # single argument self.logger.debug("Single argument: {} given".format(type(new_position))) if isinstance(new_position, basestring): #polarisation change requested energy=float(self.mono_energy.getPosition())/1000.0 #get existing energy if self.polarisationConstant: #input must be for energy raise ValueError("Input value must be a number.") new_polarisation=str(new_position) if not new_polarisation in ["LH", "LV","CR", "CL", "LH3"]: raise ValueError('Input value must be one of valid polarisation mode: "LH", "LV","CR", "CL", "LH3"') elif isinstance(new_position, numbers.Number): # energy change requested if self.polarisationConstant: #input must be for energy energy=float(new_position) gap, new_polarisation, phase = self.getIDPositions() #get existing polarisation else: raise ValueError("Polarisation is not constant, but a number: {} was given".format(new_position)) else: raise ValueError("Input value must be a string or number.") else: #2 arguments args = list(new_position) if len(args) != 2: raise ValueError("Expect 2 arguments but got %s" % len(args)) if isinstance(args[0], numbers.Number): self.logger.debug("Two arguments given and first argument {} is a number".format(args[0])) energy = float(args[0]) #range validation is done later else: raise ValueError("1st input for energy must be a number") if isinstance(args[1], basestring): new_polarisation = args[1] else: raise ValueError("2nd input for polarisation must be a string") gap, phase=self.idgapphase(Ep=energy, mode=new_polarisation) except: raise #re-raise any exception from above try block if self.feedbackPV is not None and not self.SCANNING: #stop feedback from gdascripts.utils import caput caput(self.feedbackPV, 1) self.moveDevices(gap, new_polarisation, phase, energy) self.waitWhileBusy() caput(self.feedbackPV, 0) else: self.moveDevices(gap, new_polarisation, phase, energy) def isBusy(self): '''checks the busy status of all child scannables. If and only if all child scannables are done this will be set to False. ''' if self.getName() == "dummyenergy" or self.getName()=="dummypolarisation": sleep(0.1) return False else: #real hardware self._busy=0 for s in self.scannables.getGroupMembers(): try: self._busy += s.isBusy() except: print (s.getName() + " isBusy() throws exception ", sys.exc_info()) raise if self._busy == 0: return 0 else: return 1 def stop(self): self.mono_energy.stop() if installation.isLive(): print("ID motion stop is not supported according to ID-Group instruction. Please wait for the Gap motion to complete!") else: self.idscannable.stop() def atScanStart(self): self.rawGetPosition() #ensure ID hardware in sync at start of scan self.SCANNING=True def atScanEnd(self): self.SCANNING=False
def __init__(self, name): self.name = name self.inputNames = ['epoch'] self.extraNames = [] self.outputFormat = ['%.3f'] self.level = 9 self.target_time = None def asynchronousMoveTo(self, time): self.target_time = time def isBusy(self): if self.target_time is None: return False return time.time() <= self.target_time def getPosition(self): return time.time() t = TimeSinceScanStart('t') dt = TimeSinceLastGetPosition("dt") w = Wait("w") clock = TimeOfDay('clock') epoch = TimeSinceEpoch('epoch') timerelated = ScannableGroup() timerelated.setName("timerelated") timerelated.setGroupMembers([t, dt, w, clock, epoch])
class BeamEnergy(ScannableMotionBase): '''Create beam energy scannable that encapsulates and fan-outs control to ID gap and DCM energy. This pseudo device requies a lookup table object to provide ID parameters for calculation of ID gap from beam energy required and harmonic order. The lookup table object must be created before the instance creation of this class. The child scannables or pseudo devices must exist in jython's global namespace prior to any method call of this class instance. The lookup Table object is described by gda.function.LookupTable class.''' def __init__(self, name, gap="jgap", dcm="pgmenergy", undulatorperiod=27, lut="JIDCalibrationTable.txt"): '''Constructor - Only succeed if it find the lookup table, otherwise raise exception.''' self.lut=readLookupTable(LocalProperties.get("gda.config")+"/lookupTables/"+lut) self.gap=gap self.dcm=dcm self.lambdau=undulatorperiod if dcm is None: self.scannableNames=[gap] else: self.scannableNames=[dcm,gap] self.scannables=ScannableGroup(name, [Finder.find(x) for x in self.scannableNames]) self._busy=0 self.setName(name) self.setLevel(3) self.setOutputFormat(["%10.6f"]) self.inputNames=[name] if self.dcm == "dcmenergy": self.order=3 else: self.order=1 self.energy=self.scannables.getGroupMember(self.scannableNames[0]).getPosition() self.polarisation='H' def setPolarisation(self, value): if self.getName()=="jenergy": if value == "H" or value == "V": self.polarisation=value else: raise ValueError("Input "+str(value)+" invalid. Valid values are 'H' or 'V'.") else: print "No polaristion parameter for Hard X-ray ID" def getPolarisation(self): if self.getName()=="jenergy": return self.polarisation else: return "No polaristion parameter for Hard X-ray ID" def HarmonicEnergyRanges(self): print ("%s\t%s\t%s" % ("Harmonic", "Min Energy", "Max Energy")) keys=[int(key) for key in self.lut.keys()] for key in sorted(keys): print ("%8.0d\t%10.2f\t%10.2f" % (key,self.lut[key][2],self.lut[key][3])) def eneryRangeForOrder(self, order): return [self.lut[order][2],self.lut[order][3]] def setOrder(self,n): self.order=n def getOrder(self): return self.order def idgap(self, Ep, n): gap=20.0 if self.getName() == "ienergy": lambdaU=self.lambdau M=4 h=16 me=0.510999 gamma=1000*self.lut[n][0]/me Ksquared=(4.959368e-6*(n*gamma*gamma/(lambdaU*Ep))-2) if Ksquared < 0: raise ValueError("Ksquared must be positive!") K=math.sqrt(Ksquared) A=(2*0.0934*lambdaU*self.lut[n][1]*M/math.pi)*math.sin(math.pi/M)*(1-math.exp(-2*math.pi*h/lambdaU)) gap=(lambdaU/math.pi) * math.log(A/K)+self.lut[n][6] # if self.gap=="igap" and (gap<5.1 or gap>9.1): # raise ValueError("Required Hard X-Ray ID gap is out side allowable bound (5.1, 9.1)!") if self.gap=="jgap" and gap<16: raise ValueError("Required Soft X-Ray ID gap is out side allowable bound (>=16)!") elif (self.getName() == "jenergy" and self.getPolarisation()=="H"): if (Ep<0.11 or Ep > 1.2): raise ValueError("Demanding energy must lie between 0.11 and 1.2 keV!") Epgap = Ep*1000 # gap=3.46389+0.17197*Epgap + -5.84455e-4*Epgap**2 + 1.43759e-6*Epgap**3 + -2.2321e-9*Epgap**4 + 2.09444e-12*Epgap**5 + -1.07453e-15*Epgap**6 + 2.3039e-19*Epgap**7 gap= 0.70492 + 232.97156*Ep - 1100.88615*Ep**2 + 3841.94972*Ep**3 - 8947.83296*Ep**4 + 13823.07663*Ep**5 - 13942.57738*Ep**6 + 8816.18277*Ep**7 - 3170.55571*Ep**8 + 495.16057*Ep**9 if self.gap=="jgap" and (gap<16 or gap>200): raise ValueError("Required Soft X-Ray ID gap is below the lower bound 0f 16 mm!") elif self.getName() == "jenergy" and self.getPolarisation()=="V": if (Ep<0.21 or Ep > 1.2): raise ValueError("Demanding energy must lie between 0.21 and 1.2 keV!") gap = 4.02266 + 89.86963*Ep - 220.65942*Ep**2 + 365.46127*Ep**3 - 168.84016*Ep**4 - 560.87782*Ep**5 + 1255.06201*Ep**6 - 1164.15704*Ep**7 + 531.63871*Ep**8 - 97.25326*Ep**9 if self.gap=="jgap" and (gap<16.05 or gap>40.24): raise ValueError("Required Soft X-Ray ID gap is out side allowable bound (16.05, 40.24)!") else: raise ValueError("Unsupported scannable or polarisation mode") return gap def rawGetPosition(self): '''returns the current position of the beam energy.''' self.energy=self.scannables.getGroupMember(self.scannableNames[0]).getPosition() return self.energy; def calc(self, energy, order): return self.idgap(energy, order) def rawAsynchronousMoveTo(self, new_position): '''move beam energy to specified value. At the background this moves both ID gap and Mono Bragg to the values corresponding to this energy. If a child scannable can not be reached for whatever reason, it just prints out a message, then continue to next.''' self.energy = float(new_position) gap = 7 try: if self.getName() == "dummyenergy": gap=self.energy else: gap=self.idgap(self.energy, self.order) except: raise if self.getName() == "ienergy": if self.energy<self.eneryRangeForOrder(self.order)[0] or self.energy>self.eneryRangeForOrder(self.order)[1]: raise ValueError("Requested photon energy is out of range for this harmonic!") for s in self.scannables.getGroupMembers(): if s.getName() == self.gap: try: s.asynchronousMoveTo(gap) except: print "cannot set " + s.getName() + " to " + str(gap) raise else: try: if s.getName() == "pgmenergy": s.asynchronousMoveTo(self.energy*1000) # caput("ELECTRON-ANALYSER-01:TEST:EXCITATION_ENERGY", self.energy*1000) else: s.asynchronousMoveTo(self.energy) # caput("ELECTRON-ANALYSER-01:TEST:EXCITATION_ENERGY", self.energy*1000) except: print "cannot set " + s.getName() + " to " + str(self.energy) raise def isBusy(self): '''checks the busy status of all child scannable. If and only if all child scannable are done this will be set to False.''' self._busy=0 for s in self.scannables.getGroupMembers(): try: self._busy += s.isBusy() except: print s.getName() + " isBusy() throws exception ", sys.exc_info() raise if self._busy == 0: return 0 else: return 1 def toString(self): '''formats what to print to the terminal console.''' return self.name + " : " + str(self.rawGetPosition())