PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/src/pyclaw/util.py

https://github.com/ahmadia/pyclaw
Python | 600 lines | 557 code | 9 blank | 34 comment | 10 complexity | afd2acc16b8686577a319fc1131fa921 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. r"""
  4. Pyclaw utility methods
  5. """
  6. import time
  7. import os,sys
  8. import subprocess
  9. import logging
  10. import tempfile
  11. import numpy as np
  12. def run_app_from_main(application):
  13. r"""
  14. Runs an application from apps/, automatically parsing command line keyword
  15. arguments (key=value) as parameters to the application, with positional
  16. arguments being passed to PETSc (if it is enabled).
  17. Perhaps we should take the PETSc approach of having a database of PyClaw
  18. options that can be queried for options on specific objects within the
  19. PyClaw runtime instead of front-loading everything through the application
  20. main...
  21. """
  22. # Arguments to the PyClaw should be keyword based, positional arguments
  23. # will be passed to PETSc
  24. petsc_args, app_kwargs = _info_from_argv(sys.argv)
  25. if 'use_petsc' in app_kwargs and app_kwargs['use_petsc']:
  26. import petsc4py
  27. petsc_args = [arg.replace('--','-') for arg in sys.argv[1:] if '=' not in arg]
  28. petsc4py.init(petsc_args)
  29. output=application(**app_kwargs)
  30. return output
  31. class VerifyError(Exception):
  32. pass
  33. def gen_variants(application, verifier, kernel_languages=('Fortran',), **kwargs):
  34. r"""
  35. Generator of runnable variants of a test application given a verifier
  36. Given an application, a script for verifying its output, and a
  37. list of kernel languages to try, generates all possible variants of the
  38. application to try by taking a product of the available kernel_languages and
  39. (petclaw/pyclaw). For many applications, this will generate 4 variants:
  40. the product of the two main kernel languages ('Fortran' and 'Python'), against
  41. the the two parallel modes (petclaw and pyclaw).
  42. For more information on how the verifier function should be implemented,
  43. see util.test_app for a description, and util.check_diff for an example.
  44. All unrecognized keyword arguments are passed through to the application.
  45. """
  46. arg_dicts = build_variant_arg_dicts(kernel_languages)
  47. for test_kwargs in arg_dicts:
  48. test_kwargs.update(kwargs)
  49. yield (test_app, application, verifier, test_kwargs)
  50. return
  51. def build_variant_arg_dicts(kernel_languages=('Fortran',)):
  52. import itertools
  53. # only test petsc4py if it is available
  54. try:
  55. import petsc4py
  56. use_petsc_opts=(True,False)
  57. except Exception as err:
  58. use_petsc_opts = (False,)
  59. opt_names = 'use_petsc','kernel_language'
  60. opt_product = itertools.product(use_petsc_opts,kernel_languages)
  61. arg_dicts = [dict(zip(opt_names,argset)) for argset in opt_product]
  62. return arg_dicts
  63. def test_app_variants(application, verifier, kernel_languages, **kwargs):
  64. arg_dicts = build_variant_arg_dicts(kernel_languages)
  65. for test_kwargs in arg_dicts:
  66. test_kwargs.update(kwargs)
  67. test_app(application, verifier, test_kwargs)
  68. return
  69. def test_app(application, verifier, kwargs):
  70. r"""
  71. Test the output of a given application against its verifier method.
  72. This function performs the following two function calls::
  73. output = application(**kwargs)
  74. check_values = verifier(output)
  75. The verifier method should return None if the output is correct, otherwise
  76. it should return an indexed sequence of three items::
  77. 0 - expected value
  78. 1 - test value
  79. 2 - string describing the tolerance type (abs/rel) and value.
  80. This information is used to present descriptive help if an error is detected.
  81. For an example verifier method, see util.check_diff
  82. """
  83. print kwargs
  84. if 'use_petsc' in kwargs and not kwargs['use_petsc']:
  85. try:
  86. # don't duplicate serial test runs
  87. from petsc4py import PETSc
  88. rank = PETSc.COMM_WORLD.getRank()
  89. if rank != 0:
  90. return
  91. except ImportError, e:
  92. pass
  93. output = application(**kwargs)
  94. check_values = verifier(output)
  95. if check_values is not None:
  96. import inspect
  97. err = \
  98. """%s
  99. ********************************************************************************
  100. verification function
  101. %s
  102. args : %s
  103. norm of expected data: %s
  104. norm of test data : %s
  105. test error : %s
  106. %s
  107. ********************************************************************************
  108. """ % \
  109. (inspect.getsourcefile(application),
  110. inspect.getsource(verifier),
  111. kwargs,
  112. check_values[0],
  113. check_values[1],
  114. check_values[2],
  115. check_values[3])
  116. raise VerifyError(err)
  117. return
  118. def check_diff(expected, test, **kwargs):
  119. r"""
  120. Checks the difference between expected and test values, return None if ok
  121. This function expects either the keyword argument 'abstol' or 'reltol'.
  122. """
  123. err_norm = np.linalg.norm(expected - test)
  124. expected_norm = np.linalg.norm(expected)
  125. test_norm = np.linalg.norm(test)
  126. if 'abstol' in kwargs:
  127. if err_norm < kwargs['abstol']: return None
  128. else: return (expected_norm, test_norm, err_norm,
  129. 'abstol : %s' % kwargs['abstol'])
  130. elif 'reltol' in kwargs:
  131. if err_norm/expected_norm < kwargs['reltol']: return None
  132. else: return (expected_norm, test_norm, err_norm,
  133. 'reltol : %s' % kwargs['reltol'])
  134. else:
  135. raise Exception('Incorrect use of check_diff verifier, specify tol!')
  136. # ============================================================================
  137. # F2PY Utility Functions
  138. # ============================================================================
  139. def compile_library(source_list,module_name,interface_functions=[],
  140. local_path='./',library_path='./',f2py_flags='',
  141. FC=None,FFLAGS=None,recompile=False,clean=False):
  142. r"""
  143. Compiles and wraps fortran source into a callable module in python.
  144. This function uses f2py to create an interface from python to the fortran
  145. sources in source_list. The source_list can either be a list of names
  146. of source files in which case compile_library will search for the file in
  147. local_path and then in library_path. If a path is given, the file will be
  148. checked to see if it exists, if not it will look for the file in the above
  149. resolution order. If any source file is not found, an IOException is
  150. raised.
  151. The list interface_functions allows the user to specify which fortran
  152. functions are actually available to python. The interface functions are
  153. assumed to be in the file with their name, i.e. claw1 is located in
  154. 'claw1.f95' or 'claw1.f'.
  155. The interface from fortran may be different than the original function
  156. call in fortran so the user should make sure to check the automatically
  157. created doc string for the fortran module for proper use.
  158. Source files will not be recompiled if they have not been changed.
  159. One set of options of note is for enabling OpenMP, it requires the usual
  160. fortran flags but the OpenMP library also must be compiled in, this is
  161. done with the flag -lgomp. The call to compile_library would then be:
  162. compile_library(src,module_name,f2py_flags='-lgomp',FFLAGS='-fopenmp')
  163. For complete optimization use:
  164. FFLAGS='-O3 -fopenmp -funroll-loops -finline-functions -fdefault-real-8'
  165. :Input:
  166. - *source_list* - (list of strings) List of source files, if these are
  167. just names of the source files, i.e. 'bc1.f' then they will be searched
  168. for in the default source resolution order, if an explicit path is
  169. given, i.e. './bc1.f', then the function will use that source if it can
  170. find it.
  171. - *module_name* - (string) Name of the resulting module
  172. - *interface_functions* - (list of strings) List of function names to
  173. provide access to, if empty, all functions are accessible to python.
  174. Defaults to [].
  175. - *local_path* - (string) The base path for source resolution, defaults
  176. to './'.
  177. - *library_path* - (string) The library path for source resolution,
  178. defaults to './'.
  179. - *f2py_flags* - (string) f2py flags to be passed
  180. - *FC* - (string) Override the environment variable FC and use it to
  181. compile, note that this does not replace the compiler that f2py uses,
  182. only the object file compilation (functions that do not have
  183. interfaces)
  184. - *FFLAGS* - (string) Override the environment variable FFLAGS and pass
  185. them to the fortran compiler
  186. - *recompile* - (bool) Force recompilation of the library, defaults to
  187. False
  188. - *clean* - (bool) Force a clean build of all source files
  189. """
  190. # Setup logger
  191. logger = logging.getLogger('f2py')
  192. temp_file = tempfile.TemporaryFile()
  193. logger.info('Compiling %s' % module_name)
  194. # Force recompile if the clean flag is set
  195. if clean:
  196. recompile = True
  197. # Expand local_path and library_path
  198. local_path = os.path.expandvars(local_path)
  199. local_path = os.path.expanduser(local_path)
  200. library_path = os.path.expandvars(library_path)
  201. library_path = os.path.expanduser(library_path)
  202. # Fetch environment variables we need for compilation
  203. if FC is None:
  204. if os.environ.has_key('FC'):
  205. FC = os.environ['FC']
  206. else:
  207. FC = 'gfortran'
  208. if FFLAGS is None:
  209. if os.environ.has_key('FFLAGS'):
  210. FFLAGS = os.environ['FFLAGS']
  211. else:
  212. FFLAGS = ''
  213. # Create the list of paths to sources
  214. path_list = []
  215. for source in source_list:
  216. # Check to see if the source looks like a path, i.e. it contains the
  217. # os.path.sep character
  218. if source.find(os.path.sep) >= 0:
  219. source = os.path.expandvars(source)
  220. source = os.path.expanduser(source)
  221. # This is a path, check to see if it's valid
  222. if os.path.exists(source):
  223. path_list.append(source)
  224. continue
  225. # Otherwise, take the last part of the path and try searching for
  226. # it in the resolution order
  227. source = os.path.split(source)
  228. # Search for the source file in local_path and then library_path
  229. if os.path.exists(os.path.join(local_path,source)):
  230. path_list.append(os.path.join(local_path,source))
  231. continue
  232. elif os.path.exists(os.path.join(library_path,source)):
  233. path_list.append(os.path.join(library_path,source))
  234. continue
  235. else:
  236. raise IOError('Could not find source file %s' % source)
  237. # Compile each of the source files if the object files are not present or
  238. # if the modification date of the source file is newer than the object
  239. # file's creation date
  240. object_list = []
  241. src_list = []
  242. for path in path_list:
  243. object_path = os.path.join(os.path.split(path)[0],
  244. '.'.join((os.path.split(path)[1].split('.')[:-1][0],'o')))
  245. # Check to see if this path contains one of the interface functions
  246. if os.path.split(path)[1].split('.')[:-1][0] in interface_functions:
  247. src_list.append(path)
  248. continue
  249. # If there are no interface functions specified, then all source files
  250. # must be included in the f2py call
  251. elif len(interface_functions) == 0:
  252. src_list.append(path)
  253. continue
  254. if os.path.exists(object_path) and not clean:
  255. # Check to see if the modification date of the source file is
  256. # greater than the object file
  257. if os.path.getmtime(object_path) > os.path.getmtime(path):
  258. object_list.append(object_path)
  259. continue
  260. # Compile the source file into the object file
  261. command = '%s %s -c %s -o %s' % (FC,FFLAGS,path,object_path)
  262. logger.debug(command)
  263. subprocess.call(command,shell=True,stdout=temp_file)
  264. object_list.append(object_path)
  265. # Check to see if recompile is needed
  266. if not recompile:
  267. module_path = os.path.join('.','.'.join((module_name,'so')))
  268. if os.path.exists(module_path):
  269. for src in src_list:
  270. if os.path.getmtime(module_path) < os.path.getmtime(src):
  271. recompile = True
  272. break
  273. for obj in object_list:
  274. if os.path.getmtime(module_path) < os.path.getmtime(obj):
  275. recompile = True
  276. break
  277. else:
  278. recompile = True
  279. if recompile:
  280. # Wrap the object files into a python module
  281. f2py_command = "f2py -c"
  282. # Add standard compiler flags
  283. f2py_command = ' '.join((f2py_command,f2py_flags))
  284. f2py_command = ' '.join((f2py_command,"--f90flags='%s'" % FFLAGS))
  285. # Add module names
  286. f2py_command = ' '.join((f2py_command,'-m %s' % module_name))
  287. # Add source files
  288. f2py_command = ' '.join((f2py_command,' '.join(src_list)))
  289. # Add object files
  290. f2py_command = ' '.join((f2py_command,' '.join(object_list)))
  291. # Add interface functions
  292. if len(interface_functions) > 0:
  293. f2py_command = ' '.join( (f2py_command,'only:') )
  294. for interface in interface_functions:
  295. f2py_command = ' '.join( (f2py_command,interface) )
  296. f2py_command = ''.join( (f2py_command,' :') )
  297. logger.debug(f2py_command)
  298. status = subprocess.call(f2py_command,shell=True,stdout=temp_file)
  299. if status == 0:
  300. logger.info("Module %s compiled" % module_name)
  301. else:
  302. logger.info("Module %s failed to compile with code %s" % (module_name,status))
  303. sys.exit(13)
  304. else:
  305. logger.info("Module %s is up to date." % module_name)
  306. temp_file.seek(0)
  307. logger.debug(temp_file.read())
  308. temp_file.close()
  309. def construct_function_handle(path,function_name=None):
  310. r"""
  311. Constructs a function handle from the file at path.
  312. This function will attempt to construct a function handle from the python
  313. file at path.
  314. :Input:
  315. - *path* - (string) Path to the file containing the function
  316. - *function_name* - (string) Name of the function defined in the file
  317. that the handle will point to. Defaults to the same name as the file
  318. without the extension.
  319. :Output:
  320. - (func) Function handle to the constructed function, None if this has
  321. failed.
  322. """
  323. # Determine the resulting function_name
  324. if function_name is None:
  325. function_name = path.split('/')[-1].split('.')[0]
  326. full_path = os.path.abspath(path)
  327. if os.path.exists(full_path):
  328. suffix = path.split('.')[-1]
  329. # This is a python file and we just need to read it and map it
  330. if suffix in ['py']:
  331. execfile(full_path,globals())
  332. return eval('%s' % function_name)
  333. else:
  334. raise Exception("Invalid file type for function handle.")
  335. else:
  336. raise Exception("Invalid file path %s" % path)
  337. #---------------------------------------------------------
  338. def read_data_line(inputfile,num_entries=1,type='float'):
  339. #---------------------------------------------------------
  340. r"""
  341. Read data a single line from an input file
  342. Reads one line from an input file and returns an array of values
  343. inputfile: a file pointer to an open file object
  344. num_entries: number of entries that should be read, defaults to only 1
  345. type: Type of the values to be read in, they all most be the same type
  346. This function will return either a single value or an array of values
  347. depending on if num_entries > 1
  348. """
  349. l = []
  350. while l==[]: # skip over blank lines
  351. line = inputfile.readline()
  352. l = line.split()
  353. val = np.empty(num_entries,type)
  354. if num_entries > len(l):
  355. print 'Error in read_data_line: num_entries = ', num_entries
  356. print ' is larger than length of l = ',l
  357. try:
  358. for i in range(num_entries):
  359. exec("val[i] = %s(l[i])" % type)
  360. if num_entries == 1: # This is a convenience for calling functions
  361. return val[0]
  362. return val
  363. except(ValueError):
  364. print "Invalid type for the %s value in %s" % (i,l)
  365. print " type = ",type
  366. return None
  367. except:
  368. raise
  369. #----------------------------------------
  370. def convert_fort_double_to_float(number):
  371. #----------------------------------------
  372. r"""
  373. Converts a fortran format double to a float
  374. Converts a fortran format double to a python float.
  375. number: is a string representation of the double. Number should
  376. be of the form "1.0d0"
  377. """
  378. a = number.split('d')
  379. return float(a[0])*10**float(a[1])
  380. #-----------------------------
  381. def current_time(addtz=False):
  382. #-----------------------------
  383. # determine current time and reformat:
  384. time1 = time.asctime()
  385. year = time1[-5:]
  386. day = time1[:-14]
  387. hour = time1[-13:-5]
  388. current_time = day + year + ' at ' + hour
  389. if addtz:
  390. current_time = current_time + ' ' + time.tzname[time.daylight]
  391. return current_time
  392. def _method_info_from_argv(argv=None):
  393. """Command-line -> method call arg processing.
  394. - positional args:
  395. a b -> method('a', 'b')
  396. - intifying args:
  397. a 123 -> method('a', 123)
  398. - json loading args:
  399. a '["pi", 3.14, null]' -> method('a', ['pi', 3.14, None])
  400. - keyword args:
  401. a foo=bar -> method('a', foo='bar')
  402. - using more of the above
  403. 1234 'extras=["r2"]' -> method(1234, extras=["r2"])
  404. @param argv {list} Command line arg list. Defaults to `sys.argv`.
  405. @returns (<method-name>, <args>, <kwargs>)
  406. """
  407. import json
  408. if argv is None:
  409. argv = sys.argv
  410. method_name, arg_strs = argv[1], argv[2:]
  411. args = []
  412. kwargs = {}
  413. for s in arg_strs:
  414. if s.count('=') == 1:
  415. key, value = s.split('=', 1)
  416. else:
  417. key, value = None, s
  418. try:
  419. value = json.loads(value)
  420. except ValueError:
  421. pass
  422. if value=='True': value=True
  423. if value.lower()=='false': value=False
  424. if key:
  425. kwargs[key] = value
  426. else:
  427. args.append(value)
  428. return method_name, args, kwargs
  429. def _info_from_argv(argv=None):
  430. """Command-line -> method call arg processing.
  431. - positional args:
  432. a b -> method('a', 'b')
  433. - intifying args:
  434. a 123 -> method('a', 123)
  435. - json loading args:
  436. a '["pi", 3.14, null]' -> method('a', ['pi', 3.14, None])
  437. - keyword args:
  438. a foo=bar -> method('a', foo='bar')
  439. - using more of the above
  440. 1234 'extras=["r2"]' -> method(1234, extras=["r2"])
  441. @param argv {list} Command line arg list. Defaults to `sys.argv`.
  442. @returns (<method-name>, <args>, <kwargs>)
  443. """
  444. import json
  445. if argv is None:
  446. argv = sys.argv
  447. arg_strs = argv[1:]
  448. args = []
  449. kwargs = {}
  450. for s in arg_strs:
  451. if s.count('=') == 1:
  452. key, value = s.split('=', 1)
  453. else:
  454. key, value = None, s
  455. try:
  456. value = json.loads(value)
  457. except ValueError:
  458. pass
  459. if value=='True': value=True
  460. if value=='False': value=False
  461. if key:
  462. kwargs[key] = value
  463. else:
  464. args.append(value)
  465. return args, kwargs
  466. def _arguments_str_from_dictionary(options):
  467. """
  468. Convert method options passed as a dictionary to a str object
  469. having the form of the method arguments
  470. """
  471. option_string = ""
  472. for k in options:
  473. if isinstance(options[k], str):
  474. option_string += k+"='"+str(options[k])+"',"
  475. else:
  476. option_string += k+"="+str(options[k])+","
  477. option_string = option_string.strip(',')
  478. return option_string
  479. #-----------------------------
  480. class FrameCounter:
  481. #-----------------------------
  482. r"""
  483. Simple frame counter
  484. Simple frame counter to keep track of current frame number. This can
  485. also be used to keep multiple runs frames seperated by having multiple
  486. counters at once.
  487. Initializes to 0
  488. """
  489. def __init__(self):
  490. self.__frame = 0
  491. def __repr__(self):
  492. return str(self.__frame)
  493. def increment(self):
  494. r"""
  495. Increment the counter by one
  496. """
  497. self.__frame += 1
  498. def set_counter(self,new_frame_num):
  499. r"""
  500. Set the counter to new_frame_num
  501. """
  502. self.__frame = new_frame_num
  503. def get_counter(self):
  504. r"""
  505. Get the current frame number
  506. """
  507. return self.__frame
  508. def reset_counter(self):
  509. r"""
  510. Reset the counter to 0
  511. """
  512. self.__frame = 0