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.
Files changed (69) hide show
  1. polyphys/__init__.py +7 -0
  2. polyphys/__version__.py +22 -0
  3. polyphys/analyze/__init__.py +51 -0
  4. polyphys/analyze/clusters.py +1738 -0
  5. polyphys/analyze/correlations.py +995 -0
  6. polyphys/analyze/density.py +1138 -0
  7. polyphys/analyze/measurer.py +1338 -0
  8. polyphys/analyze/pipeline.py +1868 -0
  9. polyphys/analyze/stats.py +810 -0
  10. polyphys/analyze/sumrule.py +1159 -0
  11. polyphys/manage/__init__.py +11 -0
  12. polyphys/manage/organizer.py +2188 -0
  13. polyphys/manage/parser.py +1264 -0
  14. polyphys/manage/types.py +171 -0
  15. polyphys/manage/utils.py +777 -0
  16. polyphys/probe/__init__.py +25 -0
  17. polyphys/probe/binders.py +711 -0
  18. polyphys/probe/chain_size.py +583 -0
  19. polyphys/probe/core.py +437 -0
  20. polyphys/probe/histograms.py +863 -0
  21. polyphys/probe/pairs.py +555 -0
  22. polyphys/simulate/__init__.py +48 -0
  23. polyphys/simulate/build.py +1231 -0
  24. polyphys/simulate/core.py +237 -0
  25. polyphys/simulate/data.py +977 -0
  26. polyphys/simulate/dump.py +752 -0
  27. polyphys/simulate/log.py +788 -0
  28. polyphys/spec.py +1359 -0
  29. polyphys/tests/analyze/test_clusters.py +1058 -0
  30. polyphys/tests/analyze/test_correlations.py +708 -0
  31. polyphys/tests/analyze/test_density.py +813 -0
  32. polyphys/tests/analyze/test_measurer.py +785 -0
  33. polyphys/tests/analyze/test_pipeline.py +1881 -0
  34. polyphys/tests/analyze/test_stats.py +732 -0
  35. polyphys/tests/analyze/test_sumrule.py +917 -0
  36. polyphys/tests/conftest.py +753 -0
  37. polyphys/tests/data/lammps_build/make_starts.py +70 -0
  38. polyphys/tests/manage/test_organizer.py +1495 -0
  39. polyphys/tests/manage/test_parser.py +1222 -0
  40. polyphys/tests/manage/test_utils.py +498 -0
  41. polyphys/tests/probe/conftest.py +407 -0
  42. polyphys/tests/probe/test_binders.py +686 -0
  43. polyphys/tests/probe/test_chain_size.py +557 -0
  44. polyphys/tests/probe/test_core.py +391 -0
  45. polyphys/tests/probe/test_histograms.py +779 -0
  46. polyphys/tests/probe/test_pairs.py +475 -0
  47. polyphys/tests/simulate/test_build.py +1042 -0
  48. polyphys/tests/simulate/test_core.py +106 -0
  49. polyphys/tests/simulate/test_data.py +858 -0
  50. polyphys/tests/simulate/test_dump.py +487 -0
  51. polyphys/tests/simulate/test_log.py +667 -0
  52. polyphys/tests/strategies.py +120 -0
  53. polyphys/tests/synthetic.py +410 -0
  54. polyphys/tests/test_conftest.py +414 -0
  55. polyphys/tests/test_docs.py +82 -0
  56. polyphys/tests/test_packaging.py +97 -0
  57. polyphys/tests/test_spec.py +772 -0
  58. polyphys/tests/viz/conftest.py +32 -0
  59. polyphys/tests/viz/test_colors.py +113 -0
  60. polyphys/tests/viz/test_helpers.py +292 -0
  61. polyphys/viz/__init__.py +15 -0
  62. polyphys/viz/colors.py +204 -0
  63. polyphys/viz/helpers.py +549 -0
  64. polyphys-1.0.0.dist-info/METADATA +339 -0
  65. polyphys-1.0.0.dist-info/RECORD +69 -0
  66. polyphys-1.0.0.dist-info/WHEEL +5 -0
  67. polyphys-1.0.0.dist-info/licenses/AUTHORS.rst +13 -0
  68. polyphys-1.0.0.dist-info/licenses/LICENSE +22 -0
  69. 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)