westpa 2022.10__cp312-cp312-macosx_10_9_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 (150) 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 +59 -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 +378 -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 +486 -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 +449 -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 +427 -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 +715 -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 +830 -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 +908 -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/__main__.py +110 -0
  75. westpa/fasthist/_fasthist.cpython-312-darwin.so +0 -0
  76. westpa/mclib/__init__.py +264 -0
  77. westpa/mclib/__main__.py +28 -0
  78. westpa/mclib/_mclib.cpython-312-darwin.so +0 -0
  79. westpa/oldtools/__init__.py +4 -0
  80. westpa/oldtools/aframe/__init__.py +35 -0
  81. westpa/oldtools/aframe/atool.py +75 -0
  82. westpa/oldtools/aframe/base_mixin.py +26 -0
  83. westpa/oldtools/aframe/binning.py +178 -0
  84. westpa/oldtools/aframe/data_reader.py +560 -0
  85. westpa/oldtools/aframe/iter_range.py +200 -0
  86. westpa/oldtools/aframe/kinetics.py +117 -0
  87. westpa/oldtools/aframe/mcbs.py +146 -0
  88. westpa/oldtools/aframe/output.py +39 -0
  89. westpa/oldtools/aframe/plotting.py +90 -0
  90. westpa/oldtools/aframe/trajwalker.py +126 -0
  91. westpa/oldtools/aframe/transitions.py +469 -0
  92. westpa/oldtools/cmds/__init__.py +0 -0
  93. westpa/oldtools/cmds/w_ttimes.py +358 -0
  94. westpa/oldtools/files.py +34 -0
  95. westpa/oldtools/miscfn.py +23 -0
  96. westpa/oldtools/stats/__init__.py +4 -0
  97. westpa/oldtools/stats/accumulator.py +35 -0
  98. westpa/oldtools/stats/edfs.py +129 -0
  99. westpa/oldtools/stats/mcbs.py +89 -0
  100. westpa/tools/__init__.py +33 -0
  101. westpa/tools/binning.py +472 -0
  102. westpa/tools/core.py +340 -0
  103. westpa/tools/data_reader.py +159 -0
  104. westpa/tools/dtypes.py +31 -0
  105. westpa/tools/iter_range.py +198 -0
  106. westpa/tools/kinetics_tool.py +340 -0
  107. westpa/tools/plot.py +283 -0
  108. westpa/tools/progress.py +17 -0
  109. westpa/tools/selected_segs.py +154 -0
  110. westpa/tools/wipi.py +751 -0
  111. westpa/trajtree/__init__.py +4 -0
  112. westpa/trajtree/_trajtree.cpython-312-darwin.so +0 -0
  113. westpa/trajtree/trajtree.py +117 -0
  114. westpa/westext/__init__.py +0 -0
  115. westpa/westext/adaptvoronoi/__init__.py +3 -0
  116. westpa/westext/adaptvoronoi/adaptVor_driver.py +214 -0
  117. westpa/westext/hamsm_restarting/__init__.py +3 -0
  118. westpa/westext/hamsm_restarting/example_overrides.py +35 -0
  119. westpa/westext/hamsm_restarting/restart_driver.py +1165 -0
  120. westpa/westext/stringmethod/__init__.py +11 -0
  121. westpa/westext/stringmethod/fourier_fitting.py +69 -0
  122. westpa/westext/stringmethod/string_driver.py +253 -0
  123. westpa/westext/stringmethod/string_method.py +306 -0
  124. westpa/westext/weed/BinCluster.py +180 -0
  125. westpa/westext/weed/ProbAdjustEquil.py +100 -0
  126. westpa/westext/weed/UncertMath.py +247 -0
  127. westpa/westext/weed/__init__.py +10 -0
  128. westpa/westext/weed/weed_driver.py +182 -0
  129. westpa/westext/wess/ProbAdjust.py +101 -0
  130. westpa/westext/wess/__init__.py +6 -0
  131. westpa/westext/wess/wess_driver.py +207 -0
  132. westpa/work_managers/__init__.py +57 -0
  133. westpa/work_managers/core.py +396 -0
  134. westpa/work_managers/environment.py +134 -0
  135. westpa/work_managers/mpi.py +318 -0
  136. westpa/work_managers/processes.py +187 -0
  137. westpa/work_managers/serial.py +28 -0
  138. westpa/work_managers/threads.py +79 -0
  139. westpa/work_managers/zeromq/__init__.py +20 -0
  140. westpa/work_managers/zeromq/core.py +641 -0
  141. westpa/work_managers/zeromq/node.py +131 -0
  142. westpa/work_managers/zeromq/work_manager.py +526 -0
  143. westpa/work_managers/zeromq/worker.py +320 -0
  144. westpa-2022.10.dist-info/AUTHORS +22 -0
  145. westpa-2022.10.dist-info/LICENSE +21 -0
  146. westpa-2022.10.dist-info/METADATA +183 -0
  147. westpa-2022.10.dist-info/RECORD +150 -0
  148. westpa-2022.10.dist-info/WHEEL +5 -0
  149. westpa-2022.10.dist-info/entry_points.txt +29 -0
  150. westpa-2022.10.dist-info/top_level.txt +1 -0
