Better pysawsim.sawsim.SAWSIM detection via distutils.spawn.find_executable().
[sawsim.git] / pysawsim / sawsim.py
index 1500a93038c8322b0de24ad72bae519da4c3a3e4..0f85869dd634111a0ce0f64d65a9700895f6d8f1 100644 (file)
 """`Seminar` for running `sawsim` and parsing the results.
 """
 
+from __future__ import with_statement
+
 try:
     from collections import namedtuple
 except ImportError:  # work around Python < 2.6
     from ._collections import namedtuple
+from distutils.spawn import find_executable
 import hashlib
 from optparse import Option
 import os
 import os.path
+from random import shuffle
 import shutil
 from uuid import uuid4
 
+from . import __version__
 from .manager import MANAGERS, get_manager, InvokeJob
 
 
-SAWSIM = 'sawsim'  # os.path.expand(os.path.join('~', 'bin', 'sawsim'))
-CACHE_DIR = os.path.expanduser(os.path.join('~', '.sawsim_cache'))
+_multiprocess_can_split_ = True
+"""Allow nosetests to split tests between processes.
+"""
+
+SAWSIM = find_executable('sawsim')
+if SAWSIM == None:
+    SAWSIM = os.path.join('bin', 'sawsim')
+
+CACHE_DIR = os.path.expanduser(os.path.join('~', '.sawsim-cache'))
 DEFAULT_PARAM_STRING = (
     '-s cantilever,hooke,0.05 -N1 '
     '-s folded,null -N8 '
@@ -53,9 +65,8 @@ Event = namedtuple(
 
 class SawsimRunner (object):
     """
-    >>> from .manager.thread import ThreadManager
-    >>> m = ThreadManager()
-    >>> sr = SawsimRunner(sawsim='bin/sawsim', manager=m)
+    >>> m = get_manager()()
+    >>> sr = SawsimRunner(manager=m)
     >>> for run in sr(param_string=DEFAULT_PARAM_STRING, N=2):
     ...     print 'New run'
     ...     for i,event in enumerate(run):
@@ -82,32 +93,32 @@ class SawsimRunner (object):
     """
 
     optparse_options = [
-        Option("-s","--sawsim", dest="sawsim",
-               metavar="PATH",
-               help="Set sawsim binary (%default).",
+        Option('-s','--sawsim', dest='sawsim',
+               metavar='PATH',
+               help='Set sawsim binary (%default).',
                default=SAWSIM),
-        Option("-p","--params", dest="param_string",
-               metavar="PARAMS",
-               help="Initial params for fitting (%default).",
+        Option('-p','--params', dest='param_string',
+               metavar='PARAMS',
+               help='Initial params for fitting (%default).',
                default=DEFAULT_PARAM_STRING),
-        Option("-N", "--number-of-runs", dest="N",
-               metavar="INT", type='int',
-               help="Number of sawsim runs at each point in parameter space (%default).",
+        Option('-N', '--number-of-runs', dest='N',
+               metavar='INT', type='int',
+               help='Number of sawsim runs at each point in parameter space (%default).',
                default=400),
-        Option("-m", "--manager", dest="manager",
-               metavar="STRING",
-               help="Job manager name (one of %s) (%%default)."
+        Option('-m', '--manager', dest='manager',
+               metavar='STRING',
+               help='Job manager name (one of %s) (default: auto-select).'
                % (', '.join(MANAGERS)),
-               default=MANAGERS[0]),
-        Option("-C","--use-cache", dest="use_cache",
-               help="Use cached simulations if they exist (vs. running new simulations) (%default)",
-               default=False, action="store_true"),
-        Option("--clean-cache", dest="clean_cache",
-               help="Remove previously cached simulations if they exist (%default)",
-               default=False, action="store_true"),
-        Option("-d","--cache-dir", dest="cache_dir",
-               metavar="STRING",
-               help="Cache directory for sawsim unfolding forces (%default).",
+               default=None),
+        Option('-C','--use-cache', dest='use_cache',
+               help='Use cached simulations if they exist (vs. running new simulations) (%default)',
+               default=False, action='store_true'),
+        Option('--clean-cache', dest='clean_cache',
+               help='Remove previously cached simulations if they exist (%default)',
+               default=False, action='store_true'),
+        Option('-d','--cache-dir', dest='cache_dir',
+               metavar='STRING',
+               help='Cache directory for sawsim unfolding forces (%default).',
                default=CACHE_DIR),
     ]
 
@@ -180,7 +191,7 @@ class SawsimRunner (object):
             [job.id for job in jobs.itervalues()])
         for i,job in jobs.iteritems():
             j = complete_jobs[job.id]
-            assert j.status == 0, j.data
+            assert j.status == 0, j.data['error']
             if self._use_cache == True:
                 self._cache_run(d, j.data['stdout'])
             yield self.parse(j.data['stdout'])
@@ -191,7 +202,7 @@ class SawsimRunner (object):
         """
         >>> s = SawsimRunner()
         >>> s._param_cache_dir(DEFAULT_PARAM_STRING)  # doctest: +ELLIPSIS
-        '/.../.sawsim_cache/...'
+        '/.../.sawsim-cache/...'
         """
         return os.path.join(
             self._cache_dir, hashlib.sha256(param_string).hexdigest())
@@ -204,7 +215,9 @@ class SawsimRunner (object):
 
     def _load_cached_data(self, param_string):
         pcd = self._param_cache_dir(param_string)
-        for filename in os.listdir(pcd):
+        filenames = os.listdir(pcd)
+        shuffle(filenames)
+        for filename in filenames:
             if not filename.endswith('.dat'):
                 continue
             with open(os.path.join(pcd, filename), 'r') as f:
@@ -263,11 +276,11 @@ def main(argv=None):
     Options:
       -h, --help            show this help message and exit
       -s PATH, --sawsim=PATH
-                            Set sawsim binary (sawsim).
+                            Set sawsim binary (...sawsim).
       ...
     >>> print e
     0
-    >>> main(['--sawsim', 'bin/sawsim', '-N', '2'])
+    >>> main(['-N', '2'])
     ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
     #Force (N)  Initial state  Final state
     ...         folded         unfolded
@@ -285,8 +298,13 @@ def main(argv=None):
 
     sr = SawsimRunner()
 
-    usage = "%prog [options]"
-    parser = OptionParser(usage)
+    usage = '%prog [options]'
+    epilog = '\n'.join([
+            'Python wrapper around `sawsim`.  Distribute `N` runs using',
+            'one of the possible job "managers".  Also supports caching',
+            'results to speed future runs.'
+            ])
+    parser = OptionParser(usage, epilog=epilog)
     for option in sr.optparse_options:
         parser.add_option(option)