westpa 2022.12__cp312-cp312-macosx_10_13_x86_64.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.

Potentially problematic release.


This version of westpa might be problematic. Click here for more details.

Files changed (149) 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 +746 -0
  5. westpa/analysis/statistics.py +27 -0
  6. westpa/analysis/trajectories.py +360 -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 +596 -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 +547 -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 +833 -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_trace.py +599 -0
  38. westpa/core/__init__.py +0 -0
  39. westpa/core/_rc.py +673 -0
  40. westpa/core/binning/__init__.py +55 -0
  41. westpa/core/binning/_assign.cpython-312-darwin.so +0 -0
  42. westpa/core/binning/assign.py +455 -0
  43. westpa/core/binning/binless.py +96 -0
  44. westpa/core/binning/binless_driver.py +54 -0
  45. westpa/core/binning/binless_manager.py +190 -0
  46. westpa/core/binning/bins.py +47 -0
  47. westpa/core/binning/mab.py +506 -0
  48. westpa/core/binning/mab_driver.py +54 -0
  49. westpa/core/binning/mab_manager.py +198 -0
  50. westpa/core/data_manager.py +1694 -0
  51. westpa/core/extloader.py +74 -0
  52. westpa/core/h5io.py +995 -0
  53. westpa/core/kinetics/__init__.py +24 -0
  54. westpa/core/kinetics/_kinetics.cpython-312-darwin.so +0 -0
  55. westpa/core/kinetics/events.py +147 -0
  56. westpa/core/kinetics/matrates.py +156 -0
  57. westpa/core/kinetics/rate_averaging.py +266 -0
  58. westpa/core/progress.py +218 -0
  59. westpa/core/propagators/__init__.py +54 -0
  60. westpa/core/propagators/executable.py +719 -0
  61. westpa/core/reweight/__init__.py +14 -0
  62. westpa/core/reweight/_reweight.cpython-312-darwin.so +0 -0
  63. westpa/core/reweight/matrix.py +126 -0
  64. westpa/core/segment.py +119 -0
  65. westpa/core/sim_manager.py +835 -0
  66. westpa/core/states.py +359 -0
  67. westpa/core/systems.py +93 -0
  68. westpa/core/textio.py +74 -0
  69. westpa/core/trajectory.py +330 -0
  70. westpa/core/we_driver.py +910 -0
  71. westpa/core/wm_ops.py +43 -0
  72. westpa/core/yamlcfg.py +391 -0
  73. westpa/fasthist/__init__.py +34 -0
  74. westpa/fasthist/_fasthist.cpython-312-darwin.so +0 -0
  75. westpa/mclib/__init__.py +271 -0
  76. westpa/mclib/__main__.py +28 -0
  77. westpa/mclib/_mclib.cpython-312-darwin.so +0 -0
  78. westpa/oldtools/__init__.py +4 -0
  79. westpa/oldtools/aframe/__init__.py +35 -0
  80. westpa/oldtools/aframe/atool.py +75 -0
  81. westpa/oldtools/aframe/base_mixin.py +26 -0
  82. westpa/oldtools/aframe/binning.py +178 -0
  83. westpa/oldtools/aframe/data_reader.py +560 -0
  84. westpa/oldtools/aframe/iter_range.py +200 -0
  85. westpa/oldtools/aframe/kinetics.py +117 -0
  86. westpa/oldtools/aframe/mcbs.py +153 -0
  87. westpa/oldtools/aframe/output.py +39 -0
  88. westpa/oldtools/aframe/plotting.py +90 -0
  89. westpa/oldtools/aframe/trajwalker.py +126 -0
  90. westpa/oldtools/aframe/transitions.py +469 -0
  91. westpa/oldtools/cmds/__init__.py +0 -0
  92. westpa/oldtools/cmds/w_ttimes.py +361 -0
  93. westpa/oldtools/files.py +34 -0
  94. westpa/oldtools/miscfn.py +23 -0
  95. westpa/oldtools/stats/__init__.py +4 -0
  96. westpa/oldtools/stats/accumulator.py +35 -0
  97. westpa/oldtools/stats/edfs.py +129 -0
  98. westpa/oldtools/stats/mcbs.py +96 -0
  99. westpa/tools/__init__.py +33 -0
  100. westpa/tools/binning.py +472 -0
  101. westpa/tools/core.py +340 -0
  102. westpa/tools/data_reader.py +159 -0
  103. westpa/tools/dtypes.py +31 -0
  104. westpa/tools/iter_range.py +198 -0
  105. westpa/tools/kinetics_tool.py +340 -0
  106. westpa/tools/plot.py +283 -0
  107. westpa/tools/progress.py +17 -0
  108. westpa/tools/selected_segs.py +154 -0
  109. westpa/tools/wipi.py +751 -0
  110. westpa/trajtree/__init__.py +4 -0
  111. westpa/trajtree/_trajtree.cpython-312-darwin.so +0 -0
  112. westpa/trajtree/trajtree.py +117 -0
  113. westpa/westext/__init__.py +0 -0
  114. westpa/westext/adaptvoronoi/__init__.py +3 -0
  115. westpa/westext/adaptvoronoi/adaptVor_driver.py +214 -0
  116. westpa/westext/hamsm_restarting/__init__.py +3 -0
  117. westpa/westext/hamsm_restarting/example_overrides.py +35 -0
  118. westpa/westext/hamsm_restarting/restart_driver.py +1165 -0
  119. westpa/westext/stringmethod/__init__.py +11 -0
  120. westpa/westext/stringmethod/fourier_fitting.py +69 -0
  121. westpa/westext/stringmethod/string_driver.py +253 -0
  122. westpa/westext/stringmethod/string_method.py +306 -0
  123. westpa/westext/weed/BinCluster.py +180 -0
  124. westpa/westext/weed/ProbAdjustEquil.py +100 -0
  125. westpa/westext/weed/UncertMath.py +247 -0
  126. westpa/westext/weed/__init__.py +10 -0
  127. westpa/westext/weed/weed_driver.py +192 -0
  128. westpa/westext/wess/ProbAdjust.py +101 -0
  129. westpa/westext/wess/__init__.py +6 -0
  130. westpa/westext/wess/wess_driver.py +217 -0
  131. westpa/work_managers/__init__.py +57 -0
  132. westpa/work_managers/core.py +396 -0
  133. westpa/work_managers/environment.py +134 -0
  134. westpa/work_managers/mpi.py +318 -0
  135. westpa/work_managers/processes.py +187 -0
  136. westpa/work_managers/serial.py +28 -0
  137. westpa/work_managers/threads.py +79 -0
  138. westpa/work_managers/zeromq/__init__.py +20 -0
  139. westpa/work_managers/zeromq/core.py +641 -0
  140. westpa/work_managers/zeromq/node.py +131 -0
  141. westpa/work_managers/zeromq/work_manager.py +526 -0
  142. westpa/work_managers/zeromq/worker.py +320 -0
  143. westpa-2022.12.dist-info/AUTHORS +22 -0
  144. westpa-2022.12.dist-info/LICENSE +21 -0
  145. westpa-2022.12.dist-info/METADATA +193 -0
  146. westpa-2022.12.dist-info/RECORD +149 -0
  147. westpa-2022.12.dist-info/WHEEL +6 -0
  148. westpa-2022.12.dist-info/entry_points.txt +29 -0
  149. westpa-2022.12.dist-info/top_level.txt +1 -0
