Package gromacs :: Module core
[hide private]
[frames] | no frames]

Source Code for Module gromacs.core

  1  # GromacsWrapper: core.py 
  2  # Copyright (c) 2009 Oliver Beckstein <orbeckst@gmail.com> 
  3  # Released under the GNU Public License 3 (or higher, your choice) 
  4  # See the file COPYING for details. 
  5   
  6  """ 
  7  :mod:`gromacs.core` -- Core functionality 
  8  ========================================= 
  9   
 10  Here the basic command class :class:`GromacsCommand` is defined. All 
 11  Gromacs command classes in :mod:`gromacs.tools` are automatically 
 12  generated from it. 
 13   
 14  .. autoclass:: Command 
 15     :members:  __call__, run, transform_args, Popen, help, 
 16               command_name 
 17      
 18  .. autoclass:: GromacsCommand 
 19     :members: __call__, run, transform_args, Popen, help, 
 20               check_failure, gmxdoc 
 21     :inherited-members: 
 22   
 23  .. autoclass:: PopenWithInput 
 24     :members: 
 25  """ 
 26  __docformat__ = "restructuredtext en" 
 27   
 28  import sys 
 29  import re 
 30  import subprocess 
 31  from subprocess import STDOUT, PIPE 
 32  import warnings 
 33  import errno 
 34   
 35  import logging 
 36  logger = logging.getLogger('gromacs.core') 
 37   
 38   
 39  from gromacs import GromacsError, GromacsFailureWarning 
 40   
