westpa 2022.12__cp313-cp313-macosx_11_0_arm64.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.
- westpa/__init__.py +14 -0
- westpa/_version.py +21 -0
- westpa/analysis/__init__.py +5 -0
- westpa/analysis/core.py +746 -0
- westpa/analysis/statistics.py +27 -0
- westpa/analysis/trajectories.py +360 -0
- westpa/cli/__init__.py +0 -0
- westpa/cli/core/__init__.py +0 -0
- westpa/cli/core/w_fork.py +152 -0
- westpa/cli/core/w_init.py +230 -0
- westpa/cli/core/w_run.py +77 -0
- westpa/cli/core/w_states.py +212 -0
- westpa/cli/core/w_succ.py +99 -0
- westpa/cli/core/w_truncate.py +68 -0
- westpa/cli/tools/__init__.py +0 -0
- westpa/cli/tools/ploterr.py +506 -0
- westpa/cli/tools/plothist.py +706 -0
- westpa/cli/tools/w_assign.py +596 -0
- westpa/cli/tools/w_bins.py +166 -0
- westpa/cli/tools/w_crawl.py +119 -0
- westpa/cli/tools/w_direct.py +547 -0
- westpa/cli/tools/w_dumpsegs.py +94 -0
- westpa/cli/tools/w_eddist.py +506 -0
- westpa/cli/tools/w_fluxanl.py +376 -0
- westpa/cli/tools/w_ipa.py +833 -0
- westpa/cli/tools/w_kinavg.py +127 -0
- westpa/cli/tools/w_kinetics.py +96 -0
- westpa/cli/tools/w_multi_west.py +414 -0
- westpa/cli/tools/w_ntop.py +213 -0
- westpa/cli/tools/w_pdist.py +515 -0
- westpa/cli/tools/w_postanalysis_matrix.py +82 -0
- westpa/cli/tools/w_postanalysis_reweight.py +53 -0
- westpa/cli/tools/w_red.py +491 -0
- westpa/cli/tools/w_reweight.py +780 -0
- westpa/cli/tools/w_select.py +226 -0
- westpa/cli/tools/w_stateprobs.py +111 -0
- westpa/cli/tools/w_trace.py +599 -0
- westpa/core/__init__.py +0 -0
- westpa/core/_rc.py +673 -0
- westpa/core/binning/__init__.py +55 -0
- westpa/core/binning/_assign.cpython-313-darwin.so +0 -0
- westpa/core/binning/assign.py +455 -0
- westpa/core/binning/binless.py +96 -0
- westpa/core/binning/binless_driver.py +54 -0
- westpa/core/binning/binless_manager.py +190 -0
- westpa/core/binning/bins.py +47 -0
- westpa/core/binning/mab.py +506 -0
- westpa/core/binning/mab_driver.py +54 -0
- westpa/core/binning/mab_manager.py +198 -0
- westpa/core/data_manager.py +1694 -0
- westpa/core/extloader.py +74 -0
- westpa/core/h5io.py +995 -0
- westpa/core/kinetics/__init__.py +24 -0
- westpa/core/kinetics/_kinetics.cpython-313-darwin.so +0 -0
- westpa/core/kinetics/events.py +147 -0
- westpa/core/kinetics/matrates.py +156 -0
- westpa/core/kinetics/rate_averaging.py +266 -0
- westpa/core/progress.py +218 -0
- westpa/core/propagators/__init__.py +54 -0
- westpa/core/propagators/executable.py +719 -0
- westpa/core/reweight/__init__.py +14 -0
- westpa/core/reweight/_reweight.cpython-313-darwin.so +0 -0
- westpa/core/reweight/matrix.py +126 -0
- westpa/core/segment.py +119 -0
- westpa/core/sim_manager.py +835 -0
- westpa/core/states.py +359 -0
- westpa/core/systems.py +93 -0
- westpa/core/textio.py +74 -0
- westpa/core/trajectory.py +330 -0
- westpa/core/we_driver.py +910 -0
- westpa/core/wm_ops.py +43 -0
- westpa/core/yamlcfg.py +391 -0
- westpa/fasthist/__init__.py +34 -0
- westpa/fasthist/_fasthist.cpython-313-darwin.so +0 -0
- westpa/mclib/__init__.py +271 -0
- westpa/mclib/__main__.py +28 -0
- westpa/mclib/_mclib.cpython-313-darwin.so +0 -0
- westpa/oldtools/__init__.py +4 -0
- westpa/oldtools/aframe/__init__.py +35 -0
- westpa/oldtools/aframe/atool.py +75 -0
- westpa/oldtools/aframe/base_mixin.py +26 -0
- westpa/oldtools/aframe/binning.py +178 -0
- westpa/oldtools/aframe/data_reader.py +560 -0
- westpa/oldtools/aframe/iter_range.py +200 -0
- westpa/oldtools/aframe/kinetics.py +117 -0
- westpa/oldtools/aframe/mcbs.py +153 -0
- westpa/oldtools/aframe/output.py +39 -0
- westpa/oldtools/aframe/plotting.py +90 -0
- westpa/oldtools/aframe/trajwalker.py +126 -0
- westpa/oldtools/aframe/transitions.py +469 -0
- westpa/oldtools/cmds/__init__.py +0 -0
- westpa/oldtools/cmds/w_ttimes.py +361 -0
- westpa/oldtools/files.py +34 -0
- westpa/oldtools/miscfn.py +23 -0
- westpa/oldtools/stats/__init__.py +4 -0
- westpa/oldtools/stats/accumulator.py +35 -0
- westpa/oldtools/stats/edfs.py +129 -0
- westpa/oldtools/stats/mcbs.py +96 -0
- westpa/tools/__init__.py +33 -0
- westpa/tools/binning.py +472 -0
- westpa/tools/core.py +340 -0
- westpa/tools/data_reader.py +159 -0
- westpa/tools/dtypes.py +31 -0
- westpa/tools/iter_range.py +198 -0
- westpa/tools/kinetics_tool.py +340 -0
- westpa/tools/plot.py +283 -0
- westpa/tools/progress.py +17 -0
- westpa/tools/selected_segs.py +154 -0
- westpa/tools/wipi.py +751 -0
- westpa/trajtree/__init__.py +4 -0
- westpa/trajtree/_trajtree.cpython-313-darwin.so +0 -0
- westpa/trajtree/trajtree.py +117 -0
- westpa/westext/__init__.py +0 -0
- westpa/westext/adaptvoronoi/__init__.py +3 -0
- westpa/westext/adaptvoronoi/adaptVor_driver.py +214 -0
- westpa/westext/hamsm_restarting/__init__.py +3 -0
- westpa/westext/hamsm_restarting/example_overrides.py +35 -0
- westpa/westext/hamsm_restarting/restart_driver.py +1165 -0
- westpa/westext/stringmethod/__init__.py +11 -0
- westpa/westext/stringmethod/fourier_fitting.py +69 -0
- westpa/westext/stringmethod/string_driver.py +253 -0
- westpa/westext/stringmethod/string_method.py +306 -0
- westpa/westext/weed/BinCluster.py +180 -0
- westpa/westext/weed/ProbAdjustEquil.py +100 -0
- westpa/westext/weed/UncertMath.py +247 -0
- westpa/westext/weed/__init__.py +10 -0
- westpa/westext/weed/weed_driver.py +192 -0
- westpa/westext/wess/ProbAdjust.py +101 -0
- westpa/westext/wess/__init__.py +6 -0
- westpa/westext/wess/wess_driver.py +217 -0
- westpa/work_managers/__init__.py +57 -0
- westpa/work_managers/core.py +396 -0
- westpa/work_managers/environment.py +134 -0
- westpa/work_managers/mpi.py +318 -0
- westpa/work_managers/processes.py +187 -0
- westpa/work_managers/serial.py +28 -0
- westpa/work_managers/threads.py +79 -0
- westpa/work_managers/zeromq/__init__.py +20 -0
- westpa/work_managers/zeromq/core.py +641 -0
- westpa/work_managers/zeromq/node.py +131 -0
- westpa/work_managers/zeromq/work_manager.py +526 -0
- westpa/work_managers/zeromq/worker.py +320 -0
- westpa-2022.12.dist-info/AUTHORS +22 -0
- westpa-2022.12.dist-info/LICENSE +21 -0
- westpa-2022.12.dist-info/METADATA +193 -0
- westpa-2022.12.dist-info/RECORD +149 -0
- westpa-2022.12.dist-info/WHEEL +6 -0
- westpa-2022.12.dist-info/entry_points.txt +29 -0
- 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
|