@@ -0,0 +1,190 @@
1
+ import logging
2
+
3
+ from westpa.core.binning.binless import BinlessMapper
4
+ from westpa.core.sim_manager import WESimManager, grouper
5
+ from westpa.core.states import InitialState, pare_basis_initial_states
6
+ from westpa.core import wm_ops
7
+ from westpa.core.segment import Segment
8
+ import numpy as np
9
+
10
+ log = logging.getLogger(__name__)
11
+
12
+
13
+ class BinlessSimManager(WESimManager):
14
+ def initialize_simulation(self, basis_states, target_states, start_states, segs_per_state=1, suppress_we=False):
15
+ if len(target_states) > 0:
16
+ if isinstance(self.system.bin_mapper, BinlessMapper):
17
+ log.error("BinlessMapper cannot be an outer binning scheme with a target state\n")
18
+
19
+ super().initialize_simulation(
20
+ basis_states, target_states, start_states, segs_per_state=segs_per_state, suppress_we=suppress_we
21
+ )
22
+
23
+ def propagate(self):
24
+ log.debug("BinlessManager in use")
25
+ segments = list(self.incomplete_segments.values())
26
+ log.debug('iteration {:d}: propagating {:d} segments'.format(self.n_iter, len(segments)))
27
+
28
+ # all futures dispatched for this iteration
29
+ futures = set()
30
+ segment_futures = set()
31
+
32
+ # Immediately dispatch any necessary initial state generation
33
+ istate_gen_futures = self.get_istate_futures()
34
+ futures.update(istate_gen_futures)
35
+
36
+ # Dispatch propagation tasks using work manager
37
+ for segment_block in grouper(self.propagator_block_size, segments):
38
+ segment_block = [_f for _f in segment_block if _f]
39
+ pbstates, pistates = pare_basis_initial_states(
40
+ self.current_iter_bstates, list(self.current_iter_istates.values()), segment_block
41
+ )
42
+ future = self.work_manager.submit(wm_ops.propagate, args=(pbstates, pistates, segment_block))
43
+ futures.add(future)
44
+ segment_futures.add(future)
45
+
46
+ while futures:
47
+ # TODO: add capacity for timeout or SIGINT here
48
+ future = self.work_manager.wait_any(futures)
49
+ futures.remove(future)
50
+
51
+ if future in segment_futures:
52
+ segment_futures.remove(future)
53
+ incoming = future.get_result()
54
+ self.n_propagated += 1
55
+
56
+ self.segments.update({segment.seg_id: segment for segment in incoming})
57
+ self.completed_segments.update({segment.seg_id: segment for segment in incoming})
58
+
59
+ new_istate_futures = self.get_istate_futures()
60
+ istate_gen_futures.update(new_istate_futures)
61
+ futures.update(new_istate_futures)
62
+
63
+ with self.data_manager.expiring_flushing_lock():
64
+ self.data_manager.update_segments(self.n_iter, incoming)
65
+
66
+ elif future in istate_gen_futures:
67
+ istate_gen_futures.remove(future)
68
+ _basis_state, initial_state = future.get_result()
69
+ log.debug('received newly-prepared initial state {!r}'.format(initial_state))
70
+ initial_state.istate_status = InitialState.ISTATE_STATUS_PREPARED
71
+ with self.data_manager.expiring_flushing_lock():
72
+ self.data_manager.update_initial_states([initial_state], n_iter=self.n_iter + 1)
73
+ self.we_driver.avail_initial_states[initial_state.state_id] = initial_state
74
+ else:
75
+ log.error('unknown future {!r} received from work manager'.format(future))
76
+ raise AssertionError('untracked future {!r}'.format(future))
77
+
78
+ # Collectively assign all segments to their bins...
79
+ self.we_driver.assign(self.segments.values())
80
+
81
+ # For cases where we need even more istates for recycled trajectories
82
+ # futures should be empty at this point.
83
+ istate_gen_futures = self.get_istate_futures()
84
+ futures.update(istate_gen_futures)
85
+
86
+ # Wait for istate_gen_futures and catch untracked futures.
87
+ while futures:
88
+ future = self.work_manager.wait_any(futures)
89
+ futures.remove(future)
90
+
91
+ if future in istate_gen_futures:
92
+ istate_gen_futures.remove(future)
93
+ _basis_state, initial_state = future.get_result()
94
+ log.debug('received newly-prepared initial state {!r}'.format(initial_state))
95
+ initial_state.istate_status = InitialState.ISTATE_STATUS_PREPARED
96
+ with self.data_manager.expiring_flushing_lock():
97
+ self.data_manager.update_initial_states([initial_state], n_iter=self.n_iter + 1)
98
+ self.we_driver.avail_initial_states[initial_state.state_id] = initial_state
99
+ else:
100
+ log.error('unknown future {!r} received from work manager'.format(future))
101
+ raise AssertionError('untracked future {!r}'.format(future))
102
+
103
+ log.debug('done with propagation')
104
+ self.save_bin_data()
105
+ self.data_manager.flush_backing()
106
+
107
+ def prepare_iteration(self):
108
+ log.debug('beginning iteration {:d}'.format(self.n_iter))
109
+
110
+ # the WE driver needs a list of all target states for this iteration
111
+ # along with information about any new weights introduced (e.g. by recycling)
112
+ target_states = self.data_manager.get_target_states(self.n_iter)
113
+ new_weights = self.data_manager.get_new_weight_data(self.n_iter)
114
+
115
+ self.we_driver.new_iteration(target_states=target_states, new_weights=new_weights)
116
+
117
+ # Get basis states used in this iteration
118
+ self.current_iter_bstates = self.data_manager.get_basis_states(self.n_iter)
119
+
120
+ # Get the segments for this iteration and separate into complete and incomplete
121
+ if self.segments is None:
122
+ segments = self.segments = {segment.seg_id: segment for segment in self.data_manager.get_segments()}
123
+ log.debug('loaded {:d} segments'.format(len(segments)))
124
+ else:
125
+ segments = self.segments
126
+ log.debug('using {:d} pre-existing segments'.format(len(segments)))
127
+
128
+ completed_segments = self.completed_segments = {}
129
+ incomplete_segments = self.incomplete_segments = {}
130
+ for segment in segments.values():
131
+ if segment.status == Segment.SEG_STATUS_COMPLETE:
132
+ completed_segments[segment.seg_id] = segment
133
+ else:
134
+ incomplete_segments[segment.seg_id] = segment
135
+ log.debug('{:d} segments are complete; {:d} are incomplete'.format(len(completed_segments), len(incomplete_segments)))
136
+
137
+ if len(incomplete_segments) == len(segments):
138
+ # Starting a new iteration
139
+ self.rc.pstatus('Beginning iteration {:d}'.format(self.n_iter))
140
+ elif incomplete_segments:
141
+ self.rc.pstatus('Continuing iteration {:d}'.format(self.n_iter))
142
+ self.rc.pstatus(
143
+ '{:d} segments remain in iteration {:d} ({:d} total)'.format(len(incomplete_segments), self.n_iter, len(segments))
144
+ )
145
+
146
+ # Get the initial states active for this iteration (so that the propagator has them if necessary)
147
+ self.current_iter_istates = {
148
+ state.state_id: state for state in self.data_manager.get_segment_initial_states(list(segments.values()))
149
+ }
150
+ log.debug('This iteration uses {:d} initial states'.format(len(self.current_iter_istates)))
151
+
152
+ # Assign this iteration's segments' initial points to bins and report on bin population
153
+ initial_pcoords = self.system.new_pcoord_array(len(segments))
154
+ initial_binning = self.system.bin_mapper.construct_bins()
155
+ for iseg, segment in enumerate(segments.values()):
156
+ initial_pcoords[iseg] = segment.pcoord[0]
157
+ initial_assignments = self.system.bin_mapper.assign(initial_pcoords)
158
+ for segment, assignment in zip(iter(segments.values()), initial_assignments):
159
+ initial_binning[assignment].add(segment)
160
+ self.report_bin_statistics(initial_binning, [], save_summary=True)
161
+ del initial_pcoords, initial_binning
162
+
163
+ self.rc.pstatus("Binless scheme in use")
164
+
165
+ self.rc.pstatus("Waiting for segments to complete...")
166
+
167
+ # Let the WE driver assign completed segments
168
+ if completed_segments and len(incomplete_segments) == 0:
169
+ self.we_driver.assign(list(completed_segments.values()))
170
+
171
+ # load restart data
172
+ self.data_manager.prepare_segment_restarts(
173
+ incomplete_segments.values(), self.current_iter_bstates, self.current_iter_istates
174
+ )
175
+
176
+ # Get the basis states and initial states for the next iteration, necessary for doing on-the-fly recycling
177
+ self.next_iter_bstates = self.data_manager.get_basis_states(self.n_iter + 1)
178
+ self.next_iter_bstate_cprobs = np.add.accumulate([bstate.probability for bstate in self.next_iter_bstates])
179
+
180
+ self.we_driver.avail_initial_states = {
181
+ istate.state_id: istate for istate in self.data_manager.get_unused_initial_states(n_iter=self.n_iter + 1)
182
+ }
183
+ log.debug('{:d} unused initial states found'.format(len(self.we_driver.avail_initial_states)))
184
+
185
+ # Invoke callbacks
186
+ self.invoke_callbacks(self.prepare_iteration)
187
+
188
+ # dispatch and immediately wait on result for prep_iter
189
+ log.debug('dispatching propagator prep_iter to work manager')
190
+ self.work_manager.submit(wm_ops.prep_iter, args=(self.n_iter, segments)).get_result()
@@ -0,0 +1,47 @@
1
+ import logging
2
+
3
+ import numpy as np
4
+
5
+ log = logging.getLogger(__name__)
6
+
7
+ EPS = np.finfo(np.float64).eps
8
+
9
+
10
+ class Bin(set):
11
+ def __init__(self, iterable=None, label=None):
12
+ super().__init__(iterable or [])
13
+ self.label = label
14
+
15
+ def __repr__(self):
16
+ return '<{classname} at 0x{id:x}, label={label!r}, count={count:d}, weight={weight:g}>'.format(
17
+ classname=self.__class__.__name__, id=id(self), label=self.label, count=len(self), weight=self.weight
18
+ )
19
+
20
+ @property
21
+ def weight(self):
22
+ 'Total weight of all walkers in this bin'
23
+
24
+ weight = 0.0
25
+ for particle in self:
26
+ weight += particle.weight
27
+ return weight
28
+
29
+ def reweight(self, new_weight):
30
+ """Reweight all walkers in this bin so that the total weight is new_weight"""
31
+
32
+ if len(self) == 0 and new_weight == 0:
33
+ return
34
+
35
+ if len(self) == 0 and new_weight != 0:
36
+ raise ValueError('cannot reweight empty ParticleCollection')
37
+
38
+ current_weight = self.weight
39
+ log.debug('reweighting collection of {:d} particles from {:g} to {:g}'.format(len(self), current_weight, new_weight))
40
+ assert (new_weight == 0 and current_weight == 0) or new_weight > 0
41
+
42
+ wrat = new_weight / current_weight
43
+ for p in self:
44
+ p.weight *= wrat
45
+
46
+ log.debug('new weight: {:g}'.format(self.weight))
47
+ assert abs(new_weight - self.weight) <= EPS * len(self)
@@ -0,0 +1,427 @@
1
+ import logging
2
+ import numpy as np
3
+ import westpa
4
+ from westpa.core.binning import FuncBinMapper
5
+ from os.path import expandvars
6
+
7
+
8
+ log = logging.getLogger(__name__)
9
+
10
+
11
+ class MABBinMapper(FuncBinMapper):
12
+ """
13
+ Adaptively place bins in between minimum and maximum segments along
14
+ the progress coordinte. Extrema and bottleneck segments are assigned
15
+ to their own bins.
16
+
17
+ """
18
+
19
+ def __init__(
20
+ self,
21
+ nbins,
22
+ direction=None,
23
+ skip=None,
24
+ bottleneck=True,
25
+ pca=False,
26
+ mab_log=False,
27
+ bin_log=False,
28
+ bin_log_path="$WEST_SIM_ROOT/binbounds.log",
29
+ ):
30
+ """
31
+ Parameters
32
+ ----------
33
+ nbins : list of int
34
+ List of int for nbins in each dimension.
35
+ direction : Union(list of int, None), default: None
36
+ List of int for 'direction' in each dimension.
37
+ Direction options are as follows:
38
+ 0 : default split at leading and lagging boundaries
39
+ 1 : split at leading boundary only
40
+ -1 : split at lagging boundary only
41
+ 86 : no splitting at either leading or lagging boundary
42
+ skip : Union(list of int, None), default: None
43
+ List of int for each dimension. Default None for skip=0.
44
+ Set to 1 to 'skip' running mab in a dimension.
45
+ bottleneck : bool, default: True
46
+ Whether to turn on or off bottleneck walker splitting.
47
+ pca : bool, default: False
48
+ Can be True or False (default) to run PCA on pcoords before bin assignment.
49
+ mab_log : bool, default: False
50
+ Whether to output mab info to west.log.
51
+ bin_log : bool, default: False
52
+ Whether to output mab bin boundaries to bin_log_path file.
53
+ bin_log_path : str, default: "$WEST_SIM_ROOT/binbounds.log"
54
+ Path to output bin boundaries.
55
+
56
+ """
57
+ # Verifying parameters
58
+ if nbins is None:
59
+ raise ValueError("nbins_per_dim is missing")
60
+ ndim = len(nbins)
61
+
62
+ if direction is None:
63
+ direction = [0] * ndim
64
+ elif len(direction) != ndim:
65
+ direction = [0] * ndim
66
+ log.warning("Direction list is not the correct dimensions, setting to defaults.")
67
+
68
+ if skip is None:
69
+ skip = [0] * ndim
70
+ elif len(skip) != ndim:
71
+ skip = [0] * ndim
72
+ log.warning("Skip list is not the correct dimensions, setting to defaults.")
73
+
74
+ kwargs = dict(
75
+ nbins_per_dim=nbins,
76
+ direction=direction,
77
+ skip=skip,
78
+ bottleneck=bottleneck,
79
+ pca=pca,
80
+ mab_log=mab_log,
81
+ bin_log=bin_log,
82
+ bin_log_path=bin_log_path,
83
+ )
84
+
85
+ n_total_bins = self.determine_total_bins(**kwargs)
86
+
87
+ super().__init__(map_mab, n_total_bins, kwargs=kwargs)
88
+
89
+ def determine_total_bins(self, nbins_per_dim, direction, skip, bottleneck, **kwargs):
90
+ """
91
+ The following is neccessary because functional bin mappers need to "reserve"
92
+ bins and tell the sim manager how many bins they will need to use, this is
93
+ determined by taking all direction/skipping info into account.
94
+
95
+ Parameters
96
+ ----------
97
+ nbins_per_dim : int
98
+ Number of total bins in each direction.
99
+ direction : list of int
100
+ Direction in each dimension. See __init__ for more information.
101
+ skip : list of int
102
+ List of 0s and 1s indicating whether to skip each dimension.
103
+ bottleneck : bool
104
+ Whether to include separate bin for bottleneck walker(s).
105
+ **kwargs : dict
106
+ Arbitary keyword arguments. Contains unneeded MAB parameters.
107
+
108
+ Returns
109
+ -------
110
+ n_total_bins : int
111
+ Number of total bins.
112
+
113
+ """
114
+ n_total_bins = np.prod(nbins_per_dim)
115
+ ndim = len(nbins_per_dim)
116
+ for i in range(ndim):
117
+ if skip[i] == 0:
118
+ if direction[i] != 0:
119
+ n_total_bins += 1 + 1 * bottleneck
120
+ else:
121
+ n_total_bins += 2 + 2 * bottleneck
122
+ else:
123
+ n_total_bins -= nbins_per_dim[i] - 1
124
+ n_total_bins += 1 * ndim # or else it will be one bin short
125
+ return n_total_bins
126
+
127
+
128
+ def map_mab(coords, mask, output, *args, **kwargs):
129
+ """
130
+ Binning which adaptively places bins based on the positions of extrema segments and
131
+ bottleneck segments, which are where the difference in probability is the greatest
132
+ along the progress coordinate. Operates per dimension and places a fixed number of
133
+ evenly spaced bins between the segments with the min and max pcoord values. Extrema and
134
+ bottleneck segments are assigned their own bins.
135
+
136
+ Parameters
137
+ ----------
138
+ coords : ndarray
139
+ An array with pcoord and weight info.
140
+ mask : ndarray
141
+ Array of 1 (True) and 0 (False), to filter out unwanted segment info.
142
+ output : list
143
+ The main list that, for each segment, holds the bin assignment.
144
+ *args : list
145
+ Variable length arguments.
146
+ **kwargs : dict
147
+ Arbitary keyword arguments. Contains most of the MAB-needed parameters.
148
+
149
+ Returns
150
+ ------
151
+ output : list
152
+ The main list that, for each segment, holds the bin assignment.
153
+
154
+ """
155
+
156
+ # Argument Processing
157
+ nbins_per_dim = kwargs.get("nbins_per_dim")
158
+ ndim = len(nbins_per_dim)
159
+ pca = kwargs.get("pca", False)
160
+ bottleneck = kwargs.get("bottleneck", True)
161
+ direction = kwargs.get("direction", ([0] * ndim))
162
+ skip = kwargs.get("skip", ([0] * ndim))
163
+ mab_log = kwargs.get("mab_log", False)
164
+ bin_log = kwargs.get("bin_log", False)
165
+ bin_log_path = kwargs.get("bin_log_path", "$WEST_SIM_ROOT/binbounds.log")
166
+
167
+ if not np.any(mask):
168
+ return output
169
+
170
+ if skip is None:
171
+ skip = [0] * ndim
172
+
173
+ allcoords = np.copy(coords)
174
+ allmask = np.copy(mask)
175
+
176
+ weights = None
177
+ isfinal = None
178
+ splitting = False
179
+ report = False
180
+
181
+ # the segments should be sent in by the driver as half initial segments and half final segments
182
+ # allcoords contains all segments
183
+ # coords should contain ONLY final segments
184
+ if coords.shape[1] > ndim:
185
+ if coords[0, -1] == 0:
186
+ report = True
187
+ if coords.shape[1] > ndim + 1:
188
+ isfinal = allcoords[:, ndim + 1].astype(np.bool_)
189
+ else:
190
+ isfinal = np.ones(coords.shape[0], dtype=np.bool_)
191
+ coords = coords[isfinal, :ndim]
192
+ weights = allcoords[isfinal, ndim + 0]
193
+ mask = mask[isfinal]
194
+ splitting = True
195
+
196
+ if not np.any(mask):
197
+ coords = allcoords[:, :ndim]
198
+ mask = allmask
199
+ weights = None
200
+ splitting = False
201
+
202
+ varcoords = np.copy(coords)
203
+ originalcoords = np.copy(coords)
204
+ if pca and len(output) > 1:
205
+ colavg = np.mean(coords, axis=0)
206
+ for i in range(len(coords)):
207
+ for j in range(len(coords[i])):
208
+ varcoords[i][j] = coords[i][j] - colavg[j]
209
+ covcoords = np.cov(np.transpose(varcoords), aweights=weights)
210
+ eigval, eigvec = np.linalg.eigh(covcoords)
211
+ eigvec = eigvec[:, np.argmax(np.absolute(eigvec), axis=1)]
212
+ for i in range(len(eigvec)):
213
+ if eigvec[i, i] < 0:
214
+ eigvec[:, i] = -1 * eigvec[:, i]
215
+ for i in range(ndim):
216
+ for j in range(len(output)):
217
+ coords[j][i] = np.dot(varcoords[j], eigvec[:, i])
218
+
219
+ maxlist = []
220
+ minlist = []
221
+ difflist = []
222
+ flipdifflist = []
223
+ for n in range(ndim):
224
+ # identify the boundary segments
225
+ maxcoord = np.max(coords[mask, n])
226
+ mincoord = np.min(coords[mask, n])
227
+ maxlist.append(maxcoord)
228
+ minlist.append(mincoord)
229
+
230
+ # detect the bottleneck segments, this uses the weights
231
+ if splitting:
232
+ temp = np.column_stack((originalcoords[mask, n], weights[mask]))
233
+ sorted_indices = temp[:, 0].argsort()
234
+ temp = temp[sorted_indices]
235
+ for p in range(len(temp)):
236
+ if temp[p][1] == 0:
237
+ temp[p][1] = 10**-323
238
+ fliptemp = np.flipud(temp)
239
+
240
+ difflist.append(None)
241
+ flipdifflist.append(None)
242
+ maxdiff = 0
243
+ flipmaxdiff = 0
244
+ for i in range(1, len(temp) - 1):
245
+ comprob = 0
246
+ flipcomprob = 0
247
+ j = i + 1
248
+ while j < len(temp):
249
+ comprob = comprob + temp[j][1]
250
+ flipcomprob = flipcomprob + fliptemp[j][1]
251
+ j = j + 1
252
+ diff = -np.log(comprob) + np.log(temp[i][1])
253
+ if diff > maxdiff:
254
+ difflist[n] = temp[i][0]
255
+ maxdiff = diff
256
+ flipdiff = -np.log(flipcomprob) + np.log(fliptemp[i][1])
257
+ if flipdiff > flipmaxdiff:
258
+ flipdifflist[n] = fliptemp[i][0]
259
+ flipmaxdiff = flipdiff
260
+
261
+ if mab_log and report:
262
+ westpa.rc.pstatus("################ MAB stats ################")
263
+ westpa.rc.pstatus("minima in each dimension: {}".format(minlist))
264
+ westpa.rc.pstatus("maxima in each dimension: {}".format(maxlist))
265
+ westpa.rc.pstatus("direction in each dimension: {}".format(direction))
266
+ westpa.rc.pstatus("skip in each dimension: {}".format(skip))
267
+ westpa.rc.pstatus("###########################################")
268
+ westpa.rc.pflush()
269
+
270
+ # assign segments to bins
271
+ # the total number of linear bins is the boundary base
272
+ boundary_base = np.prod(nbins_per_dim)
273
+
274
+ # the bottleneck base is offset by the number of boundary walkers,
275
+ # which is two per dimension unless there is a direction specified
276
+ # in a particluar dimension, then it's just one
277
+ bottleneck_base = boundary_base
278
+ n_bottleneck_filled = 0
279
+
280
+ for i in range(0, ndim):
281
+ # for single direction, 1 boundary walker
282
+ if direction[i] == 1 or direction[i] == -1:
283
+ bottleneck_base += 1
284
+ # 2 boundary walkers with 0 direction
285
+ elif direction[i] == 0:
286
+ bottleneck_base += 2
287
+ # for 86 direction, no boundary walkers so offset of 0
288
+ elif direction[i] == 86:
289
+ bottleneck_base += 0
290
+
291
+ # if a dimension is being "skipped", leave only one bin total as
292
+ # the offset
293
+ for i in range(0, ndim):
294
+ if skip[i] != 0:
295
+ boundary_base -= nbins_per_dim[i] - 1
296
+
297
+ for i in range(len(output)):
298
+ if not allmask[i]:
299
+ continue
300
+
301
+ # special means either a boundary or bottleneck walker (not a walker in the linear space)
302
+ special = False
303
+ # this holder is the bin number, which only needs to be unique for different walker groups
304
+ holder = 0
305
+ if splitting:
306
+ for n in range(ndim):
307
+ coord = allcoords[i][n]
308
+
309
+ # if skipped, just assign the walkers to the same bin (offset of boundary base)
310
+ if skip[n] != 0:
311
+ holder = boundary_base + n
312
+ break
313
+
314
+ # assign bottlenecks, taking directionality into account
315
+ if bottleneck:
316
+ if direction[n] == -1:
317
+ if coord == flipdifflist[n]:
318
+ holder = bottleneck_base + n
319
+ special = True
320
+ n_bottleneck_filled += 1
321
+ break
322
+
323
+ if direction[n] == 1:
324
+ if coord == difflist[n]:
325
+ holder = bottleneck_base + n
326
+ special = True
327
+ n_bottleneck_filled += 1
328
+ break
329
+
330
+ # both directions when using 0 or with
331
+ # special value of 86 for no lead/lag split
332
+ if direction[n] == 0 or direction[n] == 86:
333
+ if coord == difflist[n]:
334
+ holder = bottleneck_base + n
335
+ special = True
336
+ n_bottleneck_filled += 1
337
+ break
338
+ elif coord == flipdifflist[n]:
339
+ holder = bottleneck_base + n + 1
340
+ special = True
341
+ n_bottleneck_filled += 1
342
+ break
343
+
344
+ # assign boundary walkers, taking directionality into account
345
+ if direction[n] == -1:
346
+ if coord == minlist[n]:
347
+ holder = boundary_base + n
348
+ special = True
349
+ break
350
+
351
+ elif direction[n] == 1:
352
+ if coord == maxlist[n]:
353
+ holder = boundary_base + n
354
+ special = True
355
+ break
356
+
357
+ elif direction[n] == 0:
358
+ if coord == minlist[n]:
359
+ holder = boundary_base + n
360
+ special = True
361
+ break
362
+ elif coord == maxlist[n]:
363
+ holder = boundary_base + n + 1
364
+ special = True
365
+ break
366
+
367
+ # special value for direction with no lead/lag split
368
+ elif direction[n] == 86:
369
+ # westpa.rc.pstatus(f"No lead/lag split for dim {n}")
370
+ # westpa.rc.pflush()
371
+ # nornmally adds to special bin but here just leaving it forever empty
372
+ # holder = boundary_base + n
373
+ break
374
+
375
+ # the following are for the "linear" portion
376
+ if not special:
377
+ for n in range(ndim):
378
+ # if skipped, it's added to the same bin as the special walkers above
379
+ if skip[n] != 0:
380
+ holder = boundary_base + n
381
+ break
382
+
383
+ coord = allcoords[i][n]
384
+ nbins = nbins_per_dim[n]
385
+ minp = minlist[n]
386
+ maxp = maxlist[n]
387
+
388
+ bins = np.linspace(minp, maxp, nbins + 1)
389
+ bin_number = np.digitize(coord, bins) - 1
390
+
391
+ if isfinal is None or not isfinal[i]:
392
+ if bin_number >= nbins:
393
+ bin_number = nbins - 1
394
+ elif bin_number < 0:
395
+ bin_number = 0
396
+ elif bin_number >= nbins or bin_number < 0:
397
+ if np.isclose(bins[-1], coord):
398
+ bin_number = nbins - 1
399
+ elif np.isclose(bins[0], coord):
400
+ bin_number = 0
401
+ else:
402
+ raise ValueError("Walker out of boundary")
403
+
404
+ holder += bin_number * np.prod(nbins_per_dim[:n])
405
+
406
+ # output is the main list that, for each segment, holds the bin assignment
407
+ output[i] = holder
408
+
409
+ if bin_log and report:
410
+ if westpa.rc.sim_manager.n_iter:
411
+ with open(expandvars(bin_log_path), 'a') as bb_file:
412
+ # Iteration Number
413
+ bb_file.write(f'iteration: {westpa.rc.sim_manager.n_iter}\n')
414
+ bb_file.write('bin boundaries: ')
415
+ for n in range(ndim):
416
+ # Write binbounds per dim
417
+ bb_file.write(f'{np.linspace(minlist[n], maxlist[n], nbins_per_dim[n] + 1)}\t')
418
+ # Min/Max pcoord
419
+ bb_file.write(f'\nmin/max pcoord: {minlist} {maxlist}\n')
420
+ bb_file.write(f'bottleneck bins: {n_bottleneck_filled}\n')
421
+ if n_bottleneck_filled > 0:
422
+ # Bottlenecks bins exist (passes any of the if bottleneck: checks)
423
+ bb_file.write(f'bottleneck pcoord: {flipdifflist} {difflist}\n\n')
424
+ else:
425
+ bb_file.write('\n')
426
+
427
+ return output