polyphys 1.0.0__py3-none-any.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.
- polyphys/__init__.py +7 -0
- polyphys/__version__.py +22 -0
- polyphys/analyze/__init__.py +51 -0
- polyphys/analyze/clusters.py +1738 -0
- polyphys/analyze/correlations.py +995 -0
- polyphys/analyze/density.py +1138 -0
- polyphys/analyze/measurer.py +1338 -0
- polyphys/analyze/pipeline.py +1868 -0
- polyphys/analyze/stats.py +810 -0
- polyphys/analyze/sumrule.py +1159 -0
- polyphys/manage/__init__.py +11 -0
- polyphys/manage/organizer.py +2188 -0
- polyphys/manage/parser.py +1264 -0
- polyphys/manage/types.py +171 -0
- polyphys/manage/utils.py +777 -0
- polyphys/probe/__init__.py +25 -0
- polyphys/probe/binders.py +711 -0
- polyphys/probe/chain_size.py +583 -0
- polyphys/probe/core.py +437 -0
- polyphys/probe/histograms.py +863 -0
- polyphys/probe/pairs.py +555 -0
- polyphys/simulate/__init__.py +48 -0
- polyphys/simulate/build.py +1231 -0
- polyphys/simulate/core.py +237 -0
- polyphys/simulate/data.py +977 -0
- polyphys/simulate/dump.py +752 -0
- polyphys/simulate/log.py +788 -0
- polyphys/spec.py +1359 -0
- polyphys/tests/analyze/test_clusters.py +1058 -0
- polyphys/tests/analyze/test_correlations.py +708 -0
- polyphys/tests/analyze/test_density.py +813 -0
- polyphys/tests/analyze/test_measurer.py +785 -0
- polyphys/tests/analyze/test_pipeline.py +1881 -0
- polyphys/tests/analyze/test_stats.py +732 -0
- polyphys/tests/analyze/test_sumrule.py +917 -0
- polyphys/tests/conftest.py +753 -0
- polyphys/tests/data/lammps_build/make_starts.py +70 -0
- polyphys/tests/manage/test_organizer.py +1495 -0
- polyphys/tests/manage/test_parser.py +1222 -0
- polyphys/tests/manage/test_utils.py +498 -0
- polyphys/tests/probe/conftest.py +407 -0
- polyphys/tests/probe/test_binders.py +686 -0
- polyphys/tests/probe/test_chain_size.py +557 -0
- polyphys/tests/probe/test_core.py +391 -0
- polyphys/tests/probe/test_histograms.py +779 -0
- polyphys/tests/probe/test_pairs.py +475 -0
- polyphys/tests/simulate/test_build.py +1042 -0
- polyphys/tests/simulate/test_core.py +106 -0
- polyphys/tests/simulate/test_data.py +858 -0
- polyphys/tests/simulate/test_dump.py +487 -0
- polyphys/tests/simulate/test_log.py +667 -0
- polyphys/tests/strategies.py +120 -0
- polyphys/tests/synthetic.py +410 -0
- polyphys/tests/test_conftest.py +414 -0
- polyphys/tests/test_docs.py +82 -0
- polyphys/tests/test_packaging.py +97 -0
- polyphys/tests/test_spec.py +772 -0
- polyphys/tests/viz/conftest.py +32 -0
- polyphys/tests/viz/test_colors.py +113 -0
- polyphys/tests/viz/test_helpers.py +292 -0
- polyphys/viz/__init__.py +15 -0
- polyphys/viz/colors.py +204 -0
- polyphys/viz/helpers.py +549 -0
- polyphys-1.0.0.dist-info/METADATA +339 -0
- polyphys-1.0.0.dist-info/RECORD +69 -0
- polyphys-1.0.0.dist-info/WHEEL +5 -0
- polyphys-1.0.0.dist-info/licenses/AUTHORS.rst +13 -0
- polyphys-1.0.0.dist-info/licenses/LICENSE +22 -0
- polyphys-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
"""
|
|
2
|
+
==========================================================
|
|
3
|
+
:mod:`polyphys.probe.binders`
|
|
4
|
+
==========================================================
|
|
5
|
+
|
|
6
|
+
The :mod:`polyphys.probe.binders` module counts, in every frame of a
|
|
7
|
+
trajectory, how the two-patch binders of a study bind its polymer chain:
|
|
8
|
+
how many patches touch a monomer, how many binders are free, dangling by
|
|
9
|
+
one patch, or bridging two monomers, how many bridged pairs are cis or
|
|
10
|
+
trans, and the histogram of the bridged pairs' genomic distances, the loop
|
|
11
|
+
lengths. :class:`BinderStatistics` is an MDAnalysis
|
|
12
|
+
:class:`~MDAnalysis.analysis.base.AnalysisBase`, so it runs over a slice of
|
|
13
|
+
frames in serial or, split across workers, in parallel, and stores one
|
|
14
|
+
array per count in its ``results``.
|
|
15
|
+
|
|
16
|
+
The research code measured the same eight counts in the H-NS probe of each
|
|
17
|
+
study: the distances between every monomer and every patch, with no
|
|
18
|
+
minimum image, the contacts below a cutoff, and the counts of its
|
|
19
|
+
``hns_binding``. Here the distances take the minimum image along the
|
|
20
|
+
directions the study's geometry makes periodic, the cutoff and the
|
|
21
|
+
comparison are the study's :class:`~polyphys.spec.BinderSpec`,
|
|
22
|
+
and the counting is :func:`~polyphys.analyze.clusters.binding_statistics`.
|
|
23
|
+
|
|
24
|
+
Contents
|
|
25
|
+
========
|
|
26
|
+
|
|
27
|
+
- :class:`BinderStatistics`, the per-frame binding analysis.
|
|
28
|
+
|
|
29
|
+
Dependencies
|
|
30
|
+
============
|
|
31
|
+
- Python standard library: `math`, `pathlib`, `typing`.
|
|
32
|
+
- `NumPy <https://numpy.org/>`_.
|
|
33
|
+
- `MDAnalysis <https://docs.mdanalysis.org/>`_ 2.8 or later, through the
|
|
34
|
+
``mda`` extra: :class:`~MDAnalysis.analysis.base.AnalysisBase` and
|
|
35
|
+
:class:`~MDAnalysis.analysis.results.ResultsGroup`.
|
|
36
|
+
- :mod:`polyphys.analyze.clusters`:
|
|
37
|
+
:func:`~polyphys.analyze.clusters.find_direct_contacts`,
|
|
38
|
+
:func:`~polyphys.analyze.clusters.binding_statistics`, and
|
|
39
|
+
:class:`~polyphys.analyze.clusters.BindingStats`.
|
|
40
|
+
- :mod:`polyphys.spec`:
|
|
41
|
+
:data:`~polyphys.spec.BINDER_SERIES` and
|
|
42
|
+
:class:`~polyphys.spec.ProbeSpec`.
|
|
43
|
+
- :mod:`polyphys.probe.core`: ``_ProbeAnalysis``, the base whose ``run``
|
|
44
|
+
closes the pool of the ``multiprocessing`` backend, and
|
|
45
|
+
``_compute_part``, which computes each part of a split run on its own
|
|
46
|
+
copy of the analysis.
|
|
47
|
+
|
|
48
|
+
Notation
|
|
49
|
+
========
|
|
50
|
+
:math:`N_m`, :math:`N_p`, :math:`N_b = N_p / 2`
|
|
51
|
+
The numbers of monomers, patches, and binders; the two patches of
|
|
52
|
+
binder :math:`k` are the patches :math:`2k` and :math:`2k + 1` in
|
|
53
|
+
atom-ID order.
|
|
54
|
+
:math:`r_{ij}`
|
|
55
|
+
The distance between monomer :math:`i` and patch :math:`j`, in the
|
|
56
|
+
dump's length unit, the monomer diameter in the studies; along a
|
|
57
|
+
periodic direction of box length :math:`L`, the component is reduced
|
|
58
|
+
to :math:`[-L/2, L/2)` first (the minimum image).
|
|
59
|
+
:math:`r_\\mathrm{thres}`
|
|
60
|
+
The binding threshold, `cutoff`: monomer :math:`i` and patch :math:`j`
|
|
61
|
+
are in contact when :math:`r_{ij} \\le r_\\mathrm{thres}` (`inclusive`)
|
|
62
|
+
or :math:`r_{ij} < r_\\mathrm{thres}`.
|
|
63
|
+
:math:`d`
|
|
64
|
+
The genomic distance of a bridged pair, in bonds along the backbone,
|
|
65
|
+
the shorter way round on a ring; a bridge is cis when
|
|
66
|
+
:math:`d \\le` `cis_threshold` and trans otherwise.
|
|
67
|
+
|
|
68
|
+
Examples
|
|
69
|
+
========
|
|
70
|
+
A linear chain of four beads one unit apart and one binder whose two
|
|
71
|
+
patches sit 0.3 above beads 0 and 2, in a one-frame universe:
|
|
72
|
+
|
|
73
|
+
>>> import MDAnalysis as mda
|
|
74
|
+
>>> import numpy as np
|
|
75
|
+
>>> universe = mda.Universe.empty(6, trajectory=True)
|
|
76
|
+
>>> universe.atoms.positions = np.array(
|
|
77
|
+
... [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0], [3.0, 0.0, 0.0],
|
|
78
|
+
... [0.0, 0.3, 0.0], [2.0, 0.3, 0.0]])
|
|
79
|
+
>>> analysis = BinderStatistics(
|
|
80
|
+
... universe.atoms[:4], universe.atoms[4:], cutoff=0.5,
|
|
81
|
+
... topology='linear', cis_threshold=1).run()
|
|
82
|
+
>>> analysis.results['n_binders_bridging'], analysis.results['n_pairs_trans']
|
|
83
|
+
(array([1]), array([1]))
|
|
84
|
+
>>> analysis.results['loop_length_histogram']
|
|
85
|
+
array([[0, 0, 1, 0]])
|
|
86
|
+
|
|
87
|
+
.. versionadded:: 0.7.0
|
|
88
|
+
"""
|
|
89
|
+
import math
|
|
90
|
+
from pathlib import Path
|
|
91
|
+
from typing import Any, Mapping, Self, cast, get_args
|
|
92
|
+
|
|
93
|
+
import numpy as np
|
|
94
|
+
import numpy.typing as npt
|
|
95
|
+
from MDAnalysis.analysis.results import ResultsGroup
|
|
96
|
+
from MDAnalysis.core.groups import AtomGroup
|
|
97
|
+
from MDAnalysis.core.universe import Universe
|
|
98
|
+
|
|
99
|
+
from ..analyze.clusters import (
|
|
100
|
+
BindingStats,
|
|
101
|
+
Topology,
|
|
102
|
+
binding_statistics,
|
|
103
|
+
find_direct_contacts,
|
|
104
|
+
)
|
|
105
|
+
from ..spec import BINDER_SERIES, ProbeSpec
|
|
106
|
+
from ..manage.types import EntityT, GeometryT, Pathish, TopologyT
|
|
107
|
+
from .core import _ProbeAnalysis, _compute_part
|
|
108
|
+
|
|
109
|
+
__all__ = [
|
|
110
|
+
'BinderStatistics',
|
|
111
|
+
'DISTANCES_KEY',
|
|
112
|
+
'LOOP_KEY',
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
#: The result key of the per-frame loop-length histogram.
|
|
116
|
+
LOOP_KEY = 'loop_length_histogram'
|
|
117
|
+
|
|
118
|
+
#: The result key of the per-frame monomer-to-patch distance matrices, when
|
|
119
|
+
#: they are stored.
|
|
120
|
+
DISTANCES_KEY = 'distances'
|
|
121
|
+
|
|
122
|
+
# The directions that each geometry makes periodic: every one in a periodic
|
|
123
|
+
# cube, the axis only in a cylinder (LAMMPS boundary ``ff ff pp``).
|
|
124
|
+
_PERIODIC: Mapping[GeometryT, tuple[bool, bool, bool]] = {
|
|
125
|
+
'cubic': (True, True, True),
|
|
126
|
+
'cylindrical': (False, False, True),
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
_BACKENDS = ('serial', 'multiprocessing', 'dask')
|
|
130
|
+
|
|
131
|
+
# The number of patches per binder that binding_statistics counts.
|
|
132
|
+
_TWO_PATCHES = 2
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _pair_distances(
|
|
136
|
+
reference: npt.NDArray[np.float64],
|
|
137
|
+
configuration: npt.NDArray[np.float64],
|
|
138
|
+
box_lengths: npt.NDArray[np.float64],
|
|
139
|
+
periodic: tuple[bool, bool, bool],
|
|
140
|
+
) -> npt.NDArray[np.float64]:
|
|
141
|
+
"""
|
|
142
|
+
The distance between every row of `reference` and every row of
|
|
143
|
+
`configuration`, shape ``(n_reference, n_configuration)``, with the
|
|
144
|
+
minimum image along each periodic direction of `box_lengths`.
|
|
145
|
+
"""
|
|
146
|
+
diff = reference[:, None, :] - configuration[None, :, :]
|
|
147
|
+
for axis in np.flatnonzero(periodic):
|
|
148
|
+
length = box_lengths[axis]
|
|
149
|
+
diff[..., axis] -= length * np.round(diff[..., axis] / length)
|
|
150
|
+
return np.asarray(np.sqrt(np.einsum('ijk,ijk->ij', diff, diff)),
|
|
151
|
+
dtype=np.float64)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class BinderStatistics(_ProbeAnalysis):
|
|
155
|
+
"""
|
|
156
|
+
The binding of two-patch binders to one polymer chain, counted in every
|
|
157
|
+
frame.
|
|
158
|
+
|
|
159
|
+
.. versionadded:: 0.7.0
|
|
160
|
+
.. versionchanged:: 1.0.0
|
|
161
|
+
`cis_threshold` has no default.
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
chain : :class:`~MDAnalysis.core.groups.AtomGroup`
|
|
166
|
+
The chain, at least two monomers, in atom-ID order: the order along
|
|
167
|
+
the backbone, which ``Universe.select_atoms`` gives.
|
|
168
|
+
patches : :class:`~MDAnalysis.core.groups.AtomGroup`
|
|
169
|
+
The binders' patches, in atom-ID order, from the same universe as
|
|
170
|
+
`chain`: the two patches of binder :math:`k` at positions
|
|
171
|
+
:math:`2k` and :math:`2k + 1`, so their number is a positive
|
|
172
|
+
multiple of `patches_per_binder`. The binder cores are not needed.
|
|
173
|
+
cutoff : float
|
|
174
|
+
The binding threshold :math:`r_\\mathrm{thres}`, in the dump's
|
|
175
|
+
length unit; finite and positive.
|
|
176
|
+
topology : :py:obj:`~.TopologyT`
|
|
177
|
+
``'linear'`` or ``'ring'``, whether the chain is open or closed,
|
|
178
|
+
which sets the genomic distance of a bridged pair and the length of
|
|
179
|
+
the loop-length histogram.
|
|
180
|
+
cis_threshold : int
|
|
181
|
+
The largest genomic distance, in bonds, of a cis bridge;
|
|
182
|
+
non-negative, with no default.
|
|
183
|
+
inclusive : bool, default True
|
|
184
|
+
Whether a pair at exactly `cutoff` is a contact
|
|
185
|
+
(:math:`r \\le r_\\mathrm{thres}`) or not
|
|
186
|
+
(:math:`r < r_\\mathrm{thres}`).
|
|
187
|
+
patches_per_binder : int, default 2
|
|
188
|
+
The number of patches per binder; only ``2`` is accepted, which
|
|
189
|
+
:func:`~polyphys.analyze.clusters.binding_statistics` counts.
|
|
190
|
+
periodic : tuple of bool, default (False, False, False)
|
|
191
|
+
Whether the minimum image is taken along ``x``, ``y``, and ``z``,
|
|
192
|
+
with the box lengths of each frame. The research probe took none;
|
|
193
|
+
:meth:`from_spec` sets every direction in a cube and ``z`` only in
|
|
194
|
+
a cylinder.
|
|
195
|
+
store_distances : bool, default False
|
|
196
|
+
Whether the monomer-to-patch distance matrix of every frame is
|
|
197
|
+
kept in ``results`` and written by :meth:`save`.
|
|
198
|
+
chain_label, patch_label, binder_label : :py:obj:`~.EntityT`
|
|
199
|
+
The species labels that name the files :meth:`save` writes;
|
|
200
|
+
``'Mon'``, ``'HnsPatch'``, and ``'HnsCore'`` by default, the H-NS
|
|
201
|
+
studies' names.
|
|
202
|
+
verbose : bool, default False
|
|
203
|
+
Whether to show a progress bar during
|
|
204
|
+
:meth:`~MDAnalysis.analysis.base.AnalysisBase.run`.
|
|
205
|
+
|
|
206
|
+
Attributes
|
|
207
|
+
----------
|
|
208
|
+
chain : :class:`~MDAnalysis.core.groups.AtomGroup`
|
|
209
|
+
The chain.
|
|
210
|
+
patches : :class:`~MDAnalysis.core.groups.AtomGroup`
|
|
211
|
+
The patches.
|
|
212
|
+
cutoff : float
|
|
213
|
+
The binding threshold.
|
|
214
|
+
topology : :py:obj:`~.TopologyT`
|
|
215
|
+
The chain's topology.
|
|
216
|
+
inclusive : bool
|
|
217
|
+
Whether a pair at exactly `cutoff` is a contact.
|
|
218
|
+
cis_threshold : int
|
|
219
|
+
The largest genomic distance of a cis bridge, in bonds.
|
|
220
|
+
patches_per_binder : int
|
|
221
|
+
The number of patches per binder, ``2``.
|
|
222
|
+
periodic : tuple of bool
|
|
223
|
+
The directions along which the minimum image is taken.
|
|
224
|
+
store_distances : bool
|
|
225
|
+
Whether the distance matrices are kept.
|
|
226
|
+
chain_label : :py:obj:`~.EntityT`
|
|
227
|
+
The species label of the chain in the saved files.
|
|
228
|
+
patch_label : :py:obj:`~.EntityT`
|
|
229
|
+
The species label of the patches in the saved files.
|
|
230
|
+
binder_label : :py:obj:`~.EntityT`
|
|
231
|
+
The species label of the binders in the saved files.
|
|
232
|
+
n_lengths : int
|
|
233
|
+
The length of a loop-length histogram row: ``n_monomers // 2 + 1``
|
|
234
|
+
on a ring, ``n_monomers`` on a linear chain, as
|
|
235
|
+
:class:`~polyphys.analyze.clusters.BindingStats` sizes it.
|
|
236
|
+
results : :class:`~MDAnalysis.analysis.results.Results`
|
|
237
|
+
After :meth:`~MDAnalysis.analysis.base.AnalysisBase.run`: one int64
|
|
238
|
+
array of shape ``(n_frames,)`` per attribute of
|
|
239
|
+
:class:`~polyphys.analyze.clusters.FrameBinding` in
|
|
240
|
+
:data:`~polyphys.spec.BINDER_SERIES`, keyed by that name;
|
|
241
|
+
``'loop_length_histogram'``, int64 of shape ``(n_frames, n_lengths)``,
|
|
242
|
+
whose row ``[t, d]`` is the number of distinct bridged pairs at
|
|
243
|
+
:math:`d` bonds in frame ``t``, with column 0 always 0; and, with
|
|
244
|
+
`store_distances`, ``'distances'``, float64 of shape ``(n_frames,
|
|
245
|
+
n_monomers, n_patches)``.
|
|
246
|
+
n_frames : int
|
|
247
|
+
After :meth:`~MDAnalysis.analysis.base.AnalysisBase.run`, the number of
|
|
248
|
+
frames analyzed, from :class:`~MDAnalysis.analysis.base.AnalysisBase`.
|
|
249
|
+
frames, times : numpy.ndarray
|
|
250
|
+
After :meth:`~MDAnalysis.analysis.base.AnalysisBase.run`, the indices
|
|
251
|
+
and times of the frames analyzed, shape ``(n_frames,)``.
|
|
252
|
+
|
|
253
|
+
Raises
|
|
254
|
+
------
|
|
255
|
+
ValueError
|
|
256
|
+
If `chain` has fewer than two atoms; if `patches` is empty, is not from
|
|
257
|
+
the universe of `chain`, or has a number of atoms that is not a
|
|
258
|
+
multiple of `patches_per_binder`; if `cutoff` is not a finite positive
|
|
259
|
+
number; if `topology` is not ``'linear'`` or ``'ring'``; if
|
|
260
|
+
`cis_threshold` is negative; if `patches_per_binder` is not ``2``; if
|
|
261
|
+
`periodic` is not three booleans; or if a label is empty. During
|
|
262
|
+
:meth:`~MDAnalysis.analysis.base.AnalysisBase.run`, if a periodic
|
|
263
|
+
direction is asked for and the frame carries no box.
|
|
264
|
+
|
|
265
|
+
Notes
|
|
266
|
+
-----
|
|
267
|
+
Per frame, the analysis forms :math:`r_{ij}` for every monomer and
|
|
268
|
+
patch, with the minimum image along the periodic directions, marks the
|
|
269
|
+
contacts with :func:`~polyphys.analyze.clusters.find_direct_contacts`,
|
|
270
|
+
and counts them with
|
|
271
|
+
:func:`~polyphys.analyze.clusters.binding_statistics`, whose rules hold
|
|
272
|
+
here: the patch and binder counts use the contacts as marked, so a
|
|
273
|
+
patch within the cutoff of two adjacent monomers is one engaged patch;
|
|
274
|
+
the bridged pairs use one monomer per patch, the lowest-index one, so
|
|
275
|
+
each bridging binder joins exactly two monomers; and cis and trans
|
|
276
|
+
count distinct monomer pairs. The histogram row is the bincount of the
|
|
277
|
+
bridged pairs' genomic distances.
|
|
278
|
+
|
|
279
|
+
The binder model is the bivalent patchy protein of Brackley et al.
|
|
280
|
+
[1]_, with a patch at each pole of a repulsive core, that the H-NS
|
|
281
|
+
studies follow [2]_; the dangling, cis, and trans modes and the cis
|
|
282
|
+
threshold of two intervening monomers are those of Joyeux and Vreede
|
|
283
|
+
[3]_, whose cis bridge joins sites "separated by at most two DNA
|
|
284
|
+
beads", a genomic distance of at most 3 bonds: `cis_threshold` of 3,
|
|
285
|
+
which the H-NS studies state. A patch that sits between two
|
|
286
|
+
consecutive monomers can touch both, which Brackley [4]_ notes as the
|
|
287
|
+
one way a single patch reaches two beads; the single-patch rule
|
|
288
|
+
resolves it.
|
|
289
|
+
|
|
290
|
+
The MDAnalysis contract: ``_prepare`` allocates the arrays for the
|
|
291
|
+
frames of the run, ``_single_frame`` writes the current frame's
|
|
292
|
+
row, and ``_conclude`` does nothing. The class is marked
|
|
293
|
+
parallelizable, with ``serial``, ``multiprocessing``, and ``dask``
|
|
294
|
+
backends; each worker fills the arrays of its own frames, which
|
|
295
|
+
``_get_aggregator`` concatenates along the frame axis
|
|
296
|
+
(``ResultsGroup.ndarray_hstack`` for the eight series,
|
|
297
|
+
``ResultsGroup.ndarray_vstack`` for the histogram and the distances).
|
|
298
|
+
|
|
299
|
+
Cost per frame: :math:`O(N_m N_p)` time for the distances and the
|
|
300
|
+
contacts, plus :math:`O(N_b \\log N_b)` to make the bridged pairs
|
|
301
|
+
distinct, and :math:`O(N_m N_p)` space for the distance and contact
|
|
302
|
+
matrices. Over a run of :math:`F` frames, :math:`O(F)` space per
|
|
303
|
+
series, :math:`O(F N_m / 2)` for the histogram, and, with
|
|
304
|
+
`store_distances`, :math:`8 F N_m N_p` bytes for the float64
|
|
305
|
+
distances, held in memory until :meth:`save`, as the research code
|
|
306
|
+
held its list of matrices. Positions come from MDAnalysis as float32,
|
|
307
|
+
so each distance carries a relative round-off of about :math:`10^{-7}`
|
|
308
|
+
before it is compared with the cutoff.
|
|
309
|
+
|
|
310
|
+
References
|
|
311
|
+
----------
|
|
312
|
+
.. [1] C. A. Brackley, S. Taylor, A. Papantonis, P. R. Cook, and D.
|
|
313
|
+
Marenduzzo, "Nonspecific bridging-induced attraction drives
|
|
314
|
+
clustering of DNA-binding proteins and genome organization", Proc.
|
|
315
|
+
Natl. Acad. Sci. U.S.A. 110, E3605-E3611 (2013).
|
|
316
|
+
https://doi.org/10.1073/pnas.1302950110
|
|
317
|
+
.. [2] Y. Jung, A. Sadeghi, and B.-Y. Ha, "Modeling the compaction of
|
|
318
|
+
bacterial chromosomes by biomolecular crowding and the cross-linking
|
|
319
|
+
protein H-NS", Sci. Rep. 14, 139 (2024).
|
|
320
|
+
https://doi.org/10.1038/s41598-023-50355-2
|
|
321
|
+
.. [3] M. Joyeux and J. Vreede, "A Model of H-NS Mediated Compaction of
|
|
322
|
+
Bacterial DNA", Biophys. J. 104, 1615-1622 (2013).
|
|
323
|
+
https://doi.org/10.1016/j.bpj.2013.02.043
|
|
324
|
+
.. [4] C. A. Brackley, "Polymer compaction and bridging-induced
|
|
325
|
+
clustering of protein-inspired patchy particles", J. Phys.: Condens.
|
|
326
|
+
Matter 32, 314002 (2020). https://doi.org/10.1088/1361-648X/ab7f6c
|
|
327
|
+
|
|
328
|
+
Examples
|
|
329
|
+
--------
|
|
330
|
+
A ring of six beads and two binders over one frame: binder 0 bridges
|
|
331
|
+
beads 0 and 2, two bonds apart; binder 1 dangles from bead 4 with its
|
|
332
|
+
other patch far away.
|
|
333
|
+
|
|
334
|
+
>>> import MDAnalysis as mda
|
|
335
|
+
>>> import numpy as np
|
|
336
|
+
>>> chain = np.column_stack([np.arange(6.0), np.zeros(6), np.zeros(6)])
|
|
337
|
+
>>> patches = np.array([[0.0, 0.3, 0.0], [2.0, 0.3, 0.0],
|
|
338
|
+
... [4.0, 0.3, 0.0], [9.0, 9.0, 9.0]])
|
|
339
|
+
>>> universe = mda.Universe.empty(10, trajectory=True)
|
|
340
|
+
>>> universe.atoms.positions = np.vstack([chain, patches])
|
|
341
|
+
>>> analysis = BinderStatistics(
|
|
342
|
+
... universe.atoms[:6], universe.atoms[6:], cutoff=0.5,
|
|
343
|
+
... topology='ring', cis_threshold=4).run()
|
|
344
|
+
>>> [int(analysis.results[key][0]) for key in BINDER_SERIES]
|
|
345
|
+
[3, 1, 3, 0, 1, 1, 1, 0]
|
|
346
|
+
>>> analysis.results['loop_length_histogram']
|
|
347
|
+
array([[0, 0, 1, 0]])
|
|
348
|
+
|
|
349
|
+
The eight values are, in the order of
|
|
350
|
+
:data:`~polyphys.spec.BINDER_SERIES`: three patch-monomer
|
|
351
|
+
contacts, one free and three engaged patches, no free binder, one
|
|
352
|
+
bridging and one dangling binder, one cis pair and no trans pair.
|
|
353
|
+
|
|
354
|
+
With the minimum image along ``x`` in a box of length 10, a patch at
|
|
355
|
+
``x = 9.8`` touches bead 0:
|
|
356
|
+
|
|
357
|
+
>>> universe.trajectory.ts.dimensions = [10.0, 10.0, 10.0, 90, 90, 90]
|
|
358
|
+
>>> universe.atoms[9].position = [9.8, 0.0, 0.0]
|
|
359
|
+
>>> wrapped = BinderStatistics(
|
|
360
|
+
... universe.atoms[:6], universe.atoms[6:], cutoff=0.5,
|
|
361
|
+
... topology='ring', cis_threshold=4,
|
|
362
|
+
... periodic=(True, False, False)).run()
|
|
363
|
+
>>> int(wrapped.results['n_binders_bridging'][0])
|
|
364
|
+
2
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
_analysis_algorithm_is_parallelizable = True
|
|
368
|
+
|
|
369
|
+
def __init__(
|
|
370
|
+
self,
|
|
371
|
+
chain: AtomGroup,
|
|
372
|
+
patches: AtomGroup,
|
|
373
|
+
*,
|
|
374
|
+
cutoff: float,
|
|
375
|
+
topology: TopologyT,
|
|
376
|
+
cis_threshold: int,
|
|
377
|
+
inclusive: bool = True,
|
|
378
|
+
patches_per_binder: int = _TWO_PATCHES,
|
|
379
|
+
periodic: tuple[bool, bool, bool] = (False, False, False),
|
|
380
|
+
store_distances: bool = False,
|
|
381
|
+
chain_label: EntityT = 'Mon',
|
|
382
|
+
patch_label: EntityT = 'HnsPatch',
|
|
383
|
+
binder_label: EntityT = 'HnsCore',
|
|
384
|
+
verbose: bool = False,
|
|
385
|
+
) -> None:
|
|
386
|
+
super().__init__(chain.universe.trajectory, verbose=verbose)
|
|
387
|
+
if chain.n_atoms < 2:
|
|
388
|
+
raise ValueError(
|
|
389
|
+
f"'chain' must hold at least two monomers, not "
|
|
390
|
+
f"{chain.n_atoms}.")
|
|
391
|
+
if patches.universe is not chain.universe:
|
|
392
|
+
raise ValueError(
|
|
393
|
+
"'patches' must come from the universe of 'chain'.")
|
|
394
|
+
if patches_per_binder != _TWO_PATCHES:
|
|
395
|
+
raise ValueError(
|
|
396
|
+
f"'patches_per_binder' must be {_TWO_PATCHES}, not "
|
|
397
|
+
f"{patches_per_binder!r}: binding_statistics counts "
|
|
398
|
+
"two-patch binders only.")
|
|
399
|
+
if patches.n_atoms < 1 or patches.n_atoms % patches_per_binder:
|
|
400
|
+
raise ValueError(
|
|
401
|
+
f"'patches' must hold a positive multiple of "
|
|
402
|
+
f"{patches_per_binder} atoms, not {patches.n_atoms}.")
|
|
403
|
+
if not (isinstance(cutoff, (int, float))
|
|
404
|
+
and not isinstance(cutoff, bool)
|
|
405
|
+
and math.isfinite(cutoff) and cutoff > 0):
|
|
406
|
+
raise ValueError(
|
|
407
|
+
f"'cutoff' must be a finite positive number, not "
|
|
408
|
+
f"{cutoff!r}.")
|
|
409
|
+
if topology not in get_args(Topology):
|
|
410
|
+
raise ValueError(
|
|
411
|
+
f"'topology' must be one of {list(get_args(Topology))}, "
|
|
412
|
+
f"not {topology!r}.")
|
|
413
|
+
if cis_threshold < 0:
|
|
414
|
+
raise ValueError(
|
|
415
|
+
f"'cis_threshold' must be non-negative, not "
|
|
416
|
+
f"{cis_threshold!r}.")
|
|
417
|
+
periodic = tuple(periodic) # type: ignore[assignment]
|
|
418
|
+
if len(periodic) != 3 \
|
|
419
|
+
or any(not isinstance(flag, (bool, np.bool_))
|
|
420
|
+
for flag in periodic):
|
|
421
|
+
raise ValueError(
|
|
422
|
+
f"'periodic' must be three booleans, not {periodic!r}.")
|
|
423
|
+
for name, label in (('chain_label', chain_label),
|
|
424
|
+
('patch_label', patch_label),
|
|
425
|
+
('binder_label', binder_label)):
|
|
426
|
+
if not (isinstance(label, str) and label):
|
|
427
|
+
raise ValueError(
|
|
428
|
+
f"'{name}' must be a non-empty species label, not "
|
|
429
|
+
f"{label!r}.")
|
|
430
|
+
self.chain = chain
|
|
431
|
+
self.patches = patches
|
|
432
|
+
self.cutoff = float(cutoff)
|
|
433
|
+
self.topology = topology
|
|
434
|
+
self.inclusive = bool(inclusive)
|
|
435
|
+
self.cis_threshold = int(cis_threshold)
|
|
436
|
+
self.patches_per_binder = patches_per_binder
|
|
437
|
+
self.periodic = cast(tuple[bool, bool, bool],
|
|
438
|
+
tuple(bool(flag) for flag in periodic))
|
|
439
|
+
self.store_distances = bool(store_distances)
|
|
440
|
+
self.chain_label = chain_label
|
|
441
|
+
self.patch_label = patch_label
|
|
442
|
+
self.binder_label = binder_label
|
|
443
|
+
self.n_lengths = BindingStats(
|
|
444
|
+
chain.n_atoms, cast(Topology, topology),
|
|
445
|
+
self.cis_threshold).loop_length_histogram.size
|
|
446
|
+
|
|
447
|
+
@classmethod
|
|
448
|
+
def from_spec(
|
|
449
|
+
cls,
|
|
450
|
+
universe: Universe,
|
|
451
|
+
spec: ProbeSpec,
|
|
452
|
+
*,
|
|
453
|
+
verbose: bool = False,
|
|
454
|
+
) -> Self:
|
|
455
|
+
"""
|
|
456
|
+
Build the analysis of a study's binders from its probe spec.
|
|
457
|
+
|
|
458
|
+
The chain is ``universe.select_atoms(spec.selections[spec.chain])``
|
|
459
|
+
and the patches
|
|
460
|
+
``universe.select_atoms(spec.selections[spec.binders.patches])``;
|
|
461
|
+
the cutoff, the comparison, the cis threshold, the patches per
|
|
462
|
+
binder, whether the distances are stored, and the labels are the
|
|
463
|
+
spec's :class:`~polyphys.spec.BinderSpec`; the topology
|
|
464
|
+
is the project's; and the minimum image is taken along every
|
|
465
|
+
direction in a cube and along ``z`` only in a cylinder.
|
|
466
|
+
|
|
467
|
+
.. versionadded:: 0.7.0
|
|
468
|
+
|
|
469
|
+
Parameters
|
|
470
|
+
----------
|
|
471
|
+
universe : :class:`~MDAnalysis.core.universe.Universe`
|
|
472
|
+
The trajectory, as :func:`~polyphys.probe.core.make_universe`
|
|
473
|
+
opens it.
|
|
474
|
+
spec : :class:`~polyphys.spec.ProbeSpec`
|
|
475
|
+
The study's probe spec, with `binders` set.
|
|
476
|
+
verbose : bool, default False
|
|
477
|
+
Whether to show a progress bar during
|
|
478
|
+
:meth:`~MDAnalysis.analysis.base.AnalysisBase.run`.
|
|
479
|
+
|
|
480
|
+
Returns
|
|
481
|
+
-------
|
|
482
|
+
BinderStatistics
|
|
483
|
+
The analysis, not yet run.
|
|
484
|
+
|
|
485
|
+
Raises
|
|
486
|
+
------
|
|
487
|
+
ValueError
|
|
488
|
+
If the spec has no `binders`, or if the selections pick fewer
|
|
489
|
+
than two monomers or a number of patches that is not a positive
|
|
490
|
+
multiple of the patches per binder.
|
|
491
|
+
|
|
492
|
+
Examples
|
|
493
|
+
--------
|
|
494
|
+
>>> import MDAnalysis as mda
|
|
495
|
+
>>> import numpy as np
|
|
496
|
+
>>> from polyphys.tests.synthetic import RingCubBinders
|
|
497
|
+
>>> from polyphys.spec import (
|
|
498
|
+
... BinderSpec, ProbeSpec, ProjectSpec)
|
|
499
|
+
>>> project = ProjectSpec(
|
|
500
|
+
... parser=RingCubBinders, group='nucleoid', divisor=0.01,
|
|
501
|
+
... crowder_attr='phi_bulk_c',
|
|
502
|
+
... physical_attrs=('space', 'nmon', 'nhns'),
|
|
503
|
+
... species_sizes={'Mon': 'dmon', 'Hns': 'dhns'},
|
|
504
|
+
... tseries_properties=(
|
|
505
|
+
... 'nBoundTHnsPatch', 'nFreeTHnsPatch', 'nEngagedTHnsPatch',
|
|
506
|
+
... 'nFreeTHnsCore', 'nBridgeTHnsCore', 'nDangleTHnsCore',
|
|
507
|
+
... 'nCisTHnsCore', 'nTransTHnsCore', 'gyrTMon'))
|
|
508
|
+
>>> spec = ProbeSpec(
|
|
509
|
+
... project, {'Mon': 'type 1', 'HnsPatch': 'type 2'}, 'Mon',
|
|
510
|
+
... ('gyrT',), unwrap=True,
|
|
511
|
+
... binders=BinderSpec('HnsPatch', 'HnsCore', cutoff=0.685,
|
|
512
|
+
... cis_threshold=3))
|
|
513
|
+
>>> universe = mda.Universe.empty(6, trajectory=True)
|
|
514
|
+
>>> universe.add_TopologyAttr('types', ['1'] * 4 + ['2'] * 2)
|
|
515
|
+
>>> analysis = BinderStatistics.from_spec(universe, spec)
|
|
516
|
+
>>> analysis.chain.n_atoms, analysis.patches.n_atoms
|
|
517
|
+
(4, 2)
|
|
518
|
+
>>> analysis.topology, analysis.periodic, analysis.cutoff
|
|
519
|
+
('ring', (True, True, True), 0.685)
|
|
520
|
+
"""
|
|
521
|
+
if spec.binders is None:
|
|
522
|
+
raise ValueError(
|
|
523
|
+
f"{spec.name}: the probe spec has no 'binders'.")
|
|
524
|
+
binders = spec.binders
|
|
525
|
+
chain = universe.select_atoms(spec.selections[spec.chain])
|
|
526
|
+
patches = universe.select_atoms(spec.selections[binders.patches])
|
|
527
|
+
return cls(chain, patches, cutoff=binders.cutoff,
|
|
528
|
+
topology=spec.project.topology,
|
|
529
|
+
inclusive=binders.inclusive,
|
|
530
|
+
cis_threshold=binders.cis_threshold,
|
|
531
|
+
patches_per_binder=binders.patches_per_binder,
|
|
532
|
+
periodic=_PERIODIC[spec.geometry],
|
|
533
|
+
store_distances=binders.store_distances,
|
|
534
|
+
chain_label=spec.chain, patch_label=binders.patches,
|
|
535
|
+
binder_label=binders.binders, verbose=verbose)
|
|
536
|
+
|
|
537
|
+
@classmethod
|
|
538
|
+
def get_supported_backends(cls) -> tuple[str, ...]:
|
|
539
|
+
"""
|
|
540
|
+
The backends :meth:`~MDAnalysis.analysis.base.AnalysisBase.run`
|
|
541
|
+
accepts: ``'serial'``, ``'multiprocessing'``, and ``'dask'``.
|
|
542
|
+
|
|
543
|
+
Examples
|
|
544
|
+
--------
|
|
545
|
+
>>> BinderStatistics.get_supported_backends()
|
|
546
|
+
('serial', 'multiprocessing', 'dask')
|
|
547
|
+
"""
|
|
548
|
+
return _BACKENDS
|
|
549
|
+
|
|
550
|
+
@property
|
|
551
|
+
def keys(self) -> tuple[str, ...]:
|
|
552
|
+
"""
|
|
553
|
+
The keys of ``results``, in the order :meth:`save` writes them:
|
|
554
|
+
the eight series of
|
|
555
|
+
:data:`~polyphys.spec.BINDER_SERIES`, the loop-length
|
|
556
|
+
histogram, and, with `store_distances`, the distances.
|
|
557
|
+
|
|
558
|
+
Examples
|
|
559
|
+
--------
|
|
560
|
+
>>> import MDAnalysis as mda
|
|
561
|
+
>>> universe = mda.Universe.empty(4, trajectory=True)
|
|
562
|
+
>>> analysis = BinderStatistics(
|
|
563
|
+
... universe.atoms[:2], universe.atoms[2:], cutoff=0.5,
|
|
564
|
+
... topology='linear', cis_threshold=3)
|
|
565
|
+
>>> analysis.keys[-2:]
|
|
566
|
+
('n_pairs_trans', 'loop_length_histogram')
|
|
567
|
+
"""
|
|
568
|
+
keys = (*BINDER_SERIES, LOOP_KEY)
|
|
569
|
+
if self.store_distances:
|
|
570
|
+
keys = (*keys, DISTANCES_KEY)
|
|
571
|
+
return keys
|
|
572
|
+
|
|
573
|
+
def _compute(
|
|
574
|
+
self, indexed_frames: np.ndarray, verbose: Any = None, *,
|
|
575
|
+
progressbar_kwargs: Any = None,
|
|
576
|
+
) -> Any:
|
|
577
|
+
"""
|
|
578
|
+
Compute one part of the run on a copy of this analysis, so that
|
|
579
|
+
serial parts do not overwrite each other.
|
|
580
|
+
"""
|
|
581
|
+
return _compute_part(self, indexed_frames, verbose,
|
|
582
|
+
progressbar_kwargs=progressbar_kwargs)
|
|
583
|
+
|
|
584
|
+
def _get_aggregator(self) -> ResultsGroup:
|
|
585
|
+
"""
|
|
586
|
+
Concatenate each worker's arrays along the frame axis.
|
|
587
|
+
"""
|
|
588
|
+
return ResultsGroup(lookup={
|
|
589
|
+
key: (ResultsGroup.ndarray_hstack if key in BINDER_SERIES
|
|
590
|
+
else ResultsGroup.ndarray_vstack)
|
|
591
|
+
for key in self.keys})
|
|
592
|
+
|
|
593
|
+
def _prepare(self) -> None:
|
|
594
|
+
"""
|
|
595
|
+
Allocate the arrays for the frames of this run or worker: int64
|
|
596
|
+
series and histogram rows, float64 distances.
|
|
597
|
+
"""
|
|
598
|
+
for attr in BINDER_SERIES:
|
|
599
|
+
self.results[attr] = np.empty(self.n_frames, dtype=np.int64)
|
|
600
|
+
self.results[LOOP_KEY] = np.zeros(
|
|
601
|
+
(self.n_frames, self.n_lengths), dtype=np.int64)
|
|
602
|
+
if self.store_distances:
|
|
603
|
+
self.results[DISTANCES_KEY] = np.empty(
|
|
604
|
+
(self.n_frames, self.chain.n_atoms, self.patches.n_atoms),
|
|
605
|
+
dtype=np.float64)
|
|
606
|
+
|
|
607
|
+
def _single_frame(self) -> None:
|
|
608
|
+
"""
|
|
609
|
+
Form the distances of the current frame, mark the contacts, count
|
|
610
|
+
them, and store the counts and the histogram row for this frame.
|
|
611
|
+
"""
|
|
612
|
+
if any(self.periodic):
|
|
613
|
+
dimensions = self._ts.dimensions
|
|
614
|
+
if dimensions is None:
|
|
615
|
+
raise ValueError(
|
|
616
|
+
f"frame {self._ts.frame} carries no box, which the "
|
|
617
|
+
f"periodic directions {self.periodic} need.")
|
|
618
|
+
box_lengths = np.asarray(dimensions[:3], dtype=np.float64)
|
|
619
|
+
else:
|
|
620
|
+
box_lengths = np.zeros(3)
|
|
621
|
+
distances = _pair_distances(
|
|
622
|
+
np.asarray(self.chain.positions, dtype=np.float64),
|
|
623
|
+
np.asarray(self.patches.positions, dtype=np.float64),
|
|
624
|
+
box_lengths, self.periodic)
|
|
625
|
+
contacts = find_direct_contacts(distances, self.cutoff,
|
|
626
|
+
inclusive=self.inclusive)
|
|
627
|
+
frame = binding_statistics(contacts, cast(Topology, self.topology),
|
|
628
|
+
self.cis_threshold)
|
|
629
|
+
index = self._frame_index
|
|
630
|
+
for attr in BINDER_SERIES:
|
|
631
|
+
self.results[attr][index] = getattr(frame, attr)
|
|
632
|
+
self.results[LOOP_KEY][index] = np.bincount(
|
|
633
|
+
frame.bridged_pairs[:, 2], minlength=self.n_lengths)
|
|
634
|
+
if self.store_distances:
|
|
635
|
+
self.results[DISTANCES_KEY][index] = distances
|
|
636
|
+
|
|
637
|
+
def save(self, save_to: Pathish, name: str) -> list[Path]:
|
|
638
|
+
"""
|
|
639
|
+
Save each array as ``<name>-<series>.npy``, under the research
|
|
640
|
+
code's names.
|
|
641
|
+
|
|
642
|
+
.. versionadded:: 0.7.0
|
|
643
|
+
|
|
644
|
+
Parameters
|
|
645
|
+
----------
|
|
646
|
+
save_to : str or pathlib.Path
|
|
647
|
+
An existing directory.
|
|
648
|
+
name : str
|
|
649
|
+
The artifact's name, ``<parser.name>-<parser.group>`` in the
|
|
650
|
+
studies, such as ``'N200...ens1-nucleoid'``.
|
|
651
|
+
|
|
652
|
+
Returns
|
|
653
|
+
-------
|
|
654
|
+
list of pathlib.Path
|
|
655
|
+
The files written, in the order of :attr:`keys`: the eight
|
|
656
|
+
series as ``<stem><patch_label>`` or ``<stem><binder_label>``
|
|
657
|
+
(``nBoundTHnsPatch``, ``nFreeTHnsPatch``, ``nEngagedTHnsPatch``,
|
|
658
|
+
``nFreeTHnsCore``, ``nBridgeTHnsCore``, ``nDangleTHnsCore``,
|
|
659
|
+
``nCisTHnsCore``, ``nTransTHnsCore`` with the default labels),
|
|
660
|
+
the histogram as ``loopLengthHistT<chain_label>``, and, with
|
|
661
|
+
`store_distances`, the distances as
|
|
662
|
+
``distMatT<chain_label><patch_label>``.
|
|
663
|
+
|
|
664
|
+
Raises
|
|
665
|
+
------
|
|
666
|
+
RuntimeError
|
|
667
|
+
If :meth:`~MDAnalysis.analysis.base.AnalysisBase.run` has not been
|
|
668
|
+
called.
|
|
669
|
+
|
|
670
|
+
Notes
|
|
671
|
+
-----
|
|
672
|
+
The research code summed the loop-length histogram over the frames
|
|
673
|
+
into ``loopLengthHist<chain_label>``; the per-frame rows saved here
|
|
674
|
+
sum to it along the frame axis.
|
|
675
|
+
|
|
676
|
+
Examples
|
|
677
|
+
--------
|
|
678
|
+
>>> import tempfile
|
|
679
|
+
>>> import MDAnalysis as mda
|
|
680
|
+
>>> import numpy as np
|
|
681
|
+
>>> universe = mda.Universe.empty(4, trajectory=True)
|
|
682
|
+
>>> universe.atoms.positions = np.array(
|
|
683
|
+
... [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0],
|
|
684
|
+
... [0.0, 0.3, 0.0], [5.0, 5.0, 5.0]])
|
|
685
|
+
>>> analysis = BinderStatistics(
|
|
686
|
+
... universe.atoms[:2], universe.atoms[2:], cutoff=0.5,
|
|
687
|
+
... topology='linear', cis_threshold=3).run()
|
|
688
|
+
>>> with tempfile.TemporaryDirectory() as tmp:
|
|
689
|
+
... paths = analysis.save(tmp, 'N2ens1-nucleoid')
|
|
690
|
+
... names = [path.name for path in paths]
|
|
691
|
+
... dangling = np.load(paths[5]).tolist()
|
|
692
|
+
>>> names[0]
|
|
693
|
+
'N2ens1-nucleoid-nBoundTHnsPatch.npy'
|
|
694
|
+
>>> names[5], dangling
|
|
695
|
+
('N2ens1-nucleoid-nDangleTHnsCore.npy', [1])
|
|
696
|
+
>>> names[8]
|
|
697
|
+
'N2ens1-nucleoid-loopLengthHistTMon.npy'
|
|
698
|
+
"""
|
|
699
|
+
if any(key not in self.results for key in self.keys):
|
|
700
|
+
raise RuntimeError("Call run() before save().")
|
|
701
|
+
labels = {'patches': self.patch_label, 'binders': self.binder_label}
|
|
702
|
+
stems = {attr: f"{stem}{labels[which]}"
|
|
703
|
+
for attr, (stem, which) in BINDER_SERIES.items()}
|
|
704
|
+
stems[LOOP_KEY] = f"loopLengthHistT{self.chain_label}"
|
|
705
|
+
stems[DISTANCES_KEY] = f"distMatT{self.chain_label}{self.patch_label}"
|
|
706
|
+
paths = []
|
|
707
|
+
for key in self.keys:
|
|
708
|
+
path = Path(save_to) / f"{name}-{stems[key]}.npy"
|
|
709
|
+
np.save(path, self.results[key])
|
|
710
|
+
paths.append(path)
|
|
711
|
+
return paths
|