From fe884a825d3019c2e0308eaa9a5b68bc2ec502d8 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Sat, 14 May 2016 08:14:09 -0300 Subject: [PATCH 01/30] Tries to detect and load GMXRC automatically (PR #55) --- gromacs/__init__.py | 11 +++++++++++ gromacs/cbook.py | 10 +++++----- gromacs/config.py | 25 +++++++++++++++++++++++-- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/gromacs/__init__.py b/gromacs/__init__.py index 096990d4..290ee60d 100644 --- a/gromacs/__init__.py +++ b/gromacs/__init__.py @@ -242,6 +242,17 @@ def stop_logging(): log.clear_handlers(logger) # this _should_ do the job... +# Try to load environment variables setted by GMXRC +gmxrc = config.cfg.get("Gromacs", "GMXRC") +if gmxrc: + try: + config.set_gmxrc_environment(gmxrc) + except OSError: + logger = config.logger + logger.warning("Failed to automatically set the Gromacs environment" + "(GMXRC)") + + # Add gromacs command **instances** to the top level. # These serve as the equivalence of running commands in the shell. # (Note that each gromacs command is actually run when the instance is diff --git a/gromacs/cbook.py b/gromacs/cbook.py index 366ef0cb..b1949712 100644 --- a/gromacs/cbook.py +++ b/gromacs/cbook.py @@ -200,11 +200,11 @@ def _define_canned_commands(): try: _define_canned_commands() except (OSError, ImportError, GromacsError): - warnings.warn("Failed to define a number of commands in gromacs.cbook. Most likely the " - "Gromacs installation cannot be found --- source GMXRC!", - category=GromacsImportWarning) - logger.error("Failed to define a number of commands in gromacs.cbook. Most likely the " - "Gromacs installation cannot be found --- source GMXRC!") + msg = ("Failed to define a number of commands in gromacs.cbook. Most " + "likely the Gromacs installation cannot be found --- set GMXRC in " + "~/.gromacswrapper.cfg or source GMXRC directly") + warnings.warn(msg, category=GromacsImportWarning) + logger.error(msg) finally: del _define_canned_commands diff --git a/gromacs/config.py b/gromacs/config.py index d0c9213e..c4ee2691 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -165,6 +165,11 @@ # Release of the Gromacs package to which information in this sections applies. release = 5.0.5 + # GMXRC contains the path for GMXRC file which will be loaded. If not + provided is expected that it was sourced as usual before importing this + library. + GMXRC = /usr/local/gromacs/bin/GMXRC + # tools contains the command names of all Gromacs tools for which classes are generated. # Editing this list has only an effect when the package is reloaded. # (Note that this example has a much shorter list than the actual default.) @@ -219,7 +224,7 @@ """ from __future__ import absolute_import, with_statement -import os, errno +import os, errno, subprocess from ConfigParser import SafeConfigParser from pkg_resources import resource_filename, resource_listdir @@ -484,6 +489,7 @@ def __init__(self, *args, **kwargs): self.set('DEFAULT', 'managerdir', os.path.join("%(configdir)s", os.path.basename(defaults['managerdir']))) self.add_section('Gromacs') + self.set("Gromacs", "GMXRC", "") self.set("Gromacs", "tools", "pdb2gmx editconf grompp genbox genion mdrun trjcat trjconv") self.set("Gromacs", "extra", "") self.set("Gromacs", "groups", "tools") @@ -641,10 +647,25 @@ def check_setup(): check_setup() - # Gromacs tools # ------------- +#: Runs GMXRC in a subprocess and put environment variables loaded by it in +#: this environment. +def set_gmxrc_environment(gmxrc): + envvars = ['GMXPREFIX', 'GMXBIN', 'GMXLDLIB', 'GMXMAN', 'GMXDATA', + 'GROMACS_DIR', 'LD_LIBRARY_PATH', 'MANPATH', 'PKG_CONFIG_PATH', + 'GROMACS_DIR', 'PATH'] + cmdargs = ['bash', '-c', ". {0} && echo {1}".format(gmxrc, + ' '.join(['${0}'.format(v) for v in envvars]))] + try: + out = subprocess.check_output(cmdargs) + out = out.strip().split() + for key, value in zip(envvars, out): + os.environ[key] = value + except (subprocess.CalledProcessError, OSError): + pass + #: Python list of all tool file names. Filled from values in the tool #: groups in the configuration file. load_tools = [] From 76c22ce2f8fb46eaf37c916bfceb33eed6ca60c7 Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Wed, 8 Jun 2016 19:40:33 -0700 Subject: [PATCH 02/30] automatic environment settings from GMXRC (#55) - added logging at debug for the env setting (although not clear how to enable the logger at this stage unless it is started from another script which already has a gromacs logger running) - added entry to CHANGES --- CHANGES | 11 +++++++++++ gromacs/__init__.py | 2 +- gromacs/config.py | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 3d65ddbb..d6061a94 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,17 @@ CHANGELOG for GromacsWrapper ============================== +2016-xx-xx 0.5.1 +whitead, orbeckst, pslacerda + +* commands can request that the underlying subprocess.Popen uses a + shell (shell=True) +* additional alias "gmx solvate" <--> "genbox" +* new Gromacs GMXRC parameter in cfg file: if present and set to a + file then GromacsWrapper sources GMXRC and sets the environment + automatically before setting up the Gromacs commands (#55) + + 2016-05-23 0.5.0 quantifiedcode-bot, orbeckst, jandom, whitead diff --git a/gromacs/__init__.py b/gromacs/__init__.py index 290ee60d..1bb3b978 100644 --- a/gromacs/__init__.py +++ b/gromacs/__init__.py @@ -242,7 +242,7 @@ def stop_logging(): log.clear_handlers(logger) # this _should_ do the job... -# Try to load environment variables setted by GMXRC +# Try to load environment variables set by GMXRC gmxrc = config.cfg.get("Gromacs", "GMXRC") if gmxrc: try: diff --git a/gromacs/config.py b/gromacs/config.py index c4ee2691..905bff55 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -663,6 +663,7 @@ def set_gmxrc_environment(gmxrc): out = out.strip().split() for key, value in zip(envvars, out): os.environ[key] = value + logger.debug("set_gmxrc_environment(): %s = %r", key, value) except (subprocess.CalledProcessError, OSError): pass From 300816ae0717b6d1a0d2fb7170e5ddebfe972b49 Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Wed, 8 Jun 2016 22:30:21 -0700 Subject: [PATCH 03/30] improvements to setting of environment from GMXRC - checks for set_gmxrc_environment() moved inside function; Moved checking of input set_gmxrc_environment() into the function itself so that we can just safely call it from __init__ without further crud. (Also, the try/except OSError in __init__ for set_gmxrc_environment() would have never triggered because we were already catching it inside.) - use cfg.getpath() to get GMXRC from cfg file instead of cfg.get() - Added docs. --- gromacs/__init__.py | 9 +----- gromacs/config.py | 75 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/gromacs/__init__.py b/gromacs/__init__.py index 1bb3b978..9244b4f4 100644 --- a/gromacs/__init__.py +++ b/gromacs/__init__.py @@ -243,14 +243,7 @@ def stop_logging(): # Try to load environment variables set by GMXRC -gmxrc = config.cfg.get("Gromacs", "GMXRC") -if gmxrc: - try: - config.set_gmxrc_environment(gmxrc) - except OSError: - logger = config.logger - logger.warning("Failed to automatically set the Gromacs environment" - "(GMXRC)") +config.set_gmxrc_environment(config.cfg.getpath("Gromacs", "GMXRC")) # Add gromacs command **instances** to the top level. diff --git a/gromacs/config.py b/gromacs/config.py index 905bff55..0d67e5de 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -125,13 +125,46 @@ Gromacs tools and scripts ------------------------- -``load_*`` variables are lists that contain instructions to other -parts of the code which packages and scripts should be wrapped. +Fundamentally, GromacsWrapper makes existing Gromacs tools +(executables) available as functions. In order for this to work, these +executables must be found in the environment of the Python process +that runs GromacsWrapper, and the user must list all the tools that +are to be made available. -.. autodata:: load_tools +Setting up the environment +~~~~~~~~~~~~~~~~~~~~~~~~~~ -:data:`load_tools` is populated from lists of executable names in the -configuration file. +The standard way to set up the Gromacs environment is to source ``GMXRC`` in +the shell before running the Python process. ``GMXRC`` adjusts a number of +environment variables (such as :envvar:`PATH` and :envvar:`LD_LIBRARY_PATH`) +but also sets Gromacs-specific environment variables such as :envvar:`GMXBIN`, +:envvar:`GMXDATA`, and many others:: + + source /usr/local/bin/GMXRC + +(where the path to ``GMXRC`` is often set differently to disntinguish different +installed versions of Gromacs). + +Alternatively, GromacsWrapper can itself source a ``GMXRC`` file and set the +environment with the :func:`set_gmxrc_environment` function. The path to a +``GMXRC`` file can be set in the config file in the ``[Gromacs]`` section as + + [Gromacs] + + GMXRC = /usr/local/bin/GMXRC + +When GromacsWrapper starts up, it tries to set the environment using the +``GMXRC`` defined in the config file. If this is left empty or is not in the +file, nothing is being done. + +.. autofunction:: set_gmxrc_environment + + +List of tools +~~~~~~~~~~~~~ + +The list of Gromacs tools is specified in the config file in the ``[Gromacs]`` +section with the ``tools`` variable. The tool groups are strings that contain white-space separated file names of Gromacs tools. These lists determine which tools are made @@ -205,6 +238,15 @@ .. _`changes in the Gromacs tool in 5.x`: http://www.gromacs.org/Documentation/How-tos/Tool_Changes_for_5.0 +Developers should know that the lists of tools are stored in ``load_*`` +variables. These are lists that contain instructions to other parts of the code +as to which executables should be wrapped. + +.. autodata:: load_tools + +:data:`load_tools` is populated from lists of executable names in the +configuration file. + Location of template files @@ -650,14 +692,28 @@ def check_setup(): # Gromacs tools # ------------- -#: Runs GMXRC in a subprocess and put environment variables loaded by it in -#: this environment. def set_gmxrc_environment(gmxrc): + """Set the environment from ``GMXRC`` provided in *gmxrc*. + + Runs ``GMXRC`` in a subprocess and puts environment variables loaded by it + into this Python environment. + + If *gmxrc* evaluates to ``False`` then nothing is done. If errors occur + then only a warning will be logged. Thus, it should be safe to just call + this function. + + """ + envvars = ['GMXPREFIX', 'GMXBIN', 'GMXLDLIB', 'GMXMAN', 'GMXDATA', 'GROMACS_DIR', 'LD_LIBRARY_PATH', 'MANPATH', 'PKG_CONFIG_PATH', - 'GROMACS_DIR', 'PATH'] + 'PATH'] cmdargs = ['bash', '-c', ". {0} && echo {1}".format(gmxrc, ' '.join(['${0}'.format(v) for v in envvars]))] + + if not gmxrc: + logger.debug("set_gmxrc_environment(): no GMXRC, nothing done.") + return + try: out = subprocess.check_output(cmdargs) out = out.strip().split() @@ -665,7 +721,8 @@ def set_gmxrc_environment(gmxrc): os.environ[key] = value logger.debug("set_gmxrc_environment(): %s = %r", key, value) except (subprocess.CalledProcessError, OSError): - pass + logger.warning("Failed to automatically set the Gromacs environment" + "from GMXRC=%r", gmxrc) #: Python list of all tool file names. Filled from values in the tool #: groups in the configuration file. From 9ec9e215a5b25e6d5925a1e6552488d01cd014d7 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Thu, 30 Jun 2016 00:59:51 -0300 Subject: [PATCH 04/30] Load tools automatically --- .gitignore | 2 + gromacs/config.py | 3 +- gromacs/exceptions.py | 3 + gromacs/tools.py | 281 ++++++++++++++---------------------------- 4 files changed, 100 insertions(+), 189 deletions(-) diff --git a/.gitignore b/.gitignore index 94848984..fd280782 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ doc/epydoc/ doc/sphinx/build/ doc/html build/ +.idea/ +virtualenv/ dist gromacs.log doc/sphinx/*.log diff --git a/gromacs/config.py b/gromacs/config.py index 0d67e5de..44b0c7d8 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -532,8 +532,9 @@ def __init__(self, *args, **kwargs): os.path.join("%(configdir)s", os.path.basename(defaults['managerdir']))) self.add_section('Gromacs') self.set("Gromacs", "GMXRC", "") - self.set("Gromacs", "tools", "pdb2gmx editconf grompp genbox genion mdrun trjcat trjconv") + self.set("Gromacs", "tools", "") self.set("Gromacs", "extra", "") + self.set("Gromacs", "suffix", "") self.set("Gromacs", "groups", "tools") self.add_section('Logging') self.set('Logging', 'logfilename', defaults['logfilename']) diff --git a/gromacs/exceptions.py b/gromacs/exceptions.py index fa85be0b..22cb7166 100644 --- a/gromacs/exceptions.py +++ b/gromacs/exceptions.py @@ -47,6 +47,9 @@ class UsageWarning(Warning): class LowAccuracyWarning(Warning): """Warns that results may possibly have low accuracy.""" +class GromacsToolLoadingError(Exception): + """Couldn't find Gromacs commands.""" + import warnings # These warnings should always be displayed because other parameters # can have changed, eg during interactive use. diff --git a/gromacs/tools.py b/gromacs/tools.py index 84d232b3..537dadc9 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -18,15 +18,6 @@ The list of Gromacs tools to be loaded is configured in :data:`gromacs.config.gmx_tool_groups`. -It is also possible to extend the basic commands and patch in additional -functionality. For example, the :class:`GromacsCommandMultiIndex` class makes a -command accept multiple index files and concatenates them on the fly; the -behaviour mimics Gromacs' "multi-file" input that has not yet been enabled for -all tools. - -.. autoclass:: GromacsCommandMultiIndex - :members: run, _fake_multi_ndx, __del__ - Example ------- @@ -59,27 +50,13 @@ from __future__ import absolute_import __docformat__ = "restructuredtext en" -import os.path -import tempfile - -from . import config -from .core import GromacsCommand, Command -from . import utilities - -def _generate_sphinx_class_string(clsname): - return ".. class:: %(clsname)s\n :noindex:\n" % vars() +import os.path, tempfile, subprocess, atexit, warnings -#flag for 5.0 style commands -b_gmx5 = False +from . import config, utilities, exceptions +from .core import GromacsCommand -#: This dict holds all generated classes. -registry = {} -# Auto-generate classes such as: -# class g_dist(GromacsCommand): -# command_name = 'g_dist' - -aliases5to4 = { +ALIASES5TO4 = { 'grompp': 'grompp', 'eneconv': 'eneconv', 'sasa': 'g_sas', @@ -94,8 +71,8 @@ def _generate_sphinx_class_string(clsname): 'mdrun': 'mdrun', 'make_ndx': 'make_ndx', 'make_edi': 'make_edi', - 'gmxdump': 'gmxdump', - 'gmxcheck': 'gmxcheck', + 'dump': 'gmxdump', + 'check': 'gmxcheck', 'genrestr': 'genrestr', 'genion': 'genion', 'genconf': 'genconf', @@ -103,166 +80,94 @@ def _generate_sphinx_class_string(clsname): 'solvate': 'genbox', } -for name in sorted(config.load_tools): - # compatibility for 5.x 'gmx toolname': add as gmx:toolname - if name.find(':') != -1: - b_gmx5 = True - prefix = name.split(':')[0] - name = name.split(':')[1] - #make alias for backwards compatibility - - #the common case of just dropping the 'g_' - old_name = 'g_' + name - - #check against uncommon name changes - #have to check each one, since it's possible there are suffixes like for double precision - for c5, c4 in aliases5to4.iteritems(): +TOOLS_V4 = ("do_dssp", "editconf", "eneconv", "g_anadock", "g_anaeig", + "g_analyze", "g_angle", "g_bar", "g_bond", "g_bundle", "g_chi", + "g_cluster", "g_clustsize", "g_confrms", "g_covar", "g_current", + "g_density", "g_densmap", "g_densorder", "g_dielectric", + "g_dipoles", "g_disre", "g_dist", "g_dos", "g_dyecoupl", "g_dyndom", + "genbox", "genconf", "g_enemat", "g_energy", "genion", "genrestr", + "g_filter", "g_gyrate", "g_h2order", "g_hbond", "g_helix", + "g_helixorient", "g_hydorder", "g_kinetics", "g_lie", "g_luck", + "g_mdmat", "g_membed", "g_mindist", "g_morph", "g_msd", + "gmxcheck", "gmxdump", "g_nmeig", "g_nmens", "g_nmtraj", "g_options", + "g_order", "g_pme_error", "g_polystat", "g_potential", + "g_principal", "g_protonate", "g_rama", "g_rdf", "g_rms", + "g_rmsdist", "g_rmsf", "grompp", "g_rotacf", "g_rotmat", + "g_saltbr", "g_sans", "g_sas", "g_select", "g_sgangle", "g_sham", + "g_sigeps", "g_sorient", "g_spatial", "g_spol", "g_tcaf", + "g_traj", "g_tune_pme", "g_vanhove", "g_velacc", "g_wham", + "g_wheel", "g_x2top", "g_xrama", "make_edi", "make_ndx", "mdrun", + "mk_angndx", "ngmx", "pdb2gmx", "tpbconv", "trjcat", "trjconv", + "trjorder", "xpm2p") + + +def append_suffix(name): + suffix = config.cfg.get('Gromacs', 'suffix') + if suffix: + name += '_' + suffix + return name + + +def load_v5_tools(): + driver = append_suffix('gmx') + try: + out = subprocess.check_output([driver, '-quiet', 'help', 'commands']) + except subprocess.CalledProcessError: + raise exceptions.GromacsToolLoadingError("Failed to load v5 tools") + + registry = {} + for line in str(out).encode('ascii').splitlines()[5:-1]: + if line[4] != ' ': + name = line[4:line.index(' ', 4)] + fancy = name.replace('-', '_').capitalize() + registry[fancy] = type(fancy, (GromacsCommand,), + {'command_name':name, 'driver': driver}) + return registry + + +def load_v4_tools(): + tools = [append_suffix(t) for t in TOOLS_V4] + registry = {} + for tool in tools: + fancy = tool.capitalize() + registry[fancy] = type(fancy, (GromacsCommand,), {'command_name':tool}) + + try: + null = open(os.devnull, 'w') + subprocess.check_call(['g_luck'], stdout=null, stderr=null) + except subprocess.CalledProcessError: + raise exceptions.GromacsToolLoadingError("Failed to load v4 tools") + return registry + + + +version = config.cfg.get('Gromacs', 'release') +major_release = version.split('.')[0] + +if major_release == '5': + registry = load_v5_tools() + + # Aliases to run unmodified GromacsWrapper scripts on a machine without + # Gromacs 4.x + for name in registry.copy(): + for c4, c5 in ALIASES5TO4.items(): + #have to check each one, since it's possible there are suffixes like + # for double precision if name.startswith(c5): - #maintain suffix - old_name = c4 + name.split(c5)[1] + # mantain suffix + registry[c4 + name.split(c5)] = registry[name] break - - # make names valid python identifiers and use convention that class names are capitalized - clsname = name.replace('.','_').replace('-','_').capitalize() - old_clsname = old_name.replace('.','_').replace('-','_').capitalize() - cls = type(clsname, (GromacsCommand,), {'command_name':name, - 'driver':prefix, - '__doc__': "Gromacs tool '%(prefix) %(name)r'." % vars()}) - #add alias for old name - #No need to see if old_name == name since we'll just clobber the item in registry - registry[old_clsname] = cls - else: - # make names valid python identifiers and use convention that class names are capitalized - clsname = name.replace('.','_').replace('-','_').capitalize() - cls = type(clsname, (GromacsCommand,), {'command_name':name, - '__doc__': "Gromacs tool %(name)r." % vars()}) - registry[clsname] = cls # registry keeps track of all classes - # dynamically build the module doc string - __doc__ += _generate_sphinx_class_string(clsname) - -# modify/fix classes as necessary -# Note: -# - check if class was defined in first place -# - replace class -# - update local context AND registry as done below - -class GromacsCommandMultiIndex(GromacsCommand): - def __init__(self, **kwargs): - """Initialize instance. - - 1) Sets up the combined index file. - 2) Inititialize :class:`~gromacs.core.GromacsCommand` with the - new index file. - - See the documentation for :class:`gromacs.core.GromacsCommand` for details. - """ - kwargs = self._fake_multi_ndx(**kwargs) - super(GromacsCommandMultiIndex, self).__init__(**kwargs) - - def run(self,*args,**kwargs): - """Run the command; make a combined multi-index file if necessary.""" - kwargs = self._fake_multi_ndx(**kwargs) - return super(GromacsCommandMultiIndex, self).run(*args, **kwargs) - - def _fake_multi_ndx(self, **kwargs): - """Combine multiple index file into a single one and return appropriate kwargs. - - Calling the method combines multiple index files into a a single - temporary one so that Gromacs tools that do not (yet) support multi - file input for index files can be used transparently as if they did. - - If a temporary index file is required then it is deleted once the - object is destroyed. - - :Returns: - The method returns the input keyword arguments with the necessary - changes to use the temporary index files. - - :Keywords: - Only the listed keywords have meaning for the method: - - *n* : filename or list of filenames - possibly multiple index files; *n* is replaced by the name of - the temporary index file. - *s* : filename - structure file (tpr, pdb, ...) or ``None``; if a structure file is - supplied then the Gromacs default index groups are automatically added - to the temporary indexs file. - - :Example: - Used in derived classes that replace the standard - :meth:`run` (or :meth:`__init__`) methods with something like:: - - def run(self,*args,**kwargs): - kwargs = self._fake_multi_ndx(**kwargs) - return super(G_mindist, self).run(*args, **kwargs) - - """ - ndx = kwargs.get('n') - if not (ndx is None or type(ndx) is str): - if len(ndx) > 1: - # g_mindist cannot deal with multiple ndx files (at least 4.0.5) - # so we combine them in a temporary file; it is unlinked in __del__. - # self.multi_ndx stores file name for __del__ - fd, self.multi_ndx = tempfile.mkstemp(suffix='.ndx', prefix='multi_') - make_ndx = Make_ndx(f=kwargs.get('s'), n=ndx) - rc,out,err = make_ndx(o=self.multi_ndx, input=['q'], # concatenate all index files - stdout=False, stderr=False) - self.orig_ndx = ndx - kwargs['n'] = self.multi_ndx - return kwargs - - def __del__(self): - """Clean up temporary multi-index files if they were used.""" - # XXX: does not seem to work when closing the interpreter?! - try: - # self.multi_ndx <-- _fake_multi_index() - utilities.unlink_gmx(self.multi_ndx) - except (AttributeError, OSError): - pass - # XXX: type error --- can't use super in __del__? - #super(GromacsCommandMultiIndex, self).__del__() - -# patching up... - -if 'G_mindist' in registry: - - # let G_mindist handle multiple ndx files - class G_mindist(GromacsCommandMultiIndex): - """Gromacs tool 'g_mindist' (with patch to handle multiple ndx files).""" - command_name = registry['G_mindist'].command_name - driver = registry['G_mindist'].driver - __doc__ = registry['G_mindist'].__doc__ - - registry['G_mindist'] = G_mindist - if b_gmx5: - registry['Mindist'] = G_mindist - -if 'G_dist' in registry: - # let G_dist handle multiple ndx files - class G_dist(GromacsCommandMultiIndex): - """Gromacs tool 'g_dist' (with patch to handle multiple ndx files).""" - command_name = registry['G_dist'].command_name - driver = registry['G_dist'].driver - __doc__ = registry['G_dist'].__doc__ - - registry['G_dist'] = G_dist - if b_gmx5: - registry['Distance'] = G_dist - - -# TODO: generate multi index classes via type(), not copy&paste as above... - - -# 5.0.5 compatibility hack -if 'Convert_tpr' in registry: - registry['Tpbconv'] = registry['Convert_tpr'] - -# finally, add everything -globals().update(registry) # add classes to module's scope -__all__ = registry.keys() + else: + #the common case of just adding the 'g_' + registry['G_%s' % name.lower()] = registry[name] -# and clean up the module scope -cls = clsname = name = rec = doc = None # make sure they exist, because the next line -del rec, name, cls, clsname, doc # would throw NameError if no tool was configured +elif major_release == '4': + registry = load_v4_tools() +else: + raise exceptions.GromacsToolLoadingError("Unknow Gromacs version %s" % + version) + +# finally add command classes to module's scope +globals().update(registry) +__all__ = registry.keys() From b551c0c792c6ef933a752b4d194ead831a6e2845 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Fri, 1 Jul 2016 23:20:51 -0300 Subject: [PATCH 05/30] new merge_ndx() function --- gromacs/tools.py | 115 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 19 deletions(-) diff --git a/gromacs/tools.py b/gromacs/tools.py index 537dadc9..89f9726b 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -80,6 +80,7 @@ 'solvate': 'genbox', } + TOOLS_V4 = ("do_dssp", "editconf", "eneconv", "g_anadock", "g_anaeig", "g_analyze", "g_angle", "g_bar", "g_bond", "g_bundle", "g_chi", "g_cluster", "g_clustsize", "g_confrms", "g_covar", "g_current", @@ -109,23 +110,31 @@ def append_suffix(name): def load_v5_tools(): + """ Load Gromacs 5.x tools and returns a dict of tool names mapped to + GromacsCommand classes + :return: dict of GromacsCommand classes + """ driver = append_suffix('gmx') try: out = subprocess.check_output([driver, '-quiet', 'help', 'commands']) except subprocess.CalledProcessError: raise exceptions.GromacsToolLoadingError("Failed to load v5 tools") - registry = {} + tools = {} for line in str(out).encode('ascii').splitlines()[5:-1]: if line[4] != ' ': name = line[4:line.index(' ', 4)] fancy = name.replace('-', '_').capitalize() - registry[fancy] = type(fancy, (GromacsCommand,), + tools[fancy] = type(fancy, (GromacsCommand,), {'command_name':name, 'driver': driver}) - return registry + return tools def load_v4_tools(): + """ Load Gromacs 4.x tools and returns a dict of tool names mapped to + GromacsCommand classes + :return: dict of GromacsCommand classes + """ tools = [append_suffix(t) for t in TOOLS_V4] registry = {} for tool in tools: @@ -140,27 +149,11 @@ def load_v4_tools(): return registry - version = config.cfg.get('Gromacs', 'release') major_release = version.split('.')[0] if major_release == '5': registry = load_v5_tools() - - # Aliases to run unmodified GromacsWrapper scripts on a machine without - # Gromacs 4.x - for name in registry.copy(): - for c4, c5 in ALIASES5TO4.items(): - #have to check each one, since it's possible there are suffixes like - # for double precision - if name.startswith(c5): - # mantain suffix - registry[c4 + name.split(c5)] = registry[name] - break - else: - #the common case of just adding the 'g_' - registry['G_%s' % name.lower()] = registry[name] - elif major_release == '4': registry = load_v4_tools() else: @@ -168,6 +161,90 @@ def load_v4_tools(): version) +# Append class doc for each command +for cmd in registry.itervalues(): + __doc__ += ".. class:: %s\n :noindex:\n" % cmd.__name__ + + +# Aliases command names to run unmodified GromacsWrapper scripts on a machine +# without Gromacs 4.x +for name in registry.copy(): + for c4, c5 in ALIASES5TO4.iteritems(): + # have to check each one, since it's possible there are suffixes + # like for double precision + if name.startswith(c5): + # mantain suffix + registry[c4 + name.split(c5)] = registry[name] + break + else: + # the common case of just adding the 'g_' + registry['G_%s' % name.lower()] = registry[name] + + +def merge_ndx(*args): + """ Takes one or more index files and optionally one structure file and + returns a path for a new merged index file. + + :param args: index files and zero or one structure file + :return: path for the new merged index file + """ + ndxs = [] + struct = None + for fname in args: + if fname.endswith('.ndx'): + ndxs.append(fname) + else: + assert struct is None, "only one structure file supported" + struct = fname + + fd, multi_ndx = tempfile.mkstemp(suffix='.ndx', prefix='multi_') + + def unlink_multindx(fname): + os.unlink(fname) + atexit.register(unlink_multindx, multi_ndx) + + if struct: + make_ndx = registry['Make_ndx'](f=struct, n=ndxs, o=multi_ndx) + else: + make_ndx = registry['Make_ndx'](n=ndxs, o=multi_ndx) + + _, _, _ = make_ndx(input=['q'], stdout=False, stderr=False) + return multi_ndx + + +class GromacsCommandMultiIndex(GromacsCommand): + def __init__(self, **kwargs): + warnings.warn("use merge_ndx() instead", DeprecationWarning) + kwargs = self._fake_multi_ndx(**kwargs) + super(GromacsCommandMultiIndex, self).__init__(**kwargs) + + def run(self,*args,**kwargs): + kwargs = self._fake_multi_ndx(**kwargs) + return super(GromacsCommandMultiIndex, self).run(*args, **kwargs) + + def _fake_multi_ndx(self, **kwargs): + ndx = kwargs.get('n') + if not (ndx is None or type(ndx) is str): + if len(ndx) > 1: + kwargs['n'] = merge_ndx(ndx, kwargs.get('s')) + return kwargs + + +for name4, name5 in [('G_mindist', 'Mindist'), ('G_dist', 'Distance')]: + klass = type(name4, (GromacsCommandMultiIndex,), { + 'command_name': registry[name5].command_name, + 'driver': registry[name5].driver, + '__doc__': registry[name5].__doc__ + }) + registry[name4] = klass + registry[name5] = klass + + +# 5.0.5 compatibility hack +if 'Convert_tpr' in registry: + registry['Tpbconv'] = registry['Convert_tpr'] + + # finally add command classes to module's scope globals().update(registry) __all__ = registry.keys() From 0237b2b065e7c4d45549ac6c5170fe35377b7439 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Fri, 1 Jul 2016 23:42:38 -0300 Subject: [PATCH 06/30] minor improvements --- gromacs/tools.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gromacs/tools.py b/gromacs/tools.py index 89f9726b..614bd359 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -52,7 +52,7 @@ import os.path, tempfile, subprocess, atexit, warnings -from . import config, utilities, exceptions +from . import config, exceptions from .core import GromacsCommand @@ -198,10 +198,7 @@ def merge_ndx(*args): struct = fname fd, multi_ndx = tempfile.mkstemp(suffix='.ndx', prefix='multi_') - - def unlink_multindx(fname): - os.unlink(fname) - atexit.register(unlink_multindx, multi_ndx) + atexit.register(os.unlink, multi_ndx) if struct: make_ndx = registry['Make_ndx'](f=struct, n=ndxs, o=multi_ndx) From 8370ec7f863c29e83565de0c7925fc38bedd6468 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Sat, 2 Jul 2016 01:44:54 -0300 Subject: [PATCH 07/30] tools config is optional --- gromacs/__init__.py | 4 +++- gromacs/config.py | 28 +++++----------------- gromacs/tools.py | 57 ++++++++++++++++++++++++++++++++------------- 3 files changed, 50 insertions(+), 39 deletions(-) diff --git a/gromacs/__init__.py b/gromacs/__init__.py index 9244b4f4..61468f06 100644 --- a/gromacs/__init__.py +++ b/gromacs/__init__.py @@ -243,7 +243,9 @@ def stop_logging(): # Try to load environment variables set by GMXRC -config.set_gmxrc_environment(config.cfg.getpath("Gromacs", "GMXRC")) +gmxrc = config.cfg.getpath("Gromacs", "GMXRC") +if gmxrc: + config.set_gmxrc_environment(gmxrc) # Add gromacs command **instances** to the top level. diff --git a/gromacs/config.py b/gromacs/config.py index 44b0c7d8..2387f277 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -690,30 +690,22 @@ def check_setup(): check_setup() -# Gromacs tools -# ------------- - def set_gmxrc_environment(gmxrc): - """Set the environment from ``GMXRC`` provided in *gmxrc*. + """Set the environment from ``GMXRC``. - Runs ``GMXRC`` in a subprocess and puts environment variables loaded by it + Runs ``gmxrc`` in a subprocess and puts environment variables loaded by it into this Python environment. - If *gmxrc* evaluates to ``False`` then nothing is done. If errors occur - then only a warning will be logged. Thus, it should be safe to just call - this function. - + :param gmxrc: GMXRC file path """ - envvars = ['GMXPREFIX', 'GMXBIN', 'GMXLDLIB', 'GMXMAN', 'GMXDATA', 'GROMACS_DIR', 'LD_LIBRARY_PATH', 'MANPATH', 'PKG_CONFIG_PATH', 'PATH'] cmdargs = ['bash', '-c', ". {0} && echo {1}".format(gmxrc, ' '.join(['${0}'.format(v) for v in envvars]))] - if not gmxrc: - logger.debug("set_gmxrc_environment(): no GMXRC, nothing done.") - return + if not os.path.isfile(gmxrc): + raise ValueError("'%s' must be a file") try: out = subprocess.check_output(cmdargs) @@ -722,12 +714,4 @@ def set_gmxrc_environment(gmxrc): os.environ[key] = value logger.debug("set_gmxrc_environment(): %s = %r", key, value) except (subprocess.CalledProcessError, OSError): - logger.warning("Failed to automatically set the Gromacs environment" - "from GMXRC=%r", gmxrc) - -#: Python list of all tool file names. Filled from values in the tool -#: groups in the configuration file. -load_tools = [] -for g in cfg.getlist('Gromacs', 'groups', sort=False): - load_tools.extend(cfg.getlist('Gromacs', g)) -del g + logger.error("Failed to set the environment from GMXRC=%r", gmxrc) diff --git a/gromacs/tools.py b/gromacs/tools.py index 614bd359..a6a819d7 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -102,7 +102,20 @@ "trjorder", "xpm2p") +version = config.cfg.get('Gromacs', 'release') +major_release = version.split('.')[0] + + +def tool_factory(clsname, name, driver): + """ GromacsCommand derived class factory. """ + return type(clsname, (GromacsCommand,), { + 'command_name':name, + 'driver': driver + }) + + def append_suffix(name): + """ Append (or not) the optional command suffix. """ suffix = config.cfg.get('Gromacs', 'suffix') if suffix: name += '_' + suffix @@ -110,9 +123,8 @@ def append_suffix(name): def load_v5_tools(): - """ Load Gromacs 5.x tools and returns a dict of tool names mapped to - GromacsCommand classes - :return: dict of GromacsCommand classes + """ Load Gromacs 5.x tools. + :return: dict mapping tool names to GromacsCommand classes """ driver = append_suffix('gmx') try: @@ -125,32 +137,45 @@ def load_v5_tools(): if line[4] != ' ': name = line[4:line.index(' ', 4)] fancy = name.replace('-', '_').capitalize() - tools[fancy] = type(fancy, (GromacsCommand,), - {'command_name':name, 'driver': driver}) + tools[fancy] = tool_factory(fancy, name, driver) return tools def load_v4_tools(): - """ Load Gromacs 4.x tools and returns a dict of tool names mapped to - GromacsCommand classes - :return: dict of GromacsCommand classes + """ Load Gromacs 4.x tools. + :return: dict mapping tool names to GromacsCommand classes """ - tools = [append_suffix(t) for t in TOOLS_V4] - registry = {} - for tool in tools: + names = config.cfg.get("Gromacs", "tools") + if not names: + names = [append_suffix(t) for t in TOOLS_V4] + else: + names = names.split() + + tools = {} + for tool in names: fancy = tool.capitalize() - registry[fancy] = type(fancy, (GromacsCommand,), {'command_name':tool}) + tools[fancy] = tool_factory(fancy, tool, None) try: null = open(os.devnull, 'w') subprocess.check_call(['g_luck'], stdout=null, stderr=null) except subprocess.CalledProcessError: raise exceptions.GromacsToolLoadingError("Failed to load v4 tools") - return registry + return tools -version = config.cfg.get('Gromacs', 'release') -major_release = version.split('.')[0] +def load_extra_tools(): + """ Load extra tools. + :return: dict mapping tool names to GromacsCommand classes + """ + names = config.cfg.get("Gromacs", "extra").split() + driver = append_suffix('gmx') if major_release == '5' else None + tools = {} + for name in names: + fancy = name.capitalize().replace('-', '_') + tools[name] = tool_factory(fancy, name, driver) + return tools + if major_release == '5': registry = load_v5_tools() @@ -159,6 +184,7 @@ def load_v4_tools(): else: raise exceptions.GromacsToolLoadingError("Unknow Gromacs version %s" % version) +registry.update(load_extra_tools()) # Append class doc for each command @@ -211,7 +237,6 @@ def merge_ndx(*args): class GromacsCommandMultiIndex(GromacsCommand): def __init__(self, **kwargs): - warnings.warn("use merge_ndx() instead", DeprecationWarning) kwargs = self._fake_multi_ndx(**kwargs) super(GromacsCommandMultiIndex, self).__init__(**kwargs) From 73d3e38d72234eb28b9d46cc1e940f0fb517fd2d Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Sun, 3 Jul 2016 21:08:52 -0300 Subject: [PATCH 08/30] Patching GromacsCommandMultiIndex only if tool at registry --- gromacs/tools.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/gromacs/tools.py b/gromacs/tools.py index bf5dd29f..6767d846 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -254,13 +254,14 @@ def _fake_multi_ndx(self, **kwargs): for name4, name5 in [('G_mindist', 'Mindist'), ('G_dist', 'Distance')]: - klass = type(name4, (GromacsCommandMultiIndex,), { - 'command_name': registry[name5].command_name, - 'driver': registry[name5].driver, - '__doc__': registry[name5].__doc__ - }) - registry[name4] = klass - registry[name5] = klass + if name5 in registry: + klass = type(name4, (GromacsCommandMultiIndex,), { + 'command_name': registry[name5].command_name, + 'driver': registry[name5].driver, + '__doc__': registry[name5].__doc__ + }) + registry[name4] = klass + registry[name5] = klass # 5.0.5 compatibility hack From fe0bfecd80ba39ca24670e6e9878a7ab7058c183 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Sun, 3 Jul 2016 21:41:59 -0300 Subject: [PATCH 09/30] Reverting things that don't belong this branch --- gromacs/config.py | 17 ++++++++--------- gromacs/tools.py | 24 ++++++++++++++++-------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/gromacs/config.py b/gromacs/config.py index 0285f956..e63b35eb 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -523,17 +523,15 @@ def __init__(self, *args, **kwargs): args = tuple([self] + list(args)) SafeConfigParser.__init__(*args, **kwargs) # old style class ... grmbl # defaults - self.set('DEFAULT', 'configdir', defaults['configdir']) - self.set('DEFAULT', 'qscriptdir', os.path.join("%(configdir)s", - os.path.basename(defaults['qscriptdir']))) - self.set('DEFAULT', 'templatesdir', os.path.join("%(configdir)s", - os.path.basename(defaults['templatesdir']))) - self.set('DEFAULT', 'managerdir', os.path.join("%(configdir)s", - os.path.basename(defaults['managerdir']))) + self.set('DEFAULT', 'qscriptdir', + os.path.join("%(configdir)s", os.path.basename(defaults['qscriptdir']))) + self.set('DEFAULT', 'templatesdir', + os.path.join("%(configdir)s", os.path.basename(defaults['templatesdir']))) + self.set('DEFAULT', 'managerdir', + os.path.join("%(configdir)s", os.path.basename(defaults['managerdir']))) self.add_section('Gromacs') self.set("Gromacs", "GMXRC", "") - self.set("Gromacs", "tools", "pdb2gmx editconf grompp genbox genion" - "mdrun trjcat trjconv") + self.set("Gromacs", "tools", "pdb2gmx editconf grompp genbox genion mdrun trjcat trjconv") self.set("Gromacs", "extra", "") self.set("Gromacs", "suffix", "") self.set("Gromacs", "groups", "tools") @@ -691,6 +689,7 @@ def check_setup(): check_setup() + # Gromacs tools # ------------- diff --git a/gromacs/tools.py b/gromacs/tools.py index 6767d846..7f39cf42 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -12,25 +12,33 @@ By convention, a class has the capitalized name of the corresponding Gromacs tool; dots are replaced by underscores to make it a valid python identifier. -Gromacs 5 tools (e.g, `sasa`) are aliased to their Gromacs 4 tool names -(e.g, `g_sas`) for backwards compatibility. +Gromacs 5 tools (e.g, `sasa`) are aliased to their Gromacs 4 tool names (e.g, `g_sas`) +for backwards compatibility. The list of Gromacs tools to be loaded is configured in :data:`gromacs.config.gmx_tool_groups`. +It is also possible to extend the basic commands and patch in additional +functionality. For example, the :class:`GromacsCommandMultiIndex` class makes a +command accept multiple index files and concatenates them on the fly; the +behaviour mimics Gromacs' "multi-file" input that has not yet been enabled for +all tools. + +.. autoclass:: GromacsCommandMultiIndex + :members: run, _fake_multi_ndx, __del__ + Example ------- -In this example we create two instances of the :class:`gromacs.tools.Trjconv` -command (which runs the Gromacs ``trjconv`` command):: +In this example we create two instances of the :class:`gromacs.tools.Trjconv` command (which +runs the Gromacs ``trjconv`` command):: import gromacs.tools as tools trjconv = tools.Trjconv() trjconv_compact = tools.Trjconv(ur='compact', center=True, boxcenter='tric', pbc='mol', input=('protein','system'), - doc="Returns a compact representation of the" - "system centered on the protein") + doc="Returns a compact representation of the system centered on the protein") The first one, ``trjconv``, behaves as the standard commandline tool but the second one, ``trjconv_compact``, will by default create a compact @@ -72,8 +80,8 @@ 'mdrun': 'mdrun', 'make_ndx': 'make_ndx', 'make_edi': 'make_edi', - 'dump': 'gmxdump', - 'check': 'gmxcheck', + 'gmxdump': 'gmxdump', + 'gmxcheck': 'gmxcheck', 'genrestr': 'genrestr', 'genion': 'genion', 'genconf': 'genconf', From a2fc574c91be2131c5b7a830634da1db0af4b85b Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Sun, 3 Jul 2016 23:25:16 -0300 Subject: [PATCH 10/30] Code reduction --- gromacs/config.py | 22 +++++++++----- gromacs/tools.py | 73 +++++++++++------------------------------------ 2 files changed, 32 insertions(+), 63 deletions(-) diff --git a/gromacs/config.py b/gromacs/config.py index e63b35eb..b7c9e2aa 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -533,7 +533,6 @@ def __init__(self, *args, **kwargs): self.set("Gromacs", "GMXRC", "") self.set("Gromacs", "tools", "pdb2gmx editconf grompp genbox genion mdrun trjcat trjconv") self.set("Gromacs", "extra", "") - self.set("Gromacs", "suffix", "") self.set("Gromacs", "groups", "tools") self.add_section('Logging') self.set('Logging', 'logfilename', defaults['logfilename']) @@ -725,9 +724,18 @@ def set_gmxrc_environment(gmxrc): logger.warning("Failed to automatically set the Gromacs environment" "from GMXRC=%r", gmxrc) -#: Python list of all tool file names. Filled from values in the tool -#: groups in the configuration file. -load_tools = [] -for g in cfg.getlist('Gromacs', 'groups', sort=False): - load_tools.extend(cfg.getlist('Gromacs', g)) -del g \ No newline at end of file + +def get_tools(): + """ Get tool names from all configured groups. + + :return: list of tool names + + """ + load_tools = [] + for g in cfg.getlist('Gromacs', 'groups', sort=False): + load_tools.extend(cfg.getlist('Gromacs', g, sort=False)) + return load_tools + + +RELEASE = cfg.get('Gromacs', 'release') +MAJOR_RELEASE = RELEASE.split('.')[0] diff --git a/gromacs/tools.py b/gromacs/tools.py index 7f39cf42..37ae3c03 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -90,54 +90,21 @@ } -TOOLS_V4 = ("do_dssp", "editconf", "eneconv", "g_anadock", "g_anaeig", - "g_analyze", "g_angle", "g_bar", "g_bond", "g_bundle", "g_chi", - "g_cluster", "g_clustsize", "g_confrms", "g_covar", "g_current", - "g_density", "g_densmap", "g_densorder", "g_dielectric", - "g_dipoles", "g_disre", "g_dist", "g_dos", "g_dyecoupl", "g_dyndom", - "genbox", "genconf", "g_enemat", "g_energy", "genion", "genrestr", - "g_filter", "g_gyrate", "g_h2order", "g_hbond", "g_helix", - "g_helixorient", "g_hydorder", "g_kinetics", "g_lie", "g_luck", - "g_mdmat", "g_membed", "g_mindist", "g_morph", "g_msd", - "gmxcheck", "gmxdump", "g_nmeig", "g_nmens", "g_nmtraj", "g_options", - "g_order", "g_pme_error", "g_polystat", "g_potential", - "g_principal", "g_protonate", "g_rama", "g_rdf", "g_rms", - "g_rmsdist", "g_rmsf", "grompp", "g_rotacf", "g_rotmat", - "g_saltbr", "g_sans", "g_sas", "g_select", "g_sgangle", "g_sham", - "g_sigeps", "g_sorient", "g_spatial", "g_spol", "g_tcaf", - "g_traj", "g_tune_pme", "g_vanhove", "g_velacc", "g_wham", - "g_wheel", "g_x2top", "g_xrama", "make_edi", "make_ndx", "mdrun", - "mk_angndx", "ngmx", "pdb2gmx", "tpbconv", "trjcat", "trjconv", - "trjorder", "xpm2p") - - -version = config.cfg.get('Gromacs', 'release') -major_release = version.split('.')[0] - - -def tool_factory(clsname, name, driver): - """ GromacsCommand derived class factory. """ +def tool_factory(clsname, name): + """ GromacsCommand derived type factory. """ return type(clsname, (GromacsCommand,), { - 'command_name':name, - 'driver': driver + 'command_name': name, + 'driver': 'gmx' if config.MAJOR_RELEASE == '5' else None }) -def append_suffix(name): - """ Append (or not) the optional command suffix. """ - suffix = config.cfg.get('Gromacs', 'suffix') - if suffix: - name += '_' + suffix - return name - - def load_v5_tools(): """ Load Gromacs 5.x tools. + :return: dict mapping tool names to GromacsCommand classes """ - driver = append_suffix('gmx') try: - out = subprocess.check_output([driver, '-quiet', 'help', 'commands']) + out = subprocess.check_output(['gmx', '-quiet', 'help', 'commands']) except subprocess.CalledProcessError: raise exceptions.GromacsToolLoadingError("Failed to load v5 tools") @@ -146,25 +113,19 @@ def load_v5_tools(): if line[4] != ' ': name = line[4:line.index(' ', 4)] fancy = name.replace('-', '_').capitalize() - tools[fancy] = tool_factory(fancy, name, driver) + tools[fancy] = tool_factory(fancy, name) return tools def load_v4_tools(): """ Load Gromacs 4.x tools. + :return: dict mapping tool names to GromacsCommand classes """ - names = config.cfg.get("Gromacs", "tools") - if not names: - names = [append_suffix(t) for t in TOOLS_V4] - else: - names = names.split() - tools = {} - for tool in names: - fancy = tool.capitalize() - tools[fancy] = tool_factory(fancy, tool, None) - + for name in config.get_tools(): + fancy = name.capitalize().replace('-', '_') + tools[fancy] = tool_factory(fancy, name) try: null = open(os.devnull, 'w') subprocess.check_call(['g_luck'], stdout=null, stderr=null) @@ -175,24 +136,24 @@ def load_v4_tools(): def load_extra_tools(): """ Load extra tools. + :return: dict mapping tool names to GromacsCommand classes """ names = config.cfg.get("Gromacs", "extra").split() - driver = append_suffix('gmx') if major_release == '5' else None tools = {} for name in names: fancy = name.capitalize().replace('-', '_') - tools[name] = tool_factory(fancy, name, driver) + tools[name] = tool_factory(fancy, name) return tools -if major_release == '5': +if config.MAJOR_RELEASE == '5': registry = load_v5_tools() -elif major_release == '4': +elif config.MAJOR_RELEASE == '4': registry = load_v4_tools() else: raise exceptions.GromacsToolLoadingError("Unknow Gromacs version %s" % - version) + config.RELEASE) registry.update(load_extra_tools()) @@ -279,4 +240,4 @@ def _fake_multi_ndx(self, **kwargs): # finally add command classes to module's scope globals().update(registry) -__all__ = registry.keys() \ No newline at end of file +__all__ = registry.keys() From 16e91409bf92a2dfcea41d565534bbabec8cfec4 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Tue, 12 Jul 2016 22:11:11 -0300 Subject: [PATCH 11/30] some refactoring --- gromacs/config.py | 11 +++++++---- gromacs/tools.py | 27 +++++++++++++++++---------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/gromacs/config.py b/gromacs/config.py index b7c9e2aa..b147a5bf 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -266,7 +266,7 @@ """ from __future__ import absolute_import, with_statement -import os, errno, subprocess +import os, subprocess from ConfigParser import SafeConfigParser from pkg_resources import resource_filename, resource_listdir @@ -729,7 +729,6 @@ def get_tools(): """ Get tool names from all configured groups. :return: list of tool names - """ load_tools = [] for g in cfg.getlist('Gromacs', 'groups', sort=False): @@ -737,5 +736,9 @@ def get_tools(): return load_tools -RELEASE = cfg.get('Gromacs', 'release') -MAJOR_RELEASE = RELEASE.split('.')[0] +RELEASE = None +MAJOR_RELEASE = None + +if cfg.get('Gromacs', 'release'): + RELEASE = cfg.get('Gromacs', 'release') + MAJOR_RELEASE = RELEASE.split('.')[0] diff --git a/gromacs/tools.py b/gromacs/tools.py index 37ae3c03..ebe18aba 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -59,7 +59,10 @@ from __future__ import absolute_import __docformat__ = "restructuredtext en" -import os.path, tempfile, subprocess, atexit, warnings +import os.path +import tempfile +import subprocess +import atexit from . import config, exceptions from .core import GromacsCommand @@ -90,16 +93,19 @@ } -def tool_factory(clsname, name): +def tool_factory(clsname, name, driver, doc=None): """ GromacsCommand derived type factory. """ - return type(clsname, (GromacsCommand,), { + clsdict = { 'command_name': name, - 'driver': 'gmx' if config.MAJOR_RELEASE == '5' else None - }) + 'driver': driver + } + if doc: + clsdict['__doc__'] = doc + return type(clsname, (GromacsCommand,), clsdict) def load_v5_tools(): - """ Load Gromacs 5.x tools. + """ Load Gromacs 5.x tools automatically inferred from running ``gmx help``. :return: dict mapping tool names to GromacsCommand classes """ @@ -113,7 +119,7 @@ def load_v5_tools(): if line[4] != ' ': name = line[4:line.index(' ', 4)] fancy = name.replace('-', '_').capitalize() - tools[fancy] = tool_factory(fancy, name) + tools[fancy] = tool_factory(fancy, name, 'gmx') return tools @@ -125,10 +131,10 @@ def load_v4_tools(): tools = {} for name in config.get_tools(): fancy = name.capitalize().replace('-', '_') - tools[fancy] = tool_factory(fancy, name) + tools[fancy] = tool_factory(fancy, name, None) try: null = open(os.devnull, 'w') - subprocess.check_call(['g_luck'], stdout=null, stderr=null) + subprocess.check_call(['grompp'], stdout=null, stderr=null) except subprocess.CalledProcessError: raise exceptions.GromacsToolLoadingError("Failed to load v4 tools") return tools @@ -143,7 +149,8 @@ def load_extra_tools(): tools = {} for name in names: fancy = name.capitalize().replace('-', '_') - tools[name] = tool_factory(fancy, name) + driver = 'gmx' if config.MAJOR_RELEASE == '5' else None + tools[name] = tool_factory(fancy, name, driver) return tools From f20def7cd3774982364a5c451ee2df1aeaceac50 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Tue, 12 Jul 2016 23:58:47 -0300 Subject: [PATCH 12/30] some refactoring --- gromacs/config.py | 2 +- gromacs/tools.py | 154 ++++++++++++++++++++++++++-------------------- 2 files changed, 89 insertions(+), 67 deletions(-) diff --git a/gromacs/config.py b/gromacs/config.py index b147a5bf..738309ef 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -725,7 +725,7 @@ def set_gmxrc_environment(gmxrc): "from GMXRC=%r", gmxrc) -def get_tools(): +def get_tool_names(): """ Get tool names from all configured groups. :return: list of tool names diff --git a/gromacs/tools.py b/gromacs/tools.py index ebe18aba..88054978 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -68,31 +68,52 @@ from .core import GromacsCommand +V4TOOLS = ("demux.pl", "g_cluster", "g_dyndom", "g_mdmat", "g_principal", + "g_select", "g_wham", "mdrun", "do_dssp", "g_clustsize", "g_enemat", + "g_membed", "g_protonate", "g_sgangle", "g_wheel", "mdrun_d", + "editconf", "g_confrms", "g_energy", "g_mindist", "g_rama", "g_sham", + "g_x2top", "mk_angndx", "eneconv", "g_covar", "g_filter", "g_morph", + "g_rdf", "g_sigeps", "genbox", "pdb2gmx", "g_anadock", "g_current", + "g_gyrate", "g_msd", "g_sorient", "genconf", "g_anaeig", "g_density", + "g_h2order", "g_nmeig", "g_rms", "g_spatial", "genion", "tpbconv", + "g_analyze", "g_densmap", "g_hbond", "g_nmens", "g_rmsdist", + "g_spol", "genrestr", "trjcat", "g_angle", "g_dielectric", "g_helix", + "g_nmtraj", "g_rmsf", "g_tcaf", "gmxcheck", "trjconv", "g_bar", + "g_dih", "g_helixorient", "g_order", "g_rotacf", "g_traj", "gmxdump", + "trjorder", "g_bond", "g_dipoles", "g_kinetics", "g_pme_error", + "g_rotmat", "g_tune_pme", "grompp", "xplor2gmx.pl", "g_bundle", + "g_disre", "g_lie", "g_polystat", "g_saltbr", "g_vanhove", + "make_edi", "xpm2ps", "g_chi", "g_dist", "g_luck", "g_potential", + "g_sas", "g_velacc", "make_ndx") + + ALIASES5TO4 = { - 'grompp': 'grompp', - 'eneconv': 'eneconv', - 'sasa': 'g_sas', - 'distance': 'g_dist', 'convert_tpr': 'tpbconv', - 'editconf': 'editconf', - 'pdb2gmx': 'pdb2gmx', - 'trjcat': 'trjcat', - 'trjconv': 'trjconv', - 'trjorder': 'trjorder', - 'xpm2ps': 'xpm2ps', - 'mdrun': 'mdrun', - 'make_ndx': 'make_ndx', - 'make_edi': 'make_edi', - 'gmxdump': 'gmxdump', - 'gmxcheck': 'gmxcheck', - 'genrestr': 'genrestr', - 'genion': 'genion', - 'genconf': 'genconf', - 'do_dssp': 'do_dssp', + 'check': 'gmxcheck', + 'distance': 'g_dist', + 'dump': 'gmxdump', + 'sasa': 'g_sas', 'solvate': 'genbox', } +class GromacsCommandMultiIndex(GromacsCommand): + def __init__(self, **kwargs): + kwargs = self._fake_multi_ndx(**kwargs) + super(GromacsCommandMultiIndex, self).__init__(**kwargs) + + def run(self,*args,**kwargs): + kwargs = self._fake_multi_ndx(**kwargs) + return super(GromacsCommandMultiIndex, self).run(*args, **kwargs) + + def _fake_multi_ndx(self, **kwargs): + ndx = kwargs.get('n') + if not (ndx is None or type(ndx) is str): + if len(ndx) > 1: + kwargs['n'] = merge_ndx(ndx, kwargs.get('s')) + return kwargs + + def tool_factory(clsname, name, driver, doc=None): """ GromacsCommand derived type factory. """ clsdict = { @@ -126,11 +147,28 @@ def load_v5_tools(): def load_v4_tools(): """ Load Gromacs 4.x tools. + Tries to load tools (1) automatically from ``GMXBIN`` and (2) fails over to + configured tool groups if there are too many in the path and (3) then to a + prefilled list if none was configured. + :return: dict mapping tool names to GromacsCommand classes """ + names = [] + if 'GMXBIN' in os.environ: + for bin in os.listdir(os.environ['GMXBIN']): + if os.access(bin, os.X_OK) and not os.path.isdir(bin): + names.append(bin) + + # directory contains more binaries than there are gromacs tools? + if len(names) > len(V4TOOLS): + names = config.get_tool_names() + + if len(names) == 0: + names = V4TOOLS[:] + tools = {} - for name in config.get_tools(): - fancy = name.capitalize().replace('-', '_') + for name in names: + fancy = name.capitalize().replace('.', '_') tools[fancy] = tool_factory(fancy, name, None) try: null = open(os.devnull, 'w') @@ -154,36 +192,6 @@ def load_extra_tools(): return tools -if config.MAJOR_RELEASE == '5': - registry = load_v5_tools() -elif config.MAJOR_RELEASE == '4': - registry = load_v4_tools() -else: - raise exceptions.GromacsToolLoadingError("Unknow Gromacs version %s" % - config.RELEASE) -registry.update(load_extra_tools()) - - -# Append class doc for each command -for cmd in registry.itervalues(): - __doc__ += ".. class:: %s\n :noindex:\n" % cmd.__name__ - - -# Aliases command names to run unmodified GromacsWrapper scripts on a machine -# without Gromacs 4.x -for name in registry.copy(): - for c4, c5 in ALIASES5TO4.iteritems(): - # have to check each one, since it's possible there are suffixes - # like for double precision - if name.startswith(c5): - # mantain suffix - registry[c4 + name.split(c5)] = registry[name] - break - else: - # the common case of just adding the 'g_' - registry['G_%s' % name.lower()] = registry[name] - - def merge_ndx(*args): """ Takes one or more index files and optionally one structure file and returns a path for a new merged index file. @@ -212,32 +220,46 @@ def merge_ndx(*args): return multi_ndx -class GromacsCommandMultiIndex(GromacsCommand): - def __init__(self, **kwargs): - kwargs = self._fake_multi_ndx(**kwargs) - super(GromacsCommandMultiIndex, self).__init__(**kwargs) +if config.MAJOR_RELEASE == '5': + registry = load_v5_tools() +elif config.MAJOR_RELEASE == '4': + registry = load_v4_tools() +else: + raise exceptions.GromacsToolLoadingError("Unknow Gromacs version %s" % + config.RELEASE) +registry.update(load_extra_tools()) - def run(self,*args,**kwargs): - kwargs = self._fake_multi_ndx(**kwargs) - return super(GromacsCommandMultiIndex, self).run(*args, **kwargs) - def _fake_multi_ndx(self, **kwargs): - ndx = kwargs.get('n') - if not (ndx is None or type(ndx) is str): - if len(ndx) > 1: - kwargs['n'] = merge_ndx(ndx, kwargs.get('s')) - return kwargs +# Append class doc for each command +for cmd in registry.itervalues(): + __doc__ += ".. class:: %s\n :noindex:\n" % cmd.__name__ + + +# Aliases command names to run unmodified GromacsWrapper scripts on a machine +# without Gromacs 4.x +for name in registry.copy(): + for c4, c5 in ALIASES5TO4.iteritems(): + # have to check each one, since it's possible there are suffixes + # like for double precision + if name.startswith(c5): + # mantain suffix + registry[c4 + name.split(c5)] = registry[name] + break + else: + # the common case of just adding the 'g_' + registry['G_%s' % name.lower()] = registry[name] for name4, name5 in [('G_mindist', 'Mindist'), ('G_dist', 'Distance')]: - if name5 in registry: + if name4 in registry: klass = type(name4, (GromacsCommandMultiIndex,), { 'command_name': registry[name5].command_name, 'driver': registry[name5].driver, '__doc__': registry[name5].__doc__ }) registry[name4] = klass - registry[name5] = klass + if config.MAJOR_RELEASE == '5': + registry[name5] = klass # 5.0.5 compatibility hack From 09ce4d5bf9ed0e0d98f12f78abc1b2290982294e Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Wed, 13 Jul 2016 20:56:23 -0300 Subject: [PATCH 13/30] tools option support and Gromacs 4 automatic tool detection --- gromacs/tools.py | 129 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 93 insertions(+), 36 deletions(-) diff --git a/gromacs/tools.py b/gromacs/tools.py index 88054978..00878756 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -68,12 +68,12 @@ from .core import GromacsCommand -V4TOOLS = ("demux.pl", "g_cluster", "g_dyndom", "g_mdmat", "g_principal", - "g_select", "g_wham", "mdrun", "do_dssp", "g_clustsize", "g_enemat", - "g_membed", "g_protonate", "g_sgangle", "g_wheel", "mdrun_d", - "editconf", "g_confrms", "g_energy", "g_mindist", "g_rama", "g_sham", - "g_x2top", "mk_angndx", "eneconv", "g_covar", "g_filter", "g_morph", - "g_rdf", "g_sigeps", "genbox", "pdb2gmx", "g_anadock", "g_current", +V4TOOLS = ("g_cluster", "g_dyndom", "g_mdmat", "g_principal", "g_select", + "g_wham", "mdrun", "do_dssp", "g_clustsize", "g_enemat", "g_membed", + "g_protonate", "g_sgangle", "g_wheel", "mdrun_d", "editconf", + "g_confrms", "g_energy", "g_mindist", "g_rama", "g_sham", "g_x2top", + "mk_angndx", "eneconv", "g_covar", "g_filter", "g_morph", "g_rdf", + "g_sigeps", "genbox", "pdb2gmx", "g_anadock", "g_current", "g_gyrate", "g_msd", "g_sorient", "genconf", "g_anaeig", "g_density", "g_h2order", "g_nmeig", "g_rms", "g_spatial", "genion", "tpbconv", "g_analyze", "g_densmap", "g_hbond", "g_nmens", "g_rmsdist", @@ -81,18 +81,32 @@ "g_nmtraj", "g_rmsf", "g_tcaf", "gmxcheck", "trjconv", "g_bar", "g_dih", "g_helixorient", "g_order", "g_rotacf", "g_traj", "gmxdump", "trjorder", "g_bond", "g_dipoles", "g_kinetics", "g_pme_error", - "g_rotmat", "g_tune_pme", "grompp", "xplor2gmx.pl", "g_bundle", - "g_disre", "g_lie", "g_polystat", "g_saltbr", "g_vanhove", - "make_edi", "xpm2ps", "g_chi", "g_dist", "g_luck", "g_potential", - "g_sas", "g_velacc", "make_ndx") + "g_rotmat", "g_tune_pme", "grompp", "g_bundle", "g_disre", "g_lie", + "g_polystat", "g_saltbr", "g_vanhove", "make_edi", "xpm2ps", "g_chi", + "g_dist", "g_luck", "g_potential", "g_sas", "g_velacc", "make_ndx") ALIASES5TO4 = { - 'convert_tpr': 'tpbconv', - 'check': 'gmxcheck', + 'grompp': 'grompp', + 'eneconv': 'eneconv', + 'sasa': 'g_sas', 'distance': 'g_dist', + 'convert_tpr': 'tpbconv', + 'editconf': 'editconf', + 'pdb2gmx': 'pdb2gmx', + 'trjcat': 'trjcat', + 'trjconv': 'trjconv', + 'trjorder': 'trjorder', + 'xpm2ps': 'xpm2ps', + 'mdrun': 'mdrun', + 'make_ndx': 'make_ndx', + 'make_edi': 'make_edi', 'dump': 'gmxdump', - 'sasa': 'g_sas', + 'check': 'gmxcheck', + 'genrestr': 'genrestr', + 'genion': 'genion', + 'genconf': 'genconf', + 'do_dssp': 'do_dssp', 'solvate': 'genbox', } @@ -125,29 +139,64 @@ def tool_factory(clsname, name, driver, doc=None): return type(clsname, (GromacsCommand,), clsdict) +def find_executables(path): + execs = [] + for exe in os.listdir(path): + fullexe = os.path.join(path, exe) + if (os.access(fullexe, os.X_OK) and not os.path.isdir(fullexe) and + exe not in ['GMXRC', 'GMXRC.bash', 'GMXRC.csh', 'GMXRC.zsh', + 'demux.pl', 'xplor2gmx.pl']): + execs.append(exe) + return execs + + def load_v5_tools(): - """ Load Gromacs 5.x tools automatically inferred from running ``gmx help``. + """ Load Gromacs 5.x tools automatically using some heuristic. + + Tries to load tools (1) automatically from ``GMXBIN`` and (2) fails back to + configured tool groups if there are too many in the path and (3) then to a + list from running the command ``gmx help``. :return: dict mapping tool names to GromacsCommand classes """ - try: - out = subprocess.check_output(['gmx', '-quiet', 'help', 'commands']) - except subprocess.CalledProcessError: - raise exceptions.GromacsToolLoadingError("Failed to load v5 tools") + drivers = [] + if 'GMXBIN' in os.environ: + drivers = find_executables(os.environ['GMXBIN']) + + # directory contains more binaries than there are gromacs drivers? + # (including single, double precision and mpi altogether) + if len(drivers) > 4: + drivers = config.get_tool_names() + + if len(drivers) == 0: + drivers = ['gmx', 'gmx_d', 'gmx_mpi', 'gmx_mpi_d'] tools = {} - for line in str(out).encode('ascii').splitlines()[5:-1]: - if line[4] != ' ': - name = line[4:line.index(' ', 4)] - fancy = name.replace('-', '_').capitalize() - tools[fancy] = tool_factory(fancy, name, 'gmx') + for driver in drivers: + try: + out = subprocess.check_output([driver, '-quiet', 'help', + 'commands']) + for line in str(out).encode('ascii').splitlines()[5:-1]: + if line[4] != ' ': + + name = line[4:line.index(' ', 4)] + fancy = name.replace('-', '_').capitalize() + suffix = driver.partition('_')[2] + if suffix: + fancy = '%s_%s' % (fancy, suffix) + tools[fancy] = tool_factory(fancy, name, driver) + except (subprocess.CalledProcessError, OSError): + pass + + if len(tools) == 0: + raise exceptions.GromacsToolLoadingError("Failed to load v5 tools") return tools def load_v4_tools(): - """ Load Gromacs 4.x tools. + """ Load Gromacs 4.x tools automatically using some heuristic. - Tries to load tools (1) automatically from ``GMXBIN`` and (2) fails over to + Tries to load tools (1) automatically from ``GMXBIN`` and (2) fails back to configured tool groups if there are too many in the path and (3) then to a prefilled list if none was configured. @@ -155,12 +204,11 @@ def load_v4_tools(): """ names = [] if 'GMXBIN' in os.environ: - for bin in os.listdir(os.environ['GMXBIN']): - if os.access(bin, os.X_OK) and not os.path.isdir(bin): - names.append(bin) + names = find_executables(os.environ['GMXBIN']) # directory contains more binaries than there are gromacs tools? - if len(names) > len(V4TOOLS): + # (including double single, double precision and mpi altogether) + if len(names) > len(V4TOOLS)*4: names = config.get_tool_names() if len(names) == 0: @@ -168,12 +216,15 @@ def load_v4_tools(): tools = {} for name in names: + try: + null = open(os.devnull, 'w') + subprocess.check_call([name], stdout=null, stderr=null) + except subprocess.CalledProcessError: + pass fancy = name.capitalize().replace('.', '_') tools[fancy] = tool_factory(fancy, name, None) - try: - null = open(os.devnull, 'w') - subprocess.check_call(['grompp'], stdout=null, stderr=null) - except subprocess.CalledProcessError: + + if len(tools) == 0: raise exceptions.GromacsToolLoadingError("Failed to load v4 tools") return tools @@ -225,8 +276,15 @@ def merge_ndx(*args): elif config.MAJOR_RELEASE == '4': registry = load_v4_tools() else: - raise exceptions.GromacsToolLoadingError("Unknow Gromacs version %s" % - config.RELEASE) + try: + registry = load_v5_tools() + except exceptions.GromacsToolLoadingError: + try: + registry = load_v4_tools() + except exceptions.GromacsToolLoadingError: + raise exceptions.GromacsToolLoadingError( + "Unknow Gromacs version %s" % config.RELEASE) + registry.update(load_extra_tools()) @@ -249,7 +307,6 @@ def merge_ndx(*args): # the common case of just adding the 'g_' registry['G_%s' % name.lower()] = registry[name] - for name4, name5 in [('G_mindist', 'Mindist'), ('G_dist', 'Distance')]: if name4 in registry: klass = type(name4, (GromacsCommandMultiIndex,), { From 0d407bbe283485b6a3f3522347fd870e7b8463e8 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Thu, 14 Jul 2016 20:11:28 -0300 Subject: [PATCH 14/30] First try the configured tool groups than make guesses --- gromacs/tools.py | 68 +++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/gromacs/tools.py b/gromacs/tools.py index 00878756..1c1684d2 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -11,12 +11,13 @@ with initial default values. By convention, a class has the capitalized name of the corresponding Gromacs -tool; dots are replaced by underscores to make it a valid python identifier. -Gromacs 5 tools (e.g, `sasa`) are aliased to their Gromacs 4 tool names (e.g, `g_sas`) -for backwards compatibility. +tool; dots and dashes are replaced by underscores to make it a valid python +identifier. Gromacs 5 tools (e.g, `sasa`) are aliased to their Gromacs 4 tool +names (e.g, `g_sas`) for backwards compatibility. -The list of Gromacs tools to be loaded is configured in -:data:`gromacs.config.gmx_tool_groups`. +The list of Gromacs tools to be loaded is configured with the ``tools`` and +``groups`` options of the ``~/.gromacswrapper.cfg`` file. If these options +are not provided guesses are made. It is also possible to extend the basic commands and patch in additional functionality. For example, the :class:`GromacsCommandMultiIndex` class makes a @@ -25,13 +26,12 @@ all tools. .. autoclass:: GromacsCommandMultiIndex - :members: run, _fake_multi_ndx, __del__ Example ------- -In this example we create two instances of the :class:`gromacs.tools.Trjconv` command (which -runs the Gromacs ``trjconv`` command):: +In this example we create two instances of the :class:`gromacs.tools.Trjconv` +command (which runs the Gromacs ``trjconv`` command):: import gromacs.tools as tools @@ -153,22 +153,19 @@ def find_executables(path): def load_v5_tools(): """ Load Gromacs 5.x tools automatically using some heuristic. - Tries to load tools (1) automatically from ``GMXBIN`` and (2) fails back to - configured tool groups if there are too many in the path and (3) then to a - list from running the command ``gmx help``. + Tries to load tools (1) using the driver from configured groups (2) and + fails back to automatic detection from ``GMXBIN`` (3) then to rough guesses. + + In all cases the command ``gmx help`` is ran to get all tools available. :return: dict mapping tool names to GromacsCommand classes """ - drivers = [] - if 'GMXBIN' in os.environ: - drivers = find_executables(os.environ['GMXBIN']) + drivers = config.get_tool_names() - # directory contains more binaries than there are gromacs drivers? - # (including single, double precision and mpi altogether) - if len(drivers) > 4: - drivers = config.get_tool_names() + if len(drivers) == 0 and 'GMXBIN' in os.environ: + drivers = find_executables(os.environ['GMXBIN']) - if len(drivers) == 0: + if len(drivers) == 0 or len(drivers) > 4: drivers = ['gmx', 'gmx_d', 'gmx_mpi', 'gmx_mpi_d'] tools = {} @@ -196,22 +193,17 @@ def load_v5_tools(): def load_v4_tools(): """ Load Gromacs 4.x tools automatically using some heuristic. - Tries to load tools (1) automatically from ``GMXBIN`` and (2) fails back to - configured tool groups if there are too many in the path and (3) then to a - prefilled list if none was configured. + Tries to load tools (1) in configured tool groups (2) and fails back to + automatic detection from ``GMXBIN`` (3) then to a prefilled list. :return: dict mapping tool names to GromacsCommand classes """ - names = [] - if 'GMXBIN' in os.environ: - names = find_executables(os.environ['GMXBIN']) + names = config.get_tool_names() - # directory contains more binaries than there are gromacs tools? - # (including double single, double precision and mpi altogether) - if len(names) > len(V4TOOLS)*4: - names = config.get_tool_names() + if len(names) == 0 and 'GMXBIN' in os.environ: + names = find_executables(os.environ['GMXBIN']) - if len(names) == 0: + if len(names) == 0 or len(names) > len(V4TOOLS) * 4: names = V4TOOLS[:] tools = {} @@ -282,15 +274,10 @@ def merge_ndx(*args): try: registry = load_v4_tools() except exceptions.GromacsToolLoadingError: - raise exceptions.GromacsToolLoadingError( - "Unknow Gromacs version %s" % config.RELEASE) - -registry.update(load_extra_tools()) + raise exceptions.GromacsToolLoadingError("Unable to load any tool") -# Append class doc for each command -for cmd in registry.itervalues(): - __doc__ += ".. class:: %s\n :noindex:\n" % cmd.__name__ +registry.update(load_extra_tools()) # Aliases command names to run unmodified GromacsWrapper scripts on a machine @@ -323,7 +310,12 @@ def merge_ndx(*args): if 'Convert_tpr' in registry: registry['Tpbconv'] = registry['Convert_tpr'] +# Append class doc for each command +for cmd in registry.itervalues(): + __doc__ += ".. class:: %s\n :noindex:\n" % cmd.__name__ + # finally add command classes to module's scope globals().update(registry) -__all__ = registry.keys() +__all__ = ['GromacsCommandMultiIndex', 'merge_ndx'] +__all__ = __all__ + registry.keys() From 4af0425881f149db62d0b4dd0df424c418bc94fd Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Thu, 14 Jul 2016 22:30:43 -0300 Subject: [PATCH 15/30] small fix --- gromacs/tools.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gromacs/tools.py b/gromacs/tools.py index 1c1684d2..f65f28df 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -122,9 +122,11 @@ def run(self,*args,**kwargs): def _fake_multi_ndx(self, **kwargs): ndx = kwargs.get('n') - if not (ndx is None or type(ndx) is str): + if not (ndx is None or type(ndx) is basestring): if len(ndx) > 1: - kwargs['n'] = merge_ndx(ndx, kwargs.get('s')) + if 's' in kwargs: + ndx.append(kwargs.get('s')) + kwargs['n'] = merge_ndx(*ndx) return kwargs @@ -274,7 +276,6 @@ def merge_ndx(*args): try: registry = load_v4_tools() except exceptions.GromacsToolLoadingError: - raise exceptions.GromacsToolLoadingError("Unable to load any tool") registry.update(load_extra_tools()) From 38e42d6dbaa05662a215de46bd73687d5c6282bb Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Sun, 17 Jul 2016 02:33:56 -0300 Subject: [PATCH 16/30] more fixes and improved documentation --- gromacs/exceptions.py | 3 - gromacs/tools.py | 191 ++++++++++++++++++++++++++---------------- 2 files changed, 119 insertions(+), 75 deletions(-) diff --git a/gromacs/exceptions.py b/gromacs/exceptions.py index 22cb7166..fa85be0b 100644 --- a/gromacs/exceptions.py +++ b/gromacs/exceptions.py @@ -47,9 +47,6 @@ class UsageWarning(Warning): class LowAccuracyWarning(Warning): """Warns that results may possibly have low accuracy.""" -class GromacsToolLoadingError(Exception): - """Couldn't find Gromacs commands.""" - import warnings # These warnings should always be displayed because other parameters # can have changed, eg during interactive use. diff --git a/gromacs/tools.py b/gromacs/tools.py index f65f28df..3022ee18 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -1,70 +1,79 @@ # Copyright (c) 2009 Oliver Beckstein # Released under the GNU Public License 3 (or higher, your choice) # See the file COPYING for details. - """ :mod:`gromacs.tools` -- Gromacs commands classes ================================================ -A Gromacs command class acts as a factory function that produces an -instance of a gromacs command (:class:`gromacs.core.GromacsCommand`) -with initial default values. +A Gromacs command class produces an instance of a Gromacs tool command ( +:class:`gromacs.core.GromacsCommand`), any argument or keyword argument +supplied will be used as default values for when the command is run. -By convention, a class has the capitalized name of the corresponding Gromacs -tool; dots and dashes are replaced by underscores to make it a valid python -identifier. Gromacs 5 tools (e.g, `sasa`) are aliased to their Gromacs 4 tool -names (e.g, `g_sas`) for backwards compatibility. +Classes has the same name of the corresponding Gromacs tool with the first +letter capitalized and dot and dashes replaced by underscores to make it a +valid python identifier. Gromacs 5 tools are also aliased to their Gromacs 4 +tool names (e.g, `sasa` to `g_sas`) for backwards compatibility. -The list of Gromacs tools to be loaded is configured with the ``tools`` and -``groups`` options of the ``~/.gromacswrapper.cfg`` file. If these options -are not provided guesses are made. +The list of tools to be loaded is configured with the ``tools`` and ``groups`` +options of the ``~/.gromacswrapper.cfg`` file. Guesses are made if these +options are not provided. -It is also possible to extend the basic commands and patch in additional -functionality. For example, the :class:`GromacsCommandMultiIndex` class makes a -command accept multiple index files and concatenates them on the fly; the -behaviour mimics Gromacs' "multi-file" input that has not yet been enabled for -all tools. +In the following example we create two instances of the +:class:`gromacs.tools.Trjconv` command (which runs the Gromacs ``trjconv`` +command):: -.. autoclass:: GromacsCommandMultiIndex + from gromacs.tools import Trjconv -Example -------- - -In this example we create two instances of the :class:`gromacs.tools.Trjconv` -command (which runs the Gromacs ``trjconv`` command):: - - import gromacs.tools as tools - - trjconv = tools.Trjconv() - trjconv_compact = tools.Trjconv(ur='compact', center=True, boxcenter='tric', pbc='mol', + trjconv = Trjconv() + trjconv_compact = Trjconv(ur='compact', center=True, boxcenter='tric', pbc='mol', input=('protein','system'), - doc="Returns a compact representation of the system centered on the protein") + doc="Returns a compact representation of the" + " system centered on the protein") The first one, ``trjconv``, behaves as the standard commandline tool but the second one, ``trjconv_compact``, will by default create a compact representation of the input data by taking into account the shape of the unit cell. Of course, the same effect can be obtained by providing the corresponding arguments to ``trjconv`` but by naming the more specific command differently -one can easily build up a library of small tools that will solve a specifi, +one can easily build up a library of small tools that will solve a specific, repeatedly encountered problem reliably. This is particularly helpful when doing interactive work. +Multi index +----------- + +It is possible to extend the tool commands and patch in additional +functionality. For example, the :class:`GromacsCommandMultiIndex` class makes a +command accept multiple index files and concatenates them on the fly; the +behaviour mimics Gromacs' "multi-file" input that has not yet been enabled for +all tools. + +.. autoclass:: GromacsCommandMultiIndex +.. autofunction:: merge_ndx + +Helpers +------- + +.. autofunction:: tool_factory +.. autofunction:: load_v4_tools +.. autofunction:: load_v5_tools +.. autofunction:: load_extra_tools +.. autofunction:: find_executables +.. autofunction:: make_valid_identifier +.. autoexception:: GromacsToolLoadingError + Gromacs tools ------------- -.. The docs for the tool classes are auto generated. -.. autoclass:: Mdrun - :members: """ from __future__ import absolute_import -__docformat__ = "restructuredtext en" import os.path import tempfile import subprocess import atexit -from . import config, exceptions +from . import config from .core import GromacsCommand @@ -86,12 +95,10 @@ "g_dist", "g_luck", "g_potential", "g_sas", "g_velacc", "make_ndx") -ALIASES5TO4 = { +NAMES5TO4 = { + # unchanged names 'grompp': 'grompp', 'eneconv': 'eneconv', - 'sasa': 'g_sas', - 'distance': 'g_dist', - 'convert_tpr': 'tpbconv', 'editconf': 'editconf', 'pdb2gmx': 'pdb2gmx', 'trjcat': 'trjcat', @@ -101,37 +108,55 @@ 'mdrun': 'mdrun', 'make_ndx': 'make_ndx', 'make_edi': 'make_edi', - 'dump': 'gmxdump', - 'check': 'gmxcheck', 'genrestr': 'genrestr', 'genion': 'genion', 'genconf': 'genconf', 'do_dssp': 'do_dssp', + + # changed names + 'convert_tpr': 'tpbconv', + 'dump': 'gmxdump', + 'check': 'gmxcheck', 'solvate': 'genbox', + 'distance': 'g_dist', + 'sasa': 'g_sas', + 'gangle': 'g_sgangle', } +class GromacsToolLoadingError(Exception): + """Raised when could not find any Gromacs command.""" + + class GromacsCommandMultiIndex(GromacsCommand): - def __init__(self, **kwargs): - kwargs = self._fake_multi_ndx(**kwargs) - super(GromacsCommandMultiIndex, self).__init__(**kwargs) + """ Command class that accept multiple index files. + + It works combining multiple index files into a single temporary one so + that tools that do not (yet) support multi index files as input can be + used as if they did. + + It creates a new file only if multiple index files are supplied. + """ + def __init__(self, **kwargs): + kwargs = self._fake_multi_ndx(**kwargs) + super(GromacsCommandMultiIndex, self).__init__(**kwargs) - def run(self,*args,**kwargs): - kwargs = self._fake_multi_ndx(**kwargs) - return super(GromacsCommandMultiIndex, self).run(*args, **kwargs) + def run(self,*args,**kwargs): + kwargs = self._fake_multi_ndx(**kwargs) + return super(GromacsCommandMultiIndex, self).run(*args, **kwargs) - def _fake_multi_ndx(self, **kwargs): - ndx = kwargs.get('n') - if not (ndx is None or type(ndx) is basestring): - if len(ndx) > 1: - if 's' in kwargs: - ndx.append(kwargs.get('s')) - kwargs['n'] = merge_ndx(*ndx) - return kwargs + def _fake_multi_ndx(self, **kwargs): + ndx = kwargs.get('n') + if not (ndx is None or type(ndx) is basestring): + if len(ndx) > 1: + if 's' in kwargs: + ndx.append(kwargs.get('s')) + kwargs['n'] = merge_ndx(*ndx) + return kwargs def tool_factory(clsname, name, driver, doc=None): - """ GromacsCommand derived type factory. """ + """ Factory for GromacsCommand derived types. """ clsdict = { 'command_name': name, 'driver': driver @@ -141,7 +166,24 @@ def tool_factory(clsname, name, driver, doc=None): return type(clsname, (GromacsCommand,), clsdict) +def make_valid_identifier(name): + """ Turns tool names into valid identifiers. + + :param name: tool name + :return: valid identifier + """ + return name.replace('-', '_').capitalize() + + def find_executables(path): + """ Find executables in a path. + + Searches executables in a directory excluding some know commands + unusable with GromacsWrapper. + + :param path: dirname to search for + :return: list of executables + """ execs = [] for exe in os.listdir(path): fullexe = os.path.join(path, exe) @@ -179,7 +221,7 @@ def load_v5_tools(): if line[4] != ' ': name = line[4:line.index(' ', 4)] - fancy = name.replace('-', '_').capitalize() + fancy = make_valid_identifier(name) suffix = driver.partition('_')[2] if suffix: fancy = '%s_%s' % (fancy, suffix) @@ -188,7 +230,7 @@ def load_v5_tools(): pass if len(tools) == 0: - raise exceptions.GromacsToolLoadingError("Failed to load v5 tools") + raise GromacsToolLoadingError("Failed to load v5 tools") return tools @@ -215,11 +257,11 @@ def load_v4_tools(): subprocess.check_call([name], stdout=null, stderr=null) except subprocess.CalledProcessError: pass - fancy = name.capitalize().replace('.', '_') + fancy = make_valid_identifier(name) tools[fancy] = tool_factory(fancy, name, None) if len(tools) == 0: - raise exceptions.GromacsToolLoadingError("Failed to load v4 tools") + raise GromacsToolLoadingError("Failed to load v4 tools") return tools @@ -231,7 +273,7 @@ def load_extra_tools(): names = config.cfg.get("Gromacs", "extra").split() tools = {} for name in names: - fancy = name.capitalize().replace('-', '_') + fancy = make_valid_identifier(name) driver = 'gmx' if config.MAJOR_RELEASE == '5' else None tools[name] = tool_factory(fancy, name, driver) return tools @@ -272,28 +314,33 @@ def merge_ndx(*args): else: try: registry = load_v5_tools() - except exceptions.GromacsToolLoadingError: + except GromacsToolLoadingError: try: registry = load_v4_tools() - except exceptions.GromacsToolLoadingError: - raise exceptions.GromacsToolLoadingError("Unable to load any tool") + except GromacsToolLoadingError: + raise GromacsToolLoadingError("Unable to load any tool") registry.update(load_extra_tools()) # Aliases command names to run unmodified GromacsWrapper scripts on a machine -# without Gromacs 4.x -for name in registry.copy(): - for c4, c5 in ALIASES5TO4.iteritems(): +# with only 5.x +for fancy, cmd in registry.items(): + for c5, c4 in NAMES5TO4.iteritems(): # have to check each one, since it's possible there are suffixes # like for double precision + name = cmd.command_name if name.startswith(c5): - # mantain suffix - registry[c4 + name.split(c5)] = registry[name] - break + if c4 == c5: + break + else: + # mantain suffix + name = c4 + fancy.lower().split(c5)[1] + registry[make_valid_identifier(name)] = registry[fancy] + break else: # the common case of just adding the 'g_' - registry['G_%s' % name.lower()] = registry[name] + registry['G_%s' % fancy.lower()] = registry[fancy] for name4, name5 in [('G_mindist', 'Mindist'), ('G_dist', 'Distance')]: if name4 in registry: @@ -312,8 +359,8 @@ def merge_ndx(*args): registry['Tpbconv'] = registry['Convert_tpr'] # Append class doc for each command -for cmd in registry.itervalues(): - __doc__ += ".. class:: %s\n :noindex:\n" % cmd.__name__ +for name in registry.iterkeys(): + __doc__ += ".. class:: %s\n :noindex:\n" % name # finally add command classes to module's scope From f1f10a160f652b5b4bfca8d34a4c5d6564512547 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Sun, 17 Jul 2016 04:22:21 -0300 Subject: [PATCH 17/30] more fixes --- gromacs/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gromacs/tools.py b/gromacs/tools.py index 3022ee18..2d09970a 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -96,7 +96,7 @@ NAMES5TO4 = { - # unchanged names + # same name in both versions 'grompp': 'grompp', 'eneconv': 'eneconv', 'editconf': 'editconf', @@ -350,7 +350,7 @@ def merge_ndx(*args): '__doc__': registry[name5].__doc__ }) registry[name4] = klass - if config.MAJOR_RELEASE == '5': + if name5 in registry == '5': registry[name5] = klass From 0ed0b1cc8f0e2989b7a5705b7e051e9c8d9304a1 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Tue, 19 Jul 2016 15:53:33 -0300 Subject: [PATCH 18/30] more fixes --- gromacs/config.py | 16 +++++++++---- gromacs/tools.py | 61 ++++++++++++++++------------------------------- 2 files changed, 33 insertions(+), 44 deletions(-) diff --git a/gromacs/config.py b/gromacs/config.py index 738309ef..6eb05fb4 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -730,10 +730,18 @@ def get_tool_names(): :return: list of tool names """ - load_tools = [] - for g in cfg.getlist('Gromacs', 'groups', sort=False): - load_tools.extend(cfg.getlist('Gromacs', g, sort=False)) - return load_tools + names = [] + for group in cfg.get('Gromacs', 'groups').split(): + names.extend(cfg.get('Gromacs', group).split()) + return names + + +def get_extra_tool_names(): + """ Get tool names from all configured groups. + + :return: list of tool names + """ + return cfg.get('Gromacs', 'extra').split() RELEASE = None diff --git a/gromacs/tools.py b/gromacs/tools.py index 2d09970a..bb718eb2 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -121,6 +121,9 @@ 'distance': 'g_dist', 'sasa': 'g_sas', 'gangle': 'g_sgangle', + + # 5.0.5 compatibility hack + 'tpbconv': 'convert_tpr' } @@ -155,15 +158,13 @@ def _fake_multi_ndx(self, **kwargs): return kwargs -def tool_factory(clsname, name, driver, doc=None): +def tool_factory(clsname, name, driver, base=GromacsCommand): """ Factory for GromacsCommand derived types. """ clsdict = { 'command_name': name, 'driver': driver } - if doc: - clsdict['__doc__'] = doc - return type(clsname, (GromacsCommand,), clsdict) + return type(clsname, (base,), clsdict) def make_valid_identifier(name): @@ -240,6 +241,8 @@ def load_v4_tools(): Tries to load tools (1) in configured tool groups (2) and fails back to automatic detection from ``GMXBIN`` (3) then to a prefilled list. + Also load any extra tool configured in ``~/.gromacswrapper.cfg`` + :return: dict mapping tool names to GromacsCommand classes """ names = config.get_tool_names() @@ -250,13 +253,10 @@ def load_v4_tools(): if len(names) == 0 or len(names) > len(V4TOOLS) * 4: names = V4TOOLS[:] + names.extend(config.get_extra_tool_names()) + tools = {} for name in names: - try: - null = open(os.devnull, 'w') - subprocess.check_call([name], stdout=null, stderr=null) - except subprocess.CalledProcessError: - pass fancy = make_valid_identifier(name) tools[fancy] = tool_factory(fancy, name, None) @@ -265,20 +265,6 @@ def load_v4_tools(): return tools -def load_extra_tools(): - """ Load extra tools. - - :return: dict mapping tool names to GromacsCommand classes - """ - names = config.cfg.get("Gromacs", "extra").split() - tools = {} - for name in names: - fancy = make_valid_identifier(name) - driver = 'gmx' if config.MAJOR_RELEASE == '5' else None - tools[name] = tool_factory(fancy, name, driver) - return tools - - def merge_ndx(*args): """ Takes one or more index files and optionally one structure file and returns a path for a new merged index file. @@ -292,7 +278,8 @@ def merge_ndx(*args): if fname.endswith('.ndx'): ndxs.append(fname) else: - assert struct is None, "only one structure file supported" + if struct is not None: + raise ValueError("only one structure file supported") struct = fname fd, multi_ndx = tempfile.mkstemp(suffix='.ndx', prefix='multi_') @@ -307,6 +294,7 @@ def merge_ndx(*args): return multi_ndx +# Load tools if config.MAJOR_RELEASE == '5': registry = load_v5_tools() elif config.MAJOR_RELEASE == '4': @@ -320,8 +308,6 @@ def merge_ndx(*args): except GromacsToolLoadingError: raise GromacsToolLoadingError("Unable to load any tool") -registry.update(load_extra_tools()) - # Aliases command names to run unmodified GromacsWrapper scripts on a machine # with only 5.x @@ -342,28 +328,23 @@ def merge_ndx(*args): # the common case of just adding the 'g_' registry['G_%s' % fancy.lower()] = registry[fancy] + +# Patching up commands that may be useful to accept multiple index files for name4, name5 in [('G_mindist', 'Mindist'), ('G_dist', 'Distance')]: if name4 in registry: - klass = type(name4, (GromacsCommandMultiIndex,), { - 'command_name': registry[name5].command_name, - 'driver': registry[name5].driver, - '__doc__': registry[name5].__doc__ - }) - registry[name4] = klass - if name5 in registry == '5': - registry[name5] = klass - + cmd = registry[name4] + registry[name4] = tool_factory(name4, cmd.command_name, cmd.driver, + GromacsCommandMultiIndex) + if name5 in registry: + registry[name5] = registry[name4] -# 5.0.5 compatibility hack -if 'Convert_tpr' in registry: - registry['Tpbconv'] = registry['Convert_tpr'] # Append class doc for each command for name in registry.iterkeys(): __doc__ += ".. class:: %s\n :noindex:\n" % name -# finally add command classes to module's scope +# Finally add command classes to module's scope globals().update(registry) __all__ = ['GromacsCommandMultiIndex', 'merge_ndx'] -__all__ = __all__ + registry.keys() +__all__.extend(registry.keys()) From 2ff798820470a6a84d18e2d82d217b6cfa7b5e69 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Tue, 19 Jul 2016 16:31:55 -0300 Subject: [PATCH 19/30] Merged develop into automatic-tool-loading --- gromacs/tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gromacs/tools.py b/gromacs/tools.py index bb718eb2..1b8fe59f 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -162,7 +162,8 @@ def tool_factory(clsname, name, driver, base=GromacsCommand): """ Factory for GromacsCommand derived types. """ clsdict = { 'command_name': name, - 'driver': driver + 'driver': driver, + '__doc__': property(base._get_gmx_docs) } return type(clsname, (base,), clsdict) From 7a954da900fdc4e02448ea8b45885690082fa79a Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Sat, 23 Jul 2016 14:42:32 -0300 Subject: [PATCH 20/30] improved documentation --- doc/sphinx/source/configuration.txt | 92 ++++++++++++++++++++++++++++ doc/sphinx/source/index.txt | 1 + gromacs/config.py | 73 ++++++++-------------- gromacs/templates/gromacswrapper.cfg | 14 +---- gromacs/tools.py | 10 ++- 5 files changed, 122 insertions(+), 68 deletions(-) create mode 100644 doc/sphinx/source/configuration.txt diff --git a/doc/sphinx/source/configuration.txt b/doc/sphinx/source/configuration.txt new file mode 100644 index 00000000..079d3d56 --- /dev/null +++ b/doc/sphinx/source/configuration.txt @@ -0,0 +1,92 @@ +============== + Configuration +============== + +.. highlight:: ini + +This section documents how to configure the **GromacsWrapper** package. There +are options to configure where log files and templates directories are located +and options to tell exactly which commands to load into this package. Any +configuration is optional and all options has sane defaults. Further +documentation can be found at :mod:`gromacs.config`. + +Place an INI file named ``~/.gromacswrapper.cfg`` in your home directory, it +may look like the following document:: + + [Gromacs] + GMXRC = /usr/local/gromacs/bin/GMXRC + +The Gromacs software suite needs some environment variables that are set up +sourcing the ``GMXRC`` file. You may source it yourself or set an option like +the above one. If this option isn't provided, **GromacsWrapper** will guess +that Gromacs was globally installed like if it was installed by the Ubuntu's +``apt-get`` program. + +As there isn't yet any way to know which Gromacs version to use, +**GromacsWrapper** will first try to use Gromacs 5 if available, then to use +Gromacs 4. If you have both versions and want's to use version 4 or just wants +to document it, you may specify the which release version will be used:: + + [Gromacs] + GMXRC = /usr/local/gromacs/bin/GMXRC + release = 4.7 + +For now **GromacsWrapper** will guess which tools are available to put it into +:mod:`gromacs.tools`, but you can always configure it manually. Gromacs 5 has +up to 4 commands usually named:: + + [Gromacs] + GMXRC = /usr/local/gromacs/bin/GMXRC + tools = gmx gmx_d gmx_mpi gmx_mpi_d + +This option will instruct which commands to load. For Gromacs 4 you'll need to +specify more tools:: + + [Gromacs] + GMXRC = /usr/local/gromacs/bin/GMXRC + release = 4 + tools = + g_cluster g_dyndom g_mdmat g_principal g_select g_wham mdrun + do_dssp g_clustsize g_enemat g_membed g_protonate g_sgangle g_wheel mdrun_d + editconf g_confrms g_energy g_mindist g_rama g_sham g_x2top mk_angndx + eneconv g_covar g_filter g_morph g_rdf g_sigeps genbox pdb2gmx + g_anadock g_current g_gyrate g_msd g_sorient genconf + g_anaeig g_density g_h2order g_nmeig g_rms g_spatial genion tpbconv + g_analyze g_densmap g_hbond g_nmens g_rmsdist g_spol genrestr trjcat + g_angle g_dielectric g_helix g_nmtraj g_rmsf g_tcaf gmxcheck trjconv + g_bar g_dih g_helixorient g_order g_rotacf g_traj gmxdump trjorder + g_bond g_dipoles g_kinetics g_pme_error g_rotmat g_tune_pme grompp + g_bundle g_disre g_lie g_polystat g_saltbr g_vanhove make_edi xpm2ps + g_chi g_dist g_luck g_potential g_sas g_velacc make_ndx + +Other options are where some directories were located and logging +configuration:: + + [DEFAULT] + # Directory to store user templates and rc files. + configdir = ~/.gromacswrapper + + # Directory to store user supplied queuing system scripts. + qscriptdir = %(configdir)s/qscripts + + # Directory to store user supplied template files such as mdp files. + templatesdir = %(configdir)s/templates + + # Directory to store manager configuration files + managerdir = %(configdir)s/managers + + [Logging] + # name of the logfile that is written to the current directory + logfilename = gromacs.log + + # loglevels (see Python's logging module for details) + # ERROR only fatal errors + # WARN only warnings + # INFO interesting messages + # DEBUG everything + + # console messages written to screen + loglevel_console = INFO + + # file messages written to logfilename + loglevel_file = DEBUG diff --git a/doc/sphinx/source/index.txt b/doc/sphinx/source/index.txt index 2324cc81..900fe598 100644 --- a/doc/sphinx/source/index.txt +++ b/doc/sphinx/source/index.txt @@ -57,6 +57,7 @@ Contents :maxdepth: 2 installation + configuration gromacs analysis auxiliary diff --git a/gromacs/config.py b/gromacs/config.py index d0b696e4..1415d985 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -7,21 +7,20 @@ ========================================================== The config module provides configurable options for the whole package; -It mostly serves to define which gromacs tools and other scripts are -exposed in the :mod:`gromacs` package and where template files are -located. The user can configure *GromacsWrapper* by +It serves to define how to handle log files, set where template files are +located and which gromacs tools are exposed in the :mod:`gromacs` package. +The user can configure *GromacsWrapper* by -1. editing the global configuration file ``~/.gromacswrapper.cfg`` +1. editing the optional global configuration file ``~/.gromacswrapper.cfg`` -2. placing template files into directories under ``~/.gromacswrapper`` +2. placing template files into directories under ``~/.gromacswrapper/`` (:data:`gromacs.config.configdir`) which can be processed instead of the files that come with *GromacsWrapper* In order to **set up a basic configuration file and the directories** -a user should execute :func:`gromacs.config.setup` at least once. It -will prepare the user configurable area in their home directory and it -will generate a default global configuration file -``~/.gromacswrapper.cfg`` (the name is defined in +a user can execute :func:`gromacs.config.setup`. It will prepare the user +configurable area in their home directory and it will generate a default +global configuration file ``~/.gromacswrapper.cfg`` (the name is defined in :data:`CONFIGNAME`):: import gromacs @@ -163,36 +162,34 @@ List of tools ~~~~~~~~~~~~~ -The list of Gromacs tools is specified in the config file in the ``[Gromacs]`` -section with the ``tools`` variable. +The list of Gromacs tools can be specified in the config file in the +``[Gromacs]`` section with the ``tools`` variable. -The tool groups are strings that contain white-space separated file -names of Gromacs tools. These lists determine which tools are made -available as classes in :mod:`gromacs.tools`. +The tool groups are a list of names that determines which tools are made +available as classes in :mod:`gromacs.tools`. If not provided +GromacsWrapper will first try to load Gromacs 5.x then Gromacs 4.x +tools. -A typical Gromacs tools section of the config file looks like this:: +If you choose to provide a list, the Gromacs tools section of the config +file can be like this:: [Gromacs] # Release of the Gromacs package to which information in this sections applies. release = 4.5.3 - # tools contains the file names of all Gromacs tools for which classes are generated. - # Editing this list has only an effect when the package is reloaded. + # tools contains the file names of all Gromacs tools for which classes are + # generated. Editing this list has only an effect when the package is + # reloaded. # (Note that this example has a much shorter list than the actual default.) tools = editconf make_ndx grompp genion genbox - grompp pdb2gmx mdrun - - # Additional gromacs tools that should be made available. - extra = - g_count g_flux - a_gridcalc a_ri3Dc g_ri3Dc + grompp pdb2gmx mdrun mdrun_d - # which tool groups to make available as gromacs.NAME + # which tool groups to make available groups = tools extra -For Gromacs 5.x use a section like the following, where the driver -command ``gmx`` is added as a prefix:: +For Gromacs 5.x use a section like the following, where driver commands +are supplied:: [Gromacs] # Release of the Gromacs package to which information in this sections applies. @@ -206,13 +203,8 @@ # tools contains the command names of all Gromacs tools for which classes are generated. # Editing this list has only an effect when the package is reloaded. # (Note that this example has a much shorter list than the actual default.) - tools = - gmx:editconf gmx:make_ndx gmx:grompp gmx:genion gmx:solvate - gmx:insert-molecule gmx:convert-tpr - gmx:grompp gmx:pdb2gmx gmx:mdrun + tools = gmx gmx_d - # which tool groups to make available as gromacs.NAME - groups = tools For example, on the commandline you would run :: @@ -222,14 +214,6 @@ gromacs.grompp(f="md.mdp", c="system.gro", p="topol.top", o="md.tpr") -(The driver command is stripped and only the "command name" is used to -identify the command. This makes it easier to migrate GromacsWrapper -scripts from Gromacs 4.x to 5.x.). - -The driver command can be changed per-tool, allowing for mpi -and non-mpi versions to be used. For example:: - -tools = gmx_mpi:mdrun gmx:pdb2gmx .. Note:: Because of `changes in the Gromacs tool in 5.x`_, GromacsWrapper scripts might break, even if the tool @@ -238,15 +222,6 @@ .. _`changes in the Gromacs tool in 5.x`: http://www.gromacs.org/Documentation/How-tos/Tool_Changes_for_5.0 -Developers should know that the lists of tools are stored in ``load_*`` -variables. These are lists that contain instructions to other parts of the code -as to which executables should be wrapped. - -.. autodata:: load_tools - -:data:`load_tools` is populated from lists of executable names in the -configuration file. - Location of template files diff --git a/gromacs/templates/gromacswrapper.cfg b/gromacs/templates/gromacswrapper.cfg index 916a9191..bf5f4e4f 100644 --- a/gromacs/templates/gromacswrapper.cfg +++ b/gromacs/templates/gromacswrapper.cfg @@ -21,19 +21,7 @@ release = 5.1.1 # tools contains the file names of all Gromacs tools for which classes are generated. # Editing this list has only an effect when the package is reloaded. # (Generated with 'ls [^Gac]*' from the Gromacs bin dir) -tools = - gmx:cluster gmx:dyndom gmx:mdmat gmx:principal gmx:select gmx:wham gmx:mdrun gmx:convert-tpr - gmx:do_dssp gmx:clustsize gmx:enemat gmx:protonate gmx:gangle gmx:wheel gmx:mdrun_d gmx:trjcat - gmx:editconf gmx:confrms gmx:energy gmx:mindist gmx:rama gmx:sham gmx:x2top gmx:trjconv - gmx:eneconv gmx:covar gmx:filter gmx:morph gmx:rdf gmx:sigeps gmx:solvate gmx:pdb2gmx - gmx:anadock gmx:current gmx:gyrate gmx:msd gmx:sorient gmx:genconf gmx:insert-molecules - gmx:anaeig gmx:density gmx:h2order gmx:nmeig gmx:rms gmx:spatial gmx:genion - gmx:analyze gmx:densmap gmx:hbond gmx:nmens gmx:rmsdist gmx:spol gmx:genrestr - gmx:angle gmx:dielectric gmx:helix gmx:nmtraj gmx:rmsf gmx:tcaf gmx:check - gmx:bar gmx:helixorient gmx:order gmx:rotacf gmx:traj gmx:dump gmx:trjorder - gmx:dipoles gmx:kinetics gmx:pme_error gmx:rotmat gmx:tune_pme gmx:grompp gmx:mk_angndx - gmx:bundle gmx:disre gmx:lie gmx:polystat gmx:saltbr gmx:vanhove gmx:make_edi - gmx:chi gmx:distance gmx:potential gmx:sas gmx:velacc gmx:make_ndx gmx:xpm2ps +tools = gmx gmx_d # which tool groups to make available as gromacs.NAME groups = tools diff --git a/gromacs/tools.py b/gromacs/tools.py index 1b8fe59f..57775aa6 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -120,15 +120,12 @@ 'solvate': 'genbox', 'distance': 'g_dist', 'sasa': 'g_sas', - 'gangle': 'g_sgangle', - - # 5.0.5 compatibility hack - 'tpbconv': 'convert_tpr' + 'gangle': 'g_sgangle' } class GromacsToolLoadingError(Exception): - """Raised when could not find any Gromacs command.""" + """Raised when no Gromacs tool could be found.""" class GromacsCommandMultiIndex(GromacsCommand): @@ -200,7 +197,7 @@ def load_v5_tools(): """ Load Gromacs 5.x tools automatically using some heuristic. Tries to load tools (1) using the driver from configured groups (2) and - fails back to automatic detection from ``GMXBIN`` (3) then to rough guesses. + falls back to automatic detection from ``GMXBIN`` (3) then to rough guesses. In all cases the command ``gmx help`` is ran to get all tools available. @@ -284,6 +281,7 @@ def merge_ndx(*args): struct = fname fd, multi_ndx = tempfile.mkstemp(suffix='.ndx', prefix='multi_') + os.close(fd) atexit.register(os.unlink, multi_ndx) if struct: From 1948af7f686dce7eb37215ec36bcd19b13169577 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Sat, 23 Jul 2016 18:50:59 -0300 Subject: [PATCH 21/30] edit CHANGES --- CHANGES | 2 ++ gromacs/config.py | 42 +++++++++++++++--------------------------- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/CHANGES b/CHANGES index bcce2b7a..fc08b0cb 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,8 @@ pslacerda, orbeckst automatically before setting up the Gromacs commands (#55) * doc strings for Gromacs tools are loaded lazily, which speeds up the initial import of the library to almost instantaneously (PR #76) +* guess which tools to load automatically if no tools option is provided (#68) +* new documentation page on how to set up a cfg file 2016-06-29 0.5.1 whitead, dotsdl, orbeckst diff --git a/gromacs/config.py b/gromacs/config.py index 1415d985..405c29e1 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -618,42 +618,28 @@ def setup(filename=CONFIGNAME): def check_setup(): """Check if templates directories are setup and issue a warning and help. - Returns ``True`` if all files and directories are found and - ``False`` otherwise. + Set the environment variable :envvar:`GROMACSWRAPPER_SUPPRESS_SETUP_CHECK` + skip the check and make it always return ``True`` - Setting the environment variable - :envvar:`GROMACSWRAPPER_SUPPRESS_SETUP_CHECK` to 'true' ('yes' - and '1' also work) silence this function and make it always return ``True``. + :return ``True`` if directories were found and ``False`` otherwise .. versionchanged:: 0.3.1 - Uses :envvar:`GROMACSWRAPPER_SUPPRESS_SETUP_CHECK` to suppress output + Uses :envvar:`GROMACSWRAPPER_SUPPRESS_SETUP_CHECK` to suppress check (useful for scripts run on a server) """ - if os.environ.get("GROMACSWRAPPER_SUPPRESS_SETUP_CHECK", "false").lower() in ("1", "true", "yes"): - return True - - is_complete = True - show_solution = False - if not os.path.exists(CONFIGNAME): - is_complete = False - show_solution = True - print("NOTE: The global configuration file %r is missing." % CONFIGNAME) + if "GROMACSWRAPPER_SUPPRESS_SETUP_CHECK" in os.environ: + return True missing = [d for d in config_directories if not os.path.exists(d)] if len(missing) > 0: - is_complete = False - show_solution = True - print("NOTE: Some configuration directories are not set up yet: ") - print("\t%s" % '\n\t'.join(missing)) - - if show_solution: - print("NOTE: You can create the configuration file and directories with:") - print("\t>>> import gromacs") - print("\t>>> gromacs.config.setup()") - return is_complete - -check_setup() + print("NOTE: Some configuration directories are not set up yet: ") + print("\t%s" % '\n\t'.join(missing)) + print("NOTE: You can create the configuration file and directories with:") + print("\t>>> import gromacs") + print("\t>>> gromacs.config.setup()") + return False + return True def set_gmxrc_environment(gmxrc): @@ -712,3 +698,5 @@ def get_extra_tool_names(): if cfg.get('Gromacs', 'release'): RELEASE = cfg.get('Gromacs', 'release') MAJOR_RELEASE = RELEASE.split('.')[0] + +check_setup() \ No newline at end of file From c138cda56c40b5a528b47144c246db66235876ce Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Sun, 24 Jul 2016 01:25:01 -0300 Subject: [PATCH 22/30] more changes --- doc/sphinx/source/configuration.txt | 24 +++++++--- gromacs/config.py | 68 ++++++++--------------------- 2 files changed, 38 insertions(+), 54 deletions(-) diff --git a/doc/sphinx/source/configuration.txt b/doc/sphinx/source/configuration.txt index 079d3d56..f6c9e5c3 100644 --- a/doc/sphinx/source/configuration.txt +++ b/doc/sphinx/source/configuration.txt @@ -19,8 +19,8 @@ may look like the following document:: The Gromacs software suite needs some environment variables that are set up sourcing the ``GMXRC`` file. You may source it yourself or set an option like the above one. If this option isn't provided, **GromacsWrapper** will guess -that Gromacs was globally installed like if it was installed by the Ubuntu's -``apt-get`` program. +that Gromacs was globally installed like if it was installed by the ``apt-get`` +program. As there isn't yet any way to know which Gromacs version to use, **GromacsWrapper** will first try to use Gromacs 5 if available, then to use @@ -36,7 +36,6 @@ For now **GromacsWrapper** will guess which tools are available to put it into up to 4 commands usually named:: [Gromacs] - GMXRC = /usr/local/gromacs/bin/GMXRC tools = gmx gmx_d gmx_mpi gmx_mpi_d This option will instruct which commands to load. For Gromacs 4 you'll need to @@ -59,8 +58,12 @@ specify more tools:: g_bundle g_disre g_lie g_polystat g_saltbr g_vanhove make_edi xpm2ps g_chi g_dist g_luck g_potential g_sas g_velacc make_ndx -Other options are where some directories were located and logging -configuration:: + +More options +------------ + +Other options are to set where template for job submission systems and.mdp +files are located:: [DEFAULT] # Directory to store user templates and rc files. @@ -75,6 +78,9 @@ configuration:: # Directory to store manager configuration files managerdir = %(configdir)s/managers + +And there are yet options for how to handle logging:: + [Logging] # name of the logfile that is written to the current directory logfilename = gromacs.log @@ -90,3 +96,11 @@ configuration:: # file messages written to logfilename loglevel_file = DEBUG + +If needed you may set up basic configuration files and directories using +:func:`gromacs.config.setup`: + +.. code-block:: python + + import gromacs + gromacs.config.setup() diff --git a/gromacs/config.py b/gromacs/config.py index 405c29e1..b90336f9 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -9,22 +9,9 @@ The config module provides configurable options for the whole package; It serves to define how to handle log files, set where template files are located and which gromacs tools are exposed in the :mod:`gromacs` package. -The user can configure *GromacsWrapper* by -1. editing the optional global configuration file ``~/.gromacswrapper.cfg`` - -2. placing template files into directories under ``~/.gromacswrapper/`` - (:data:`gromacs.config.configdir`) which can be processed instead - of the files that come with *GromacsWrapper* - -In order to **set up a basic configuration file and the directories** -a user can execute :func:`gromacs.config.setup`. It will prepare the user -configurable area in their home directory and it will generate a default -global configuration file ``~/.gromacswrapper.cfg`` (the name is defined in -:data:`CONFIGNAME`):: - - import gromacs - gromacs.config.setup() +In order to set up a basic configuration file and the directories +a user can execute :func:`gromacs.config.setup`. If the configuration file is edited then one can force a rereading of the new config file with :func:`gromacs.config.get_configuration`:: @@ -241,50 +228,36 @@ """ from __future__ import absolute_import, with_statement -import os, subprocess -from ConfigParser import SafeConfigParser +import os +import logging +import subprocess +from ConfigParser import SafeConfigParser from pkg_resources import resource_filename, resource_listdir from . import utilities -# Defaults -# -------- -# hard-coded package defaults -#: name of the global configuration file. +# Default name of the global configuration file. CONFIGNAME = os.path.expanduser(os.path.join("~",".gromacswrapper.cfg")) -#: Holds the default values for important file and directory locations. -#: -#: :data:`configdir` -#: Directory to store user templates and configurations. -#: The default value is ``~/.gromacswrapper``. -#: :data:`qscriptdir` -#: Directory to store user supplied queuing system scripts as -#: used by :mod:`gromacs.qsub`. -#: The default value is ``~/.gromacswrapper/qscripts``. -#: :data:`templatesdir` -#: Directory to store user supplied template files such as mdp files. -#: The default value is ``~/.gromacswrapper/templates``. -#: :data:`managerdir` -#: Directory to store configuration files for different queuing system -#: managers as used in :mod:`gromacs.manager`. -#: The default value is ``~/.gromacswrapper/managers``. +# More default values +configdir = os.path.expanduser(os.path.join("~",".gromacswrapper")) defaults = { - 'configdir': os.path.expanduser(os.path.join("~",".gromacswrapper")), - 'logfilename': "gromacs.log", - 'loglevel_console': 'INFO', - 'loglevel_file': 'DEBUG', + 'configdir': configdir, + 'qscriptdir': os.path.join(configdir, 'qscripts'), + 'templatesdir': os.path.join(configdir, 'templates'), + 'managerdir': os.path.join(configdir, 'managers'), + + 'logfilename': "gromacs.log", + 'loglevel_console': 'INFO', + 'loglevel_file': 'DEBUG', } -defaults['qscriptdir'] = os.path.join(defaults['configdir'], 'qscripts') -defaults['templatesdir'] = os.path.join(defaults['configdir'], 'templates') -defaults['managerdir'] = os.path.join(defaults['configdir'], 'managers') # Logging # ------- -import logging + logger = logging.getLogger("gromacs.config") #: File name for the log file; all gromacs command and many utility functions (e.g. in @@ -302,9 +275,6 @@ # User-accessible configuration # ----------------------------- -# (written in a clumsy manner because of legacy code and because of -# the way I currently generate the documentation) - #: Directory to store user templates and rc files. #: The default value is ``~/.gromacswrapper``. configdir = defaults['configdir'] @@ -347,7 +317,7 @@ def _generate_template_dict(dirname): by external code. All template filenames are stored in :data:`config.templates`. """ - return dict((resource_basename(fn), resource_filename(__name__, dirname+'/'+fn)) + return dict((resource_basename(fn), resource_filename(__name__, dirname + '/' + fn)) for fn in resource_listdir(__name__, dirname) if not fn.endswith('~')) From cb823cdf603012e6e489610fdb147f419b1381e9 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Tue, 26 Jul 2016 20:49:54 -0300 Subject: [PATCH 23/30] more fixes --- doc/sphinx/source/configuration.txt | 21 +++++++++++++++++++-- gromacs/config.py | 6 +++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/doc/sphinx/source/configuration.txt b/doc/sphinx/source/configuration.txt index f6c9e5c3..d2544429 100644 --- a/doc/sphinx/source/configuration.txt +++ b/doc/sphinx/source/configuration.txt @@ -10,6 +10,13 @@ and options to tell exactly which commands to load into this package. Any configuration is optional and all options has sane defaults. Further documentation can be found at :mod:`gromacs.config`. +.. versionchanged:: 0.6.0 + The format of the ``tools`` variable in the ``[Gromacs]`` section of the + config file was changed for Gromacs 5 commands. + +Basic options +------------- + Place an INI file named ``~/.gromacswrapper.cfg`` in your home directory, it may look like the following document:: @@ -24,12 +31,12 @@ program. As there isn't yet any way to know which Gromacs version to use, **GromacsWrapper** will first try to use Gromacs 5 if available, then to use -Gromacs 4. If you have both versions and want's to use version 4 or just wants +Gromacs 4. If you have both versions and want to use version 4 or just want to document it, you may specify the which release version will be used:: [Gromacs] GMXRC = /usr/local/gromacs/bin/GMXRC - release = 4.7 + release = 4.6.7 For now **GromacsWrapper** will guess which tools are available to put it into :mod:`gromacs.tools`, but you can always configure it manually. Gromacs 5 has @@ -38,6 +45,7 @@ up to 4 commands usually named:: [Gromacs] tools = gmx gmx_d gmx_mpi gmx_mpi_d + This option will instruct which commands to load. For Gromacs 4 you'll need to specify more tools:: @@ -59,6 +67,15 @@ specify more tools:: g_chi g_dist g_luck g_potential g_sas g_velacc make_ndx +Commands will be available directly from the :mod:`gromacs`: + +.. code-block:: python + + import gromacs + gromacs.mdrun_d # either v5 `gmx_d mdrun` or v4 `mdrun_d` + gromacs.mdrun # either v5 `gmx mdrun` or v4 `mdrun` + + More options ------------ diff --git a/gromacs/config.py b/gromacs/config.py index b90336f9..902f44d9 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -238,7 +238,7 @@ from . import utilities -# Default name of the global configuration file. +#: Default name of the global configuration file. CONFIGNAME = os.path.expanduser(os.path.join("~",".gromacswrapper.cfg")) # More default values @@ -317,7 +317,7 @@ def _generate_template_dict(dirname): by external code. All template filenames are stored in :data:`config.templates`. """ - return dict((resource_basename(fn), resource_filename(__name__, dirname + '/' + fn)) + return dict((resource_basename(fn), resource_filename(__name__, dirname +'/'+fn)) for fn in resource_listdir(__name__, dirname) if not fn.endswith('~')) @@ -669,4 +669,4 @@ def get_extra_tool_names(): RELEASE = cfg.get('Gromacs', 'release') MAJOR_RELEASE = RELEASE.split('.')[0] -check_setup() \ No newline at end of file +check_setup() From 5325fdde327389e93963e70428d5c60f6261c853 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Tue, 26 Jul 2016 20:54:19 -0300 Subject: [PATCH 24/30] restoring documentation --- gromacs/config.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/gromacs/config.py b/gromacs/config.py index 902f44d9..21379429 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -241,7 +241,22 @@ #: Default name of the global configuration file. CONFIGNAME = os.path.expanduser(os.path.join("~",".gromacswrapper.cfg")) -# More default values +#: +#: :data:`configdir` +#: Directory to store user templates and configurations. +#: The default value is ``~/.gromacswrapper``. +#: :data:`qscriptdir` +#: Directory to store user supplied queuing system scripts as +#: used by :mod:`gromacs.qsub`. +#: The default value is ``~/.gromacswrapper/qscripts``. +#: :data:`templatesdir` +#: Directory to store user supplied template files such as mdp files. +#: The default value is ``~/.gromacswrapper/templates``. +#: :data:`managerdir` +#: Directory to store configuration files for different queuing system +#: managers as used in :mod:`gromacs.manager`. +#: The default value is ``~/.gromacswrapper/managers``. + configdir = os.path.expanduser(os.path.join("~",".gromacswrapper")) defaults = { 'configdir': configdir, From 76a9b411a8341df8f04d28e992c9440274bdb94b Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Tue, 26 Jul 2016 21:14:06 -0300 Subject: [PATCH 25/30] raises an error when old style tools are used --- gromacs/config.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gromacs/config.py b/gromacs/config.py index 21379429..feb82e06 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -230,6 +230,7 @@ import os import logging +import re import subprocess from ConfigParser import SafeConfigParser @@ -684,4 +685,15 @@ def get_extra_tool_names(): RELEASE = cfg.get('Gromacs', 'release') MAJOR_RELEASE = RELEASE.split('.')[0] + +for name in get_tool_names(): + match = re.match(r'(gmx[^:]*):.*', name) + if match: + driver = match.group(1) + raise ValueError("'%s' isn't a valid tool name anymore." + " Replace it by '%s'.\n" + "See http://gromacswrapper.readthedocs.io/en/latest/" + "configuration.html"% (name, match.group(1))) + + check_setup() From 56db575f363dadc575f0fc162353466646c8609a Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Tue, 26 Jul 2016 21:20:21 -0300 Subject: [PATCH 26/30] stylistic fix --- gromacs/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gromacs/config.py b/gromacs/config.py index feb82e06..f958586c 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -691,9 +691,9 @@ def get_extra_tool_names(): if match: driver = match.group(1) raise ValueError("'%s' isn't a valid tool name anymore." - " Replace it by '%s'.\n" - "See http://gromacswrapper.readthedocs.io/en/latest/" - "configuration.html"% (name, match.group(1))) + " Replace it by '%s'.\n See " + "http://gromacswrapper.readthedocs.io/en/latest/" + "configuration.html" % (name, match.group(1))) check_setup() From a90ff7726427745d84c6d09e48b9d1e79eb431f2 Mon Sep 17 00:00:00 2001 From: Pedro Lacerda Date: Tue, 16 Aug 2016 20:09:21 -0300 Subject: [PATCH 27/30] not import the whole package while setuping --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 6ada4254..36cf980e 100644 --- a/setup.py +++ b/setup.py @@ -5,16 +5,18 @@ # See the files INSTALL and README for details or visit # https://github.com/Becksteinlab/GromacsWrapper from __future__ import with_statement - from setuptools import setup, find_packages +import imp, os + with open("README.rst") as readme: long_description = readme.read() # Dynamically calculate the version based on gromacs.VERSION. # (but requires that we can actually import the package BEFORE it is # properly installed!) -version = __import__('gromacs.version').get_version() +version_file = os.path.join(os.path.dirname(__file__), 'gromacs', 'version.py') +version = imp.load_source('gromacs.version', version_file).get_version() setup(name="GromacsWrapper", version=version, From 52427d27ba23fcbca78bccdfef0fb58c57f26cef Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Mon, 29 Aug 2016 23:02:39 -0700 Subject: [PATCH 28/30] enable logging with envvar GW_START_LOGGING --- gromacs/__init__.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/gromacs/__init__.py b/gromacs/__init__.py index f1d82edb..8db385c9 100644 --- a/gromacs/__init__.py +++ b/gromacs/__init__.py @@ -3,8 +3,7 @@ # Released under the GNU Public License 3 (or higher, your choice) # See the file COPYING for details. -""" -:mod:`gromacs` -- GromacsWrapper Package Overview +""":mod:`gromacs` -- GromacsWrapper Package Overview ================================================= **GromacsWrapper** (package :mod:`gromacs`) is a thin shell around the `Gromacs`_ @@ -160,6 +159,11 @@ instead of displaying it on screen, as described under :ref:`input-output-label`. +Normally, one starts logging with the :func:`start_logging` function but in +order to obtain logging messages (typically at level *debug*) right from the +start one may set the environment variable :envvar:`GW_START_LOGGING` to any +value that evaluates to ``True`` (e.g., "True" or "1"). + .. _logging: http://docs.python.org/library/logging.html Version @@ -173,10 +177,13 @@ If the package was installed from a development version, the patch level will have the string "-dev" affixed to distinguish it from a release. + """ from __future__ import absolute_import __docformat__ = "restructuredtext en" +import os + from .version import VERSION, RELEASE, get_version, get_version_tuple # __all__ is extended with all gromacs command instances later @@ -240,6 +247,9 @@ def stop_logging(): logger.info("GromacsWrapper %s STOPPED logging", get_version()) log.clear_handlers(logger) # this _should_ do the job... +# for testing (maybe enable with envar GW_START_LOGGING) +if os.environ.get('GW_START_LOGGING', False): + start_logging() # Try to load environment variables set by GMXRC config.set_gmxrc_environment(config.cfg.getpath("Gromacs", "GMXRC")) From aa2444605355f3e193da6f9a60d6bd0710c5a013 Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Mon, 29 Aug 2016 23:04:02 -0700 Subject: [PATCH 29/30] make autoloading work for Gromacs 4 and no cfg file - make sure that MAJOR_RELEASE is empty if there is no cfg file - make sure that no tools are defined by default (otherwise autoloading will not look for more tools) - added debug logging to tool loading (activate with GW_START_LOGGING=true, see previous commit) - cbook: catch failed tool loading with AttributeError, too I think we can soon remove the template config file. --- gromacs/cbook.py | 20 ++++++++++---------- gromacs/config.py | 4 ++-- gromacs/templates/gromacswrapper.cfg | 11 +++++++---- gromacs/tools.py | 20 ++++++++++++++++---- 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/gromacs/cbook.py b/gromacs/cbook.py index b1949712..13cf4e30 100644 --- a/gromacs/cbook.py +++ b/gromacs/cbook.py @@ -199,7 +199,7 @@ def _define_canned_commands(): try: _define_canned_commands() -except (OSError, ImportError, GromacsError): +except (OSError, ImportError, AttributeError, GromacsError): msg = ("Failed to define a number of commands in gromacs.cbook. Most " "likely the Gromacs installation cannot be found --- set GMXRC in " "~/.gromacswrapper.cfg or source GMXRC directly") @@ -1652,7 +1652,7 @@ class Transformer(utilities.FileUtils): """ - def __init__(self, s="topol.tpr", f="traj.xtc", n=None, force=None, + def __init__(self, s="topol.tpr", f="traj.xtc", n=None, force=None, dirname=os.path.curdir, outdir=None): """Set up Transformer with structure and trajectory. @@ -1732,7 +1732,7 @@ def center_fit(self, **kwargs): """Write compact xtc that is fitted to the tpr reference structure. See :func:`gromacs.cbook.trj_fitandcenter` for details and - description of *kwargs* (including *input*, *input1*, *n* and + description of *kwargs* (including *input*, *input1*, *n* and *n1* for how to supply custom index groups). The most important ones are listed here but in most cases the defaults should work. @@ -1846,7 +1846,7 @@ def fit(self, xy=False, **kwargs): logger.info("Fitted trajectory (fitmode=%s): %r.", fitmode, kwargs['o']) return {'tpr': self.rp(kwargs['s']), 'xtc': self.rp(kwargs['o'])} - def strip_water(self, os=None, o=None, on=None, compact=False, + def strip_water(self, os=None, o=None, on=None, compact=False, resn="SOL", groupname="notwater", **kwargs): """Write xtc and tpr with water (by resname) removed. @@ -1866,7 +1866,7 @@ def strip_water(self, os=None, o=None, on=None, compact=False, Index group used for centering ["Protein"] .. Note:: If *input* is provided (see below under *kwargs*) - then *centergroup* is ignored and the group for + then *centergroup* is ignored and the group for centering is taken as the first entry in *input*. *resn* @@ -1909,12 +1909,12 @@ def strip_water(self, os=None, o=None, on=None, compact=False, TRJCONV = trj_compact # input overrides centergroup if kwargs.get('centergroup') is not None and 'input' in kwargs: - logger.warn("centergroup = %r will be superceded by input[0] = %r", kwargs['centergroup'], kwargs['input'][0]) + logger.warn("centergroup = %r will be superceded by input[0] = %r", kwargs['centergroup'], kwargs['input'][0]) _input = kwargs.get('input', [kwargs.get('centergroup', 'Protein')]) kwargs['input'] = [_input[0], groupname] # [center group, write-out selection] del _input - logger.info("Creating a compact trajectory centered on group %r", kwargs['input'][0]) - logger.info("Writing %r to the output trajectory", kwargs['input'][1]) + logger.info("Creating a compact trajectory centered on group %r", kwargs['input'][0]) + logger.info("Writing %r to the output trajectory", kwargs['input'][1]) else: TRJCONV = gromacs.trjconv kwargs['input'] = [groupname] @@ -1937,7 +1937,7 @@ def strip_water(self, os=None, o=None, on=None, compact=False, logger.info("NDX of the new system %r", newndx) gromacs.make_ndx(f=newtpr, o=newndx, input=['q'], stderr=False, stdout=False) - # PROBLEM: If self.ndx contained a custom group required for fitting then we are loosing + # PROBLEM: If self.ndx contained a custom group required for fitting then we are loosing # this group here. We could try to merge only this group but it is possible that # atom indices changed. The only way to solve this is to regenerate the group with # a selection or only use Gromacs default groups. @@ -2083,7 +2083,7 @@ def strip_fit(self, **kwargs): - *fitgroup* is only passed to :meth:`fit` and just contains the group to fit to ("backbone" by default) - .. warning:: *fitgroup* can only be a Gromacs default group and not + .. warning:: *fitgroup* can only be a Gromacs default group and not a custom group (because the indices change after stripping) - By default *fit* = "rot+trans" (and *fit* is passed to :meth:`fit`, diff --git a/gromacs/config.py b/gromacs/config.py index f958586c..4b835daf 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -491,8 +491,9 @@ def __init__(self, *args, **kwargs): self.set('DEFAULT', 'managerdir', os.path.join("%(configdir)s", os.path.basename(defaults['managerdir']))) self.add_section('Gromacs') + self.set("Gromacs", "release", "") self.set("Gromacs", "GMXRC", "") - self.set("Gromacs", "tools", "pdb2gmx editconf grompp genbox genion mdrun trjcat trjconv") + self.set("Gromacs", "tools", "") self.set("Gromacs", "extra", "") self.set("Gromacs", "groups", "tools") self.add_section('Logging') @@ -685,7 +686,6 @@ def get_extra_tool_names(): RELEASE = cfg.get('Gromacs', 'release') MAJOR_RELEASE = RELEASE.split('.')[0] - for name in get_tool_names(): match = re.match(r'(gmx[^:]*):.*', name) if match: diff --git a/gromacs/templates/gromacswrapper.cfg b/gromacs/templates/gromacswrapper.cfg index bf5f4e4f..98d9bec6 100644 --- a/gromacs/templates/gromacswrapper.cfg +++ b/gromacs/templates/gromacswrapper.cfg @@ -16,15 +16,18 @@ managerdir = %(configdir)s/managers [Gromacs] # Release of the Gromacs package to which information in this sections applies. -release = 5.1.1 +# empty: try auto-loading tools (first Gromacs 5 then Gromacs 4) +## release = 5.1.2 # tools contains the file names of all Gromacs tools for which classes are generated. # Editing this list has only an effect when the package is reloaded. -# (Generated with 'ls [^Gac]*' from the Gromacs bin dir) -tools = gmx gmx_d +# - for Gromacs 4: Generated with 'ls [^Gac]*' from the Gromacs bin dir +## tools = ... +# - for Gromacs 5: just the driver commands +## tools = gmx gmx_d # which tool groups to make available as gromacs.NAME -groups = tools +## groups = tools [Logging] # name of the logfile that is written to the current directory diff --git a/gromacs/tools.py b/gromacs/tools.py index e5065181..a08e2d50 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -70,10 +70,12 @@ import tempfile import subprocess import atexit +import logging from . import config from .core import GromacsCommand +logger = logging.getLogger("gromacs.tools") V4TOOLS = ("g_cluster", "g_dyndom", "g_mdmat", "g_principal", "g_select", "g_wham", "mdrun", "do_dssp", "g_clustsize", "g_enemat", "g_membed", @@ -201,6 +203,8 @@ def load_v5_tools(): :return: dict mapping tool names to GromacsCommand classes """ + logger.debug("Loading v5 tools...") + drivers = config.get_tool_names() if len(drivers) == 0 and 'GMXBIN' in os.environ: @@ -226,8 +230,11 @@ def load_v5_tools(): except (subprocess.CalledProcessError, OSError): pass - if len(tools) == 0: - raise GromacsToolLoadingError("Failed to load v5 tools") + if not tools: + errmsg = "Failed to load v5 tools" + logger.debug(errmsg) + raise GromacsToolLoadingError(errmsg) + logger.debug("Loaded {0} v5 tools successfully!".format(len(tools))) return tools @@ -241,6 +248,8 @@ def load_v4_tools(): :return: dict mapping tool names to GromacsCommand classes """ + logger.debug("Loading v4 tools...") + names = config.get_tool_names() if len(names) == 0 and 'GMXBIN' in os.environ: @@ -256,8 +265,11 @@ def load_v4_tools(): fancy = make_valid_identifier(name) tools[fancy] = tool_factory(fancy, name, None) - if len(tools) == 0: - raise GromacsToolLoadingError("Failed to load v4 tools") + if not tools: + errmsg = "Failed to load v4 tools" + logger.debug(errmsg) + raise GromacsToolLoadingError(errmsg) + logger.debug("Loaded {0} v4 tools successfully!".format(len(tools))) return tools From e10893478c7a4ed7a5f775540934cab124a1f0a4 Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Mon, 29 Aug 2016 23:27:41 -0700 Subject: [PATCH 30/30] make GMXRC loading work for v4 --- gromacs/config.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/gromacs/config.py b/gromacs/config.py index 4b835daf..2e6aea52 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -639,11 +639,15 @@ def set_gmxrc_environment(gmxrc): then only a warning will be logged. Thus, it should be safe to just call this function. """ - envvars = ['GMXPREFIX', 'GMXBIN', 'GMXLDLIB', 'GMXMAN', 'GMXDATA', - 'GROMACS_DIR', 'LD_LIBRARY_PATH', 'MANPATH', 'PKG_CONFIG_PATH', - 'PATH'] + # only v5: 'GMXPREFIX', 'GROMACS_DIR' + envvars = ['GMXBIN', 'GMXLDLIB', 'GMXMAN', 'GMXDATA', + 'LD_LIBRARY_PATH', 'MANPATH', 'PKG_CONFIG_PATH', + 'PATH', + 'GMXPREFIX', 'GROMACS_DIR'] + # in order to keep empty values, add ___ sentinels around result + # (will be removed later) cmdargs = ['bash', '-c', ". {0} && echo {1}".format(gmxrc, - ' '.join(['${0}'.format(v) for v in envvars]))] + ' '.join(['___${{{0}}}___'.format(v) for v in envvars]))] if not gmxrc: logger.debug("set_gmxrc_environment(): no GMXRC, nothing done.") @@ -653,6 +657,7 @@ def set_gmxrc_environment(gmxrc): out = subprocess.check_output(cmdargs) out = out.strip().split() for key, value in zip(envvars, out): + value = value.replace('___', '') # remove sentinels os.environ[key] = value logger.debug("set_gmxrc_environment(): %s = %r", key, value) except (subprocess.CalledProcessError, OSError):