1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 # The author may be contacted at <wking@drexel.edu> on the Internet, or
17 # write to Trevor King, Drudge's University, Physics Dept., 3141 Chestnut St.,
18 # Philadelphia PA 19104, USA.
21 """`Seminar` for running `sawsim` and parsing the results.
25 from collections import namedtuple
26 except ImportError: # work around Python < 2.6
27 from ._collections import namedtuple
29 from optparse import Option
32 from random import shuffle
34 from uuid import uuid4
36 from .manager import MANAGERS, get_manager, InvokeJob
39 SAWSIM = 'sawsim' # os.path.expand(os.path.join('~', 'bin', 'sawsim'))
40 CACHE_DIR = os.path.expanduser(os.path.join('~', '.sawsim-cache'))
41 DEFAULT_PARAM_STRING = (
42 '-s cantilever,hooke,0.05 -N1 '
44 "-s 'unfolded,wlc,{0.39e-9,28e-9}' "
45 "-k 'folded,unfolded,bell,{3.3e-4,0.25e-9}' "
49 # `Event` instances represent domain state transitions.
52 field_names=['force', 'initial_state', 'final_state'])
55 class SawsimRunner (object):
57 >>> from .manager.thread import ThreadManager
58 >>> m = ThreadManager()
59 >>> sr = SawsimRunner(sawsim='bin/sawsim', manager=m)
60 >>> for run in sr(param_string=DEFAULT_PARAM_STRING, N=2):
62 ... for i,event in enumerate(run):
63 ... print i, event # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
65 0 Event(force=..., initial_state='folded', final_state='unfolded')
66 1 Event(force=..., initial_state='folded', final_state='unfolded')
67 2 Event(force=..., initial_state='folded', final_state='unfolded')
68 3 Event(force=..., initial_state='folded', final_state='unfolded')
69 4 Event(force=..., initial_state='folded', final_state='unfolded')
70 5 Event(force=..., initial_state='folded', final_state='unfolded')
71 6 Event(force=..., initial_state='folded', final_state='unfolded')
72 7 Event(force=..., initial_state='folded', final_state='unfolded')
74 0 Event(force=..., initial_state='folded', final_state='unfolded')
75 1 Event(force=..., initial_state='folded', final_state='unfolded')
76 2 Event(force=..., initial_state='folded', final_state='unfolded')
77 3 Event(force=..., initial_state='folded', final_state='unfolded')
78 4 Event(force=..., initial_state='folded', final_state='unfolded')
79 5 Event(force=..., initial_state='folded', final_state='unfolded')
80 6 Event(force=..., initial_state='folded', final_state='unfolded')
81 7 Event(force=..., initial_state='folded', final_state='unfolded')
86 Option("-s","--sawsim", dest="sawsim",
88 help="Set sawsim binary (%default).",
90 Option("-p","--params", dest="param_string",
92 help="Initial params for fitting (%default).",
93 default=DEFAULT_PARAM_STRING),
94 Option("-N", "--number-of-runs", dest="N",
95 metavar="INT", type='int',
96 help="Number of sawsim runs at each point in parameter space (%default).",
98 Option("-m", "--manager", dest="manager",
100 help="Job manager name (one of %s) (%%default)."
101 % (', '.join(MANAGERS)),
102 default=MANAGERS[0]),
103 Option("-C","--use-cache", dest="use_cache",
104 help="Use cached simulations if they exist (vs. running new simulations) (%default)",
105 default=False, action="store_true"),
106 Option("--clean-cache", dest="clean_cache",
107 help="Remove previously cached simulations if they exist (%default)",
108 default=False, action="store_true"),
109 Option("-d","--cache-dir", dest="cache_dir",
111 help="Cache directory for sawsim unfolding forces (%default).",
115 def __init__(self, sawsim=None, cache_dir=None,
116 use_cache=False, clean_cache=False,
120 self._sawsim = sawsim
121 if cache_dir == None:
122 cache_dir = CACHE_DIR
123 self._cache_dir = cache_dir
124 self._use_cache = use_cache
125 self._clean_cache = clean_cache
126 self._manager = manager
127 self._local_manager = False
128 self._headline = None
130 def initialize_from_options(self, options):
131 self._sawsim = options.sawsim
132 self._cache_dir = options.cache_dir
133 self._use_cache = options.use_cache
134 self._clean_cache = options.clean_cache
135 self._manager = get_manager(options.manager)()
136 self._local_manager = True
138 for param in ['param_string', 'N']:
140 call_params[param] = getattr(options, param)
141 except AttributeError:
146 if self._local_manager == True:
147 self._manager.teardown()
149 def __call__(self, param_string, N):
150 """Run `N` simulations and yield `Event` generators for each run.
152 Use the `JobManager` instance `manager` for asynchronous job
155 If `_use_cache` is `True`, store an array of unfolding forces
156 in `cache_dir` for each simulated pull. If the cached forces
157 are already present for `param_string`, do not redo the
158 simulation unless `_clean_cache` is `True`.
161 if self._use_cache == True:
162 d = self._param_cache_dir(param_string)
163 if os.path.exists(d):
164 if self._clean_cache == True:
166 self._make_cache(param_string)
168 for data in self._load_cached_data(param_string):
174 self._make_cache(param_string)
177 for i in range(count):
178 jobs[i] = self._manager.async_invoke(InvokeJob(
179 target='%s %s' % (self._sawsim, param_string)))
180 complete_jobs = self._manager.wait(
181 [job.id for job in jobs.itervalues()])
182 for i,job in jobs.iteritems():
183 j = complete_jobs[job.id]
184 assert j.status == 0, j.data
185 if self._use_cache == True:
186 self._cache_run(d, j.data['stdout'])
187 yield self.parse(j.data['stdout'])
191 def _param_cache_dir(self, param_string):
193 >>> s = SawsimRunner()
194 >>> s._param_cache_dir(DEFAULT_PARAM_STRING) # doctest: +ELLIPSIS
195 '/.../.sawsim-cache/...'
198 self._cache_dir, hashlib.sha256(param_string).hexdigest())
200 def _make_cache(self, param_string):
201 cache_dir = self._param_cache_dir(param_string)
202 os.makedirs(cache_dir)
203 with open(os.path.join(cache_dir, 'param_string'), 'w') as f:
204 f.write('# version: %s\n%s\n' % (__version__, param_string))
206 def _load_cached_data(self, param_string):
207 pcd = self._param_cache_dir(param_string)
208 for filename in shuffle(os.listdir(pcd)):
209 if not filename.endswith('.dat'):
211 with open(os.path.join(pcd, filename), 'r') as f:
212 yield self.parse(f.read())
214 def _cache_run(self, cache_dir, stdout):
215 simulation_path = os.path.join(cache_dir, '%s.dat' % uuid4())
216 with open(simulation_path, 'w') as f:
219 def parse(self, text):
220 """Parse the output of a `sawsim` run.
222 >>> text = '''#Force (N)\\tinitial state\\tFinal state
223 ... 2.90301e-10\\tfolded\\tunfolded
224 ... 2.83948e-10\\tfolded\\tunfolded
225 ... 2.83674e-10\\tfolded\\tunfolded
226 ... 2.48384e-10\\tfolded\\tunfolded
227 ... 2.43033e-10\\tfolded\\tunfolded
228 ... 2.77589e-10\\tfolded\\tunfolded
229 ... 2.85343e-10\\tfolded\\tunfolded
230 ... 2.67796e-10\\tfolded\\tunfolded
232 >>> sr = SawsimRunner()
233 >>> events = list(sr.parse(text))
236 >>> events[0] # doctest: +ELLIPSIS
237 Event(force=2.9030...e-10, initial_state='folded', final_state='unfolded')
239 ['Force (N)', 'initial state', 'Final state']
241 for line in text.splitlines():
245 elif line.startswith('#'):
246 if self._headline == None:
247 self._headline = line[len('#'):].split('\t')
249 fields = line.split('\t')
251 raise ValueError(fields)
252 force,initial_state,final_state = fields
253 yield Event(float(force), initial_state, final_state)
260 ... except SystemExit, e:
261 ... pass # doctest: +ELLIPSIS, +REPORT_UDIFF
265 -h, --help show this help message and exit
266 -s PATH, --sawsim=PATH
267 Set sawsim binary (sawsim).
271 >>> main(['--sawsim', 'bin/sawsim', '-N', '2'])
272 ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
273 #Force (N) Initial state Final state
281 from optparse import OptionParser
289 usage = '%prog [options]'
291 'Python wrapper around `sawsim`. Distribute `N` runs using',
292 'one of the possible job "managers". Also supports caching',
293 'results to speed future runs.'
295 parser = OptionParser(usage, epilog=epilog)
296 for option in sr.optparse_options:
297 parser.add_option(option)
299 options,args = parser.parse_args(argv)
302 sr_call_params = sr.initialize_from_options(options)
305 for run in sr(**sr_call_params):
306 if first_run == True:
308 run = list(run) # force iterator evaluation
309 if sr._headline != None:
310 print '#%s' % '\t'.join(sr._headline)
312 print '\t'.join([str(x) for x in event])