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.
- 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-312-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-312-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-312-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-312-darwin.so +0 -0
- westpa/mclib/__init__.py +271 -0
- westpa/mclib/__main__.py +28 -0
- westpa/mclib/_mclib.cpython-312-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-312-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
westpa/core/we_driver.py
ADDED
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import math
|
|
3
|
+
import operator
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.random import Generator, MT19937
|
|
7
|
+
|
|
8
|
+
import westpa
|
|
9
|
+
from .segment import Segment
|
|
10
|
+
from .states import InitialState
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _group_walkers_identity(we_driver, ibin, **kwargs):
|
|
16
|
+
log.debug('using we_driver._group_walkers_identity')
|
|
17
|
+
bin_set = we_driver.next_iter_binning[ibin]
|
|
18
|
+
list_bins = [set()]
|
|
19
|
+
for i in bin_set:
|
|
20
|
+
list_bins[0].add(i)
|
|
21
|
+
return list_bins
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ConsistencyError(RuntimeError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AccuracyError(RuntimeError):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class NewWeightEntry:
|
|
33
|
+
NW_SOURCE_RECYCLED = 0
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
source_type,
|
|
38
|
+
weight,
|
|
39
|
+
prev_seg_id=None,
|
|
40
|
+
prev_init_pcoord=None,
|
|
41
|
+
prev_final_pcoord=None,
|
|
42
|
+
new_init_pcoord=None,
|
|
43
|
+
target_state_id=None,
|
|
44
|
+
initial_state_id=None,
|
|
45
|
+
):
|
|
46
|
+
self.source_type = source_type
|
|
47
|
+
self.weight = weight
|
|
48
|
+
self.prev_seg_id = prev_seg_id
|
|
49
|
+
self.prev_init_pcoord = np.asarray(prev_init_pcoord) if prev_init_pcoord is not None else None
|
|
50
|
+
self.prev_final_pcoord = np.asarray(prev_final_pcoord) if prev_final_pcoord is not None else None
|
|
51
|
+
self.new_init_pcoord = np.asarray(new_init_pcoord) if new_init_pcoord is not None else None
|
|
52
|
+
self.target_state_id = target_state_id
|
|
53
|
+
self.initial_state_id = initial_state_id
|
|
54
|
+
|
|
55
|
+
def __repr__(self):
|
|
56
|
+
return '<{} object at 0x{:x}: weight={self.weight:g} target_state_id={self.target_state_id} prev_final_pcoord={self.prev_final_pcoord}>'.format(
|
|
57
|
+
self.__class__.__name__, id(self), self=self
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class WEDriver:
|
|
62
|
+
'''A class implemented Huber & Kim's weighted ensemble algorithm over Segment objects.
|
|
63
|
+
This class handles all binning, recycling, and preparation of new Segment objects for the
|
|
64
|
+
next iteration. Binning is accomplished using system.bin_mapper, and per-bin target counts
|
|
65
|
+
are from system.bin_target_counts.
|
|
66
|
+
|
|
67
|
+
The workflow is as follows:
|
|
68
|
+
|
|
69
|
+
1) Call `new_iteration()` every new iteration, providing any recycling targets that are
|
|
70
|
+
in force and any available initial states for recycling.
|
|
71
|
+
2) Call `assign()` to assign segments to bins based on their initial and end points. This
|
|
72
|
+
returns the number of walkers that were recycled.
|
|
73
|
+
3) Call `run_we()`, optionally providing a set of initial states that will be used to
|
|
74
|
+
recycle walkers.
|
|
75
|
+
|
|
76
|
+
Note the presence of flux_matrix, transition_matrix,
|
|
77
|
+
current_iter_segments, next_iter_segments, recycling_segments,
|
|
78
|
+
initial_binning, final_binning, next_iter_binning, and new_weights (to be documented soon).
|
|
79
|
+
'''
|
|
80
|
+
|
|
81
|
+
weight_split_threshold = 2.0
|
|
82
|
+
weight_merge_cutoff = 1.0
|
|
83
|
+
largest_allowed_weight = 1.0
|
|
84
|
+
smallest_allowed_weight = 1e-310
|
|
85
|
+
|
|
86
|
+
def __init__(self, rc=None, system=None):
|
|
87
|
+
self.rc = rc or westpa.rc
|
|
88
|
+
self.system = system or self.rc.get_system_driver()
|
|
89
|
+
|
|
90
|
+
# Whether to adjust counts to exactly match target count
|
|
91
|
+
self.do_adjust_counts = True
|
|
92
|
+
|
|
93
|
+
# bin mapper and per-bin target counts (see new_iteration for initialization)
|
|
94
|
+
self.bin_mapper = None
|
|
95
|
+
self.bin_target_counts = None
|
|
96
|
+
|
|
97
|
+
# Mapping of bin index to target state
|
|
98
|
+
self.target_states = None
|
|
99
|
+
|
|
100
|
+
# binning on initial points
|
|
101
|
+
self.initial_binning = None
|
|
102
|
+
|
|
103
|
+
# binning on final points (pre-WE)
|
|
104
|
+
self.final_binning = None
|
|
105
|
+
|
|
106
|
+
# binning on initial points for next iteration
|
|
107
|
+
self.next_iter_binning = None
|
|
108
|
+
|
|
109
|
+
# Flux and rate matrices for the current iteration
|
|
110
|
+
self.flux_matrix = None
|
|
111
|
+
self.transition_matrix = None
|
|
112
|
+
|
|
113
|
+
# Information on new weights (e.g. from recycling) for the next iteration
|
|
114
|
+
self.new_weights = None
|
|
115
|
+
|
|
116
|
+
# Set of initial states passed to run_we() that are actually used for
|
|
117
|
+
# recycling targets
|
|
118
|
+
self.used_initial_states = None
|
|
119
|
+
|
|
120
|
+
self.avail_initial_states = None
|
|
121
|
+
|
|
122
|
+
self.rng = Generator(MT19937())
|
|
123
|
+
|
|
124
|
+
# Make property for subgrouping function.
|
|
125
|
+
self.subgroup_function = _group_walkers_identity
|
|
126
|
+
self.subgroup_function_kwargs = {}
|
|
127
|
+
|
|
128
|
+
self.process_config()
|
|
129
|
+
self.check_threshold_configs()
|
|
130
|
+
|
|
131
|
+
def process_config(self):
|
|
132
|
+
config = self.rc.config
|
|
133
|
+
|
|
134
|
+
config.require_type_if_present(['west', 'we', 'adjust_counts'], bool)
|
|
135
|
+
|
|
136
|
+
config.require_type_if_present(['west', 'we', 'thresholds'], bool)
|
|
137
|
+
|
|
138
|
+
self.do_adjust_counts = config.get(['west', 'we', 'adjust_counts'], True)
|
|
139
|
+
log.info('Adjust counts to exactly match target_counts: {}'.format(self.do_adjust_counts))
|
|
140
|
+
|
|
141
|
+
self.do_thresholds = config.get(['west', 'we', 'thresholds'], True)
|
|
142
|
+
log.info('Obey abolute weight thresholds: {}'.format(self.do_thresholds))
|
|
143
|
+
|
|
144
|
+
self.weight_split_threshold = config.get(['west', 'we', 'weight_split_threshold'], self.weight_split_threshold)
|
|
145
|
+
log.info('Split threshold: {}'.format(self.weight_split_threshold))
|
|
146
|
+
|
|
147
|
+
self.weight_merge_cutoff = config.get(['west', 'we', 'weight_merge_cutoff'], self.weight_merge_cutoff)
|
|
148
|
+
log.info('Merge cutoff: {}'.format(self.weight_merge_cutoff))
|
|
149
|
+
|
|
150
|
+
self.largest_allowed_weight = config.get(['west', 'we', 'largest_allowed_weight'], self.largest_allowed_weight)
|
|
151
|
+
log.info('Largest allowed weight: {}'.format(self.largest_allowed_weight))
|
|
152
|
+
|
|
153
|
+
self.smallest_allowed_weight = config.get(['west', 'we', 'smallest_allowed_weight'], self.smallest_allowed_weight)
|
|
154
|
+
log.info('Smallest allowed_weight: {}'.format(self.smallest_allowed_weight))
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def next_iter_segments(self):
|
|
158
|
+
'''Newly-created segments for the next iteration'''
|
|
159
|
+
if self.next_iter_binning is None:
|
|
160
|
+
raise RuntimeError('cannot access next iteration segments before running WE')
|
|
161
|
+
|
|
162
|
+
for _bin in self.next_iter_binning:
|
|
163
|
+
for walker in _bin:
|
|
164
|
+
yield walker
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def current_iter_segments(self):
|
|
168
|
+
'''Segments for the current iteration'''
|
|
169
|
+
for _bin in self.final_binning:
|
|
170
|
+
for walker in _bin:
|
|
171
|
+
yield walker
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def next_iter_assignments(self):
|
|
175
|
+
'''Bin assignments (indices) for initial points of next iteration.'''
|
|
176
|
+
if self.next_iter_binning is None:
|
|
177
|
+
raise RuntimeError('cannot access next iteration segments before running WE')
|
|
178
|
+
|
|
179
|
+
for ibin, _bin in enumerate(self.next_iter_binning):
|
|
180
|
+
for _walker in _bin:
|
|
181
|
+
yield ibin
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def current_iter_assignments(self):
|
|
185
|
+
'''Bin assignments (indices) for endpoints of current iteration.'''
|
|
186
|
+
for ibin, _bin in enumerate(self.final_binning):
|
|
187
|
+
for walker in _bin:
|
|
188
|
+
yield ibin
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def recycling_segments(self):
|
|
192
|
+
'''Segments designated for recycling'''
|
|
193
|
+
if len(self.target_states):
|
|
194
|
+
for ibin, tstate in self.target_states.items():
|
|
195
|
+
for segment in self.final_binning[ibin]:
|
|
196
|
+
yield segment
|
|
197
|
+
else:
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def n_recycled_segs(self):
|
|
202
|
+
'''Number of segments recycled this iteration'''
|
|
203
|
+
count = 0
|
|
204
|
+
for _segment in self.recycling_segments:
|
|
205
|
+
count += 1
|
|
206
|
+
return count
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def n_istates_needed(self):
|
|
210
|
+
'''Number of initial states needed to support recycling for this iteration'''
|
|
211
|
+
n_istates_avail = len(self.avail_initial_states)
|
|
212
|
+
return max(0, self.n_recycled_segs - n_istates_avail)
|
|
213
|
+
|
|
214
|
+
def check_threshold_configs(self):
|
|
215
|
+
'''Check to see if weight thresholds parameters are valid'''
|
|
216
|
+
if (not np.issubdtype(type(self.largest_allowed_weight), np.floating)) or (
|
|
217
|
+
not np.issubdtype(type(self.smallest_allowed_weight), np.floating)
|
|
218
|
+
):
|
|
219
|
+
try:
|
|
220
|
+
# Trying to self correct
|
|
221
|
+
self.largest_allowed_weight = float(self.largest_allowed_weight)
|
|
222
|
+
self.smallest_allowed_weight = float(self.smallest_allowed_weight)
|
|
223
|
+
except ValueError:
|
|
224
|
+
# Generate error saying thresholds are invalid
|
|
225
|
+
raise ValueError("Invalid weight thresholds specified. Please check your west.cfg.")
|
|
226
|
+
|
|
227
|
+
if np.isclose(self.largest_allowed_weight, self.smallest_allowed_weight):
|
|
228
|
+
raise ValueError("Weight threshold bounds cannot be identical.")
|
|
229
|
+
elif self.largest_allowed_weight < self.smallest_allowed_weight:
|
|
230
|
+
self.smallest_allowed_weight, self.largest_allowed_weight = self.largest_allowed_weight, self.smallest_allowed_weight
|
|
231
|
+
log.warning('Swapped largest allowed weight with smallest allowed weight to fulfill inequality (largest > smallest).')
|
|
232
|
+
|
|
233
|
+
def clear(self):
|
|
234
|
+
'''Explicitly delete all Segment-related state.'''
|
|
235
|
+
|
|
236
|
+
del self.initial_binning, self.final_binning, self.next_iter_binning
|
|
237
|
+
del self.flux_matrix, self.transition_matrix
|
|
238
|
+
del self.new_weights, self.used_initial_states, self.avail_initial_states
|
|
239
|
+
|
|
240
|
+
self.initial_binning = None
|
|
241
|
+
self.final_binning = None
|
|
242
|
+
self.next_iter_binning = None
|
|
243
|
+
self.flux_matrix = None
|
|
244
|
+
self.transition_matrix = None
|
|
245
|
+
self.avail_initial_states = None
|
|
246
|
+
self.used_initial_states = None
|
|
247
|
+
self.new_weights = None
|
|
248
|
+
|
|
249
|
+
def new_iteration(self, initial_states=None, target_states=None, new_weights=None, bin_mapper=None, bin_target_counts=None):
|
|
250
|
+
'''Prepare for a new iteration. ``initial_states`` is a sequence of all InitialState objects valid
|
|
251
|
+
for use in to generating new segments for the *next* iteration (after the one being begun with the call to
|
|
252
|
+
new_iteration); that is, these are states available to recycle to. Target states which generate recycling events
|
|
253
|
+
are specified in ``target_states``, a sequence of TargetState objects. Both ``initial_states``
|
|
254
|
+
and ``target_states`` may be empty as required.
|
|
255
|
+
|
|
256
|
+
The optional ``new_weights`` is a sequence of NewWeightEntry objects which will
|
|
257
|
+
be used to construct the initial flux matrix.
|
|
258
|
+
|
|
259
|
+
The given ``bin_mapper`` will be used for assignment, and ``bin_target_counts`` used for splitting/merging
|
|
260
|
+
target counts; each will be obtained from the system object if omitted or None.
|
|
261
|
+
'''
|
|
262
|
+
|
|
263
|
+
self.clear()
|
|
264
|
+
|
|
265
|
+
new_weights = new_weights or []
|
|
266
|
+
if initial_states is None:
|
|
267
|
+
initial_states = initial_states or []
|
|
268
|
+
|
|
269
|
+
# update mapper, in case it has changed on the system driver and has not been overridden
|
|
270
|
+
if bin_mapper is not None:
|
|
271
|
+
self.bin_mapper = bin_mapper
|
|
272
|
+
else:
|
|
273
|
+
self.bin_mapper = self.system.bin_mapper
|
|
274
|
+
|
|
275
|
+
if bin_target_counts is not None:
|
|
276
|
+
self.bin_target_counts = bin_target_counts
|
|
277
|
+
else:
|
|
278
|
+
self.bin_target_counts = np.array(self.system.bin_target_counts).copy()
|
|
279
|
+
nbins = self.bin_mapper.nbins
|
|
280
|
+
log.debug('mapper is {!r}, handling {:d} bins'.format(self.bin_mapper, nbins))
|
|
281
|
+
|
|
282
|
+
self.initial_binning = self.bin_mapper.construct_bins()
|
|
283
|
+
self.final_binning = self.bin_mapper.construct_bins()
|
|
284
|
+
self.next_iter_binning = None
|
|
285
|
+
|
|
286
|
+
flux_matrix = self.flux_matrix = np.zeros((nbins, nbins), dtype=np.float64)
|
|
287
|
+
transition_matrix = self.transition_matrix = np.zeros((nbins, nbins), np.uint)
|
|
288
|
+
|
|
289
|
+
# map target state specifications to bins
|
|
290
|
+
target_states = target_states or []
|
|
291
|
+
self.target_states = {}
|
|
292
|
+
for tstate in target_states:
|
|
293
|
+
tstate_assignment = self.bin_mapper.assign([tstate.pcoord])[0]
|
|
294
|
+
self.target_states[tstate_assignment] = tstate
|
|
295
|
+
log.debug('target state {!r} mapped to bin {}'.format(tstate, tstate_assignment))
|
|
296
|
+
self.bin_target_counts[tstate_assignment] = 0
|
|
297
|
+
|
|
298
|
+
# loop over recycled segments, adding entries to the flux matrix appropriately
|
|
299
|
+
if new_weights:
|
|
300
|
+
init_pcoords = np.empty((len(new_weights), self.system.pcoord_ndim), dtype=self.system.pcoord_dtype)
|
|
301
|
+
prev_init_pcoords = np.empty((len(new_weights), self.system.pcoord_ndim), dtype=self.system.pcoord_dtype)
|
|
302
|
+
|
|
303
|
+
for ientry, entry in enumerate(new_weights):
|
|
304
|
+
init_pcoords[ientry] = entry.new_init_pcoord
|
|
305
|
+
prev_init_pcoords[ientry] = entry.prev_init_pcoord
|
|
306
|
+
|
|
307
|
+
init_assignments = self.bin_mapper.assign(init_pcoords)
|
|
308
|
+
prev_init_assignments = self.bin_mapper.assign(prev_init_pcoords)
|
|
309
|
+
|
|
310
|
+
for entry, i, j in zip(new_weights, prev_init_assignments, init_assignments):
|
|
311
|
+
flux_matrix[i, j] += entry.weight
|
|
312
|
+
transition_matrix[i, j] += 1
|
|
313
|
+
|
|
314
|
+
del init_pcoords, prev_init_pcoords, init_assignments, prev_init_assignments
|
|
315
|
+
|
|
316
|
+
self.avail_initial_states = {state.state_id: state for state in initial_states}
|
|
317
|
+
self.used_initial_states = {}
|
|
318
|
+
|
|
319
|
+
def add_initial_states(self, initial_states):
|
|
320
|
+
'''Add newly-prepared initial states to the pool available for recycling.'''
|
|
321
|
+
for state in initial_states:
|
|
322
|
+
self.avail_initial_states[state.state_id] = state
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def all_initial_states(self):
|
|
326
|
+
'''Return an iterator over all initial states (available or used)'''
|
|
327
|
+
for state in self.avail_initial_states.values():
|
|
328
|
+
yield state
|
|
329
|
+
for state in self.used_initial_states.values():
|
|
330
|
+
yield state
|
|
331
|
+
|
|
332
|
+
def assign(self, segments, initializing=False):
|
|
333
|
+
'''Assign segments to initial and final bins, and update the (internal) lists of used and available
|
|
334
|
+
initial states. If ``initializing`` is True, then the "final" bin assignments will
|
|
335
|
+
be identical to the initial bin assignments, a condition required for seeding a new iteration from
|
|
336
|
+
pre-existing segments.'''
|
|
337
|
+
|
|
338
|
+
# collect initial and final coordinates into one place
|
|
339
|
+
all_pcoords = np.empty((2, len(segments), self.system.pcoord_ndim), dtype=self.system.pcoord_dtype)
|
|
340
|
+
|
|
341
|
+
for iseg, segment in enumerate(segments):
|
|
342
|
+
all_pcoords[0, iseg] = segment.pcoord[0, :]
|
|
343
|
+
all_pcoords[1, iseg] = segment.pcoord[-1, :]
|
|
344
|
+
|
|
345
|
+
# assign based on initial and final progress coordinates
|
|
346
|
+
initial_assignments = self.bin_mapper.assign(all_pcoords[0, :, :])
|
|
347
|
+
if initializing:
|
|
348
|
+
final_assignments = initial_assignments
|
|
349
|
+
else:
|
|
350
|
+
final_assignments = self.bin_mapper.assign(all_pcoords[1, :, :])
|
|
351
|
+
|
|
352
|
+
initial_binning = self.initial_binning
|
|
353
|
+
final_binning = self.final_binning
|
|
354
|
+
flux_matrix = self.flux_matrix
|
|
355
|
+
transition_matrix = self.transition_matrix
|
|
356
|
+
for segment, iidx, fidx in zip(segments, initial_assignments, final_assignments):
|
|
357
|
+
initial_binning[iidx].add(segment)
|
|
358
|
+
final_binning[fidx].add(segment)
|
|
359
|
+
flux_matrix[iidx, fidx] += segment.weight
|
|
360
|
+
transition_matrix[iidx, fidx] += 1
|
|
361
|
+
|
|
362
|
+
n_recycled_total = self.n_recycled_segs
|
|
363
|
+
n_new_states = n_recycled_total - len(self.avail_initial_states)
|
|
364
|
+
|
|
365
|
+
log.debug(
|
|
366
|
+
'{} walkers scheduled for recycling, {} initial states available'.format(
|
|
367
|
+
n_recycled_total, len(self.avail_initial_states)
|
|
368
|
+
)
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
if n_new_states > 0:
|
|
372
|
+
return n_new_states
|
|
373
|
+
else:
|
|
374
|
+
return 0
|
|
375
|
+
|
|
376
|
+
def _recycle_walkers(self):
|
|
377
|
+
'''Recycle walkers'''
|
|
378
|
+
|
|
379
|
+
# recall that every walker we deal with is already a new segment in the subsequent iteration,
|
|
380
|
+
# so to recycle, we actually move the appropriate Segment from the target bin to the initial state bin
|
|
381
|
+
|
|
382
|
+
self.new_weights = []
|
|
383
|
+
|
|
384
|
+
n_recycled_walkers = len(list(self.recycling_segments))
|
|
385
|
+
if not n_recycled_walkers:
|
|
386
|
+
return
|
|
387
|
+
elif n_recycled_walkers > len(self.avail_initial_states):
|
|
388
|
+
raise ConsistencyError(
|
|
389
|
+
'need {} initial states for recycling, but only {} present'.format(
|
|
390
|
+
n_recycled_walkers, len(self.avail_initial_states)
|
|
391
|
+
)
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
used_istate_ids = set()
|
|
395
|
+
istateiter = iter(self.avail_initial_states.values())
|
|
396
|
+
for ibin, target_state in self.target_states.items():
|
|
397
|
+
target_bin = self.next_iter_binning[ibin]
|
|
398
|
+
for segment in set(target_bin):
|
|
399
|
+
initial_state = next(istateiter)
|
|
400
|
+
istate_assignment = self.bin_mapper.assign([initial_state.pcoord])[0]
|
|
401
|
+
parent = self._parent_map[segment.parent_id]
|
|
402
|
+
parent.endpoint_type = Segment.SEG_ENDPOINT_RECYCLED
|
|
403
|
+
|
|
404
|
+
if log.isEnabledFor(logging.DEBUG):
|
|
405
|
+
log.debug(
|
|
406
|
+
'recycling {!r} from target state {!r} to initial state {!r}'.format(segment, target_state, initial_state)
|
|
407
|
+
)
|
|
408
|
+
log.debug('parent is {!r}'.format(parent))
|
|
409
|
+
|
|
410
|
+
segment.parent_id = -(initial_state.state_id + 1)
|
|
411
|
+
segment.pcoord[0] = initial_state.pcoord
|
|
412
|
+
|
|
413
|
+
self.new_weights.append(
|
|
414
|
+
NewWeightEntry(
|
|
415
|
+
source_type=NewWeightEntry.NW_SOURCE_RECYCLED,
|
|
416
|
+
weight=parent.weight,
|
|
417
|
+
prev_seg_id=parent.seg_id,
|
|
418
|
+
# the .copy() is crucial, otherwise the slice of pcoords will
|
|
419
|
+
# keep the parent segments' pcoord data alive unnecessarily long
|
|
420
|
+
prev_init_pcoord=parent.pcoord[0].copy(),
|
|
421
|
+
prev_final_pcoord=parent.pcoord[-1].copy(),
|
|
422
|
+
new_init_pcoord=initial_state.pcoord.copy(),
|
|
423
|
+
target_state_id=target_state.state_id,
|
|
424
|
+
initial_state_id=initial_state.state_id,
|
|
425
|
+
)
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
if log.isEnabledFor(logging.DEBUG):
|
|
429
|
+
log.debug('new weight entry is {!r}'.format(self.new_weights[-1]))
|
|
430
|
+
|
|
431
|
+
self.next_iter_binning[istate_assignment].add(segment)
|
|
432
|
+
|
|
433
|
+
initial_state.iter_used = segment.n_iter
|
|
434
|
+
log.debug('marking initial state {!r} as used'.format(initial_state))
|
|
435
|
+
used_istate_ids.add(initial_state.state_id)
|
|
436
|
+
target_bin.remove(segment)
|
|
437
|
+
|
|
438
|
+
assert len(target_bin) == 0
|
|
439
|
+
|
|
440
|
+
# Transfer newly-assigned states from "available" to "used"
|
|
441
|
+
for state_id in used_istate_ids:
|
|
442
|
+
self.used_initial_states[state_id] = self.avail_initial_states.pop(state_id)
|
|
443
|
+
|
|
444
|
+
def _split_walker(self, segment, m, bin):
|
|
445
|
+
'''Split the walker ``segment`` (in ``bin``) into ``m`` walkers'''
|
|
446
|
+
new_segments = []
|
|
447
|
+
for _inew in range(0, m):
|
|
448
|
+
new_segment = Segment(
|
|
449
|
+
n_iter=segment.n_iter, # previously incremented
|
|
450
|
+
weight=segment.weight / m,
|
|
451
|
+
parent_id=segment.parent_id,
|
|
452
|
+
wtg_parent_ids=set(segment.wtg_parent_ids),
|
|
453
|
+
pcoord=segment.pcoord.copy(),
|
|
454
|
+
status=Segment.SEG_STATUS_PREPARED,
|
|
455
|
+
)
|
|
456
|
+
new_segment.pcoord[0, :] = segment.pcoord[0, :]
|
|
457
|
+
new_segments.append(new_segment)
|
|
458
|
+
|
|
459
|
+
if log.isEnabledFor(logging.DEBUG):
|
|
460
|
+
log.debug('splitting {!r} into {:d}:\n {!r}'.format(segment, m, new_segments))
|
|
461
|
+
|
|
462
|
+
return new_segments
|
|
463
|
+
|
|
464
|
+
def _merge_walkers(self, segments, cumul_weight, bin):
|
|
465
|
+
'''Merge the given ``segments`` in ``bin``, previously sorted by weight, into one conglomerate segment.
|
|
466
|
+
``cumul_weight`` is the cumulative sum of the weights of the ``segments``; this may be None to calculate here.'''
|
|
467
|
+
|
|
468
|
+
if cumul_weight is None:
|
|
469
|
+
cumul_weight = np.add.accumulate([segment.weight for segment in segments])
|
|
470
|
+
|
|
471
|
+
glom = Segment(
|
|
472
|
+
n_iter=segments[0].n_iter, # assumed correct (and equal among all segments)
|
|
473
|
+
weight=cumul_weight[len(segments) - 1],
|
|
474
|
+
status=Segment.SEG_STATUS_PREPARED,
|
|
475
|
+
pcoord=self.system.new_pcoord_array(),
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# Select the history to use
|
|
479
|
+
# The following takes a random number in the interval 0 <= x < glom.weight, then
|
|
480
|
+
# sees where this value falls among the (sorted) weights of the segments being merged;
|
|
481
|
+
# this ensures that a walker with (e.g.) twice the weight of its brethren has twice the
|
|
482
|
+
# probability of having its history selected for continuation
|
|
483
|
+
iparent = np.digitize((self.rng.uniform(0, glom.weight),), cumul_weight)[0]
|
|
484
|
+
gparent_seg = segments[iparent]
|
|
485
|
+
|
|
486
|
+
# Inherit history from this segment ("gparent" stands for "glom parent", as opposed to historical
|
|
487
|
+
# parent).
|
|
488
|
+
glom.parent_id = gparent_seg.parent_id
|
|
489
|
+
glom.pcoord[0, :] = gparent_seg.pcoord[0, :]
|
|
490
|
+
|
|
491
|
+
# Weight comes from all segments being merged, and therefore all their
|
|
492
|
+
# parent segments
|
|
493
|
+
glom.wtg_parent_ids = set()
|
|
494
|
+
for segment in segments:
|
|
495
|
+
glom.wtg_parent_ids |= segment.wtg_parent_ids
|
|
496
|
+
|
|
497
|
+
# The historical parent of gparent is continued; all others are marked as merged
|
|
498
|
+
for segment in segments:
|
|
499
|
+
if segment is gparent_seg:
|
|
500
|
+
# we must ignore initial states here...
|
|
501
|
+
if segment.parent_id >= 0:
|
|
502
|
+
self._parent_map[segment.parent_id].endpoint_type = Segment.SEG_ENDPOINT_CONTINUES
|
|
503
|
+
else:
|
|
504
|
+
# and "unuse" an initial state here (recall that initial states are in 1:1 correspondence
|
|
505
|
+
# with the segments they initiate), except when a previously-split particle is being
|
|
506
|
+
# merged
|
|
507
|
+
if segment.parent_id >= 0:
|
|
508
|
+
self._parent_map[segment.parent_id].endpoint_type = Segment.SEG_ENDPOINT_MERGED
|
|
509
|
+
else:
|
|
510
|
+
if segment.initial_state_id in {segment.initial_state_id for segment in bin}:
|
|
511
|
+
log.debug('initial state in use by other walker; not removing')
|
|
512
|
+
else:
|
|
513
|
+
initial_state = self.used_initial_states.pop(segment.initial_state_id)
|
|
514
|
+
log.debug('freeing initial state {!r} for future use (merged)'.format(initial_state))
|
|
515
|
+
self.avail_initial_states[initial_state.state_id] = initial_state
|
|
516
|
+
initial_state.iter_used = None
|
|
517
|
+
|
|
518
|
+
if log.isEnabledFor(logging.DEBUG):
|
|
519
|
+
log.debug('merging ({:d}) {!r} into 1:\n {!r}'.format(len(segments), segments, glom))
|
|
520
|
+
|
|
521
|
+
return glom, gparent_seg
|
|
522
|
+
|
|
523
|
+
def _split_by_weight(self, bin, target_count, ideal_weight):
|
|
524
|
+
'''Split overweight particles'''
|
|
525
|
+
|
|
526
|
+
segments = np.array(sorted(bin, key=operator.attrgetter('weight')), dtype=np.object_)
|
|
527
|
+
weights = np.array(list(map(operator.attrgetter('weight'), segments)))
|
|
528
|
+
|
|
529
|
+
if len(bin) > 0:
|
|
530
|
+
assert target_count > 0
|
|
531
|
+
|
|
532
|
+
to_split = segments[weights > self.weight_split_threshold * ideal_weight]
|
|
533
|
+
|
|
534
|
+
for segment in to_split:
|
|
535
|
+
m = int(math.ceil(segment.weight / ideal_weight))
|
|
536
|
+
bin.remove(segment)
|
|
537
|
+
new_segments_list = self._split_walker(segment, m, bin)
|
|
538
|
+
bin.update(new_segments_list)
|
|
539
|
+
|
|
540
|
+
def _merge_by_weight(self, bin, target_count, ideal_weight):
|
|
541
|
+
'''Merge underweight particles'''
|
|
542
|
+
|
|
543
|
+
while True:
|
|
544
|
+
segments = np.array(sorted(bin, key=operator.attrgetter('weight')), dtype=np.object_)
|
|
545
|
+
weights = np.array(list(map(operator.attrgetter('weight'), segments)))
|
|
546
|
+
cumul_weight = np.add.accumulate(weights)
|
|
547
|
+
|
|
548
|
+
to_merge = segments[cumul_weight <= ideal_weight * self.weight_merge_cutoff]
|
|
549
|
+
if len(to_merge) < 2:
|
|
550
|
+
return
|
|
551
|
+
bin.difference_update(to_merge)
|
|
552
|
+
new_segment, parent = self._merge_walkers(to_merge, cumul_weight, bin)
|
|
553
|
+
bin.add(new_segment)
|
|
554
|
+
|
|
555
|
+
def _adjust_count(self, bin, subgroups, target_count):
|
|
556
|
+
weight_getter = operator.attrgetter('weight')
|
|
557
|
+
# Order subgroups by the sum of their weights.
|
|
558
|
+
if len(subgroups) > target_count:
|
|
559
|
+
sorted_subgroups = [set()]
|
|
560
|
+
for i in bin:
|
|
561
|
+
sorted_subgroups[0].add(i)
|
|
562
|
+
else:
|
|
563
|
+
sorted_subgroups = sorted(subgroups, key=lambda gp: sum(seg.weight for seg in gp))
|
|
564
|
+
# Loops over the groups, splitting/merging until the proper count has been reached. This way, no trajectories are accidentally destroyed.
|
|
565
|
+
|
|
566
|
+
# split
|
|
567
|
+
while len(bin) < target_count:
|
|
568
|
+
for i in sorted_subgroups:
|
|
569
|
+
log.debug('adjusting counts by splitting')
|
|
570
|
+
# always split the highest probability walker into two
|
|
571
|
+
segments = sorted(i, key=weight_getter)
|
|
572
|
+
bin.remove(segments[-1])
|
|
573
|
+
i.remove(segments[-1])
|
|
574
|
+
new_segments_list = self._split_walker(segments[-1], 2, bin)
|
|
575
|
+
i.update(new_segments_list)
|
|
576
|
+
bin.update(new_segments_list)
|
|
577
|
+
|
|
578
|
+
if len(bin) == target_count:
|
|
579
|
+
break
|
|
580
|
+
|
|
581
|
+
# merge
|
|
582
|
+
while len(bin) > target_count:
|
|
583
|
+
sorted_subgroups.reverse()
|
|
584
|
+
# Adjust to go from lowest weight group to highest to merge
|
|
585
|
+
for i in sorted_subgroups:
|
|
586
|
+
# Ensures that there are least two walkers to merge
|
|
587
|
+
if len(i) > 1:
|
|
588
|
+
log.debug('adjusting counts by merging')
|
|
589
|
+
# always merge the two lowest-probability walkers
|
|
590
|
+
segments = sorted(i, key=weight_getter)
|
|
591
|
+
bin.difference_update(segments[:2])
|
|
592
|
+
i.difference_update(segments[:2])
|
|
593
|
+
merged_segment, parent = self._merge_walkers(segments[:2], cumul_weight=None, bin=bin)
|
|
594
|
+
i.add(merged_segment)
|
|
595
|
+
bin.add(merged_segment)
|
|
596
|
+
|
|
597
|
+
# As long as we're changing the merge_walkers and split_walkers, adjust them so that they don't update the bin within the function
|
|
598
|
+
# and instead update the bin here. Assuming nothing else relies on those. Make sure with grin.
|
|
599
|
+
# in bash, "find . -name \*.py | xargs fgrep -n '_merge_walkers'"
|
|
600
|
+
if len(bin) == target_count:
|
|
601
|
+
break
|
|
602
|
+
|
|
603
|
+
def _merge_by_threshold(self, bin, subgroup):
|
|
604
|
+
# merge to satisfy weight thresholds
|
|
605
|
+
# this gets rid of weights that are too small
|
|
606
|
+
while True:
|
|
607
|
+
segments = np.array(sorted(subgroup, key=operator.attrgetter('weight')), dtype=np.object_)
|
|
608
|
+
weights = np.array(list(map(operator.attrgetter('weight'), segments)))
|
|
609
|
+
cumul_weight = np.add.accumulate(weights)
|
|
610
|
+
|
|
611
|
+
to_merge = segments[weights < self.smallest_allowed_weight]
|
|
612
|
+
if len(to_merge) < 2:
|
|
613
|
+
return
|
|
614
|
+
bin.difference_update(to_merge)
|
|
615
|
+
subgroup.difference_update(to_merge)
|
|
616
|
+
new_segment, parent = self._merge_walkers(to_merge, cumul_weight, bin)
|
|
617
|
+
bin.add(new_segment)
|
|
618
|
+
subgroup.add(new_segment)
|
|
619
|
+
|
|
620
|
+
def _split_by_threshold(self, bin, subgroup):
|
|
621
|
+
# split to satisfy weight thresholds
|
|
622
|
+
# this splits walkers that are too big
|
|
623
|
+
segments = np.array(sorted(subgroup, key=operator.attrgetter('weight')), dtype=np.object_)
|
|
624
|
+
weights = np.array(list(map(operator.attrgetter('weight'), segments)))
|
|
625
|
+
|
|
626
|
+
to_split = segments[weights > self.largest_allowed_weight]
|
|
627
|
+
for segment in to_split:
|
|
628
|
+
m = int(math.ceil(segment.weight / self.largest_allowed_weight))
|
|
629
|
+
bin.remove(segment)
|
|
630
|
+
subgroup.remove(segment)
|
|
631
|
+
new_segments_list = self._split_walker(segment, m, bin)
|
|
632
|
+
bin.update(new_segments_list)
|
|
633
|
+
subgroup.update(new_segments_list)
|
|
634
|
+
|
|
635
|
+
def _check_pre(self):
|
|
636
|
+
for ibin, _bin in enumerate(self.next_iter_binning):
|
|
637
|
+
if self.bin_target_counts[ibin] == 0 and len(_bin) > 0:
|
|
638
|
+
raise ConsistencyError('bin {:d} has target count of 0 but contains {:d} walkers'.format(ibin, len(_bin)))
|
|
639
|
+
|
|
640
|
+
def _check_post(self):
|
|
641
|
+
for segment in self.next_iter_segments:
|
|
642
|
+
if segment.weight == 0:
|
|
643
|
+
raise ConsistencyError('segment {!r} has weight of zero')
|
|
644
|
+
|
|
645
|
+
def _prep_we(self):
|
|
646
|
+
'''Prepare internal state for WE recycle/split/merge.'''
|
|
647
|
+
self._parent_map = {}
|
|
648
|
+
self.next_iter_binning = self.bin_mapper.construct_bins()
|
|
649
|
+
|
|
650
|
+
def _run_we(self):
|
|
651
|
+
'''Run recycle/split/merge. Do not call this function directly; instead, use
|
|
652
|
+
populate_initial(), rebin_current(), or construct_next().'''
|
|
653
|
+
self._recycle_walkers()
|
|
654
|
+
|
|
655
|
+
# sanity check
|
|
656
|
+
self._check_pre()
|
|
657
|
+
|
|
658
|
+
# Regardless of current particle count, always split overweight particles and merge underweight particles
|
|
659
|
+
# Then and only then adjust for correct particle count
|
|
660
|
+
total_number_of_subgroups = 0
|
|
661
|
+
total_number_of_particles = 0
|
|
662
|
+
for ibin, bin in enumerate(self.next_iter_binning):
|
|
663
|
+
if len(bin) == 0:
|
|
664
|
+
continue
|
|
665
|
+
|
|
666
|
+
# Splits the bin into subgroups as defined by the called function
|
|
667
|
+
target_count = self.bin_target_counts[ibin]
|
|
668
|
+
subgroups = self.subgroup_function(self, ibin, **self.subgroup_function_kwargs)
|
|
669
|
+
total_number_of_subgroups += len(subgroups)
|
|
670
|
+
# Clear the bin
|
|
671
|
+
segments = np.array(sorted(bin, key=operator.attrgetter('weight')), dtype=np.object_)
|
|
672
|
+
weights = np.array(list(map(operator.attrgetter('weight'), segments)))
|
|
673
|
+
ideal_weight = weights.sum() / target_count
|
|
674
|
+
bin.clear()
|
|
675
|
+
# Determines to see whether we have more sub bins than we have target walkers in a bin (or equal to), and then uses
|
|
676
|
+
# different logic to deal with those cases. Should devolve to the Huber/Kim algorithm in the case of few subgroups.
|
|
677
|
+
if len(subgroups) >= target_count:
|
|
678
|
+
for i in subgroups:
|
|
679
|
+
# Merges all members of set i. Checks to see whether there are any to merge.
|
|
680
|
+
if len(i) > 1:
|
|
681
|
+
(segment, parent) = self._merge_walkers(
|
|
682
|
+
list(i),
|
|
683
|
+
np.add.accumulate(np.array(list(map(operator.attrgetter('weight'), i)))),
|
|
684
|
+
i,
|
|
685
|
+
)
|
|
686
|
+
i.clear()
|
|
687
|
+
i.add(segment)
|
|
688
|
+
# Add all members of the set i to the bin. This keeps the bins in sync for the adjustment step.
|
|
689
|
+
bin.update(i)
|
|
690
|
+
|
|
691
|
+
if len(subgroups) > target_count:
|
|
692
|
+
self._adjust_count(bin, subgroups, target_count)
|
|
693
|
+
|
|
694
|
+
if len(subgroups) < target_count:
|
|
695
|
+
for i in subgroups:
|
|
696
|
+
self._split_by_weight(i, target_count, ideal_weight)
|
|
697
|
+
self._merge_by_weight(i, target_count, ideal_weight)
|
|
698
|
+
# Same logic here.
|
|
699
|
+
bin.update(i)
|
|
700
|
+
if self.do_adjust_counts:
|
|
701
|
+
# A modified adjustment routine is necessary to ensure we don't unnecessarily destroy trajectory pathways.
|
|
702
|
+
self._adjust_count(bin, subgroups, target_count)
|
|
703
|
+
if self.do_thresholds:
|
|
704
|
+
for i in subgroups:
|
|
705
|
+
self._split_by_threshold(bin, i)
|
|
706
|
+
self._merge_by_threshold(bin, i)
|
|
707
|
+
for iseg in bin:
|
|
708
|
+
if iseg.weight > self.largest_allowed_weight or iseg.weight < self.smallest_allowed_weight:
|
|
709
|
+
log.warning(
|
|
710
|
+
f'Unable to fulfill threshold conditions for {iseg}. The given threshold range is likely too small.'
|
|
711
|
+
)
|
|
712
|
+
total_number_of_particles += len(bin)
|
|
713
|
+
log.debug('Total number of subgroups: {!r}'.format(total_number_of_subgroups))
|
|
714
|
+
|
|
715
|
+
self._check_post()
|
|
716
|
+
|
|
717
|
+
self.new_weights = self.new_weights or []
|
|
718
|
+
|
|
719
|
+
log.debug('used initial states: {!r}'.format(self.used_initial_states))
|
|
720
|
+
log.debug('available initial states: {!r}'.format(self.avail_initial_states))
|
|
721
|
+
|
|
722
|
+
def populate_initial(self, initial_states, weights, system=None):
|
|
723
|
+
'''Create walkers for a new weighted ensemble simulation.
|
|
724
|
+
|
|
725
|
+
One segment is created for each provided initial state, then binned and split/merged
|
|
726
|
+
as necessary. After this function is called, next_iter_segments will yield the new
|
|
727
|
+
segments to create, used_initial_states will contain data about which of the
|
|
728
|
+
provided initial states were used, and avail_initial_states will contain data about
|
|
729
|
+
which initial states were unused (because their corresponding walkers were merged
|
|
730
|
+
out of existence).
|
|
731
|
+
'''
|
|
732
|
+
|
|
733
|
+
# This has to be down here to avoid an import race
|
|
734
|
+
from westpa.core.data_manager import weight_dtype
|
|
735
|
+
|
|
736
|
+
EPS = np.finfo(weight_dtype).eps
|
|
737
|
+
|
|
738
|
+
system = system or westpa.rc.get_system_driver()
|
|
739
|
+
self.new_iteration(
|
|
740
|
+
initial_states=[], target_states=[], bin_mapper=system.bin_mapper, bin_target_counts=system.bin_target_counts
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
# Create dummy segments
|
|
744
|
+
segments = []
|
|
745
|
+
for seg_id, (initial_state, weight) in enumerate(zip(initial_states, weights)):
|
|
746
|
+
dummy_segment = Segment(
|
|
747
|
+
n_iter=0,
|
|
748
|
+
seg_id=seg_id,
|
|
749
|
+
parent_id=-(initial_state.state_id + 1),
|
|
750
|
+
weight=weight,
|
|
751
|
+
wtg_parent_ids=set([-(initial_state.state_id + 1)]),
|
|
752
|
+
pcoord=system.new_pcoord_array(),
|
|
753
|
+
status=Segment.SEG_STATUS_PREPARED,
|
|
754
|
+
)
|
|
755
|
+
dummy_segment.pcoord[[0, -1]] = initial_state.pcoord
|
|
756
|
+
segments.append(dummy_segment)
|
|
757
|
+
|
|
758
|
+
# Adjust weights, if necessary
|
|
759
|
+
tprob = sum(weights)
|
|
760
|
+
if abs(1.0 - tprob) > len(weights) * EPS:
|
|
761
|
+
pscale = 1.0 / tprob
|
|
762
|
+
log.warning('Weights of initial segments do not sum to unity; scaling by {:g}'.format(pscale))
|
|
763
|
+
for segment in segments:
|
|
764
|
+
segment.weight *= pscale
|
|
765
|
+
|
|
766
|
+
self.assign(segments, initializing=True)
|
|
767
|
+
self.construct_next()
|
|
768
|
+
|
|
769
|
+
# We now have properly-constructed initial segments, except for parent information,
|
|
770
|
+
# and we need to mark initial states as used or unused
|
|
771
|
+
istates_by_id = {state.state_id: state for state in initial_states}
|
|
772
|
+
dummysegs_by_id = self._parent_map
|
|
773
|
+
|
|
774
|
+
# Don't add start states to the list of available initial states.
|
|
775
|
+
# They're only meant to be used in the first iteration, so nothing should ever be recycled into them.
|
|
776
|
+
# Thus, they're not available.
|
|
777
|
+
self.avail_initial_states = {
|
|
778
|
+
k: v for (k, v) in istates_by_id.items() if not v.istate_type == InitialState.ISTATE_TYPE_START
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
for state in self.avail_initial_states.keys():
|
|
782
|
+
if self.avail_initial_states[state].istate_type == InitialState.ISTATE_TYPE_START:
|
|
783
|
+
self.avail_initial_states.pop(state)
|
|
784
|
+
|
|
785
|
+
self.used_initial_states = {}
|
|
786
|
+
for segment in self.next_iter_segments:
|
|
787
|
+
segment.parent_id = dummysegs_by_id[segment.parent_id].parent_id
|
|
788
|
+
segment.wtg_parent_ids = set([segment.parent_id])
|
|
789
|
+
assert segment.initpoint_type == Segment.SEG_INITPOINT_NEWTRAJ
|
|
790
|
+
istate = istates_by_id[segment.initial_state_id]
|
|
791
|
+
try:
|
|
792
|
+
self.used_initial_states[istate.state_id] = self.avail_initial_states.pop(istate.state_id)
|
|
793
|
+
except KeyError:
|
|
794
|
+
# Shared by more than one segment, and already marked as used
|
|
795
|
+
pass
|
|
796
|
+
|
|
797
|
+
for used_istate in self.used_initial_states.values():
|
|
798
|
+
used_istate.iter_used = 1
|
|
799
|
+
|
|
800
|
+
def rebin_current(self, parent_segments):
|
|
801
|
+
'''Reconstruct walkers for the current iteration based on (presumably) new binning.
|
|
802
|
+
The previous iteration's segments must be provided (as ``parent_segments``) in order
|
|
803
|
+
to update endpoint types appropriately.'''
|
|
804
|
+
|
|
805
|
+
self._prep_we()
|
|
806
|
+
self._parent_map = {segment.seg_id: segment for segment in parent_segments}
|
|
807
|
+
|
|
808
|
+
# Create new segments for the next iteration
|
|
809
|
+
# We assume that everything is going to continue without being touched by recycling or WE, and
|
|
810
|
+
# adjust later
|
|
811
|
+
new_pcoord_array = self.system.new_pcoord_array
|
|
812
|
+
n_iter = None
|
|
813
|
+
|
|
814
|
+
for ibin, _bin in enumerate(self.final_binning):
|
|
815
|
+
for segment in _bin:
|
|
816
|
+
if n_iter is None:
|
|
817
|
+
n_iter = segment.n_iter
|
|
818
|
+
else:
|
|
819
|
+
assert segment.n_iter == n_iter
|
|
820
|
+
|
|
821
|
+
new_segment = Segment(
|
|
822
|
+
n_iter=segment.n_iter,
|
|
823
|
+
parent_id=segment.parent_id,
|
|
824
|
+
weight=segment.weight,
|
|
825
|
+
wtg_parent_ids=set(segment.wtg_parent_ids or []),
|
|
826
|
+
pcoord=new_pcoord_array(),
|
|
827
|
+
status=Segment.SEG_STATUS_PREPARED,
|
|
828
|
+
)
|
|
829
|
+
new_segment.pcoord[0] = segment.pcoord[0]
|
|
830
|
+
self.next_iter_binning[ibin].add(new_segment)
|
|
831
|
+
|
|
832
|
+
self._run_we()
|
|
833
|
+
|
|
834
|
+
def construct_next(self):
|
|
835
|
+
'''Construct walkers for the next iteration, by running weighted ensemble recycling
|
|
836
|
+
and bin/split/merge on the segments previously assigned to bins using ``assign``.
|
|
837
|
+
Enough unused initial states must be present in ``self.avail_initial_states`` for every recycled
|
|
838
|
+
walker to be assigned an initial state.
|
|
839
|
+
|
|
840
|
+
After this function completes, ``self.flux_matrix`` contains a valid flux matrix for this
|
|
841
|
+
iteration (including any contributions from recycling from the previous iteration), and
|
|
842
|
+
``self.next_iter_segments`` contains a list of segments ready for the next iteration,
|
|
843
|
+
with appropriate values set for weight, endpoint type, parent walkers, and so on.
|
|
844
|
+
'''
|
|
845
|
+
|
|
846
|
+
self._prep_we()
|
|
847
|
+
|
|
848
|
+
# Create new segments for the next iteration
|
|
849
|
+
# We assume that everything is going to continue without being touched by recycling or WE, and
|
|
850
|
+
# adjust later
|
|
851
|
+
new_pcoord_array = self.system.new_pcoord_array
|
|
852
|
+
n_iter = None
|
|
853
|
+
|
|
854
|
+
for ibin, _bin in enumerate(self.final_binning):
|
|
855
|
+
for segment in _bin:
|
|
856
|
+
if n_iter is None:
|
|
857
|
+
n_iter = segment.n_iter
|
|
858
|
+
else:
|
|
859
|
+
assert segment.n_iter == n_iter
|
|
860
|
+
|
|
861
|
+
segment.endpoint_type = Segment.SEG_ENDPOINT_CONTINUES
|
|
862
|
+
new_segment = Segment(
|
|
863
|
+
n_iter=segment.n_iter + 1,
|
|
864
|
+
parent_id=segment.seg_id,
|
|
865
|
+
weight=segment.weight,
|
|
866
|
+
wtg_parent_ids=[segment.seg_id],
|
|
867
|
+
pcoord=new_pcoord_array(),
|
|
868
|
+
status=Segment.SEG_STATUS_PREPARED,
|
|
869
|
+
)
|
|
870
|
+
new_segment.pcoord[0] = segment.pcoord[-1]
|
|
871
|
+
self.next_iter_binning[ibin].add(new_segment)
|
|
872
|
+
|
|
873
|
+
# Store a link to the parent segment, so we can update its endpoint status as we need,
|
|
874
|
+
# based on its ID
|
|
875
|
+
self._parent_map[segment.seg_id] = segment
|
|
876
|
+
|
|
877
|
+
self._run_we()
|
|
878
|
+
|
|
879
|
+
log.debug('used initial states: {!r}'.format(self.used_initial_states))
|
|
880
|
+
log.debug('available initial states: {!r}'.format(self.avail_initial_states))
|
|
881
|
+
|
|
882
|
+
def _log_bin_stats(self, bin, heading=None, level=logging.DEBUG):
|
|
883
|
+
if log.isEnabledFor(level):
|
|
884
|
+
weights = sorted(np.array(list(map(operator.attrgetter('weight'), bin))))
|
|
885
|
+
bin_label = getattr(bin, 'label', None) or ''
|
|
886
|
+
log_fmt = '\n '.join(
|
|
887
|
+
[
|
|
888
|
+
'',
|
|
889
|
+
'stats for bin {bin_label!r} {heading}',
|
|
890
|
+
' count: {bin.count:d}, target count: {bin.target_count:d}',
|
|
891
|
+
' total weight: {bin.weight:{weight_spec}}, ideal weight: {ideal_weight:{weight_spec}}',
|
|
892
|
+
' mean weight: {mean_weight:{weight_spec}}, stdev weight: {stdev_weight:{weight_spec}}',
|
|
893
|
+
' min weight: {min_weight:{weight_spec}}, med weight : {median_weight:{weight_spec}}'
|
|
894
|
+
+ ', max weight: {max_weight:{weight_spec}}',
|
|
895
|
+
]
|
|
896
|
+
)
|
|
897
|
+
log_msg = log_fmt.format(
|
|
898
|
+
log_fmt,
|
|
899
|
+
weight_spec='<12.6e',
|
|
900
|
+
bin_label=bin_label,
|
|
901
|
+
heading=heading,
|
|
902
|
+
bin=bin,
|
|
903
|
+
ideal_weight=bin.weight / bin.target_count,
|
|
904
|
+
mean_weight=weights.mean(),
|
|
905
|
+
stdev_weight=weights.std(),
|
|
906
|
+
min_weight=weights[0],
|
|
907
|
+
median_weight=np.median(weights),
|
|
908
|
+
max_weight=weights[-1],
|
|
909
|
+
)
|
|
910
|
+
log.log(level, log_msg)
|