@@ -0,0 +1,506 @@
1
+ import logging
2
+ from typing import List, Optional
3
+ import numpy as np
4
+ import westpa
5
+ from westpa.core.binning import FuncBinMapper
6
+ from os.path import expandvars
7
+
8
+ log = logging.getLogger(__name__)
9
+
10
+
11
+ class MABBinMapper(FuncBinMapper):
12
+ """
13
+ Adaptively place bins between minimum and maximum segments along
14
+ the progress coordinate. Extrema and bottleneck segments are assigned
15
+ to their own bins.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ nbins: List[int],
21
+ direction: Optional[List[int]] = None,
22
+ skip: Optional[List[int]] = None,
23
+ bottleneck: bool = True,
24
+ pca: bool = False,
25
+ mab_log: bool = False,
26
+ bin_log: bool = False,
27
+ bin_log_path: str = "$WEST_SIM_ROOT/binbounds.log",
28
+ ):
29
+ """
30
+ Parameters
31
+ ----------
32
+ nbins : list of int
33
+ List of number of bins in each dimension.
34
+ direction : Optional[list of int], default: None
35
+ List of directions in each dimension. Direction options:
36
+ 0 : default split at leading and lagging boundaries
37
+ 1 : split at leading boundary only
38
+ -1 : split at lagging boundary only
39
+ 86 : no splitting at either leading or lagging boundary (both bottlenecks included)
40
+ skip : Optional[list of int], default: None
41
+ List of skip flags for each dimension. Default None (no skipping).
42
+ bottleneck : bool, default: True
43
+ Whether to enable bottleneck walker splitting.
44
+ pca : bool, default: False
45
+ Whether to perform PCA on progress coordinates before bin assignment.
46
+ mab_log : bool, default: False
47
+ Whether to output MAB info to west.log.
48
+ bin_log : bool, default: False
49
+ Whether to output MAB bin boundaries to a log file.
50
+ bin_log_path : str, default: "$WEST_SIM_ROOT/binbounds.log"
51
+ Path to output bin boundaries.
52
+ """
53
+ # Verifying parameters
54
+ if nbins is None:
55
+ raise ValueError("nbins is missing")
56
+ ndim = len(nbins)
57
+
58
+ direction = direction or [0] * ndim
59
+ if len(direction) != ndim:
60
+ direction = [0] * ndim
61
+ log.warning("Direction list is not the correct dimensions, setting to defaults.")
62
+
63
+ skip = skip or [0] * ndim
64
+ if len(skip) != ndim:
65
+ skip = [0] * ndim
66
+ log.warning("Skip list is not the correct dimensions, setting to defaults.")
67
+
68
+ kwargs = dict(
69
+ nbins_per_dim=nbins,
70
+ direction=direction,
71
+ skip=skip,
72
+ bottleneck=bottleneck,
73
+ pca=pca,
74
+ mab_log=mab_log,
75
+ bin_log=bin_log,
76
+ bin_log_path=bin_log_path,
77
+ )
78
+
79
+ n_total_bins = self.determine_total_bins(**kwargs)
80
+
81
+ super().__init__(map_mab, n_total_bins, kwargs=kwargs)
82
+
83
+ def determine_total_bins(
84
+ self, nbins_per_dim: List[int], direction: List[int], skip: List[int], bottleneck: bool, **kwargs
85
+ ) -> int:
86
+ """
87
+ Calculate the total number of bins needed, taking direction and skipping into account.
88
+ This function is necessary because functional bin mappers need to "reserve"
89
+ bins and tell the sim manager how many bins they will need to use, this is
90
+ determined by taking all direction/skipping info into account.
91
+
92
+ Parameters
93
+ ----------
94
+ nbins_per_dim : list of int
95
+ Number of total bins in each dimension within the linear portion.
96
+ direction : list of int
97
+ Direction in each dimension.
98
+ skip : list of int
99
+ List indicating whether to skip each dimension.
100
+ bottleneck : bool
101
+ Whether to include a separate bin for bottleneck walker(s).
102
+ **kwargs : dict
103
+ Additional MAB parameters (unused).
104
+
105
+ Returns
106
+ -------
107
+ n_total_bins : int
108
+ Number of total bins.
109
+ """
110
+ # Update nbins_per_dim with any skipped dimensions, setting number of bins along skipped dimensions to 1
111
+ skip = np.array([bool(s) for s in skip])
112
+ nbins_per_dim = np.array(nbins_per_dim)
113
+ nbins_per_dim[skip] = 1
114
+
115
+ # Total bins is product of all linear bins plus and special bins
116
+ n_total_bins = nbins_per_dim.prod()
117
+ for direct, skip_dim in zip(direction, skip):
118
+ if not skip_dim:
119
+ if direct in [-1, 1]:
120
+ # 1 lead or lag bin + 1 bottleneck bin
121
+ n_total_bins += 1 + 1 * bottleneck
122
+ elif direct == 0:
123
+ # 2 lead/lag bins + 2 bottleneck bins
124
+ n_total_bins += 2 + 2 * bottleneck
125
+ elif direct == 86:
126
+ # 0 lead/lag + 2 bottleneck bins
127
+ n_total_bins += 2 * bottleneck
128
+ return n_total_bins
129
+
130
+
131
+ def map_mab(coords: np.ndarray, mask: np.ndarray, output: List[int], *args, **kwargs) -> List[int]:
132
+ """
133
+ Adaptively place bins based on extrema and bottleneck segments along the progress coordinate.
134
+
135
+ Bottleneck segments are where the difference in probability is the greatest
136
+ along the progress coordinate. Operates per dimension (unless skipped) and places a fixed number of
137
+ evenly spaced bins between the segments with the min and max pcoord values. Extrema and
138
+ bottleneck segments are assigned their own bins.
139
+
140
+ Parameters
141
+ ----------
142
+ coords : np.ndarray
143
+ An array with pcoord and weight info.
144
+ mask : np.ndarray
145
+ Boolean array to filter out unwanted segments.
146
+ output : list
147
+ The main list that, for each segment, holds the bin assignment.
148
+ *args : list
149
+ Additional arguments.
150
+ **kwargs : dict
151
+ Additional keyword arguments. Contains most of the MAB-needed parameters.
152
+
153
+ Returns
154
+ ------
155
+ output : list
156
+ List with bin assignments for each segment.
157
+ """
158
+
159
+ # Argument Processing
160
+ nbins_per_dim = kwargs.get("nbins_per_dim")
161
+ ndim = len(nbins_per_dim)
162
+ pca = kwargs.get("pca", False)
163
+ bottleneck = kwargs.get("bottleneck", True)
164
+ direction = kwargs.get("direction", [0] * ndim)
165
+ skip = kwargs.get("skip", [0] * ndim)
166
+ mab_log = kwargs.get("mab_log", False)
167
+ bin_log = kwargs.get("bin_log", False)
168
+ bin_log_path = kwargs.get("bin_log_path", "$WEST_SIM_ROOT/binbounds.log")
169
+
170
+ if not np.any(mask):
171
+ return output
172
+
173
+ if skip is None:
174
+ skip = [0] * ndim
175
+
176
+ allcoords = coords.copy()
177
+ allmask = mask.copy()
178
+
179
+ weights = None
180
+ isfinal = None
181
+ splitting = False
182
+ report = False
183
+
184
+ # the segments should be sent in by the driver as half initial segments and half final segments
185
+ # allcoords contains all segments
186
+ # coords should contain ONLY final segments
187
+ if coords.shape[1] > ndim:
188
+ if coords[0, -1] == 0:
189
+ report = True
190
+ if coords.shape[1] > ndim + 1:
191
+ isfinal = allcoords[:, ndim + 1].astype(bool)
192
+ else:
193
+ isfinal = np.ones(coords.shape[0], dtype=bool)
194
+ coords = coords[isfinal, :ndim]
195
+ weights = allcoords[isfinal, ndim]
196
+ mask = mask[isfinal]
197
+ splitting = True
198
+
199
+ if not np.any(mask):
200
+ coords = allcoords[:, :ndim]
201
+ mask = allmask
202
+ weights = None
203
+ splitting = False
204
+
205
+ originalcoords = np.copy(coords)
206
+ if pca and len(output) > 1:
207
+ coords = apply_pca(coords, weights)
208
+
209
+ # Computing special bins (bottleneck and boundary bins)
210
+ minlist, maxlist, bottlenecks_forward, bottlenecks_reverse = calculate_bin_boundaries(
211
+ originalcoords, weights, mask, skip, splitting, bottleneck
212
+ )
213
+
214
+ if mab_log and report:
215
+ log_mab_stats(minlist, maxlist, direction, skip)
216
+
217
+ # Assign segments to bins
218
+ n_bottleneck_filled = bin_assignment(
219
+ allcoords,
220
+ allmask,
221
+ minlist,
222
+ maxlist,
223
+ bottlenecks_forward,
224
+ bottlenecks_reverse,
225
+ nbins_per_dim,
226
+ direction,
227
+ skip,
228
+ splitting,
229
+ bottleneck,
230
+ output,
231
+ )
232
+
233
+ # Report MAB bin statistics
234
+ if bin_log and report and westpa.rc.sim_manager.n_iter:
235
+ log_bin_boundaries(
236
+ skip,
237
+ bottleneck,
238
+ direction,
239
+ bin_log_path,
240
+ minlist,
241
+ maxlist,
242
+ nbins_per_dim,
243
+ n_bottleneck_filled,
244
+ bottlenecks_forward,
245
+ bottlenecks_reverse,
246
+ )
247
+
248
+ return output
249
+
250
+
251
+ def apply_pca(coords, weights):
252
+ colavg = np.mean(coords, axis=0)
253
+ varcoords = coords - colavg
254
+ covcoords = np.cov(varcoords.T, aweights=weights)
255
+ eigval, eigvec = np.linalg.eigh(covcoords)
256
+ eigvec = eigvec[:, np.argmax(np.abs(eigvec), axis=1)]
257
+ eigvec[:, np.diag(eigvec) < 0] *= -1
258
+ return np.dot(varcoords, eigvec)
259
+
260
+
261
+ def calculate_bin_boundaries(coords, weights, mask, skip, splitting, bottleneck):
262
+ """
263
+ This function calculates minima, maxima, and bottleneck segments.
264
+ """
265
+ skip = np.array([bool(s) for s in skip])
266
+
267
+ # Initialize lists to hold minima and maxima along each dimension
268
+ minlist, maxlist = [], []
269
+ # Initialize lists to hold bottleneck segments along each dimension
270
+ bottlenecks_forward, bottlenecks_reverse = [None] * len(coords[0]), [None] * len(coords[0])
271
+ # number of unmasked coords
272
+ n_coords = mask.sum()
273
+ # Grabbing all unmasked coords and weights
274
+ unmasked_coords = coords[mask, :]
275
+ unmasked_weights = weights[mask] if weights is not None else None
276
+ # Replace any zero weights with non-zero values so that log(weight) is well-defined
277
+ if unmasked_weights is not None:
278
+ unmasked_weights[unmasked_weights == 0] = 10**-323
279
+ # Looping over each dimension of progress coordinate, even those being skipped
280
+ for n in range(len(coords[0])):
281
+ # We calculate the min and max pcoord along each dimension (boundary segments) even if skipping
282
+ maxlist.append(np.max(coords[mask, n]))
283
+ minlist.append(np.min(coords[mask, n]))
284
+ # Now we calculate the bottleneck segments
285
+ if splitting and bottleneck and not skip[n]:
286
+ bottlenecks_forward[n], bottlenecks_reverse[n] = detect_bottlenecks(unmasked_coords, unmasked_weights, n_coords, n)
287
+
288
+ return minlist, maxlist, bottlenecks_forward, bottlenecks_reverse
289
+
290
+
291
+ def detect_bottlenecks(unmasked_coords, unmasked_weights, n_coords, n):
292
+ """
293
+ Detect the bottleneck segments along the given coordinate n, this uses the weights
294
+ """
295
+ # Grabbing all unmasked coords in current dimension, plus corresponding weights
296
+ # Sort by current dimension in coord, smallest to largest
297
+ sorted_indices = unmasked_coords[:, n].argsort(kind='stable')
298
+
299
+ # Grab sorted coords and weights
300
+ coords_srt = unmasked_coords[sorted_indices, :]
301
+ weights_srt = unmasked_weights[sorted_indices]
302
+
303
+ # Also sort in reverse order for opposite direction
304
+ coords_srt_flip = np.flipud(coords_srt)
305
+ weights_srt_flip = np.flipud(weights_srt)
306
+
307
+ # Initialize the max directional differences along current dimension as None (these may not be updated)
308
+ bottleneck_coords, bottleneck_coords_flip = None, None
309
+ maxdiff, maxdiff_flip = -np.inf, -np.inf
310
+
311
+ # Looping through all non-boundary coords
312
+ # Compute the cumulative weight on either side of each non-boundary walker
313
+ for i in range(1, n_coords - 1):
314
+ # Summing up weights of all walkers ahead of current walker along current dim in both directions
315
+ cumulative_prob = np.sum(weights_srt[i + 1 :])
316
+ cumulative_prob_flip = np.sum(weights_srt_flip[i + 1 :])
317
+ # Compute the difference of log cumulative weight of current walker and all walkers ahead of it (Z im the MAB paper)
318
+ # We use the log as weights vary over many orders of magnitude
319
+ # Note a negative Z indicates the cumulative weight ahead of the current walker is larger than the weight of the current walker,
320
+ # while a positive Z indicates the cumulative weight ahead of the current walker is smaller, indicating a barrier
321
+ Z = np.log(weights_srt[i]) - np.log(cumulative_prob)
322
+ Z_flip = np.log(weights_srt_flip[i]) - np.log(cumulative_prob_flip)
323
+ # Update ALL coords of the current walker into bottlenecks_forward if it is largest
324
+ # This way we uniquely identify a walker by its full set of coordinates
325
+ if Z > maxdiff:
326
+ bottleneck_coords = coords_srt[i, :]
327
+ maxdiff = Z
328
+ if Z_flip > maxdiff_flip:
329
+ bottleneck_coords_flip = coords_srt_flip[i, :]
330
+ maxdiff_flip = Z_flip
331
+ return bottleneck_coords, bottleneck_coords_flip
332
+
333
+
334
+ def log_mab_stats(minlist, maxlist, direction, skip):
335
+ westpa.rc.pstatus("################ MAB stats ################")
336
+ westpa.rc.pstatus(f"minima in each dimension: {minlist}")
337
+ westpa.rc.pstatus(f"maxima in each dimension: {maxlist}")
338
+ westpa.rc.pstatus(f"direction in each dimension: {direction}")
339
+ westpa.rc.pstatus(f"skip in each dimension: {skip}")
340
+ westpa.rc.pstatus("###########################################")
341
+ westpa.rc.pflush()
342
+
343
+
344
+ def bin_assignment(
345
+ coords,
346
+ mask,
347
+ minlist,
348
+ maxlist,
349
+ bottlenecks_forward,
350
+ bottlenecks_reverse,
351
+ nbins_per_dim,
352
+ direction,
353
+ skip,
354
+ splitting,
355
+ bottleneck,
356
+ output,
357
+ ):
358
+ """
359
+ Assign segments to bins based on the minima, maxima, and
360
+ bottleneck segments along the progress coordinate.
361
+ """
362
+ # Update nbins_per_dim with any skipped dimensions, setting number of bins along skipped dimensions to 1
363
+ skip = np.array([bool(s) for s in skip])
364
+ nbins_per_dim = np.array(nbins_per_dim)
365
+ nbins_per_dim[skip] = 1
366
+ direction = np.array(direction)
367
+
368
+ ndim = len(nbins_per_dim)
369
+ n_bottleneck_filled = 0
370
+
371
+ # Boolean arrays that track use of special bins along each dimension
372
+ skip_bneck_fwd = np.array([d == -1 if bottleneck else True for d in direction]) + skip
373
+ skip_bneck_rev = np.array([d == 1 if bottleneck else True for d in direction]) + skip
374
+ skip_lead = np.array([d in [86, -1] for d in direction]) + skip
375
+ skip_lag = np.array([d in [86, 1] for d in direction]) + skip
376
+
377
+ # List of dimensions that are not skipped
378
+ active_dims = np.array([n for n in range(ndim) if not skip[n]])
379
+
380
+ # Compute the boundary bin ID offsets
381
+ # In forward direction, this is all the linear bins
382
+ boundary_bin_id_offset_fwd = nbins_per_dim.prod()
383
+ # In reverse, we add the number of forward boundary bins to the offset
384
+ boundary_bin_id_offset_rev = boundary_bin_id_offset_fwd + (~skip_lead).sum()
385
+
386
+ # Compute the bottleneck bin ID offsets
387
+ # In forward direction, bin IDs are offset by all linear and boundary bins
388
+ bneck_bin_id_offset_fwd = boundary_bin_id_offset_rev + (~skip_lag).sum()
389
+ # In reverse, we add the number of forward bottleneck bins to the offset
390
+ bneck_bin_id_offset_rev = bneck_bin_id_offset_fwd + (~skip_bneck_fwd).sum()
391
+
392
+ # Bin assignment loop over all walkers
393
+ for i in range(len(output)):
394
+ # Skip masked walkers, these walkers bin IDs are unchanged
395
+ if not mask[i]:
396
+ continue
397
+ # Initialize bin ID and special tracker for current coord
398
+ # The special variable indicates a boundary or bottleneck walker (not assigned to the linear space)
399
+ bin_id, special = 0, False
400
+
401
+ # Searching for bottleneck bins first
402
+ if splitting and bottleneck:
403
+ for n in active_dims:
404
+ # Grab coord(s) of current walker
405
+ coord = coords[i][:ndim]
406
+ # Assign bottlenecks, taking directionality into account
407
+ # Check both directions when using 0 or 86
408
+ # Note: 86 implies no leading or lagging bins, but does add bottlenecks for *both* directions when bottleneck is enabled
409
+ # Note: All bottleneck bins will typically be filled unless a walker is simultaneously in bottleneck bins along multiple dimensions
410
+ # or there are too few walkers to compute free energy barriers
411
+ if (coord == bottlenecks_forward[n]).all() and not skip_bneck_fwd[n]:
412
+ bin_id = bneck_bin_id_offset_fwd + n - skip_bneck_fwd[:n].sum()
413
+ special = True
414
+ n_bottleneck_filled += 1
415
+ break
416
+ elif (coord == bottlenecks_reverse[n]).all() and not skip_bneck_rev[n]:
417
+ bin_id = bneck_bin_id_offset_rev + n - skip_bneck_rev[:n].sum()
418
+ special = True
419
+ n_bottleneck_filled += 1
420
+ break
421
+
422
+ # Now check for boundary walkers, taking directionality into account
423
+ # This should only be done after fully checking for bottleneck walkers
424
+ if splitting and not special:
425
+ for n in active_dims:
426
+ # Grab coord of current walker along current dimension
427
+ coord = coords[i, n]
428
+ if (coord == maxlist[n]) and not skip_lead[n]:
429
+ bin_id = boundary_bin_id_offset_fwd + n - skip_lead[:n].sum()
430
+ special = True
431
+ break
432
+ elif (coord == minlist[n]) and not skip_lag[n]:
433
+ bin_id = boundary_bin_id_offset_rev + n - skip_lag[:n].sum()
434
+ special = True
435
+ break
436
+
437
+ # Now check for linear bin walkers
438
+ if not special:
439
+ # Again we loop over the dimensions
440
+ # Note: no need to worry about skipping as we've already set all skipped dimensions to 1 bin
441
+ for n in range(ndim):
442
+ coord = coords[i][n]
443
+ nbins = nbins_per_dim[n]
444
+ minp = minlist[n]
445
+ maxp = maxlist[n]
446
+
447
+ # Generate the bins along this dimension
448
+ bins = np.linspace(minp, maxp, nbins + 1)
449
+
450
+ # Assign walker to a bin along this dimension
451
+ bin_number = np.digitize(coord, bins) - 1 # note np.digitize is 1-indexed
452
+
453
+ # Sometimes the walker is exactly at the max/min value,
454
+ # which would put it in the next bin
455
+ if bin_number == nbins:
456
+ bin_number -= 1
457
+ elif bin_number == -1:
458
+ bin_number = 0
459
+ elif bin_number > nbins or bin_number < -1:
460
+ raise ValueError("Walker out of boundary.")
461
+
462
+ # Assign to bin within the full dimensional space
463
+ bin_id += bin_number * np.prod(nbins_per_dim[:n])
464
+
465
+ # Output is the main list that, for each segment, holds the bin assignment
466
+ output[i] = bin_id
467
+ return n_bottleneck_filled
468
+
469
+
470
+ def log_bin_boundaries(
471
+ skip,
472
+ bottleneck,
473
+ direction,
474
+ bin_log_path,
475
+ minlist,
476
+ maxlist,
477
+ nbins_per_dim,
478
+ n_bottleneck_filled,
479
+ bottlenecks_forward,
480
+ bottlenecks_reverse,
481
+ ):
482
+ ndim = len(nbins_per_dim)
483
+ skip = np.array([bool(s) for s in skip])
484
+ active_dims = np.array([n for n in range(ndim) if not skip[n]])
485
+ max_bottleneck = np.sum([1 if direction[n] in [-1, 1] else 2 for n in active_dims]) if bottleneck else 0
486
+ with open(expandvars(bin_log_path), 'a') as bb_file:
487
+ # Iteration Number
488
+ bb_file.write(f'Iteration: {westpa.rc.sim_manager.n_iter}\n')
489
+ bb_file.write('MAB linear bin boundaries: ')
490
+ for n in range(ndim):
491
+ # Write binbounds per dim
492
+ bb_file.write(f'{np.linspace(minlist[n], maxlist[n], nbins_per_dim[n] + 1)}\t')
493
+ # Min/Max pcoord
494
+ bb_file.write(f'\nLagging pcoord in each dimension: {minlist}\n')
495
+ bb_file.write(f'Leading pcoord in each dimension: {maxlist}\n')
496
+ # Bottlenecks bins exist
497
+ if bottleneck:
498
+ bb_file.write(f'Number of bottleneck bins filled: {n_bottleneck_filled} / {max_bottleneck}\n')
499
+ for n in active_dims:
500
+ if direction[n] in [0, 1, 86]:
501
+ bb_file.write(f'Dimension {n} forward bottleneck walker at: {list(bottlenecks_forward[n])}\n')
502
+ if direction[n] in [0, -1, 86]:
503
+ bb_file.write(f'Dimension {n} backward bottleneck walker at: {list(bottlenecks_reverse[n])}\n')
504
+ bb_file.write('\n')
505
+ else:
506
+ bb_file.write('\n')
@@ -0,0 +1,54 @@
1
+ import logging
2
+
3
+ import numpy as np
4
+ from westpa.core.we_driver import WEDriver
5
+
6
+ log = logging.getLogger(__name__)
7
+
8
+
9
+ class MABDriver(WEDriver):
10
+ def assign(self, segments, initializing=False):
11
+ '''Assign segments to initial and final bins, and update the (internal) lists of used and available
12
+ initial states. This function is adapted to the MAB scheme, so that the inital and final segments are
13
+ sent to the bin mapper at the same time, otherwise the inital and final bin boundaries can be inconsistent.'''
14
+
15
+ log.debug("MABDriver in use.")
16
+ # collect initial and final coordinates into one place
17
+ n_segments = len(segments)
18
+ all_pcoords = np.empty((n_segments * 2, self.system.pcoord_ndim + 2), dtype=self.system.pcoord_dtype)
19
+
20
+ for iseg, segment in enumerate(segments):
21
+ all_pcoords[iseg] = np.append(segment.pcoord[0, :], [segment.weight, 0.0])
22
+ all_pcoords[n_segments + iseg] = np.append(segment.pcoord[-1, :], [segment.weight, 1.0])
23
+
24
+ # assign based on initial and final progress coordinates
25
+ assignments = self.bin_mapper.assign(all_pcoords)
26
+ initial_assignments = assignments[:n_segments]
27
+ if initializing:
28
+ final_assignments = initial_assignments
29
+ else:
30
+ final_assignments = assignments[n_segments:]
31
+
32
+ initial_binning = self.initial_binning
33
+ final_binning = self.final_binning
34
+ flux_matrix = self.flux_matrix
35
+ transition_matrix = self.transition_matrix
36
+ for segment, iidx, fidx in zip(segments, initial_assignments, final_assignments):
37
+ initial_binning[iidx].add(segment)
38
+ final_binning[fidx].add(segment)
39
+ flux_matrix[iidx, fidx] += segment.weight
40
+ transition_matrix[iidx, fidx] += 1
41
+
42
+ n_recycled_total = self.n_recycled_segs
43
+ n_new_states = n_recycled_total - len(self.avail_initial_states)
44
+
45
+ log.debug(
46
+ '{} walkers scheduled for recycling, {} initial states available'.format(
47
+ n_recycled_total, len(self.avail_initial_states)
48
+ )
49
+ )
50
+
51
+ if n_new_states > 0:
52
+ return n_new_states
53
+ else:
54
+ return 0