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,55 @@
|
|
|
1
|
+
from . import _assign
|
|
2
|
+
from . import assign, bins
|
|
3
|
+
|
|
4
|
+
from .assign import (
|
|
5
|
+
NopMapper,
|
|
6
|
+
FuncBinMapper,
|
|
7
|
+
PiecewiseBinMapper,
|
|
8
|
+
RectilinearBinMapper,
|
|
9
|
+
RecursiveBinMapper,
|
|
10
|
+
VectorizingFuncBinMapper,
|
|
11
|
+
VoronoiBinMapper,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from .mab import map_mab, MABBinMapper
|
|
15
|
+
from .binless import map_binless, BinlessMapper
|
|
16
|
+
|
|
17
|
+
from .mab_driver import MABDriver
|
|
18
|
+
from .mab_manager import MABSimManager
|
|
19
|
+
from .binless_manager import BinlessSimManager
|
|
20
|
+
from .binless_driver import BinlessDriver
|
|
21
|
+
|
|
22
|
+
from ._assign import accumulate_labeled_populations, assign_and_label, accumulate_state_populations_from_labeled
|
|
23
|
+
from ._assign import assignments_list_to_table
|
|
24
|
+
|
|
25
|
+
from .assign import coord_dtype, index_dtype
|
|
26
|
+
from .bins import Bin
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
'_assign',
|
|
31
|
+
'assign',
|
|
32
|
+
'bins',
|
|
33
|
+
'NopMapper',
|
|
34
|
+
'FuncBinMapper',
|
|
35
|
+
'PiecewiseBinMapper',
|
|
36
|
+
'RectilinearBinMapper',
|
|
37
|
+
'RecursiveBinMapper',
|
|
38
|
+
'VectorizingFuncBinMapper',
|
|
39
|
+
'VoronoiBinMapper',
|
|
40
|
+
'map_mab',
|
|
41
|
+
'map_binless',
|
|
42
|
+
'MABBinMapper',
|
|
43
|
+
'BinlessMapper',
|
|
44
|
+
'MABDriver',
|
|
45
|
+
'MABSimManager',
|
|
46
|
+
'BinlessDriver',
|
|
47
|
+
'BinlessSimManager',
|
|
48
|
+
'accumulate_labeled_populations',
|
|
49
|
+
'assign_and_label',
|
|
50
|
+
'accumulate_state_populations_from_labeled',
|
|
51
|
+
'assignments_list_to_table',
|
|
52
|
+
'coord_dtype',
|
|
53
|
+
'index_dtype',
|
|
54
|
+
'Bin',
|
|
55
|
+
]
|
|
Binary file
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Bin assignment for WEST simulations. This module defines "bin mappers" which take
|
|
3
|
+
vectors of coordinates (or rather, coordinate tuples), and assign each a definite
|
|
4
|
+
integer value identifying a bin. Critical portions are implemented in a Cython
|
|
5
|
+
extension module.
|
|
6
|
+
|
|
7
|
+
A number of pre-defined bin mappers are available here:
|
|
8
|
+
|
|
9
|
+
* :class:`RectilinearBinMapper`, for bins divided by N-dimensional grids
|
|
10
|
+
* :class:`FuncBinMapper`, for functions which directly calculate bin assignments
|
|
11
|
+
for a number of coordinate values. This is best used with C/Cython/Numba
|
|
12
|
+
functions, or intellegently-tuned numpy-based Python functions.
|
|
13
|
+
* :class:`VectorizingFuncBinMapper`, for functions which calculate a bin
|
|
14
|
+
assignment for a single coordinate value. This is best used for arbitrary
|
|
15
|
+
Python functions.
|
|
16
|
+
* :class:`PiecewiseBinMapper`, for using a set of boolean-valued functions, one
|
|
17
|
+
per bin, to determine assignments. This is likely to be much slower than a
|
|
18
|
+
`FuncBinMapper` or `VectorizingFuncBinMapper` equipped with an appropriate
|
|
19
|
+
function, and its use is discouraged.
|
|
20
|
+
|
|
21
|
+
One "super-mapper" is available, for assembling more complex bin spaces from
|
|
22
|
+
simpler components:
|
|
23
|
+
|
|
24
|
+
* :class:`RecursiveBinMapper`, for nesting one set of bins within another.
|
|
25
|
+
|
|
26
|
+
Users are also free to implement their own mappers. A bin mapper must implement, at
|
|
27
|
+
least, an ``assign(coords, mask=None, output=None)`` method, which is responsible
|
|
28
|
+
for mapping each of the vector of coordinate tuples ``coords`` to an integer
|
|
29
|
+
(np.uint16) indicating a what bin that coordinate tuple falls into. The optional
|
|
30
|
+
``mask`` (a numpy bool array) specifies that some coordinates are to be skipped; this is used,
|
|
31
|
+
for instance, by the recursive (nested) bin mapper to minimize the number of calculations
|
|
32
|
+
required to definitively assign a coordinate tuple to a bin. Similarly, the optional
|
|
33
|
+
``output`` must be an integer (uint16) array of the same length as ``coords``, into which
|
|
34
|
+
assignments are written. The ``assign()`` function must return a reference to ``output``.
|
|
35
|
+
(This is used to avoid allocating many temporary output arrays in complex binning
|
|
36
|
+
scenarios.)
|
|
37
|
+
|
|
38
|
+
A user-defined bin mapper must also make an ``nbins`` property available, containing
|
|
39
|
+
the total number of bins within the mapper.
|
|
40
|
+
|
|
41
|
+
'''
|
|
42
|
+
|
|
43
|
+
import hashlib
|
|
44
|
+
import logging
|
|
45
|
+
import pickle
|
|
46
|
+
|
|
47
|
+
import numpy as np
|
|
48
|
+
|
|
49
|
+
from .bins import Bin
|
|
50
|
+
from ._assign import output_map, apply_down, apply_down_argmin_across, rectilinear_assign
|
|
51
|
+
|
|
52
|
+
# All bin numbers are 16-bit unsigned ints, with one element (65525) reserved to
|
|
53
|
+
# indicate unknown or unassigned points. This allows up to 65,536 bins, making
|
|
54
|
+
# rate and flux matrices up to 32 GB (2**32 elements * 8 bytes). If you need more
|
|
55
|
+
# bins, change index_dtype here and index_dtype and index_t in _assign.pyx.
|
|
56
|
+
index_dtype = np.uint16
|
|
57
|
+
UNKNOWN_INDEX = 65535
|
|
58
|
+
|
|
59
|
+
# All coordinates are currently 32-bit floats. If you need 64-bit, change
|
|
60
|
+
# coord_dtype here and coord_t in _assign.pyx.
|
|
61
|
+
coord_dtype = np.float32
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
log = logging.getLogger(__name__)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class BinMapper:
|
|
68
|
+
hashfunc = hashlib.sha256
|
|
69
|
+
|
|
70
|
+
def __init__(self):
|
|
71
|
+
self.labels = None
|
|
72
|
+
self.nbins = 0
|
|
73
|
+
|
|
74
|
+
def construct_bins(self, type_=Bin):
|
|
75
|
+
'''Construct and return an array of bins of type ``type``'''
|
|
76
|
+
return np.array([type_() for _i in range(self.nbins)], dtype=np.object_)
|
|
77
|
+
|
|
78
|
+
def pickle_and_hash(self):
|
|
79
|
+
'''Pickle this mapper and calculate a hash of the result (thus identifying the
|
|
80
|
+
contents of the pickled data), returning a tuple ``(pickled_data, hash)``.
|
|
81
|
+
This will raise PickleError if this mapper cannot be pickled, in which case
|
|
82
|
+
code that would otherwise rely on detecting a topology change must assume
|
|
83
|
+
a topology change happened, even if one did not.
|
|
84
|
+
'''
|
|
85
|
+
|
|
86
|
+
pkldat = pickle.dumps(self, pickle.HIGHEST_PROTOCOL)
|
|
87
|
+
hash = self.hashfunc(pkldat)
|
|
88
|
+
return (pkldat, hash.hexdigest())
|
|
89
|
+
|
|
90
|
+
def __repr__(self):
|
|
91
|
+
return '<{} at 0x{:x} with {:d} bins>'.format(self.__class__.__name__, id(self), self.nbins or 0)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class NopMapper(BinMapper):
|
|
95
|
+
'''Put everything into one bin.'''
|
|
96
|
+
|
|
97
|
+
def __init__(self):
|
|
98
|
+
super().__init__()
|
|
99
|
+
self.nbins = 1
|
|
100
|
+
self.labels = ['nop']
|
|
101
|
+
|
|
102
|
+
def assign(self, coords, mask=None, output=None):
|
|
103
|
+
if output is None:
|
|
104
|
+
output = np.zeros((len(coords),), dtype=index_dtype)
|
|
105
|
+
|
|
106
|
+
if mask is None:
|
|
107
|
+
mask = np.ones((len(coords),), dtype=np.bool_)
|
|
108
|
+
else:
|
|
109
|
+
mask = np.require(mask, dtype=np.bool_)
|
|
110
|
+
|
|
111
|
+
output[mask] = 0
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class RectilinearBinMapper(BinMapper):
|
|
115
|
+
'''Bin into a rectangular grid based on tuples of float values'''
|
|
116
|
+
|
|
117
|
+
def __init__(self, boundaries):
|
|
118
|
+
super().__init__()
|
|
119
|
+
self._boundaries = None
|
|
120
|
+
self._boundlens = None
|
|
121
|
+
self.ndim = 0
|
|
122
|
+
self.nbins = 0
|
|
123
|
+
|
|
124
|
+
# the setter function below handles all of the required wrangling
|
|
125
|
+
self.boundaries = boundaries
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def boundaries(self):
|
|
129
|
+
return self._boundaries
|
|
130
|
+
|
|
131
|
+
@boundaries.setter
|
|
132
|
+
def boundaries(self, boundaries):
|
|
133
|
+
del self._boundaries, self.labels
|
|
134
|
+
self._boundaries = []
|
|
135
|
+
self.labels = []
|
|
136
|
+
for boundset in boundaries:
|
|
137
|
+
boundarray = np.ascontiguousarray(boundset, dtype=coord_dtype)
|
|
138
|
+
db = np.diff(boundarray)
|
|
139
|
+
if (db <= 0).any():
|
|
140
|
+
raise ValueError('boundary set must be strictly monotonically increasing')
|
|
141
|
+
self._boundaries.append(boundarray)
|
|
142
|
+
self._boundlens = np.array([len(boundset) for boundset in self._boundaries], dtype=index_dtype)
|
|
143
|
+
self.ndim = len(self._boundaries)
|
|
144
|
+
self.nbins = np.multiply.accumulate([1] + [len(bounds) - 1 for bounds in self._boundaries])[-1]
|
|
145
|
+
|
|
146
|
+
_boundaries = self._boundaries
|
|
147
|
+
binspace_shape = tuple(self._boundlens[:] - 1)
|
|
148
|
+
for index in np.ndindex(binspace_shape):
|
|
149
|
+
label = (
|
|
150
|
+
'['
|
|
151
|
+
+ ', '.join(
|
|
152
|
+
f'({boundarray[index[idim]]!s}, {boundarray[index[idim] + 1]!s})' for idim, boundarray in enumerate(_boundaries)
|
|
153
|
+
)
|
|
154
|
+
+ ']'
|
|
155
|
+
)
|
|
156
|
+
self.labels.append(label)
|
|
157
|
+
|
|
158
|
+
def assign(self, coords, mask=None, output=None):
|
|
159
|
+
try:
|
|
160
|
+
passed_coord_dtype = coords.dtype
|
|
161
|
+
except AttributeError:
|
|
162
|
+
coords = np.require(coords, dtype=coord_dtype)
|
|
163
|
+
else:
|
|
164
|
+
if passed_coord_dtype != coord_dtype:
|
|
165
|
+
coords = np.require(coords, dtype=coord_dtype)
|
|
166
|
+
|
|
167
|
+
if coords.ndim != 2:
|
|
168
|
+
raise TypeError('coords must be 2-dimensional')
|
|
169
|
+
|
|
170
|
+
if mask is None:
|
|
171
|
+
mask = np.ones((len(coords),), dtype=np.bool_)
|
|
172
|
+
elif len(mask) != len(coords):
|
|
173
|
+
raise TypeError('mask [shape {}] has different length than coords [shape {}]'.format(mask.shape, coords.shape))
|
|
174
|
+
|
|
175
|
+
if output is None:
|
|
176
|
+
output = np.empty((len(coords),), dtype=index_dtype)
|
|
177
|
+
elif len(output) != len(coords):
|
|
178
|
+
raise TypeError('output has different length than coords')
|
|
179
|
+
|
|
180
|
+
rectilinear_assign(coords, mask, output, self.boundaries, self._boundlens)
|
|
181
|
+
|
|
182
|
+
return output
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class PiecewiseBinMapper(BinMapper):
|
|
186
|
+
'''Binning using a set of functions returing boolean values; if the Nth function
|
|
187
|
+
returns True for a coordinate tuple, then that coordinate is in the Nth bin.'''
|
|
188
|
+
|
|
189
|
+
def __init__(self, functions):
|
|
190
|
+
self.functions = functions
|
|
191
|
+
self.nbins = len(functions)
|
|
192
|
+
self.index_dtype = np.min_scalar_type(self.nbins)
|
|
193
|
+
self.labels = [str(func) for func in functions]
|
|
194
|
+
|
|
195
|
+
def assign(self, coords, mask=None, output=None):
|
|
196
|
+
if output is None:
|
|
197
|
+
output = np.zeros((len(coords),), dtype=index_dtype)
|
|
198
|
+
|
|
199
|
+
if mask is None:
|
|
200
|
+
mask = np.ones((len(coords),), dtype=np.bool_)
|
|
201
|
+
else:
|
|
202
|
+
mask = np.require(mask, dtype=np.bool_)
|
|
203
|
+
|
|
204
|
+
coord_subset = coords[mask]
|
|
205
|
+
fnvals = np.empty((len(coord_subset), len(self.functions)), dtype=index_dtype)
|
|
206
|
+
for ifn, fn in enumerate(self.functions):
|
|
207
|
+
rsl = np.apply_along_axis(fn, 0, coord_subset)
|
|
208
|
+
if rsl.ndim > 1:
|
|
209
|
+
# this should work like a squeeze, unless the function returned something truly
|
|
210
|
+
# stupid (e.g., a 3d array with at least two dimensions greater than 1), in which
|
|
211
|
+
# case a broadcast error will occur
|
|
212
|
+
fnvals[:, ifn] = rsl.flat
|
|
213
|
+
else:
|
|
214
|
+
fnvals[:, ifn] = rsl
|
|
215
|
+
amask = np.require(fnvals.argmax(axis=1), dtype=index_dtype)
|
|
216
|
+
output[mask] = amask
|
|
217
|
+
return output
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class FuncBinMapper(BinMapper):
|
|
221
|
+
'''Binning using a custom function which must iterate over input coordinate
|
|
222
|
+
sets itself.'''
|
|
223
|
+
|
|
224
|
+
def __init__(self, func, nbins, args=None, kwargs=None):
|
|
225
|
+
self.func = func
|
|
226
|
+
self.nbins = nbins
|
|
227
|
+
self.args = args or ()
|
|
228
|
+
self.kwargs = kwargs or {}
|
|
229
|
+
self.labels = ['{!r} bin {:d}'.format(func, ibin) for ibin in range(nbins)]
|
|
230
|
+
|
|
231
|
+
def assign(self, coords, mask=None, output=None):
|
|
232
|
+
try:
|
|
233
|
+
passed_coord_dtype = coords.dtype
|
|
234
|
+
except AttributeError:
|
|
235
|
+
coords = np.require(coords, dtype=coord_dtype)
|
|
236
|
+
else:
|
|
237
|
+
if passed_coord_dtype != coord_dtype:
|
|
238
|
+
coords = np.require(coords, dtype=coord_dtype)
|
|
239
|
+
|
|
240
|
+
if coords.ndim != 2:
|
|
241
|
+
raise TypeError('coords must be 2-dimensional')
|
|
242
|
+
if mask is None:
|
|
243
|
+
mask = np.ones((len(coords),), dtype=np.bool_)
|
|
244
|
+
elif len(mask) != len(coords):
|
|
245
|
+
raise TypeError('mask [shape {}] has different length than coords [shape {}]'.format(mask.shape, coords.shape))
|
|
246
|
+
|
|
247
|
+
if output is None:
|
|
248
|
+
output = np.empty((len(coords),), dtype=index_dtype)
|
|
249
|
+
elif len(output) != len(coords):
|
|
250
|
+
raise TypeError('output has different length than coords')
|
|
251
|
+
|
|
252
|
+
self.func(coords, mask, output, *self.args, **self.kwargs)
|
|
253
|
+
|
|
254
|
+
return output
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class VectorizingFuncBinMapper(BinMapper):
|
|
258
|
+
'''Binning using a custom function which is evaluated once for each (unmasked)
|
|
259
|
+
coordinate tuple provided.'''
|
|
260
|
+
|
|
261
|
+
def __init__(self, func, nbins, args=None, kwargs=None):
|
|
262
|
+
self.func = func
|
|
263
|
+
self.args = args or ()
|
|
264
|
+
self.kwargs = kwargs or {}
|
|
265
|
+
self.nbins = nbins
|
|
266
|
+
self.index_dtype = np.min_scalar_type(self.nbins)
|
|
267
|
+
self.labels = ['{!r} bin {:d}'.format(func, ibin) for ibin in range(nbins)]
|
|
268
|
+
|
|
269
|
+
def assign(self, coords, mask=None, output=None):
|
|
270
|
+
try:
|
|
271
|
+
passed_coord_dtype = coords.dtype
|
|
272
|
+
except AttributeError:
|
|
273
|
+
coords = np.require(coords, dtype=coord_dtype)
|
|
274
|
+
else:
|
|
275
|
+
if passed_coord_dtype != coord_dtype:
|
|
276
|
+
coords = np.require(coords, dtype=coord_dtype)
|
|
277
|
+
|
|
278
|
+
if coords.ndim != 2:
|
|
279
|
+
raise TypeError('coords must be 2-dimensional')
|
|
280
|
+
if mask is None:
|
|
281
|
+
mask = np.ones((len(coords),), dtype=np.bool_)
|
|
282
|
+
elif len(mask) != len(coords):
|
|
283
|
+
raise TypeError('mask [shape {}] has different length than coords [shape {}]'.format(mask.shape, coords.shape))
|
|
284
|
+
|
|
285
|
+
if output is None:
|
|
286
|
+
output = np.empty((len(coords),), dtype=index_dtype)
|
|
287
|
+
elif len(output) != len(coords):
|
|
288
|
+
raise TypeError('output has different length than coords')
|
|
289
|
+
|
|
290
|
+
apply_down(self.func, self.args, self.kwargs, coords, mask, output)
|
|
291
|
+
|
|
292
|
+
return output
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class VoronoiBinMapper(BinMapper):
|
|
296
|
+
'''A one-dimensional mapper which assigns a multidimensional pcoord to the
|
|
297
|
+
closest center based on a distance metric. Both the list of centers and the
|
|
298
|
+
distance function must be supplied.'''
|
|
299
|
+
|
|
300
|
+
def __init__(self, dfunc, centers, dfargs=None, dfkwargs=None):
|
|
301
|
+
self.dfunc = dfunc
|
|
302
|
+
self.dfargs = dfargs or ()
|
|
303
|
+
self.dfkwargs = dfkwargs or {}
|
|
304
|
+
self.centers = np.asarray(centers)
|
|
305
|
+
self.nbins = self.centers.shape[0]
|
|
306
|
+
self.ndim = self.centers.shape[1]
|
|
307
|
+
self.labels = ['center={!r}'.format(center) for center in self.centers]
|
|
308
|
+
|
|
309
|
+
# Sanity check: does the distance map the centers to themselves?
|
|
310
|
+
check = self.assign(self.centers)
|
|
311
|
+
if (check != np.arange(len(self.centers))).any():
|
|
312
|
+
raise TypeError('dfunc does not map centers to themselves')
|
|
313
|
+
|
|
314
|
+
def assign(self, coords, mask=None, output=None):
|
|
315
|
+
try:
|
|
316
|
+
passed_coord_dtype = coords.dtype
|
|
317
|
+
except AttributeError:
|
|
318
|
+
coords = np.require(coords, dtype=coord_dtype)
|
|
319
|
+
else:
|
|
320
|
+
if passed_coord_dtype != coord_dtype:
|
|
321
|
+
coords = np.require(coords, dtype=coord_dtype)
|
|
322
|
+
|
|
323
|
+
if coords.ndim != 2:
|
|
324
|
+
raise TypeError('coords must be 2-dimensional')
|
|
325
|
+
if mask is None:
|
|
326
|
+
mask = np.ones((len(coords),), dtype=np.bool_)
|
|
327
|
+
elif len(mask) != len(coords):
|
|
328
|
+
raise TypeError('mask [shape {}] has different length than coords [shape {}]'.format(mask.shape, coords.shape))
|
|
329
|
+
|
|
330
|
+
if output is None:
|
|
331
|
+
output = np.empty((len(coords),), dtype=index_dtype)
|
|
332
|
+
elif len(output) != len(coords):
|
|
333
|
+
raise TypeError('output has different length than coords')
|
|
334
|
+
|
|
335
|
+
apply_down_argmin_across(self.dfunc, (self.centers,) + self.dfargs, self.dfkwargs, self.nbins, coords, mask, output)
|
|
336
|
+
|
|
337
|
+
return output
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class RecursiveBinMapper(BinMapper):
|
|
341
|
+
'''Nest mappers one within another.'''
|
|
342
|
+
|
|
343
|
+
def __init__(self, base_mapper, start_index=0):
|
|
344
|
+
self.base_mapper = base_mapper
|
|
345
|
+
self.nbins = base_mapper.nbins
|
|
346
|
+
|
|
347
|
+
# Targets for recursion
|
|
348
|
+
self._recursion_targets = {}
|
|
349
|
+
|
|
350
|
+
# Which bins must we recurse into?
|
|
351
|
+
self._recursion_map = np.zeros((self.base_mapper.nbins,), dtype=np.bool_)
|
|
352
|
+
|
|
353
|
+
self.start_index = start_index
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def labels(self):
|
|
357
|
+
for ilabel in range(self.base_mapper.nbins):
|
|
358
|
+
if self._recursion_map[ilabel]:
|
|
359
|
+
for label in self._recursion_targets[ilabel].labels:
|
|
360
|
+
yield label
|
|
361
|
+
else:
|
|
362
|
+
yield self.base_mapper.labels[ilabel]
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def start_index(self):
|
|
366
|
+
return self._start_index
|
|
367
|
+
|
|
368
|
+
@start_index.setter
|
|
369
|
+
def start_index(self, new_index):
|
|
370
|
+
self._start_index = new_index
|
|
371
|
+
not_recursed = ~self._recursion_map
|
|
372
|
+
n_not_recursed = not_recursed.sum()
|
|
373
|
+
if n_not_recursed == self.nbins:
|
|
374
|
+
self._output_map = np.arange(self._start_index, self._start_index + self.nbins, dtype=index_dtype)
|
|
375
|
+
elif n_not_recursed > 0:
|
|
376
|
+
# This looks like uninitialized access, but self._output_map is always set during __init__
|
|
377
|
+
# (by self.start_index = 0, or whatever value was passed in), so this modifies the existing
|
|
378
|
+
# set chosen above
|
|
379
|
+
self._output_map[not_recursed] = np.arange(self._start_index, self._start_index + n_not_recursed, dtype=index_dtype)
|
|
380
|
+
else:
|
|
381
|
+
# No un-replaced bins
|
|
382
|
+
self._output_map = None
|
|
383
|
+
|
|
384
|
+
n_own_bins = self.base_mapper.nbins - self._recursion_map.sum()
|
|
385
|
+
startindex = self.start_index + n_own_bins
|
|
386
|
+
for mapper in self._recursion_targets.values():
|
|
387
|
+
mapper.start_index = startindex
|
|
388
|
+
startindex += mapper.nbins
|
|
389
|
+
|
|
390
|
+
def add_mapper(self, mapper, replaces_bin_at):
|
|
391
|
+
'''Replace the bin containing the coordinate tuple ``replaces_bin_at`` with the
|
|
392
|
+
specified ``mapper``.'''
|
|
393
|
+
|
|
394
|
+
replaces_bin_at = np.require(replaces_bin_at, dtype=coord_dtype)
|
|
395
|
+
if replaces_bin_at.ndim < 1:
|
|
396
|
+
replaces_bin_at.shape = (1, 1)
|
|
397
|
+
elif replaces_bin_at.ndim < 2:
|
|
398
|
+
replaces_bin_at.shape = (1, replaces_bin_at.shape[0])
|
|
399
|
+
elif replaces_bin_at.ndim > 2 or replaces_bin_at.shape[1] > 1:
|
|
400
|
+
raise TypeError('a single coordinate vector is required')
|
|
401
|
+
|
|
402
|
+
self.nbins += mapper.nbins - 1
|
|
403
|
+
|
|
404
|
+
ibin = self.base_mapper.assign(replaces_bin_at)[0]
|
|
405
|
+
log.debug('replacing bin {!r} containing {!r} with {!r}'.format(ibin, replaces_bin_at, mapper))
|
|
406
|
+
if self._recursion_map[ibin]:
|
|
407
|
+
# recursively add; this doesn't change anything for us except our
|
|
408
|
+
# total bin count, which has been accounted for above
|
|
409
|
+
self._recursion_targets[ibin].add_mapper(mapper, replaces_bin_at[0])
|
|
410
|
+
else:
|
|
411
|
+
# replace a bin on our mapper
|
|
412
|
+
self._recursion_map[ibin] = True
|
|
413
|
+
mapper = RecursiveBinMapper(mapper)
|
|
414
|
+
self._recursion_targets[ibin] = mapper
|
|
415
|
+
|
|
416
|
+
# we have updated our list of recursed bins, so set our own start index to trigger a recursive
|
|
417
|
+
# reassignment of mappers' output values
|
|
418
|
+
# Note that we're reordering the recursion targets based on outer bin numbers (dict keys) first,
|
|
419
|
+
# so the order the mappers were added no longer matters...
|
|
420
|
+
self._recursion_targets = {k: self._recursion_targets[k] for k in sorted(self._recursion_targets)}
|
|
421
|
+
self.start_index = self.start_index
|
|
422
|
+
|
|
423
|
+
def assign(self, coords, mask=None, output=None):
|
|
424
|
+
if mask is None:
|
|
425
|
+
mask = np.ones((len(coords),), dtype=np.bool_)
|
|
426
|
+
|
|
427
|
+
if output is None:
|
|
428
|
+
output = np.empty((len(coords),), dtype=index_dtype)
|
|
429
|
+
|
|
430
|
+
# mapping mask -- which output values come from our base
|
|
431
|
+
# region set and therefore must be remapped
|
|
432
|
+
mmask = np.zeros((len(coords),), dtype=np.bool_)
|
|
433
|
+
|
|
434
|
+
# Assign based on this mapper
|
|
435
|
+
self.base_mapper.assign(coords, mask, output)
|
|
436
|
+
|
|
437
|
+
# Which coordinates do we need to reassign, because they landed in
|
|
438
|
+
# bins with embedded mappers?
|
|
439
|
+
rmasks = {}
|
|
440
|
+
for rindex, mapper in self._recursion_targets.items():
|
|
441
|
+
omask = output == rindex
|
|
442
|
+
mmask |= omask
|
|
443
|
+
rmasks[rindex] = omask
|
|
444
|
+
|
|
445
|
+
# remap output from our (base) mapper
|
|
446
|
+
# omap may be None if every bin has a recursive mapper in it
|
|
447
|
+
omap = self._output_map
|
|
448
|
+
if omap is not None:
|
|
449
|
+
output_map(output, omap, mask & ~mmask)
|
|
450
|
+
|
|
451
|
+
# do any recursive assignments necessary
|
|
452
|
+
for rindex, mapper in self._recursion_targets.items():
|
|
453
|
+
mapper.assign(coords, mask & rmasks[rindex], output)
|
|
454
|
+
|
|
455
|
+
return output
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from westpa.core.binning import FuncBinMapper
|
|
3
|
+
from westpa.core.extloader import get_object
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
log = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def map_binless(coords, mask, output, *args, **kwargs):
|
|
10
|
+
'''Adaptively groups walkers according to a user-defined grouping function
|
|
11
|
+
that is defined externally. Very general implementation but limited to
|
|
12
|
+
only a two dimensional progress coordinate (for now).'''
|
|
13
|
+
|
|
14
|
+
n_groups = kwargs.get("n_groups")
|
|
15
|
+
n_dims = kwargs.get("n_dims")
|
|
16
|
+
group_function = get_object(kwargs.get("group_function"))
|
|
17
|
+
log.debug(f'binless arguments: {kwargs}')
|
|
18
|
+
try:
|
|
19
|
+
group_function_kwargs = kwargs.get('group_function_kwargs')['group_arguments']
|
|
20
|
+
except KeyError:
|
|
21
|
+
group_function_kwargs = {}
|
|
22
|
+
ndim = n_dims
|
|
23
|
+
|
|
24
|
+
if not np.any(mask):
|
|
25
|
+
return output
|
|
26
|
+
|
|
27
|
+
allcoords = np.copy(coords)
|
|
28
|
+
allmask = np.copy(mask)
|
|
29
|
+
|
|
30
|
+
isfinal = None
|
|
31
|
+
splitting = False
|
|
32
|
+
|
|
33
|
+
# the segments should be sent in by the driver as half initial segments and half final segments
|
|
34
|
+
# allcoords contains all segments
|
|
35
|
+
# coords should contain ONLY final segments
|
|
36
|
+
if coords.shape[1] > ndim:
|
|
37
|
+
if coords.shape[1] > ndim + 1:
|
|
38
|
+
isfinal = allcoords[:, ndim + 1].astype(np.bool_)
|
|
39
|
+
else:
|
|
40
|
+
isfinal = np.ones(coords.shape[0], dtype=np.bool_)
|
|
41
|
+
coords = coords[isfinal, :ndim]
|
|
42
|
+
mask = mask[isfinal]
|
|
43
|
+
splitting = True
|
|
44
|
+
|
|
45
|
+
# in case where there is no final segments but initial ones in range
|
|
46
|
+
if not np.any(mask):
|
|
47
|
+
coords = allcoords[:, :ndim]
|
|
48
|
+
mask = allmask
|
|
49
|
+
splitting = False
|
|
50
|
+
|
|
51
|
+
# filter the list of coordinates (which contains coordinates outside of the binless region)
|
|
52
|
+
# to obtain only the ones we want to cluster
|
|
53
|
+
# this is done with all dimensions at once
|
|
54
|
+
binless_coords = coords[mask]
|
|
55
|
+
nsegs_binless = len(binless_coords)
|
|
56
|
+
|
|
57
|
+
# we need to make sure that the number of segments in the binless region is greater than
|
|
58
|
+
# the number of clusters we request
|
|
59
|
+
# if only one segment in the binless region, assign it to a single cluster
|
|
60
|
+
if nsegs_binless == 1:
|
|
61
|
+
clusters = [0]
|
|
62
|
+
|
|
63
|
+
# if there are more than one segment in the binless region but still the total is less than
|
|
64
|
+
# our target number, adjust our target number to be the number of segments in the binless
|
|
65
|
+
# region minus one
|
|
66
|
+
elif nsegs_binless < n_groups:
|
|
67
|
+
clusters = group_function(binless_coords, nsegs_binless, splitting, **group_function_kwargs)
|
|
68
|
+
# if there are enough segments in the binless region, proceed as planned
|
|
69
|
+
elif nsegs_binless >= n_groups:
|
|
70
|
+
clusters = group_function(binless_coords, n_groups, splitting, **group_function_kwargs)
|
|
71
|
+
|
|
72
|
+
# this is a good place to say this... output is a list which matches the length of allcoords
|
|
73
|
+
# allcoords is a collection of all initial and final segment coords for that iteration
|
|
74
|
+
# we first filtered those to only contain the final data points, since those are the ones we care
|
|
75
|
+
# about clustering
|
|
76
|
+
# we then filtered to only have the coords in the binless region, since, again, those are what we care about
|
|
77
|
+
# we then assigned each to a cluster which is essentially a slitting index
|
|
78
|
+
# all that's left is to find where each binless segment is in the output and insert the cluster index there
|
|
79
|
+
for idx, val in enumerate(binless_coords):
|
|
80
|
+
if ndim > 1:
|
|
81
|
+
mask2 = np.logical_and(allcoords[:, 0] == val[0], allcoords[:, 1] == val[1])
|
|
82
|
+
else:
|
|
83
|
+
mask2 = allcoords[:, 0] == val[0]
|
|
84
|
+
output[mask2] = clusters[idx]
|
|
85
|
+
|
|
86
|
+
return output
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class BinlessMapper(FuncBinMapper):
|
|
90
|
+
'''Adaptively group walkers according to a user-defined grouping
|
|
91
|
+
function that is defined externally.'''
|
|
92
|
+
|
|
93
|
+
def __init__(self, ngroups, ndims, group_function, **group_function_kwargs):
|
|
94
|
+
kwargs = dict(n_groups=ngroups, n_dims=ndims, group_function=group_function, group_function_kwargs=group_function_kwargs)
|
|
95
|
+
n_total_groups = ngroups
|
|
96
|
+
super().__init__(map_binless, n_total_groups, kwargs=kwargs)
|
|
@@ -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 BinlessDriver(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("BinlessDriver 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
|