westpa 2022.13__cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. westpa/__init__.py +14 -0
  2. westpa/_version.py +21 -0
  3. westpa/analysis/__init__.py +5 -0
  4. westpa/analysis/core.py +749 -0
  5. westpa/analysis/statistics.py +27 -0
  6. westpa/analysis/trajectories.py +369 -0
  7. westpa/cli/__init__.py +0 -0
  8. westpa/cli/core/__init__.py +0 -0
  9. westpa/cli/core/w_fork.py +152 -0
  10. westpa/cli/core/w_init.py +230 -0
  11. westpa/cli/core/w_run.py +77 -0
  12. westpa/cli/core/w_states.py +212 -0
  13. westpa/cli/core/w_succ.py +99 -0
  14. westpa/cli/core/w_truncate.py +68 -0
  15. westpa/cli/tools/__init__.py +0 -0
  16. westpa/cli/tools/ploterr.py +506 -0
  17. westpa/cli/tools/plothist.py +706 -0
  18. westpa/cli/tools/w_assign.py +597 -0
  19. westpa/cli/tools/w_bins.py +166 -0
  20. westpa/cli/tools/w_crawl.py +119 -0
  21. westpa/cli/tools/w_direct.py +557 -0
  22. westpa/cli/tools/w_dumpsegs.py +94 -0
  23. westpa/cli/tools/w_eddist.py +506 -0
  24. westpa/cli/tools/w_fluxanl.py +376 -0
  25. westpa/cli/tools/w_ipa.py +832 -0
  26. westpa/cli/tools/w_kinavg.py +127 -0
  27. westpa/cli/tools/w_kinetics.py +96 -0
  28. westpa/cli/tools/w_multi_west.py +414 -0
  29. westpa/cli/tools/w_ntop.py +213 -0
  30. westpa/cli/tools/w_pdist.py +515 -0
  31. westpa/cli/tools/w_postanalysis_matrix.py +82 -0
  32. westpa/cli/tools/w_postanalysis_reweight.py +53 -0
  33. westpa/cli/tools/w_red.py +491 -0
  34. westpa/cli/tools/w_reweight.py +780 -0
  35. westpa/cli/tools/w_select.py +226 -0
  36. westpa/cli/tools/w_stateprobs.py +111 -0
  37. westpa/cli/tools/w_timings.py +113 -0
  38. westpa/cli/tools/w_trace.py +599 -0
  39. westpa/core/__init__.py +0 -0
  40. westpa/core/_rc.py +673 -0
  41. westpa/core/binning/__init__.py +55 -0
  42. westpa/core/binning/_assign.c +36018 -0
  43. westpa/core/binning/_assign.cpython-312-aarch64-linux-gnu.so +0 -0
  44. westpa/core/binning/_assign.pyx +370 -0
  45. westpa/core/binning/assign.py +454 -0
  46. westpa/core/binning/binless.py +96 -0
  47. westpa/core/binning/binless_driver.py +54 -0
  48. westpa/core/binning/binless_manager.py +189 -0
  49. westpa/core/binning/bins.py +47 -0
  50. westpa/core/binning/mab.py +506 -0
  51. westpa/core/binning/mab_driver.py +54 -0
  52. westpa/core/binning/mab_manager.py +197 -0
  53. westpa/core/data_manager.py +1761 -0
  54. westpa/core/extloader.py +74 -0
  55. westpa/core/h5io.py +1079 -0
  56. westpa/core/kinetics/__init__.py +24 -0
  57. westpa/core/kinetics/_kinetics.c +45174 -0
  58. westpa/core/kinetics/_kinetics.cpython-312-aarch64-linux-gnu.so +0 -0
  59. westpa/core/kinetics/_kinetics.pyx +815 -0
  60. westpa/core/kinetics/events.py +147 -0
  61. westpa/core/kinetics/matrates.py +156 -0
  62. westpa/core/kinetics/rate_averaging.py +266 -0
  63. westpa/core/progress.py +218 -0
  64. westpa/core/propagators/__init__.py +54 -0
  65. westpa/core/propagators/executable.py +592 -0
  66. westpa/core/propagators/loaders.py +196 -0
  67. westpa/core/reweight/__init__.py +14 -0
  68. westpa/core/reweight/_reweight.c +36899 -0
  69. westpa/core/reweight/_reweight.cpython-312-aarch64-linux-gnu.so +0 -0
  70. westpa/core/reweight/_reweight.pyx +439 -0
  71. westpa/core/reweight/matrix.py +126 -0
  72. westpa/core/segment.py +119 -0
  73. westpa/core/sim_manager.py +839 -0
  74. westpa/core/states.py +359 -0
  75. westpa/core/systems.py +93 -0
  76. westpa/core/textio.py +74 -0
  77. westpa/core/trajectory.py +603 -0
  78. westpa/core/we_driver.py +910 -0
  79. westpa/core/wm_ops.py +43 -0
  80. westpa/core/yamlcfg.py +298 -0
  81. westpa/fasthist/__init__.py +34 -0
  82. westpa/fasthist/_fasthist.c +38755 -0
  83. westpa/fasthist/_fasthist.cpython-312-aarch64-linux-gnu.so +0 -0
  84. westpa/fasthist/_fasthist.pyx +222 -0
  85. westpa/mclib/__init__.py +271 -0
  86. westpa/mclib/__main__.py +28 -0
  87. westpa/mclib/_mclib.c +34610 -0
  88. westpa/mclib/_mclib.cpython-312-aarch64-linux-gnu.so +0 -0
  89. westpa/mclib/_mclib.pyx +226 -0
  90. westpa/oldtools/__init__.py +4 -0
  91. westpa/oldtools/aframe/__init__.py +35 -0
  92. westpa/oldtools/aframe/atool.py +75 -0
  93. westpa/oldtools/aframe/base_mixin.py +26 -0
  94. westpa/oldtools/aframe/binning.py +178 -0
  95. westpa/oldtools/aframe/data_reader.py +560 -0
  96. westpa/oldtools/aframe/iter_range.py +200 -0
  97. westpa/oldtools/aframe/kinetics.py +117 -0
  98. westpa/oldtools/aframe/mcbs.py +153 -0
  99. westpa/oldtools/aframe/output.py +39 -0
  100. westpa/oldtools/aframe/plotting.py +88 -0
  101. westpa/oldtools/aframe/trajwalker.py +126 -0
  102. westpa/oldtools/aframe/transitions.py +469 -0
  103. westpa/oldtools/cmds/__init__.py +0 -0
  104. westpa/oldtools/cmds/w_ttimes.py +361 -0
  105. westpa/oldtools/files.py +34 -0
  106. westpa/oldtools/miscfn.py +23 -0
  107. westpa/oldtools/stats/__init__.py +4 -0
  108. westpa/oldtools/stats/accumulator.py +35 -0
  109. westpa/oldtools/stats/edfs.py +129 -0
  110. westpa/oldtools/stats/mcbs.py +96 -0
  111. westpa/tools/__init__.py +33 -0
  112. westpa/tools/binning.py +472 -0
  113. westpa/tools/core.py +340 -0
  114. westpa/tools/data_reader.py +159 -0
  115. westpa/tools/dtypes.py +31 -0
  116. westpa/tools/iter_range.py +198 -0
  117. westpa/tools/kinetics_tool.py +343 -0
  118. westpa/tools/plot.py +283 -0
  119. westpa/tools/progress.py +17 -0
  120. westpa/tools/selected_segs.py +154 -0
  121. westpa/tools/wipi.py +751 -0
  122. westpa/trajtree/__init__.py +4 -0
  123. westpa/trajtree/_trajtree.c +17829 -0
  124. westpa/trajtree/_trajtree.cpython-312-aarch64-linux-gnu.so +0 -0
  125. westpa/trajtree/_trajtree.pyx +130 -0
  126. westpa/trajtree/trajtree.py +117 -0
  127. westpa/westext/__init__.py +0 -0
  128. westpa/westext/adaptvoronoi/__init__.py +3 -0
  129. westpa/westext/adaptvoronoi/adaptVor_driver.py +214 -0
  130. westpa/westext/hamsm_restarting/__init__.py +3 -0
  131. westpa/westext/hamsm_restarting/example_overrides.py +35 -0
  132. westpa/westext/hamsm_restarting/restart_driver.py +1165 -0
  133. westpa/westext/stringmethod/__init__.py +11 -0
  134. westpa/westext/stringmethod/fourier_fitting.py +69 -0
  135. westpa/westext/stringmethod/string_driver.py +253 -0
  136. westpa/westext/stringmethod/string_method.py +306 -0
  137. westpa/westext/weed/BinCluster.py +180 -0
  138. westpa/westext/weed/ProbAdjustEquil.py +100 -0
  139. westpa/westext/weed/UncertMath.py +247 -0
  140. westpa/westext/weed/__init__.py +10 -0
  141. westpa/westext/weed/weed_driver.py +192 -0
  142. westpa/westext/wess/ProbAdjust.py +101 -0
  143. westpa/westext/wess/__init__.py +6 -0
  144. westpa/westext/wess/wess_driver.py +217 -0
  145. westpa/work_managers/__init__.py +57 -0
  146. westpa/work_managers/core.py +396 -0
  147. westpa/work_managers/environment.py +134 -0
  148. westpa/work_managers/mpi.py +318 -0
  149. westpa/work_managers/processes.py +201 -0
  150. westpa/work_managers/serial.py +28 -0
  151. westpa/work_managers/threads.py +79 -0
  152. westpa/work_managers/zeromq/__init__.py +20 -0
  153. westpa/work_managers/zeromq/core.py +635 -0
  154. westpa/work_managers/zeromq/node.py +131 -0
  155. westpa/work_managers/zeromq/work_manager.py +526 -0
  156. westpa/work_managers/zeromq/worker.py +320 -0
  157. westpa-2022.13.dist-info/METADATA +179 -0
  158. westpa-2022.13.dist-info/RECORD +162 -0
  159. westpa-2022.13.dist-info/WHEEL +7 -0
  160. westpa-2022.13.dist-info/entry_points.txt +30 -0
  161. westpa-2022.13.dist-info/licenses/LICENSE +21 -0
  162. westpa-2022.13.dist-info/top_level.txt +1 -0