41 -class Command(object):
42 """Wrap simple script or command.""" 43 #: Derive a class from command; typically one only has to set *command_name* 44 #: to the name of the script or executable. The full path is required if it 45 #: cannot be found by searching :envvar:`PATH`. 46 command_name = None 47
48 - def __init__(self,*args,**kwargs):
49 """Set up the command class. 50 51 The arguments can always be provided as standard positional 52 arguments such as 53 54 ``"-c", "config.conf", "-o", "output.dat", "--repeats=3", "-v", "input.dat"`` 55 56 In addition one can also use keyword arguments such as 57 58 ``c="config.conf", o="output.dat", repeats=3, v=True`` 59 60 These are automatically transformed appropriately according to 61 simple rules: 62 63 * Any single-character keywords are assumed to be POSIX-style 64 options and will be prefixed with a single dash and the value 65 separated by a space. 66 67 * Any other keyword is assumed to be a GNU-style long option 68 and thus will be prefixed with two dashes and the value will 69 be joined directly with an equals sign and no space. 70 71 If this does not work (as for instance for the options of the 72 UNIX ``find`` command) then provide options and values in the 73 sequence of positional arguments. 74 """ 75 76 self.args = args 77 self.kwargs = kwargs
78
79 - def run(self,*args,**kwargs):
80 """Run the command; args/kwargs are added or replace the ones given to the constructor.""" 81 _args, _kwargs = self._combine_arglist(args, kwargs) 82 return self._run_command(*_args, **_kwargs)
83
84 - def _combine_arglist(self, args, kwargs):
85 """Combine the default values and the supplied values.""" 86 _args = self.args + args 87 _kwargs = self.kwargs.copy() 88 _kwargs.update(kwargs) 89 return _args, _kwargs
90
91 - def _run_command(self,*args,**kwargs):
92 """Execute the command; see the docs for __call__.""" 93 use_input = kwargs.pop('use_input', True) # hack to run command WITHOUT input (-h...) 94 p = self.Popen(*args, **kwargs) 95 out, err = p.communicate(use_input=use_input) # special Popen knows input! 96 rc = p.returncode 97 result = rc, out, err 98 return result
99
100 - def _commandline(self, *args, **kwargs):
101 """Returns the command line (without pipes) as a list.""" 102 # transform_args() is a hook (used in GromacsCommand very differently!) 103 return [self.command_name] + self.transform_args(*args,**kwargs)
104
105 - def commandline(self, *args, **kwargs):
106 """Returns the commandline that run() uses (without pipes).""" 107 # this mirrors the setup in run() 108 _args, _kwargs = self._combine_arglist(args, kwargs) 109 return self._commandline(*_args, **_kwargs)
110
111 - def Popen(self, *args,**kwargs):
112 """Returns a special Popen instance (:class:`PopenWithInput`). 113 114 The instance has its input pre-set so that calls to 115 :meth:`~PopenWithInput.communicate` will not need to supply 116 input. This is necessary if one wants to chain the output from 117 one command to an input from another. 118 119 :TODO: 120 Write example. 121 """ 122 123 stderr = kwargs.pop('stderr', STDOUT) # default: Merge with stdout 124 if stderr is False: # False: capture it 125 stderr = PIPE 126 elif stderr is True: 127 stderr = None # use stderr 128 129 stdout = kwargs.pop('stdout', None) # either set to PIPE for capturing output 130 if stdout is False: # ... or to False 131 stdout = PIPE 132 elif stdout is True: 133 stdout = None # for consistency, make True write to screen 134 135 stdin = kwargs.pop('stdin', None) 136 input = kwargs.pop('input', None) 137 if input: 138 stdin = PIPE 139 if type(input) is str: 140 # make sure that input is a simple string with \n line endings 141 if not input.endswith('\n'): 142 input += '\n' 143 else: 144 try: 145 # make sure that input is a simple string with \n line endings 146 input = '\n'.join(map(str, input)) + '\n' 147 except TypeError: 148 # so maybe we are a file or something ... and hope for the best 149 pass 150 151 cmd = self._commandline(*args, **kwargs) # lots of magic happening here 152 # (cannot move out of method because filtering of stdin etc) 153 try: 154 p = PopenWithInput(cmd, stdin=stdin, stderr=stderr, stdout=stdout, 155 universal_newlines=True, input=input) 156 except OSError,err: 157 logger.error(" ".join(cmd)) # log command line 158 if err.errno == errno.ENOENT: 159 errmsg = "Failed to find command %r, maybe its not on PATH or GMXRC must be sourced?" % self.command_name 160 logger.fatal(errmsg) 161 raise OSError(errmsg) 162 else: 163 logger.exception("Setting up command %r raised an exception." % self.command_name) 164 raise 165 logger.debug(p.command_string) 166 return p
167
168 - def transform_args(self, *args, **kwargs):
169 """Transform arguments and return them as a list suitable for Popen.""" 170 options = [] 171 for option,value in kwargs.items(): 172 if not option.startswith('-'): 173 # heuristic for turning key=val pairs into options 174 # (fails for commands such as 'find' -- then just use args) 175 if len(option) == 1: 176 option = '-' + option # POSIX style 177 else: 178 option = '--' + option # GNU option 179 if value is True: 180 options.append(option) 181 continue 182 elif value is False: 183 raise ValueError('A False value is ambiguous for option %r' % option) 184 185 if option[:2] == '--': 186 options.append(option + '=' + str(value)) # GNU option 187 else: 188 options.extend((option, str(value))) # POSIX style 189 return options + list(args)
190
191 - def help(self,long=False):
192 """Print help; same as using ``?`` in ``ipython``. long=True also gives call signature.""" 193 print "\ncommand: %s\n\n" % self.command_name 194 print self.__doc__ 195 if long: 196 print "\ncall method: command():\n" 197 print self.__call__.__doc__
198
199 - def __call__(self,*args,**kwargs):
200 """Run command with the given arguments:: 201 202 rc,stdout,stderr = command(*args, input=None, **kwargs) 203 204 All positional parameters \*args and all gromacs \*\*kwargs are passed on 205 to the Gromacs command. input and output keywords allow communication 206 with the process via the python subprocess module. 207 208 :Arguments: 209 *input* : string, sequence 210 to be fed to the process' standard input; 211 elements of a sequence are concatenated with 212 newlines, including a trailing one [``None``] 213 *stdin* 214 ``None`` or automatically set to ``PIPE`` if input given [``None``] 215 *stdout* 216 how to handle the program's stdout stream [``None``] 217 218 filehandle 219 anything that behaves like a file object 220 ``None`` or ``True`` 221 to see output on screen 222 ``False`` or ``PIPE`` 223 returns the output as a string in the stdout parameter 224 225 *stderr* 226 how to handle the stderr stream [``STDOUT``] 227 228 ``STDOUT`` 229 merges standard error with the standard out stream 230 ``False`` or ``PIPE`` 231 returns the output as a string in the stderr return parameter 232 ``None`` or ``True`` 233 keeps it on stderr (and presumably on screen) 234 235 All other kwargs are passed on to the Gromacs tool. 236 237 :Returns: 238 239 The shell return code rc of the command is always returned. Depending 240 on the value of output, various strings are filled with output from the 241 command. 242 243 :Notes: 244 245 By default, the process stdout and stderr are merged. 246 247 In order to chain different commands via pipes one must use the special 248 :class:`PopenWithInput` object (see :meth:`GromacsCommand.Popen` method) instead of the simple 249 call described here and first construct the pipeline explicitly and then 250 call the :meth:`PopenWithInput.communicate` method. 251 252 ``STDOUT`` and ``PIPE`` are objects provided by the :mod:`subprocess` module. Any 253 python stream can be provided and manipulated. This allows for chaining 254 of commands. Use :: 255 256 from subprocess import PIPE, STDOUT 257 258 when requiring these special streams (and the special boolean 259 switches ``True``/``False`` cannot do what you need.) 260 261 (TODO: example for chaining commands) 262 """ 263 return self.run(*args,**kwargs)
264 265
266 -class GromacsCommand(Command):
267 """Base class for wrapping a g_* command. 268 269 Limitations: User must have sourced ``GMXRC`` so that the python script can 270 inherit the environment and find the gromacs programs. 271 272 The class doc string is dynamically replaced by the documentation of the 273 gromacs command when an instance is created. 274 """ 275 # TODO: setup the environment from GMXRC (can use env=DICT in Popen/call) 276 277 command_name = None 278 doc_pattern = """.*?(?P<DOCS>DESCRIPTION.*)""" 279 gmxfatal_pattern = """----+\n # ---- decorator line 280 \s*Program\s+(?P<program_name>\w+), # Program name, 281 \s+VERSION\s+(?P<version>[\w.]+)\s*\n # VERSION 4.0.5 282 (?P<message>.*?)\n # full message, multiple lines 283 \s* # empty line (?) 284 ----+\n # ---- decorator line 285 """ 286 # matches gmx_fatal() output 287 # ------------------------------------------------------- 288 # Program <program_name>, VERSION <version> 289 # ... <message> 290 # ------------------------------------------------------- 291 292 #: Available failure modes. 293 failuremodes = ('raise', 'warn', None) 294
295 - def __init__(self,*args,**kwargs):
296 """Set up the command with gromacs flags as keyword arguments. 297 298 The following are generic instructions; refer to the Gromacs 299 command usage information that should have appeared before 300 this generic documentation. 301 302 As an example, a generic Gromacs command could use the following flags:: 303 304 cmd = GromacsCommand('v', f=['md1.xtc','md2.xtc'], o='processed.xtc', t=200, ...) 305 306 which would correspond to running the command in the shell as :: 307 308 GromacsCommand -v -f md1.xtc md2.xtc -o processed.xtc -t 200 309 310 **Gromacs command line arguments** 311 312 Gromacs boolean switches (such as ``-v``) are given as python 313 positional arguments (``'v'``) or as keyword argument (``v=True``); 314 note the quotes in the first case. Negating a boolean switch can be 315 done with ``'nov'``, ``nov=True`` or ``v=False`` (and even ``nov=False`` 316 works as expected: it is the same as ``v=True``). 317 318 Any Gromacs options that take parameters are handled as keyword 319 arguments. If an option takes multiple arguments (such as the 320 multi-file input ``-f file1 file2 ...``) then the list of files must be 321 supplied as a python list. 322 323 If a keyword has the python value ``None`` then it will *not* be 324 added to the Gromacs command line; this allows for flexible 325 scripting if it is not known in advance if an input file is 326 needed. In this case the default value of the gromacs tool 327 is used. 328 329 Keywords must be legal python keywords or the interpreter raises a 330 :exc:`SyntaxError` but of course Gromacs commandline arguments are 331 not required to be legal python. In this case "quote" the option 332 with an underscore (``_``) and the underscore will be silently 333 stripped. For instance, ``-or`` translates to the illegal keyword 334 ``or`` so it must be underscore-quoted:: 335 336 cmd(...., _or='mindistres.xvg') 337 338 **Command execution** 339 340 The command is executed with the :meth:`~GromacsCommand.run` method or by 341 calling it as a function. The two next lines are equivalent:: 342 343 cmd(...) 344 cmd.run(...) 345 346 When the command is run one can override options that were given at 347 initialization or one can add additional ones. The same rules for 348 supplying Gromacs flags apply as described above. 349 350 **Non-Gromacs keyword arguments** 351 352 The other keyword arguments (listed below) are not passed on to the 353 Gromacs tool but determine how the command class behaves. They are 354 only useful when instantiating a class. This is mostly of interest 355 to developers. 356 357 :Keywords: 358 *failure* 359 determines how a failure of the gromacs command is treated; it 360 can be one of the following: 361 362 'raise' 363 raises GromacsError if command fails 364 'warn' 365 issue a :exc:`GromacsFailureWarning` 366 ``None`` 367 just continue silently 368 369 *doc* : string 370 additional documentation [] 371 """ 372 373 self.failuremode = kwargs.pop('failure','raise') 374 self.extra_doc = kwargs.pop('doc',None) 375 if not self.failuremode in self.failuremodes: 376 raise ValueError('failuremode must be one of\n%(failuremodes)r' % vars(self)) 377 self.gmxargs = self._combineargs(*args, **kwargs) 378 self.__doc__ = self.gmxdoc
379
380 - def _combine_arglist(self, args, kwargs):
381 """Combine the default values and the supplied values.""" 382 gmxargs = self.gmxargs.copy() 383 gmxargs.update(self._combineargs(*args,**kwargs)) 384 return (), gmxargs # Gromacs tools don't have positional args --> args = ()
385
386 - def check_failure(self, result, msg='Gromacs tool failed', command_string=None):
387 rc, out, err = result 388 if not command_string is None: 389 msg += '\nCommand invocation: ' + str(command_string) 390 had_success = (rc == 0) 391 if not had_success: 392 gmxoutput = "\n".join([x for x in [out, err] if not x is None]) 393 m = re.search(self.gmxfatal_pattern, gmxoutput, re.VERBOSE | re.DOTALL) 394 if m: 395 formatted_message = ['GMX_FATAL '+line for line in m.group('message').split('\n')] 396 msg = "\n".join(\ 397 [msg, "Gromacs command %(program_name)r fatal error message:" % m.groupdict()] + 398 formatted_message) 399 if self.failuremode == 'raise': 400 raise GromacsError(rc, msg) 401 elif self.failuremode == 'warn': 402 warnings.warn(msg + '\nError code: %r\n' % rc, category=GromacsFailureWarning) 403 elif self.failuremode is None: 404 pass 405 else: 406 raise ValueError('unknown failure mode %r' % self.failuremode) 407 return had_success
408
409 - def _combineargs(self,*args,**kwargs):
410 """Add switches as 'options' with value True to the options dict.""" 411 d = dict([(arg, True) for arg in args]) # switches are kwargs with value True 412 d.update(kwargs) 413 return d
414
415 - def _build_arg_list(self,**kwargs):
416 """Build list of arguments from the dict; keys must be valid gromacs flags.""" 417 arglist = [] 418 for flag,value in kwargs.items(): 419 # XXX: check flag against allowed values 420 flag = str(flag) 421 if flag.startswith('_'): 422 flag = flag[1:] # python-illegal keywords are '_'-quoted 423 if not flag.startswith('-'): 424 flag = '-' + flag # now flag is guaranteed to start with '-' 425 if value is True: 426 arglist.append(flag) # simple command line flag 427 elif value is False: 428 if flag.startswith('-no'): 429 # negate a negated flag ('noX=False' --> X=True --> -X ... but who uses that?) 430 arglist.append('-'+flag[3:]) 431 else: 432 arglist.append('-no'+flag[1:]) # gromacs switches booleans by prefixing 'no' 433 elif value is None: 434 pass # ignore flag = None 435 else: 436 try: 437 arglist.extend([flag] + value) # option with value list 438 except TypeError: 439 arglist.extend([flag, value]) # option with single value 440 return map(str, arglist) # all arguments MUST be strings
441
442 - def _run_command(self,*args,**kwargs):
443 """Execute the gromacs command; see the docs for __call__.""" 444 use_input = kwargs.pop('use_input', True) # hack to run command WITHOUT input (-h...) 445 p = self.Popen(*args, **kwargs) 446 out, err = p.communicate(use_input=use_input) # special Popen knows input! 447 rc = p.returncode 448 result = rc, out, err 449 self.check_failure(result, command_string=p.command_string) 450 return result
451
452 - def transform_args(self,*args,**kwargs):
453 """Combine arguments and turn them into gromacs tool arguments.""" 454 newargs = self._combineargs(*args,**kwargs) 455 return self._build_arg_list(**newargs)
456
457 - def _get_gmx_docs(self):
458 """Extract standard gromacs doc by running the program and chopping the header.""" 459 # Uses the class-wide arguments so that 'canned invocations' in cbook 460 # are accurately reflected. Might be a problem when these invocations 461 # supply wrong arguments... TODO: maybe check rc for that? 462 # use_input=False needed for running commands in cbook that have input pre-defined 463 old_level = logger.getEffectiveLevel() # temporarily throttle logger to avoid 464 logger.setLevel(9999) # reading about the help function invocation or not found 465 try: 466 rc,docs,nothing = self.run('h', stdout=PIPE, use_input=False) 467 finally: 468 logger.setLevel(old_level) # ALWAYS restore logging.... 469 m = re.match(self.doc_pattern, docs, re.DOTALL) # keep from DESCRIPTION onwards 470 if m is None: 471 return "(No Gromacs documentation available)" 472 return m.group('DOCS')
473
474 - def gmxdoc():
475 doc = """Usage for the underlying Gromacs tool (cached).""" 476 def fget(self): 477 if not (hasattr(self, '__doc_cache') and self.__doc_cache): 478 self.__doc_cache = self._get_gmx_docs() 479 docs = self.__doc_cache 480 if self.extra_doc: 481 docs = '\n'.join([self.extra_doc,'', 482 "Documentation of the gromacs tool", 34*'=', 483 docs]) 484 return docs
485 return locals()
486 gmxdoc = property(**gmxdoc()) 487 488 489
490 -class PopenWithInput(subprocess.Popen):
491 """Popen class that knows its input. 492 493 1. Set up the instance, including all the input it shoould receive. 494 2. Call :meth:`PopenWithInput.communicate` later. 495 496 .. Note:: Some versions of python have a bug in the subprocess module 497 ( `issue 5179`_ ) which does not clean up open file 498 descriptors. Eventually code (such as this one) fails with the 499 error: 500 501 *OSError: [Errno 24] Too many open files* 502 503 A weak workaround is to increase the available number of open 504 file descriptors with ``ulimit -n 2048`` and run analysis in 505 different scripts. 506 507 .. _issue 5179: http://bugs.python.org/issue5179 508 """ 509
510 - def __init__(self,*args,**kwargs):
511 """Initialize with the standard :class:`subprocess.Popen` arguments. 512 513 :Keywords: 514 *input* 515 string that is piped into the command 516 517 """ 518 kwargs.setdefault('close_fds', True) # fixes 'Too many open fds' with 2.6 519 self.input = kwargs.pop('input',None) 520 self.command = args[0] 521 try: 522 input_string = 'printf "' + \ 523 self.input.replace('\n','\\n') + '" | ' # display newlines 524 except (TypeError, AttributeError): 525 input_string = "" 526 self.command_string = input_string + " ".join(self.command) 527 super(PopenWithInput,self).__init__(*args,**kwargs)
528 - def communicate(self, use_input=True):
529 """Run the command, using the input that was set up on __init__ (for *use_input* = ``True``)""" 530 if use_input: 531 return super(PopenWithInput,self).communicate(self.input) 532 else: 533 return super(PopenWithInput,self).communicate()
534 - def __str__(self):
535 return "<Popen on %r>" % self.command_string
536