PageRenderTime 643ms CodeModel.GetById 43ms RepoModel.GetById 4ms app.codeStats 0ms

/Framework/PythonInterface/plugins/algorithms/LoadNMoldyn4Ascii.py

https://github.com/wdzhou/mantid
Python | 309 lines | 210 code | 49 blank | 50 comment | 32 complexity | 9178e889ba64e552f075475538f171c5 MD5 | raw file
  1. #pylint: disable=no-init
  2. from __future__ import (absolute_import, division, print_function)
  3. from mantid.simpleapi import *
  4. from mantid.kernel import *
  5. from mantid.api import *
  6. import numpy as np
  7. import scipy.constants as sc
  8. import ast
  9. import fnmatch
  10. import re
  11. import os
  12. #------------------------------------------------------------------------------
  13. VARIABLE_REGEX = re.compile(r'#\s+variable name:\s+(.*)')
  14. TYPE_REGEX = re.compile(r'#\s+type:\s+([A-z]+)')
  15. AXIS_REGEX = re.compile(r'#\s+axis:\s+([A-z]+)\|([A-z]+)')
  16. UNIT_REGEX = re.compile(r'#\s+units:\s+(.*)')
  17. SLICE_1D_HEADER_REGEX = re.compile(r'#slice:\[([0-9]+)[A-z]*\]')
  18. SLICE_2D_HEADER_REGEX = re.compile(r'#slice:\[([0-9]+)[A-z]*,\s+([0-9]+)[A-z]*\]')
  19. #------------------------------------------------------------------------------
  20. class LoadNMoldyn4Ascii(PythonAlgorithm):
  21. _axis_cache = None
  22. _data_directory = None
  23. #------------------------------------------------------------------------------
  24. def category(self):
  25. return 'Inelastic\\DataHandling;Simulation'
  26. def summary(self):
  27. return 'Imports functions from .dat files output by nMOLDYN 4.'
  28. #------------------------------------------------------------------------------
  29. def PyInit(self):
  30. self.declareProperty(FileProperty('Directory', '',
  31. action=FileAction.Directory),
  32. doc='Path to directory containg .dat files')
  33. self.declareProperty(StringArrayProperty('Functions'),
  34. doc='Names of functions to attempt to load from file')
  35. self.declareProperty(WorkspaceProperty('OutputWorkspace', '',
  36. direction=Direction.Output),
  37. doc='Output workspace name')
  38. #------------------------------------------------------------------------------
  39. def validateInputs(self):
  40. issues = dict()
  41. if len(self.getProperty('Functions').value) == 0:
  42. issues['Functions'] = 'Must specify at least one function to load'
  43. return issues
  44. #------------------------------------------------------------------------------
  45. def PyExec(self):
  46. self._axis_cache = {}
  47. self._data_directory = self.getPropertyValue('Directory')
  48. # Convert the simplified function names to the actual file names
  49. data_directory_files = [os.path.splitext(f)[0] for f in fnmatch.filter(os.listdir(self._data_directory), '*.dat')]
  50. logger.debug('All data files: {0}'.format(data_directory_files))
  51. functions_input = [x.strip().replace(',', '') for x in self.getProperty('Functions').value]
  52. functions = [f for f in data_directory_files if f.replace(',', '') in functions_input]
  53. logger.debug('Functions to load: {0}'.format(functions))
  54. loaded_function_workspaces = []
  55. for func_name in functions:
  56. try:
  57. # Load the intensity data
  58. function = self._load_function(func_name)
  59. # Load (or retrieve) the axis data
  60. v_axis = self._load_axis(function[2][0])
  61. x_axis = self._load_axis(function[2][1])
  62. # Perform axis unit conversions
  63. v_axis = self._axis_conversion(*v_axis)
  64. x_axis = self._axis_conversion(*x_axis)
  65. # Create the workspace for function
  66. create_workspace = AlgorithmManager.Instance().create('CreateWorkspace')
  67. create_workspace.initialize()
  68. create_workspace.setLogging(False)
  69. create_workspace.setProperty('OutputWorkspace', func_name)
  70. create_workspace.setProperty('DataX', x_axis[0])
  71. create_workspace.setProperty('DataY', function[0])
  72. create_workspace.setProperty('NSpec', v_axis[0].size)
  73. create_workspace.setProperty('UnitX', x_axis[1])
  74. create_workspace.setProperty('YUnitLabel', function[1])
  75. create_workspace.setProperty('VerticalAxisValues', v_axis[0])
  76. create_workspace.setProperty('VerticalAxisUnit', v_axis[1])
  77. create_workspace.setProperty('WorkspaceTitle', func_name)
  78. create_workspace.execute()
  79. loaded_function_workspaces.append(func_name)
  80. except ValueError as rerr:
  81. logger.warning('Failed to load function {0}. Error was: {1}'.format(func_name, str(rerr)))
  82. # Process the loaded workspaces
  83. out_ws_name = self.getPropertyValue('OutputWorkspace')
  84. if len(loaded_function_workspaces) == 0:
  85. raise RuntimeError('Failed to load any functions for data')
  86. GroupWorkspaces(InputWorkspaces=loaded_function_workspaces,
  87. OutputWorkspace=out_ws_name)
  88. # Set the output workspace
  89. self.setProperty('OutputWorkspace', out_ws_name)
  90. #------------------------------------------------------------------------------
  91. def _load_function(self, function_name):
  92. """
  93. Loads a function from the data directory.
  94. @param function_name Name of the function to load
  95. @return Tuple of (Numpy array of data, unit, (v axis name, x axis name))
  96. @exception ValueError If function is not found
  97. """
  98. function_filename = os.path.join(self._data_directory, '{0}.dat'.format(function_name))
  99. if not os.path.isfile(function_filename):
  100. raise ValueError('File for function "{0}" not found'.format(function_name))
  101. data = None
  102. axis = (None, None)
  103. unit = None
  104. with open(function_filename, 'rU') as f_handle:
  105. while True:
  106. line = f_handle.readline()
  107. if not line:
  108. break
  109. # Ignore empty lines
  110. if len(line[0]) == 0:
  111. pass
  112. # Parse header lines
  113. elif line[0] == '#':
  114. variable_match = VARIABLE_REGEX.match(line)
  115. if variable_match and variable_match.group(1) != function_name:
  116. raise ValueError('Function name differs from file name')
  117. axis_match = AXIS_REGEX.match(line)
  118. if axis_match:
  119. axis = (axis_match.group(1), axis_match.group(2))
  120. unit_match = UNIT_REGEX.match(line)
  121. if unit_match:
  122. unit = unit_match.group(1)
  123. slice_match = SLICE_2D_HEADER_REGEX.match(line)
  124. if slice_match:
  125. dimensions = (int(slice_match.group(1)), int(slice_match.group(2)))
  126. # Now parse the data
  127. data = self._load_2d_slice(f_handle, dimensions)
  128. return (data, unit, axis)
  129. #------------------------------------------------------------------------------
  130. def _load_axis(self, axis_name):
  131. """
  132. Loads an axis by name from the data directory.
  133. @param axis_name Name of axis to load
  134. @return Tuple of (Numpy array of data, unit, name)
  135. @exception ValueError If axis is not found
  136. """
  137. if axis_name in self._axis_cache:
  138. return self._axis_cache[axis_name]
  139. axis_filename = os.path.join(self._data_directory, '{0}.dat'.format(axis_name))
  140. if not os.path.isfile(axis_filename):
  141. raise ValueError('File for axis "{0}" not found'.format(axis_name))
  142. data = None
  143. unit = None
  144. with open(axis_filename, 'rU') as f_handle:
  145. while True:
  146. line = f_handle.readline()
  147. if not line:
  148. break
  149. # Ignore empty lines
  150. if len(line[0]) == 0:
  151. pass
  152. # Parse header lines
  153. elif line[0] == '#':
  154. variable_match = VARIABLE_REGEX.match(line)
  155. if variable_match and variable_match.group(1) != axis_name:
  156. raise ValueError('Axis name differs from file name')
  157. unit_match = UNIT_REGEX.match(line)
  158. if unit_match:
  159. unit = unit_match.group(1)
  160. slice_match = SLICE_1D_HEADER_REGEX.match(line)
  161. if slice_match:
  162. length = int(slice_match.group(1))
  163. # Now parse the data
  164. data = self._load_1d_slice(f_handle, length)
  165. return (data, unit, axis_name)
  166. #------------------------------------------------------------------------------
  167. def _load_1d_slice(self, f_handle, length):
  168. """
  169. Loads a 1D slice from the open file.
  170. @param f_handle Handle to the open file with the iterator at the slice header
  171. @param length Length of data
  172. @return Numpy array of length [length]
  173. """
  174. data = np.ndarray(shape=(length), dtype=float)
  175. for idx in range(length):
  176. line = f_handle.readline()
  177. # End of file or empty line (either way end of data)
  178. if not line or len(line) == 0:
  179. break
  180. data[idx] = ast.literal_eval(line)
  181. return data
  182. #------------------------------------------------------------------------------
  183. def _load_2d_slice(self, f_handle, dimensions):
  184. """
  185. Loads a 2D slice from the open file.
  186. @param f_handle Handle to the open file with the iterator at the slice header
  187. @param dimensions Tuple containing dimensions (rows/vertical axis, cols/x axis)
  188. @return Numpy array of shape [dimensions]
  189. """
  190. data = np.ndarray(shape=dimensions, dtype=float)
  191. for v_idx in range(dimensions[0]):
  192. line = f_handle.readline()
  193. # End of file or empty line (either way end of data)
  194. if not line or len(line) == 0:
  195. break
  196. values = [ast.literal_eval(s) for s in line.split()]
  197. data[v_idx] = np.array(values)
  198. return data
  199. #------------------------------------------------------------------------------
  200. def _axis_conversion(self, data, unit, name):
  201. """
  202. Converts an axis to a Mantid axis type (possibly performing a unit
  203. conversion).
  204. @param data The axis data as Numpy array
  205. @param unit The axis unit as read from the file
  206. @param name The axis name as read from the file
  207. @return Tuple containing updated axis details
  208. """
  209. logger.debug('Axis for conversion: name={0}, unit={1}'.format(name, unit))
  210. # Q (nm**-1) to Q (Angstrom**-1)
  211. if name.lower() == 'q' and unit.lower() == 'inv_nm':
  212. logger.information('Axis {0} will be converted to Q in Angstrom**-1'.format(name))
  213. unit = 'MomentumTransfer'
  214. data /= sc.nano # nm to m
  215. data *= sc.angstrom # m to Angstrom
  216. # Frequency (THz) to Energy (meV)
  217. elif name.lower() == 'frequency' and unit.lower() == 'thz':
  218. logger.information('Axis {0} will be converted to energy in meV'.format(name))
  219. unit = 'Energy'
  220. data *= sc.tera # THz to Hz
  221. data *= sc.value('Planck constant in eV s') # Hz to eV
  222. data /= sc.milli # eV to meV
  223. # Time (ps) to TOF (s)
  224. elif name.lower() == 'time' and unit.lower() == 'ps':
  225. logger.information('Axis {0} will be converted to time in microsecond'.format(name))
  226. unit = 'TOF'
  227. data *= sc.micro # ps to us
  228. # No conversion
  229. else:
  230. unit = 'Empty'
  231. return (data, unit, name)
  232. #------------------------------------------------------------------------------
  233. AlgorithmFactory.subscribe(LoadNMoldyn4Ascii)