@@ -0,0 +1,832 @@
1
+ import base64
2
+ import codecs
3
+ import hashlib
4
+ import os
5
+ import warnings
6
+
7
+ import numpy as np
8
+
9
+ import westpa
10
+ from westpa.core import h5io
11
+ from westpa.cli.tools import w_assign, w_direct, w_reweight
12
+
13
+ from westpa.tools import WESTParallelTool, WESTDataReader, ProgressIndicatorComponent, Plotter
14
+
15
+ from westpa.tools import WIPIDataset, __get_data_for_iteration__, WIPIScheme
16
+
17
+
18
+ warnings.filterwarnings('ignore', category=DeprecationWarning)
19
+ warnings.filterwarnings('ignore', category=RuntimeWarning)
20
+ warnings.filterwarnings('ignore', category=FutureWarning)
21
+ warnings.filterwarnings('ignore', category=ImportWarning)
22
+ warnings.filterwarnings('ignore')
23
+
24
+
25
+ class WIPI(WESTParallelTool):
26
+ '''
27
+ Welcome to w_ipa (WESTPA Interactive Python Analysis)!
28
+ From here, you can run traces, look at weights, progress coordinates, etc.
29
+ This is considered a 'stateful' tool; that is, the data you are pulling is always pulled
30
+ from the current analysis scheme and iteration.
31
+ By default, the first analysis scheme in west.cfg is used, and you are set at iteration 1.
32
+
33
+ ALL PROPERTIES ARE ACCESSED VIA w or west
34
+ To see the current iteration, try:
35
+
36
+ w.iteration
37
+ OR
38
+ west.iteration
39
+
40
+ to set it, simply plug in a new value.
41
+
42
+ w.iteration = 100
43
+
44
+ To change/list the current analysis schemes:
45
+
46
+ w.list_schemes
47
+ w.scheme = OUTPUT FROM w.list_schemes
48
+
49
+ To see the states and bins defined in the current analysis scheme:
50
+
51
+ w.states
52
+ w.bin_labels
53
+
54
+ All information about the current iteration is available in an object called 'current':
55
+
56
+ w.current
57
+ walkers, summary, states, seg_id, weights, parents, kinavg, pcoord, bins, populations, and auxdata, if it exists.
58
+
59
+ In addition, the function w.trace(seg_id) will run a trace over a seg_id in the current iteration and return a dictionary
60
+ containing all pertinent information about that seg_id's history. It's best to store this, as the trace can be expensive.
61
+
62
+ Run help on any function or property for more information!
63
+
64
+ Happy analyzing!
65
+
66
+ '''
67
+
68
+ def __init__(self):
69
+ super().__init__()
70
+ self.data_reader = WESTDataReader()
71
+ self.wm_env.default_work_manager = self.wm_env.default_parallel_work_manager
72
+ self.progress = ProgressIndicatorComponent()
73
+
74
+ self._iter = 1
75
+ self.config_required = True
76
+ self.version = "1.0B"
77
+ # Set to matplotlib if you want that. But why would you?
78
+ # Well, whatever, we'll just set it to that for now.
79
+ self.interface = 'matplotlib'
80
+ self._scheme = None
81
+
82
+ def add_args(self, parser):
83
+ self.progress.add_args(parser)
84
+ self.data_reader.add_args(parser)
85
+ rgroup = parser.add_argument_group('runtime options')
86
+ rgroup.add_argument(
87
+ '--analysis-only',
88
+ '-ao',
89
+ dest='analysis_mode',
90
+ action='store_true',
91
+ help='''Use this flag to run the analysis and return to the terminal.''',
92
+ )
93
+ rgroup.add_argument(
94
+ '--reanalyze',
95
+ '-ra',
96
+ dest='reanalyze',
97
+ action='store_true',
98
+ help='''Use this flag to delete the existing files and reanalyze.''',
99
+ )
100
+ rgroup.add_argument(
101
+ '--ignore-hash', '-ih', dest='ignore_hash', action='store_true', help='''Ignore hash and don't regenerate files.'''
102
+ )
103
+ rgroup.add_argument(
104
+ '--debug', '-d', dest='debug_mode', action='store_true', help='''Debug output largely intended for development.'''
105
+ )
106
+ rgroup.add_argument('--terminal', '-t', dest='plotting', action='store_true', help='''Plot output in terminal.''')
107
+ # There is almost certainly a better way to handle this, but we'll sort that later.
108
+ import argparse
109
+
110
+ rgroup.add_argument('--f', '-f', dest='extra', default='blah', help=argparse.SUPPRESS)
111
+
112
+ parser.set_defaults(compression=True)
113
+
114
+ def process_args(self, args):
115
+ self.progress.process_args(args)
116
+ self.data_reader.process_args(args)
117
+ with self.data_reader:
118
+ self.niters = self.data_reader.current_iteration - 1
119
+ self.__config = westpa.rc.config
120
+ self.__settings = self.__config['west']['analysis']
121
+ for ischeme, scheme in enumerate(self.__settings['analysis_schemes']):
122
+ if (
123
+ self.__settings['analysis_schemes'][scheme]['enabled'] is True
124
+ or self.__settings['analysis_schemes'][scheme]['enabled'] is None
125
+ ):
126
+ self.scheme = scheme
127
+ self.data_args = args
128
+ self.analysis_mode = args.analysis_mode
129
+ self.reanalyze = args.reanalyze
130
+ self.ignore_hash = args.ignore_hash
131
+ self.debug_mode = args.debug_mode
132
+ if args.plotting:
133
+ self.interface = 'text'
134
+
135
+ def hash_args(self, args, extra=None, path=None):
136
+ '''Create unique hash stamp to determine if arguments/file is different from before.'''
137
+ '''Combine with iteration to know whether or not file needs updating.'''
138
+ # Why are we not loading this functionality into the individual tools?
139
+ # While it may certainly be useful to store arguments (and we may well do that),
140
+ # it's rather complex and nasty to deal with pickling and hashing arguments through
141
+ # the various namespaces.
142
+ # In addition, it's unlikely that the functionality is desired at the individual tool level,
143
+ # since we'll always just rewrite a file when we call the function.
144
+ # return hashlib.md5(pickle.dumps([args, extra])).hexdigest()
145
+ # We don't care about the path, so we'll remove it.
146
+ # Probably a better way to do this, but who cares.
147
+ cargs = list(args)
148
+ for iarg, arg in enumerate(cargs):
149
+ if path in arg:
150
+ cargs[iarg] = arg.replace(path, '').replace('/', '')
151
+ if arg == '--disable-averages':
152
+ cargs.remove('--disable-averages')
153
+ to_hash = cargs + [extra]
154
+ # print(args)
155
+ # print(to_hash)
156
+ # print(str(to_hash).encode('base64'))
157
+ if self.debug_mode:
158
+ for iarg, arg in enumerate(to_hash):
159
+ if isinstance(arg, list):
160
+ for il, l in enumerate(arg):
161
+ print('arg {num:02d} -- {arg:<20}'.format(num=il + iarg, arg=h5io.tostr(l)))
162
+ else:
163
+ print('arg {num:02d} -- {arg:<20}'.format(num=iarg, arg=h5io.tostr(arg)))
164
+ # print('args: {}'.format(to_hash))
165
+ # This SHOULD produce the same output, maybe? That would be nice, anyway.
166
+ # But we'll need to test it more.
167
+ return hashlib.md5(base64.b64encode(str(to_hash).encode())).hexdigest()
168
+
169
+ def stamp_hash(self, h5file_name, new_hash):
170
+ '''Loads a file, stamps it, and returns the opened file in read only'''
171
+ h5file = h5io.WESTPAH5File(h5file_name, 'r+')
172
+ h5file.attrs['arg_hash'] = new_hash
173
+ h5file.close()
174
+ h5file = h5io.WESTPAH5File(h5file_name, 'r')
175
+ return h5file
176
+
177
+ def analysis_structure(self):
178
+ '''
179
+ Run automatically on startup. Parses through the configuration file, and loads up all the data files from the different
180
+ analysis schematics. If they don't exist, it creates them automatically by hooking in to existing analysis routines
181
+ and going from there.
182
+
183
+ It does this by calling in the make_parser_and_process function for w_{assign,reweight,direct} using a custom built list
184
+ of args. The user can specify everything in the configuration file that would have been specified on the command line.
185
+
186
+ For instance, were one to call w_direct as follows:
187
+
188
+ w_direct --evolution cumulative --step-iter 1 --disable-correl
189
+
190
+ the west.cfg would look as follows:
191
+
192
+ west:
193
+ analysis:
194
+ w_direct:
195
+ evolution: cumulative
196
+ step_iter: 1
197
+ extra: ['disable-correl']
198
+
199
+ Alternatively, if one wishes to use the same options for both w_direct and w_reweight, the key 'w_direct' can be replaced
200
+ with 'kinetics'.
201
+ '''
202
+ # Make sure everything exists.
203
+ try:
204
+ os.mkdir(self.__settings['directory'])
205
+ except Exception:
206
+ pass
207
+ # Now, check to see whether they exist, and then load them.
208
+ self.__analysis_schemes__ = {}
209
+ # We really need to implement some sort of default behavior if an analysis scheme isn't set.
210
+ # Right now, we just crash. That isn't really graceful.
211
+ for scheme in self.__settings['analysis_schemes']:
212
+ if self.__settings['analysis_schemes'][scheme]['enabled']:
213
+ if self.work_manager.running is False:
214
+ self.work_manager.startup()
215
+ path = os.path.join(os.getcwd(), self.__settings['directory'], scheme)
216
+ # if 'postanalysis' in self.__settings['analysis_schemes'][scheme] and 'postanalysis' in self.__settings['postanalysis']:
217
+ # Should clean this up. But it uses the default global setting if a by-scheme one isn't set.
218
+ if 'postanalysis' in self.__settings:
219
+ if 'postanalysis' in self.__settings['analysis_schemes'][scheme]:
220
+ pass
221
+ else:
222
+ self.__settings['analysis_schemes'][scheme]['postanalysis'] = self.__settings['postanalysis']
223
+ try:
224
+ os.mkdir(path)
225
+ except Exception:
226
+ pass
227
+ self.__analysis_schemes__[scheme] = {}
228
+ try:
229
+ if (
230
+ self.__settings['analysis_schemes'][scheme]['postanalysis'] is True
231
+ or self.__settings['postanalysis'] is True
232
+ ):
233
+ analysis_files = ['assign', 'direct', 'reweight']
234
+ else:
235
+ analysis_files = ['assign', 'direct']
236
+ except Exception:
237
+ analysis_files = ['assign', 'direct']
238
+ self.__settings['analysis_schemes'][scheme]['postanalysis'] = False
239
+ reanalyze_kinetics = False
240
+ assign_hash = None
241
+ for name in analysis_files:
242
+ arg_hash = None
243
+ if self.reanalyze is True:
244
+ reanalyze_kinetics = True
245
+ try:
246
+ os.remove(os.path.join(path, '{}.h5'.format(name)))
247
+ except Exception:
248
+ pass
249
+ else:
250
+ try:
251
+ # Try to load the hash. If we fail to load the hash or the file, we need to reload.
252
+ # if self.reanalyze == True:
253
+ # raise ValueError('Reanalyze set to true.')
254
+ self.__analysis_schemes__[scheme][name] = h5io.WESTPAH5File(
255
+ os.path.join(path, '{}.h5'.format(name)), 'r'
256
+ )
257
+ arg_hash = self.__analysis_schemes__[scheme][name].attrs['arg_hash']
258
+ if name == 'assign':
259
+ assign_hash = arg_hash
260
+ except Exception:
261
+ pass
262
+ # We shouldn't rely on this.
263
+ # self.reanalyze = True
264
+ if True:
265
+ if name == 'assign':
266
+ assign = w_assign.WAssign()
267
+
268
+ w_assign_config = {'output': os.path.join(path, '{}.h5'.format(name))}
269
+ try:
270
+ w_assign_config.update(self.__settings['w_assign'])
271
+ except Exception:
272
+ pass
273
+ try:
274
+ w_assign_config.update(self.__settings['analysis_schemes'][scheme]['w_assign'])
275
+ except Exception:
276
+ pass
277
+ args = []
278
+ for key, value in w_assign_config.items():
279
+ if key != 'extra':
280
+ args.append(str('--') + str(key).replace('_', '-'))
281
+ args.append(str(value))
282
+ # This is for stuff like disabling correlation analysis, etc.
283
+ if 'extra' in list(w_assign_config.keys()):
284
+ # We're sorting to ensure that the order doesn't matter.
285
+ for value in sorted(w_assign_config['extra']):
286
+ args.append(str('--') + str(value).replace('_', '-'))
287
+ # We're just calling the built in function.
288
+ # This is a lot cleaner than what we had in before, and far more workable.
289
+ args.append('--config-from-file')
290
+ args.append('--scheme-name')
291
+ args.append('{}'.format(scheme))
292
+ # Why are we calling this if we're not sure we're remaking the file?
293
+ # We need to load up the bin mapper and states and see if they're the same.
294
+ assign.make_parser_and_process(args=args)
295
+ import pickle
296
+
297
+ # new_hash = self.hash_args(args=args, path=path, extra=[self.niters, pickle.dumps(assign.binning.mapper), assign.states])
298
+ # We need to encode it properly to ensure that some OS specific thing doesn't kill us. Same goes for the args, ultimately.
299
+ # Mostly, we just need to ensure that we're consistent.
300
+ new_hash = self.hash_args(
301
+ args=args,
302
+ path=path,
303
+ extra=[
304
+ int(self.niters),
305
+ codecs.encode(pickle.dumps(assign.binning.mapper), "base64"),
306
+ base64.b64encode(str(assign.states).encode()),
307
+ ],
308
+ )
309
+ # Let's check the hash. If the hash is the same, we don't need to reload.
310
+ if self.debug_mode is True:
311
+ print('{:<10}: old hash, new hash -- {}, {}'.format(name, arg_hash, new_hash))
312
+ if self.ignore_hash is False and (arg_hash != new_hash or self.reanalyze is True):
313
+ # If the hashes are different, or we need to reanalyze, delete the file.
314
+ try:
315
+ os.remove(os.path.join(path, '{}.h5'.format(name)))
316
+ except Exception:
317
+ pass
318
+ print('Reanalyzing file {}.h5 for scheme {}.'.format(name, scheme))
319
+ # reanalyze_kinetics = True
320
+ # We want to use the work manager we have here. Otherwise, just let the tool sort out what it needs, honestly.
321
+ assign.work_manager = self.work_manager
322
+
323
+ assign.go()
324
+ assign.data_reader.close()
325
+
326
+ # Stamp w/ hash, then reload as read only.
327
+ self.__analysis_schemes__[scheme][name] = self.stamp_hash(
328
+ os.path.join(path, '{}.h5'.format(name)), new_hash
329
+ )
330
+ del assign
331
+ # Update the assignment hash.
332
+ assign_hash = new_hash
333
+
334
+ # Since these are all contained within one tool, now, we want it to just... load everything.
335
+ if name == 'direct' or name == 'reweight':
336
+ if name == 'direct':
337
+ analysis = w_direct.WDirect()
338
+ if name == 'reweight':
339
+ analysis = w_reweight.WReweight()
340
+
341
+ analysis_config = {
342
+ 'assignments': os.path.join(path, '{}.h5'.format('assign')),
343
+ 'output': os.path.join(path, '{}.h5'.format(name)),
344
+ 'kinetics': os.path.join(path, '{}.h5'.format(name)),
345
+ }
346
+
347
+ # Pull from general analysis options, then general SPECIFIC options for each analysis,
348
+ # then general options for that analysis scheme, then specific options for the analysis type in the scheme.
349
+
350
+ try:
351
+ analysis_config.update(self.__settings['kinetics'])
352
+ except Exception:
353
+ pass
354
+ try:
355
+ analysis_config.update(self.__settings['w_{}'.format(name)])
356
+ except Exception:
357
+ pass
358
+ try:
359
+ analysis_config.update(self.__settings['analysis_schemes'][scheme]['kinetics'])
360
+ except Exception:
361
+ pass
362
+ try:
363
+ analysis_config.update(self.__settings['analysis_schemes'][scheme]['w_{}'.format(name)])
364
+ except Exception:
365
+ pass
366
+
367
+ # We're pulling in a default set of arguments, then updating them with arguments from the west.cfg file, if appropriate, after setting the appropriate command
368
+ # Then, we call the magic function 'make_parser_and_process' with the arguments we've pulled in.
369
+ # The tool has no real idea it's being called outside of its actual function, and we're good to go.
370
+ args = ['all']
371
+ for key, value in analysis_config.items():
372
+ if key != 'extra':
373
+ args.append(str('--') + str(key).replace('_', '-'))
374
+ args.append(str(value))
375
+ # This is for stuff like disabling correlation analysis, etc.
376
+ if 'extra' in list(analysis_config.keys()):
377
+ for value in sorted(analysis_config['extra']):
378
+ args.append(str('--') + str(value).replace('_', '-'))
379
+ # We want to not display the averages, so...
380
+ args.append('--disable-averages')
381
+ new_hash = self.hash_args(args=args, path=path, extra=[int(self.niters), assign_hash])
382
+ # if arg_hash != new_hash or self.reanalyze == True or reanalyze_kinetics == True:
383
+ if self.debug_mode is True:
384
+ print('{:<10}: old hash, new hash -- {}, {}'.format(name, arg_hash, new_hash))
385
+ if self.ignore_hash is False and (arg_hash != new_hash or reanalyze_kinetics is True):
386
+ try:
387
+ os.remove(os.path.join(path, '{}.h5'.format(name)))
388
+ except Exception:
389
+ pass
390
+ print('Reanalyzing file {}.h5 for scheme {}.'.format(name, scheme))
391
+ analysis.make_parser_and_process(args=args)
392
+ # We want to hook into the existing work manager.
393
+ analysis.work_manager = self.work_manager
394
+
395
+ analysis.go()
396
+
397
+ # Open!
398
+ self.__analysis_schemes__[scheme][name] = self.stamp_hash(
399
+ os.path.join(path, '{}.h5'.format(name)), new_hash
400
+ )
401
+ del analysis
402
+
403
+ # Make sure this doesn't get too far out, here. We need to keep it alive as long as we're actually analyzing things.
404
+ # self.work_manager.shutdown()
405
+ print("")
406
+ print("Complete!")
407
+
408
+ @property
409
+ def assign(self):
410
+ return self.__analysis_schemes__[str(self.scheme)]['assign']
411
+
412
+ @property
413
+ def direct(self):
414
+ """
415
+ The output from w_kinavg.py from the current scheme.
416
+ """
417
+ return self.__analysis_schemes__[str(self.scheme)]['direct']
418
+
419
+ @property
420
+ def state_labels(self):
421
+ print("State labels and definitions!")
422
+ for istate, state in enumerate(self.assign['state_labels']):
423
+ print('{}: {}'.format(istate, state))
424
+ print('{}: {}'.format(istate + 1, 'Unknown'))
425
+
426
+ @property
427
+ def bin_labels(self):
428
+ print("Bin definitions! ")
429
+ for istate, state in enumerate(self.assign['bin_labels']):
430
+ print('{}: {}'.format(istate, state))
431
+
432
+ @property
433
+ def west(self):
434
+ return self.data_reader.data_manager.we_h5file
435
+
436
+ @property
437
+ def reweight(self):
438
+ if self.__settings['analysis_schemes'][str(self.scheme)]['postanalysis'] is True:
439
+ return self.__analysis_schemes__[str(self.scheme)]['reweight']
440
+ else:
441
+ value = "This sort of analysis has not been enabled."
442
+ current = {
443
+ 'bin_prob_evolution': value,
444
+ 'color_prob_evolution': value,
445
+ 'conditional_flux_evolution': value,
446
+ 'rate_evolution': value,
447
+ 'state_labels': value,
448
+ 'state_prob_evolution': value,
449
+ }
450
+ current.update({'bin_populations': value, 'iterations': value})
451
+ return current
452
+
453
+ @property
454
+ def scheme(self):
455
+ '''
456
+ Returns and sets what scheme is currently in use.
457
+ To see what schemes are available, run:
458
+
459
+ w.list_schemes
460
+
461
+ '''
462
+ # Let's do this a few different ways.
463
+ # We want to return things about the DIFFERENT schemes, if possible.
464
+ if self._scheme is None:
465
+ self._scheme = WIPIScheme(
466
+ scheme=self.__analysis_schemes__, name=self._schemename, parent=self, settings=self.__settings
467
+ )
468
+
469
+ # This just ensures that when we call it, it's clean.
470
+ self._scheme.name = None
471
+ return self._scheme
472
+
473
+ @scheme.setter
474
+ def scheme(self, scheme):
475
+ self._future = None
476
+ self._current = None
477
+ self._past = None
478
+ if scheme in self.__settings['analysis_schemes']:
479
+ pass
480
+ else:
481
+ for ischeme, schemename in enumerate(self.__settings['analysis_schemes']):
482
+ if ischeme == scheme:
483
+ scheme = schemename
484
+ if (
485
+ self.__settings['analysis_schemes'][scheme]['enabled'] is True
486
+ or self.__settings['analysis_schemes'][scheme]['enabled'] is None
487
+ ):
488
+ self._schemename = scheme
489
+ else:
490
+ print("Scheme cannot be changed to scheme: {}; it is not enabled!".format(scheme))
491
+
492
+ @property
493
+ def list_schemes(self):
494
+ '''
495
+ Lists what schemes are configured in west.cfg file.
496
+ Schemes should be structured as follows, in west.cfg:
497
+
498
+ west:
499
+ system:
500
+ analysis:
501
+ directory: analysis
502
+ analysis_schemes:
503
+ scheme.1:
504
+ enabled: True
505
+ states:
506
+ - label: unbound
507
+ coords: [[7.0]]
508
+ - label: bound
509
+ coords: [[2.7]]
510
+ bins:
511
+ - type: RectilinearBinMapper
512
+ boundaries: [[0.0, 2.80, 7, 10000]]
513
+ '''
514
+ # print("The following schemes are available:")
515
+ # print("")
516
+ # for ischeme, scheme in enumerate(self.__settings['analysis_schemes']):
517
+ # print('{}. Scheme: {}'.format(ischeme, scheme))
518
+ # print("")
519
+ # print("Set via name, or via the index listed.")
520
+ # print("")
521
+ # print("Current scheme: {}".format(self.scheme))
522
+ self._scheme.list_schemes
523
+
524
+ @property
525
+ def iteration(self):
526
+ '''
527
+ Returns/sets the current iteration.
528
+ '''
529
+ # print("The current iteration is {}".format(self._iter))
530
+ return self._iter
531
+
532
+ @iteration.setter
533
+ def iteration(self, value):
534
+ print("Setting iteration to iter {}.".format(value))
535
+ if value <= 0:
536
+ print("Iteration must begin at 1.")
537
+ value = 1
538
+ if value > self.niters:
539
+ print("Cannot go beyond {} iterations!".format(self.niters))
540
+ print("Setting to {}".format(self.niters))
541
+ value = self.niters
542
+ # We want to trigger a rebuild on our current/past/future bits.
543
+ # The scheme should automatically reset to the proper iteration, but
544
+ # future needs to be manually triggered.
545
+ self._iter = value
546
+ self._future = None
547
+ return self._iter
548
+
549
+ @property
550
+ def current(self):
551
+ '''
552
+ The current iteration. See help for __get_data_for_iteration__
553
+ '''
554
+ return self.scheme[self.scheme.scheme].current
555
+
556
+ @property
557
+ def past(self):
558
+ '''
559
+ The previous iteration. See help for __get_data_for_iteration__
560
+ '''
561
+ return self.scheme[self.scheme.scheme].past
562
+
563
+ def trace(self, seg_id):
564
+ '''
565
+ Runs a trace on a seg_id within the current iteration, all the way back to the beginning,
566
+ returning a dictionary containing all interesting information:
567
+
568
+ seg_id, pcoord, states, bins, weights, iteration, auxdata (optional)
569
+
570
+ sorted in chronological order.
571
+
572
+
573
+ Call with a seg_id.
574
+ '''
575
+ if seg_id >= self.current.walkers:
576
+ print("Walker seg_id # {} is beyond the max count of {} walkers.".format(seg_id, self.current.walkers))
577
+ return 1
578
+ pi = self.progress.indicator
579
+ with pi:
580
+ pi.new_operation('Tracing scheme:iter:seg_id {}:{}:{}'.format(self.scheme, self.iteration, seg_id), self.iteration)
581
+ current = {'seg_id': [], 'pcoord': [], 'states': [], 'weights': [], 'iteration': [], 'bins': []}
582
+ keys = []
583
+ try:
584
+ current['auxdata'] = {}
585
+ for key in list(self.current['auxdata'].keys()):
586
+ current['auxdata'][key] = []
587
+ key = []
588
+ except Exception:
589
+ pass
590
+ for iter in reversed(list(range(1, self.iteration + 1))):
591
+ iter_group = self.data_reader.get_iter_group(iter)
592
+ current['pcoord'].append(iter_group['pcoord'][seg_id, :, :])
593
+ current['states'].append(self.assign['trajlabels'][iter - 1, seg_id, :])
594
+ current['bins'].append(self.assign['assignments'][iter - 1, seg_id, :])
595
+ current['seg_id'].append(seg_id)
596
+ current['weights'].append(iter_group['seg_index']['weight'][seg_id])
597
+ current['iteration'].append(iter)
598
+ try:
599
+ for key in keys:
600
+ current['auxdata'][key].append(iter_group['auxdata'][key][seg_id])
601
+ except Exception:
602
+ pass
603
+ seg_id = iter_group['seg_index']['parent_id'][seg_id]
604
+ if seg_id < 0:
605
+ # Necessary for steady state simulations. This means they started in that iteration.
606
+ break
607
+ pi.progress += 1
608
+ current['seg_id'] = list(reversed(current['seg_id']))
609
+ current['iteration'] = list(reversed(current['iteration']))
610
+ current['states'] = np.concatenate(np.array(list(reversed(current['states']))))
611
+ current['bins'] = np.concatenate(np.array(list(reversed(current['bins']))))
612
+ current['weights'] = np.array(list(reversed(current['weights'])))
613
+ current['pcoord'] = np.concatenate(np.array(list(reversed(current['pcoord']))))
614
+ try:
615
+ for key in keys():
616
+ current['auxdata'][key] = np.concatenate(np.array(list(reversed(current['auxdata'][key]))))
617
+ except Exception:
618
+ pass
619
+ current['state_labels'] = self.assign['state_labels']
620
+ for i in ['pcoord', 'states', 'bins', 'weights']:
621
+ current[i] = WIPIDataset(raw=current[i], key=i)
622
+ if i == 'weights':
623
+ current[i].plotter = Plotter(
624
+ np.log10(current[i].raw), str('log10 of ' + str(i)), iteration=current[i].raw.shape[0], interface=self.interface
625
+ )
626
+ else:
627
+ current[i].plotter = Plotter(current[i].raw, i, iteration=current[i].raw.shape[0], interface=self.interface)
628
+ current[i].plot = current[i].plotter.plot
629
+ return WIPIDataset(raw=current, key=seg_id)
630
+
631
+ @property
632
+ def future(self, value=None):
633
+ '''
634
+ Similar to current/past, but keyed differently and returns different datasets.
635
+ See help for Future.
636
+ '''
637
+ if self._future is None:
638
+ self._future = self.Future(raw=self.__get_children__(), key=None)
639
+ self._future.iteration = self.iteration + 1
640
+ return self._future
641
+
642
+ class Future(WIPIDataset):
643
+ # This isn't a real fancy one.
644
+ def __getitem__(self, value):
645
+ if isinstance(value, str):
646
+ print(list(self.__dict__.keys()))
647
+ try:
648
+ return self.__dict__['raw'][value]
649
+ except Exception:
650
+ print('{} is not a valid data structure.'.format(value))
651
+ elif isinstance(value, int) or isinstance(value, np.int64):
652
+ # Otherwise, we assume they're trying to index for a seg_id.
653
+ # if value < self.parent.walkers:
654
+ current = {}
655
+ current['pcoord'] = self.__dict__['raw']['pcoord'][value]
656
+ current['states'] = self.__dict__['raw']['states'][value]
657
+ current['bins'] = self.__dict__['raw']['bins'][value]
658
+ current['parents'] = self.__dict__['raw']['parents'][value]
659
+ current['seg_id'] = self.__dict__['raw']['seg_id'][value]
660
+ current['weights'] = self.__dict__['raw']['weights'][value]
661
+ try:
662
+ current['auxdata'] = {}
663
+ for key in list(self.__dict__['raw']['auxdata'].keys()):
664
+ current['auxdata'][key] = self.__dict__['raw']['auxdata'][key][value]
665
+ except Exception:
666
+ pass
667
+ current = WIPIDataset(current, 'Segment {} in Iter {}'.format(value, self.iteration))
668
+ return current
669
+
670
+ def __get_children__(self):
671
+ '''
672
+ Returns all information about the children of a given walker in the current iteration.
673
+ Used to generate and create the future object, if necessary.
674
+ '''
675
+
676
+ if self.iteration == self.niters:
677
+ print("Currently at iteration {}, which is the max. There are no children!".format(self.iteration))
678
+ return 0
679
+ iter_data = __get_data_for_iteration__(value=self.iteration + 1, parent=self)
680
+ future = {
681
+ 'weights': [],
682
+ 'pcoord': [],
683
+ 'parents': [],
684
+ 'summary': iter_data['summary'],
685
+ 'seg_id': [],
686
+ 'walkers': iter_data['walkers'],
687
+ 'states': [],
688
+ 'bins': [],
689
+ }
690
+ for seg_id in range(0, self.current.walkers):
691
+ children = np.where(iter_data['parents'] == seg_id)[0]
692
+ if len(children) == 0:
693
+ error = "No children for seg_id {}.".format(seg_id)
694
+ future['weights'].append(error)
695
+ future['pcoord'].append(error)
696
+ future['parents'].append(error)
697
+ future['seg_id'].append(error)
698
+ future['states'].append(error)
699
+ future['bins'].append(error)
700
+ else:
701
+ # Now, we're gonna put them in the thing.
702
+ value = self.iteration + 1
703
+ future['weights'].append(iter_data['weights'][children])
704
+ future['pcoord'].append(iter_data['pcoord'][...][children, :, :])
705
+ try:
706
+ aux_data = iter_data['auxdata'][...][children, :, :]
707
+ try:
708
+ future['aux_data'].append(aux_data)
709
+ except Exception:
710
+ future['aux_data'] = aux_data
711
+ except Exception:
712
+ pass
713
+ future['parents'].append(iter_data['parents'][children])
714
+ future['seg_id'].append(iter_data['seg_id'][children])
715
+ future['states'].append(self.assign['trajlabels'][value - 1, children, :])
716
+ future['bins'].append(self.assign['assignments'][value - 1, children, :])
717
+ return future
718
+
719
+ def go(self):
720
+ '''
721
+ Function automatically called by main() when launched via the command line interface.
722
+ Generally, call main, not this function.
723
+ '''
724
+ w = self
725
+
726
+ print("")
727
+ print("Welcome to w_ipa (WESTPA Interactive Python Analysis) v. {}!".format(w.version))
728
+ print("Run w.introduction for a more thorough introduction, or w.help to see a list of options.")
729
+ print("Running analysis & loading files.")
730
+ self.data_reader.open()
731
+ self.analysis_structure()
732
+ # Seems to be consistent with other tools, such as w_assign. For setting the iterations.
733
+ self.data_reader.open()
734
+ self.niters = self.data_reader.current_iteration - 1
735
+ self.iteration = self.niters
736
+ try:
737
+ print('Your current scheme, system and iteration are : {}, {}, {}'.format(w.scheme, os.getcwd(), w.iteration))
738
+ except Exception:
739
+ pass
740
+
741
+ @property
742
+ def introduction(self):
743
+ '''
744
+ Just spits out an introduction, in case someone doesn't call help.
745
+ '''
746
+ help_string = '''
747
+ Call as a dictionary item or a .attribute:
748
+
749
+ w.past, w.current, w.future:
750
+
751
+ {current}
752
+
753
+ Raw schemes can be accessed as follows:
754
+
755
+ w.scheme.{scheme_keys}
756
+
757
+ and contain mostly the same datasets associated with w.
758
+
759
+ The following give raw access to the h5 files associated with the current scheme
760
+
761
+ w.west
762
+ w.assign
763
+ w.direct
764
+ w.reweight
765
+
766
+ OTHER:
767
+
768
+ {w}
769
+
770
+ '''.format(
771
+ current=self.__format_keys__(self.current.__dir__(), split=' ', offset=12),
772
+ scheme_keys=self.__format_keys__(list(self._scheme.raw.keys())),
773
+ w=self.__format_keys__(self.__dir__(), offset=8, max_length=0, split='', prepend='w.'),
774
+ )
775
+ print(help_string)
776
+
777
+ # Just a little function to be used with the introduction.
778
+ def __format_keys__(self, keys, split='/', offset=0, max_length=80, prepend=''):
779
+ rtn = ''
780
+ run_length = 0
781
+ for key in keys:
782
+ rtn += prepend + str(key) + split
783
+ run_length += len(str(key))
784
+ if run_length >= max_length:
785
+ run_length = offset
786
+ rtn += '\n' + ' ' * offset
787
+ if rtn[-1] == split:
788
+ return rtn[:-1]
789
+ else:
790
+ return rtn
791
+
792
+ @property
793
+ def help(self):
794
+ '''Just a minor function to call help on itself. Only in here to really help someone get help.'''
795
+ help(self)
796
+
797
+ def _repr_pretty_(self, p, cycle):
798
+ self.introduction
799
+ return " "
800
+
801
+ def __dir__(self):
802
+ return_list = ['past', 'current', 'future']
803
+ # For the moment, don't expose direct, reweight, or assign, as these are scheme dependent files.
804
+ # They do exist, and always link to the current scheme, however.
805
+ return_list += ['iteration', 'niters', 'scheme', 'list_schemes', 'bin_labels', 'state_labels', 'west', 'trace']
806
+ return sorted(set(return_list))
807
+
808
+
809
+ def entry_point():
810
+ west = WIPI()
811
+ w = west
812
+ # We're gonna print some defaults.
813
+ w.main()
814
+ if w.analysis_mode is False:
815
+ from IPython import embed
816
+ import IPython
817
+
818
+ # We're using this to set magic commands.
819
+ # Mostly, we're using it to allow tab completion of objects stored in dictionaries.
820
+ try:
821
+ # Worked on MacOS. Probably just an older version.
822
+ c = IPython.Config()
823
+ except Exception:
824
+ # Seems to be necessary on Linux, and likely on newer installs.
825
+ c = IPython.terminal.ipapp.load_default_config()
826
+ c.IPCompleter.greedy = True
827
+ embed(banner1='', exit_msg='Leaving w_ipa... goodbye.', config=c)
828
+ print("")
829
+
830
+
831
+ if __name__ == '__main__':
832
+ entry_point()