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,237 @@
|
|
|
1
|
+
"""\
|
|
2
|
+
=====================================================================
|
|
3
|
+
Shared LAMMPS pieces --- :mod:`polyphys.simulate.core`
|
|
4
|
+
=====================================================================
|
|
5
|
+
|
|
6
|
+
The :mod:`~polyphys.simulate.core` module holds what the LAMMPS log, dump,
|
|
7
|
+
and data-file modules of :mod:`polyphys.simulate` share: the simulation box,
|
|
8
|
+
:class:`Box`, and :class:`LammpsFormatError`, the base class of their
|
|
9
|
+
errors.
|
|
10
|
+
|
|
11
|
+
.. versionadded:: 0.6.0
|
|
12
|
+
|
|
13
|
+
The box
|
|
14
|
+
=======
|
|
15
|
+
|
|
16
|
+
A LAMMPS box has an origin :math:`\\mathbf{o} = (x_{lo}, y_{lo}, z_{lo})`,
|
|
17
|
+
lengths :math:`l_x = x_{hi} - x_{lo}`, :math:`l_y`, and :math:`l_z`, and, when
|
|
18
|
+
it is a restricted triclinic box, the tilt factors :math:`xy`, :math:`xz`, and
|
|
19
|
+
:math:`yz`. Its edge vectors are
|
|
20
|
+
|
|
21
|
+
.. math::
|
|
22
|
+
|
|
23
|
+
\\mathbf{A} = (l_x, 0, 0), \\quad \\mathbf{B} = (xy, l_y, 0), \\quad
|
|
24
|
+
\\mathbf{C} = (xz, yz, l_z).
|
|
25
|
+
|
|
26
|
+
Scaled coordinates :math:`(s_a, s_b, s_c)`, the fractions of the edge
|
|
27
|
+
vectors that dumps write as ``xs ys zs``, map to Cartesian coordinates
|
|
28
|
+
:math:`\\mathbf{r} = \\mathbf{o} + s_a \\mathbf{A} + s_b \\mathbf{B} + s_c
|
|
29
|
+
\\mathbf{C}`, and the image flags :math:`(i_x, i_y, i_z)` unwrap a wrapped
|
|
30
|
+
position to :math:`\\mathbf{r} + i_x \\mathbf{A} + i_y \\mathbf{B} + i_z
|
|
31
|
+
\\mathbf{C}`. In an orthogonal box the tilt factors are zero. Lengths are in
|
|
32
|
+
the length unit of the run; nothing is converted.
|
|
33
|
+
|
|
34
|
+
References
|
|
35
|
+
==========
|
|
36
|
+
|
|
37
|
+
The LAMMPS documentation of `Howto_triclinic
|
|
38
|
+
<https://docs.lammps.org/Howto_triclinic.html>`_, and these lines of the
|
|
39
|
+
LAMMPS source, release ``stable_30Sep2026_update1``:
|
|
40
|
+
|
|
41
|
+
- The edge vectors: `domain.cpp, lines 256 to 270
|
|
42
|
+
<https://github.com/lammps/lammps/blob/098ffdefa525cabc82272925de02e9884f58c657/src/domain.cpp#L256-L270>`_.
|
|
43
|
+
- Scaled to Cartesian coordinates: `domain.cpp, lines 2336 to 2345
|
|
44
|
+
<https://github.com/lammps/lammps/blob/098ffdefa525cabc82272925de02e9884f58c657/src/domain.cpp#L2336-L2345>`_
|
|
45
|
+
(``Domain::lamda2x``).
|
|
46
|
+
- Unwrapping by image flags: `domain.cpp, lines 1919 to 1934
|
|
47
|
+
<https://github.com/lammps/lammps/blob/098ffdefa525cabc82272925de02e9884f58c657/src/domain.cpp#L1919-L1934>`_
|
|
48
|
+
(``Domain::unmap``).
|
|
49
|
+
|
|
50
|
+
Dependencies
|
|
51
|
+
============
|
|
52
|
+
- Python standard library: `dataclasses`, `gzip`, `pathlib`
|
|
53
|
+
- Third-party: `numpy`
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
import gzip
|
|
57
|
+
from dataclasses import dataclass, field
|
|
58
|
+
from pathlib import Path
|
|
59
|
+
from typing import IO, Any
|
|
60
|
+
|
|
61
|
+
import numpy as np
|
|
62
|
+
import numpy.typing as npt
|
|
63
|
+
|
|
64
|
+
__all__ = ["Box", "LammpsFormatError"]
|
|
65
|
+
|
|
66
|
+
FloatArray = npt.NDArray[np.float64]
|
|
67
|
+
IntArray = npt.NDArray[np.int64]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class LammpsFormatError(ValueError):
|
|
71
|
+
"""A LAMMPS file is cut off or does not follow its format.
|
|
72
|
+
|
|
73
|
+
The base class of :class:`~polyphys.simulate.log.BrokenLogError`,
|
|
74
|
+
:class:`~polyphys.simulate.dump.BrokenDumpError`, and
|
|
75
|
+
:class:`~polyphys.simulate.data.BrokenDataError`, so one ``except``
|
|
76
|
+
clause catches a broken file of any of the three kinds. It is a
|
|
77
|
+
:class:`ValueError`.
|
|
78
|
+
|
|
79
|
+
.. versionadded:: 0.6.0
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True, eq=False)
|
|
84
|
+
class Box:
|
|
85
|
+
r"""A LAMMPS simulation box, orthogonal or restricted triclinic.
|
|
86
|
+
|
|
87
|
+
.. versionadded:: 0.6.0
|
|
88
|
+
|
|
89
|
+
Attributes
|
|
90
|
+
----------
|
|
91
|
+
lo, hi : numpy.ndarray
|
|
92
|
+
Shape ``(3,)``, float64: :math:`(x_{lo}, y_{lo}, z_{lo})` and
|
|
93
|
+
:math:`(x_{hi}, y_{hi}, z_{hi})` of the box itself, with `lo` below
|
|
94
|
+
`hi`, in the length unit of the run. Read-only.
|
|
95
|
+
tilt : numpy.ndarray
|
|
96
|
+
Shape ``(3,)``, float64: the tilt factors :math:`(xy, xz, yz)`;
|
|
97
|
+
zeros by default. Read-only.
|
|
98
|
+
boundary : tuple of str or None
|
|
99
|
+
The three boundary flags, such as ``('pp', 'pp', 'ff')``, as a dump
|
|
100
|
+
gives them; None when the file does not say, as in a data file.
|
|
101
|
+
triclinic : bool
|
|
102
|
+
True for a restricted triclinic box, whose tilt factors LAMMPS
|
|
103
|
+
stores even when they are zero.
|
|
104
|
+
|
|
105
|
+
Raises
|
|
106
|
+
------
|
|
107
|
+
ValueError
|
|
108
|
+
If `lo`, `hi`, or `tilt` does not have shape ``(3,)``, `lo` is not
|
|
109
|
+
below `hi`, or an orthogonal box has a non-zero tilt factor.
|
|
110
|
+
|
|
111
|
+
Examples
|
|
112
|
+
--------
|
|
113
|
+
>>> box = Box(lo=np.zeros(3), hi=np.full(3, 6.0),
|
|
114
|
+
... tilt=np.array([1.0, 0.5, -0.5]), triclinic=True)
|
|
115
|
+
>>> box.lengths
|
|
116
|
+
array([6., 6., 6.])
|
|
117
|
+
>>> box.edges()
|
|
118
|
+
array([[ 6. , 0. , 0. ],
|
|
119
|
+
[ 1. , 6. , 0. ],
|
|
120
|
+
[ 0.5, -0.5, 6. ]])
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
lo: FloatArray
|
|
124
|
+
hi: FloatArray
|
|
125
|
+
tilt: FloatArray = field(default_factory=lambda: np.zeros(3))
|
|
126
|
+
boundary: tuple[str, str, str] | None = None
|
|
127
|
+
triclinic: bool = False
|
|
128
|
+
|
|
129
|
+
def __post_init__(self) -> None:
|
|
130
|
+
for name in ("lo", "hi", "tilt"):
|
|
131
|
+
value = _frozen(getattr(self, name), np.float64)
|
|
132
|
+
if value.shape != (3,):
|
|
133
|
+
raise ValueError(
|
|
134
|
+
f"'{name}' must have shape (3,), got {value.shape}"
|
|
135
|
+
)
|
|
136
|
+
object.__setattr__(self, name, value)
|
|
137
|
+
if not np.all(self.lo < self.hi):
|
|
138
|
+
raise ValueError(f"'lo' {self.lo} must be below 'hi' {self.hi}")
|
|
139
|
+
if not self.triclinic and np.any(self.tilt != 0):
|
|
140
|
+
raise ValueError(
|
|
141
|
+
f"an orthogonal box has zero tilt factors, got {self.tilt}"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def lengths(self) -> FloatArray:
|
|
146
|
+
"""The box lengths :math:`(l_x, l_y, l_z)`, `hi` minus `lo`."""
|
|
147
|
+
return self.hi - self.lo
|
|
148
|
+
|
|
149
|
+
def edges(self) -> FloatArray:
|
|
150
|
+
r"""Return the box edge vectors as the rows of a matrix.
|
|
151
|
+
|
|
152
|
+
Returns
|
|
153
|
+
-------
|
|
154
|
+
numpy.ndarray
|
|
155
|
+
Shape ``(3, 3)``, float64: the rows are
|
|
156
|
+
:math:`\mathbf{A} = (l_x, 0, 0)`,
|
|
157
|
+
:math:`\mathbf{B} = (xy, l_y, 0)`, and
|
|
158
|
+
:math:`\mathbf{C} = (xz, yz, l_z)`.
|
|
159
|
+
"""
|
|
160
|
+
lx, ly, lz = self.lengths
|
|
161
|
+
xy, xz, yz = self.tilt
|
|
162
|
+
return np.array(
|
|
163
|
+
[[lx, 0.0, 0.0], [xy, ly, 0.0], [xz, yz, lz]], dtype=np.float64
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def to_cartesian(self, scaled: npt.ArrayLike) -> FloatArray:
|
|
167
|
+
r"""Return Cartesian coordinates of scaled coordinates.
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
scaled : array_like
|
|
172
|
+
Shape ``(n_atoms, 3)``: the fractions :math:`(s_a, s_b, s_c)` of
|
|
173
|
+
the edge vectors, as in the ``xs ys zs`` or ``xsu ysu zsu``
|
|
174
|
+
columns of a dump.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
numpy.ndarray
|
|
179
|
+
Shape ``(n_atoms, 3)``, float64: :math:`\mathbf{o} + s_a
|
|
180
|
+
\mathbf{A} + s_b \mathbf{B} + s_c \mathbf{C}`. Time and memory
|
|
181
|
+
are :math:`O(n_a)` for :math:`n_a` atoms.
|
|
182
|
+
|
|
183
|
+
Examples
|
|
184
|
+
--------
|
|
185
|
+
>>> box = Box(lo=np.array([1.0, 0.0, 0.0]),
|
|
186
|
+
... hi=np.array([5.0, 4.0, 4.0]))
|
|
187
|
+
>>> box.to_cartesian([[0.5, 0.25, 0.0]])
|
|
188
|
+
array([[3., 1., 0.]])
|
|
189
|
+
"""
|
|
190
|
+
cartesian: FloatArray = self.lo + np.asarray(
|
|
191
|
+
scaled, dtype=np.float64) @ self.edges()
|
|
192
|
+
return cartesian
|
|
193
|
+
|
|
194
|
+
def unwrap(
|
|
195
|
+
self, positions: npt.ArrayLike, image: npt.ArrayLike
|
|
196
|
+
) -> FloatArray:
|
|
197
|
+
r"""Return wrapped positions unwrapped by their image flags.
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
positions : array_like
|
|
202
|
+
Shape ``(n_atoms, 3)``: Cartesian coordinates wrapped into the
|
|
203
|
+
box.
|
|
204
|
+
image : array_like
|
|
205
|
+
Shape ``(n_atoms, 3)``: the integer image flags
|
|
206
|
+
:math:`(i_x, i_y, i_z)`.
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
numpy.ndarray
|
|
211
|
+
Shape ``(n_atoms, 3)``, float64: :math:`\mathbf{r} + i_x
|
|
212
|
+
\mathbf{A} + i_y \mathbf{B} + i_z \mathbf{C}`. Time and memory
|
|
213
|
+
are :math:`O(n_a)`.
|
|
214
|
+
|
|
215
|
+
Examples
|
|
216
|
+
--------
|
|
217
|
+
>>> box = Box(lo=np.zeros(3), hi=np.full(3, 10.0))
|
|
218
|
+
>>> box.unwrap([[9.5, 5.0, 5.0]], [[-1, 0, 0]])
|
|
219
|
+
array([[-0.5, 5. , 5. ]])
|
|
220
|
+
"""
|
|
221
|
+
unwrapped: FloatArray = np.asarray(positions, dtype=np.float64) + (
|
|
222
|
+
np.asarray(image, dtype=np.float64) @ self.edges())
|
|
223
|
+
return unwrapped
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _frozen(value: Any, dtype: type[np.generic]) -> npt.NDArray[Any]:
|
|
227
|
+
"""Return `value` as a read-only array copy of type `dtype`."""
|
|
228
|
+
array = np.array(value, dtype=dtype)
|
|
229
|
+
array.flags.writeable = False
|
|
230
|
+
return array
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _open_text(path: Path, errors: str = "strict") -> IO[str]:
|
|
234
|
+
"""Open a file as UTF-8 text, through gzip when its name ends in .gz."""
|
|
235
|
+
if path.suffix == ".gz":
|
|
236
|
+
return gzip.open(path, "rt", encoding="utf-8", errors=errors)
|
|
237
|
+
return path.open("r", encoding="utf-8", errors=errors)
|